/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/apps/jrpcgen/JrpcgenConst.java,v 1.1 2003/08/13 12:03:45 haraldalbrecht Exp $
 *
 * Copyright (c) 1999, 2000
 * Lehrstuhl fuer Prozessleittechnik (PLT), RWTH Aachen
 * D-52064 Aachen, Germany.
 * All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Library General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program (see the file LICENSE.txt for more
 * details); if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
 */

package org.acplt.oncrpc.apps.jrpcgen;

import java.io.IOException;
import java.util.Objects;

/**
 * The <code>JrpcgenConst</code> class represents a single constant defined
 * in an rpcgen "x"-file.
 *
 * @version $Revision: 1.1 $ $Date: 2003/08/13 12:03:45 $ $State: Exp $ $Locker:  $
 * @author Harald Albrecht
 */
public class JrpcgenConst extends JrpcgenXdrDefinition {

	/**
	 * Type definition for a table of XDR constants.
	 */
	public static class Table extends JrpcgenItemTable<JrpcgenConst> {}

	/**
	 * Delivers the passed constant expression as an r-value. The passed constant
	 * expression may contain a constant value or an identifier of a constant known
	 * to the global table of constants.
	 *  
	 * <p>Passing for example the string literal <code>"42"</code>, the
	 * method returns the passed string <code>"42"</code>.
	 * 
	 * <p>Passing for example the string literal <code>"VERSION"</code>, the method
	 * may return the string <code>"demo.VERSION"</code> in case the global table
	 * of constants contains a constant definition with the identifier
	 * <code>VERSION</code> nested in the enclosure (namespace) <code>demo</code>.
	 * Otherwise the passed string <code>"VERSION"</code> is returned. 
	 * 
	 * <p>Passing for example the string literal <code>"ENUM_FIRST"</code>, the
	 * method may return the string
	 * <code>"EnumerationValues.ENUM_FIRST.getEncoding()"</code>
	 * in case the global table of constants contains a constant definition with
	 * the identifier <code>ENUM_FIRST</code> nested in the enclosure (namespace)
	 * <code>EnumerationValues</code> originating from an enumeration definition
	 * provided that XDR ernumerations are mapped by Java enumerations. 
	 *  
	 * @param constantExpression A string containing a constant value or the identifier
	 * of a constant definition known to the global table of constant definitions.
	 * @param context The context of the rpcgen run, providing the table of global
	 * constant definitions collected so far.
	 * @return The passed constant expression or the string representation of the
	 * reference to the constant identified by the passed constant expression.
	 */
	public static String getAsRValue(String constantExpression, JrpcgenContext context) {
		return getAsRValue(constantExpression, null, context);
	}
	
	/**
	 * Delivers the passed constant expression as an r-value. The passed constant
	 * expression may contain a constant value or an identifier of a constant known
	 * to the global table of constants.
	 *  
	 * <p>Passing for example the string literal <code>"42"</code>, the
	 * method returns the passed string <code>"42"</code>.
	 * 
	 * <p>Given a target enclosure (namespace) <code>element</code> and
	 * a global constant definition with the identifier <code>VERSION</code>
	 * nested in the enclosure (namespace) <code>demo</code>, passing the string
	 * literals <code>"VERSION"</code> and <code>"element"</code> results in
	 * returning the string <code>"demo.VERSION"</code>.
	 * 
	 * <p>Given a target enclosure (namespace) <code>demo</code> and
	 * a global constant definition with the identifier <code>VERSION</code>
	 * nested in the enclosure (namespace) <code>demo</code>, passing the string
	 * literals <code>"VERSION"</code> and <code>"demo"</code> results in returning
	 * the string <code>"VERSION"</code>.
	 * 
	 * <p>Given a target enclosure (namespace) <code>element</code> and
	 * a global constant definition with the identifier <code>ENUM_FIRST</code>
	 * nested in the enclosure (namespace) <code>EnumerationValues</code>
	 * originating from an enumeration definition, passing the string literals
	 * <code>"ENUM_FIRST"</code> and <code>"element"</code> results in returning
	 * the string <code>"EnumerationValues.ENUM_FIRST.getEncoding()"</code>
	 * provided that XDR enumerations are mapped by Java enumerations
	 * (see {@link JrpcgenEnum.Element}). 
	 *  
	 * @param constantExpression A string containing a constant value or the identifier
	 * of a constant definition known to the global table of constant definitions.
	 * @param forEnclosure The target enclosure (namespace), where the r-value is
	 * intended to be used.
	 * @param context The context of the rpcgen run, providing the table of global
	 * constant definitions collected so far.
	 * @return The passed constant expression or the string representation of the
	 * reference to the constant identified by the passed constant expression.
	 */
	public static String getAsRValue(String constantExpression, String forEnclosure, JrpcgenContext context) {
	
		/*
		 * Initialize the r-value with the passed constant expression.
		 * This value is returned in case the constant expression
		 * does not refer to a constant.
		 */
		String rValue = constantExpression;
		
		/*
		 * Let's see, whether the passed constant expression refers
		 * to a constant definition. Therefore it needs to start with a letter.
		 */
		if (isLetter(constantExpression.charAt(0))) {
			JrpcgenConst constant = context.globalDefinitions().getItem(JrpcgenConst.class, constantExpression);
			
			if (constant != null) {
				/*
				 * The passed constant expression refers to a constant.
				 * The r-value of the constant will be returned.
				 */
				rValue = constant.getAsRValue(forEnclosure);
			}
		}
		
		return rValue;
	}
	
	/**
	 * Delivers the passed constant expression as a case value, which is intended
	 * to be used within a switch-case-statement. The passed constant expression
	 * may contain a constant value or an identifier of a constant known to the
	 * global table of constants.
	 * 
	 * <p>Passing for example the string literal <code>"42"</code>, the method
	 * returns the passed string <code>"42"</code>.
	 * 
	 * <p>Given a target enclosure (namespace) <code>element</code> and a global
	 * constant definition with the identifier <code>VERSION</code> nested in
	 * the enclosure (namespace) <code>demo</code>, passing the string literals
	 * <code>"VERSION"</code> and <code>"element"</code> result in returning
	 * the string <code>"demo.VERSION"</code>.
	 * 
	 * <p>Given a target enclosure (namespace) <code>element</code> and
	 * a global constant definition with the identifier <code>ENUM_FIRST</code>
	 * nested in the enclosure (namespace) <code>EnumerationValues</code>
	 * originating from an enumeration definition, passing the string literals
	 * <code>"ENUM_FIRST"</code> and <code>"element"</code> results in returning
	 * the string <code>"ENUM_FIRST"</code> provided that XDR enumerations
	 * are mapped by Java enumerations (see {@link JrpcgenEnum.Element}). 
	 * 
	 * @param constantExpression A string containing a constant value or the identifier
	 * of a constant definition known to the global table of constant definitions.
	 * @param forEnclosure The target enclosure (namespace), where the r-value is
	 * intended to be used.
	 * @param context The context of the rpcgen run, providing the table of global
	 * constant definitions collected so far.
	 * @return The passed constant expression or the string representation of the
	 * reference to the constant identified by the passed constant expression.
	 */
	public static String getAsCaseValue(String constantExpression, String forEnclosure, JrpcgenContext context) {
		/*
		 * Initialize the case value with the passed constant expression.
		 */
		String caseValue = constantExpression;
		
		/*
		 * Let's see if the passed constant expression refers to a constant
		 * definition in the global table of constants.
		 */
		if (isLetter(constantExpression.charAt(0))) {
			JrpcgenConst constant = context.globalDefinitions().getItem(JrpcgenConst.class, constantExpression);
			
			/*
			 * Does the constant expression refer to a constant?
			 */
			if (constant != null) {
				/*
				 * The case value of the constant will be returned.
				 */
				caseValue = constant.getAsCaseValue(forEnclosure);
			}
		} /* endif (The passed value starts with a letter) */
		
		return caseValue;
	}
	
    /**
     * Constructs a <code>JrpcgenConst</code> and sets the identifier and
     * the associated value.
     *
     * @param context The context the new contant belongs to.
     * @param identifier Constant identifier to define.
     * @param value Value assigned to constant.
     */
    public JrpcgenConst(JrpcgenContext context, String identifier, String value) {
    	this(context, identifier, value, null);
    }

    /**
     * Constructs a <code>JrpcgenConst</code> and sets the identifier and
     * the associated value of an enumeration etc.
     *
     * @param context The context the new contant belongs to.
     * @param identifier Constant identifier to define.
     * @param value Value assigned to constant.
     * @param enclosure Name of enclosing enumeration, etc.
     */
    public JrpcgenConst(JrpcgenContext context, String identifier, String value, String enclosure) {
    	super(identifier, Type.CONST);
    	this.context = context;
        this.value = value;
        this.enclosure = enclosure;
    }

    /**
     * Delivers the context the constant definition belongs to.
     * 
     * @return The context teh constant definition belongs to.
     */
    final public JrpcgenContext getContext() {
    	return context;
    }
    
    /**
     * Delivers the value defined for the constant.
     * 
     * @return The value of the constant.
     */
    final public String getValue() {
    	return value;
    }
    
    /**
     * Sets the value represented by the constant.
     * 
     * @param value The value of the constant in string 
     *        representation.
     */
    final public void setValue(String value) {
    	this.value = value;
    	this.identifierValueStartsWith = null;
    	this.trailingPartOfValue = null;
    }
    
    /**
     * Returns the name of the enclosure the constant
     * lives in.
     * 
     * @return The name of the enclosure.
     */
    final public String getEnclosure() {
    	return enclosure;
    }
    
    /**
     * Sets the name of the enclosure the constant
     * lives in.
     * 
     * @param enclosure The name of the enclosure.
     */
    final public void setEnclosure(String enclosure) {
    	this.enclosure = enclosure;
    }
    
    /**
     * Delivers the representation of the constant
     * with respect to the passed enclosure name.
     * 
     * <p>The passed enclosure name identifies the
     * target enclosure (namespace), where the
     * requested constant representation is intended
     * to be used.  
     * 
     * @param forEnclosure The name of the target enclosure. 
     * @return One of the following representations
     *         will be returned:
     *         <ul>
     *         <li>The value of the constant, if the constant
     *             is a global constant.</li>
     *         <li>The identifier of the constant, if the
     *             constant lives in the target enclosure.</li>
     *         <li>The joined version of the enclosure the constant
     *             lives in and the identifier of the constant,
     *             delimited by a dot.
     *         </ul>
     */
    public String getForEnclosure(String forEnclosure) {
    	if (enclosure == null)
    		return value;

    	if (enclosure.equals(forEnclosure)) {
    		return getIdentifier();
    	} else {
    		return "" + enclosure + "." + getIdentifier();
    	}
    }

    /**
	 * Delivers an r-value representation of the constant.
	 *  
     * <p>The passed enclosure name identifies the
     * target enclosure (namespace), where the
     * requested constant representation is intended
     * to be used.
     *   
	 * @param forEnclosure The name of the target enclosure.
     * @return One of the following representations
     *         will be returned:
     *         <ul>
     *         <li>The value of the constant, if the constant
     *             is a global constant.</li>
     *         <li>The identifier of the constant, if the
     *             constant lives in the target enclosure.</li>
     *         <li>The joined version of the enclosure the constant
     *             lives in and the identifier of the constant,
     *             delimited by a dot.
     *         </ul>
     */
    public String getAsRValue(String forEnclosure) {
    	if (enclosure == null)
    		return value; 
    	
    	if (enclosure.equals(forEnclosure)) {
    		return getIdentifier();
    	} else {
    		return "" + enclosure + '.' + getIdentifier();
    	}
    }
    
    /**
	 * Delivers a case value representation of the constant.
	 *  
     * <p>The passed enclosure name identifies the
     * target enclosure (namespace), where the
     * requested constant representation is intended
     * to be used.
     *   
	 * @param forEnclosure The name of the target enclosure.
     * @return One of the following representations
     *         will be returned:
     *         <ul>
     *         <li>The value of the constant, if the constant
     *             is a global constant.</li>
     *         <li>The identifier of the constant, if the
     *             constant lives in the target enclosure.</li>
     *         <li>The joined version of the enclosure the constant
     *             lives in and the identifier of the constant,
     *             delimited by a dot.
     *         </ul>
     */
    public String getAsCaseValue(String forEnclosure) {
    	/*
    	 * A case reference to a constant does not
    	 * differ from a value reference to a constant.
    	 */
    	return getAsRValue(forEnclosure);
    }

    /**
     * Delivers an r-value representation of the value
     * defined by the constant. This is useful for example
     * writing the declaration of an enumeration element.
     * 
     * <p>The passed eclosure name identifies the
     * target enclosure (namespace), where the
     * requested value representation is intended
     * to be used.
     * 
     * @param forEnclosure The name of the target enclosure.
     * @return One of the following representations
     *         will be returned:
     *         <ul>
     *         <li>The value of the constant, if the constant
     *             is a global constant or independent of
     *             another constant.</li>
     *         <li>If the value is dependent on another constant,
     *             the r-value representation of that constant
     *             with respect to the target enclosure is taken
     *             and where applicable joined with the trailing
     *             part of the value following the identifier of
     *             the other constant.</li>
     *         </ul>
     */
    public String getValueAsRValue(String forEnclosure) {
    	/*
    	 * Without an enclosure the value of this constant
    	 * is returned.
    	 */
    	if (enclosure == null)
    		return value;
    
		/*
		 * Initialize the r-value with the value of this constant.
		 */
		String rValue = value;
		
		/*
		 * Does the value start with an identifier and
		 * therefore is maybe dependant on another constant?
		 */
		if (getIdentifierValueStartsWith() != null) {
			JrpcgenConst constant = context.globalDefinitions().getItem(JrpcgenConst.class, identifierValueStartsWith);
			
			/*
			 * Does the identifier refer to a constant?
			 */
			if (constant != null) {
				/*
				 * Get the r-value of the constant and concat the trailing
				 * part of the value behind the identifier.
				 */
				rValue = constant.getAsRValue(forEnclosure).concat(trailingPartOfValue);
			}
		} /* endif (The passed value starts with a letter) */
		
		return rValue;
    }
    

    /**
     * Returns the value as integer literal (and thus resolving identifiers
     * recursively, if necessary). This is only possible for simple
     * subsitutions, that is A is defined as B, B as C, and C as 42, thus
     * A is eventually defined as 42.
     *
     * <p>This simple kind of resolving is necessary when defining a particular
     * version of an ONC/RPC protocol. We need to be able to resolve the
     * version to an integer literal because we need to append the version
     * number to any remote procedure defined to avoid identifier clashes if
     * the same remote procedure is defined for several versions.
     *
     * @return integer literal as <code>String</code> or <code>null</code>,
     *   if the identifier could not be resolved to an integer literal.
     */
    public String resolveValue() {
        if ( value.length() > 0 ) {
            //
            // If the value is an integer literal, then we just have to
            // return it. That's it.
            //
            if ( Character.isDigit(value.charAt(0))
                 || (value.charAt(0) == '-') ) {
                return value;
            }
            //
            // It's an identifier, which we now have to resolve. First,
            // look it up in the list of global identifiers. Then recursively
            // resolve the value.
            // A valid identifier for a constant value points to a constant
            // itself. Remind that elements of enumerations are placed as constant
            // values in the global definition table as well.
            //
            JrpcgenConst id = context.globalDefinitions().getItem(JrpcgenConst.class, value);
            
            if (id != null)
                return id.resolveValue();
        }
        
        return null;
    }

    public void writeDeclaration(JrpcgenJavaFile javaFile) {
        //
        // This simple test avoids endless recursions: we already dumped this
        // particular constant in some place, so we should not proceed.
        //
        if ( ! alreadyDeclared ) {
        	/*
        	 * Initialize the value to write with
        	 * the value of this constant
        	 */
        	String valueToWrite = value;
        	
            /*
             * Let's see, whether the value of this constant is given by another
             * constant or by an element of an enumeration.
             * Try to detect an identifier at the beginning of the value.
             * 
             * Remind this feature as an extension to the specification
             * of RFC 4506, which let's constants be defined by decimal, hexadecimal
             * or octal constants only. The value of an element of an enumeration
             * may be given by a decimal constant, a hexadecimal constant, an octal constant
             * or the identifier of a constant. Consequently, calculations within the
             * values of constants or enumeration elements are not covered by the RFC 4506.
             */
        	String dependency = detectIdentifier(value);
        	
        	/*
        	 * Mark this constant as already declared in order
        	 * to avoid ambigous declarations.
        	 */
            alreadyDeclared = true;
            

            /*
             * Did we detect a dependency?
             */
            if (dependency != null) {
            	/*
            	 * Maybe there is a global constant defined for the dependency.
            	 */
            	JrpcgenConst constant = context.globalDefinitions().getItem(JrpcgenConst.class, dependency);
            	
            	if (constant != null) {
            		/*
            		 * Does the referenced constant lives within the same enclosure?
            		 */
            		if (Objects.equals(enclosure, constant.enclosure)) {
            			/*
            			 * The referenced constant lives in the same scope.
            			 * If it has not been declared yet, delcare it now!
            			 */
            			constant.writeDeclaration(javaFile);
            		} /* endif (The referenced constant lives within the same scope) */

            		/*
            		 * Set the value to write as concatenation of the reference
            		 * to the constant and the possibly trailing part of teh value
            		 * behind the dependency identifier
            		 */
            		valueToWrite = constant.getAsRValue(enclosure).concat(value.substring(dependency.length()));
            	} /* endif (The value refers to another global constant) */
            } /* endif (The value seems to be an identifier) */
            
            /*
             * Write the documentation.
             */
            writeDocumentation(javaFile);
            
            /*
             * Write the declaration.
             */
            javaFile.newLine().beginLine().append("public static final int ").append(getIdentifier())
            	.append(" = ").append(valueToWrite).println(';');

        } /* endif (We have not declared this constant yet.) */
    }
    
    /**
     * Dumps the constant as well as its value to <code>System.out</code>.
     */
    public void dump() {
    	System.out.print(Type.CONST.name().concat(" "));
    	dump(System.out).println();
    }
    
    public <T extends Appendable> T dump(T appendable) {
    	try {
        	appendable.append(getIdentifier()).append('=').append(value);
    	} catch (IOException ioException) {
    		// Will be ignored at this place.
    	}
    	
    	return appendable;
    }
    
    @Override
    public String toString() {
    	return dump(new StringBuilder()).toString();
    }

    /**
     * Detects a character sequence at the beginning of the passed
     * expression, which holds the definition of an identifier as
     * described in RFC 4506:
     * <quote>An identifier is a letter followed by an optional sequence of letters,
     * digits, or underbar ('_').  The case of identifiers is not ignored.</quote>
     * <p>Letters are defined by the character set ['a'-'z','A'-'Z'] within this
     * implementation.
     * <p>Digits are defined by the character set ['0'-'9'] within this implementation.
     *  
     * @param expression An expression possibly starting with an identifier like
     *        character sequence.
     * @return The detected identifier at the beginning of the passed expression or
     *         {@code null} in case the passed expression does not start with a letter.
     */
    public static String detectIdentifier(String expression) {
    	String identifier = null;

    	/*
    	 * At least one character needs to be in the expression,
    	 * in order to be able to hold an identifier. 
    	 */
    	if ((expression != null) && (expression.length() > 0)) {
        	int identifierEnd = 0;
        	
        	/*
        	 * As described in RFC 4506 an identifier is built as follows:
        	 * 
        	 * "An identifier is a letter followed by an optional sequence of letters,
        	 * digits, or underbar ('_').  The case of identifiers is not ignored."
        	 * 
        	 * The test methods java.lang.Character.isLetter(char) and
        	 * java.lang.Character.isLetterOrDigit(char) are used to test
        	 * for letters and digits, respectively. The underbar is tested
        	 * by a direct comparison with the character '_'.
        	 *  
        	 * Let's see, wether the passed expression starts with a letter.
        	 */
        	if (isLetter(expression.charAt(identifierEnd))) {
        		/*
        		 * The expression starts with a letter. A letter is sufficient
        		 * to be used as an identifier.
        		 * Let's see whether there are more characters, which are part
        		 * of the identifier.
        		 */
        		int length = expression.length();
        		
        		/*
        		 * Loop over the remaining characters as long as there
        		 * are characters and the current character is a letter,
        		 * digit or underbar.
        		 */
        		for (identifierEnd = 1;
        				(identifierEnd < length) && isLetterOrDigitOrUnderbar(expression.charAt(identifierEnd));
        				++identifierEnd);
        		
        		/*
        		 * The identifier is now given by the substring beginning
        		 * index 0 and ending one before index 'identifierEnd'.
        		 */
        		identifier = expression.substring(0, identifierEnd);
        	} /* endif (The first character is a letter) */
    	} /* endif (The expression contains at least one character) */
    	
    	
    	return identifier;
    }
    
    /**
     * Checks whether the passed character is a US-ASCII letter out of
     * the set '[a-z, A-Z]'. The lower case and the upper case letters
     * are placed in a closed sequence within the US-ASCII
     * charatcter set beginning with 'a' and 'A', respectively, and
     * ending with 'z' and 'Z', repsectively. 
     * 
     * @param character The character to test
     * @return {@code true} if the passed character is identified as a US-ASCII
     *         letter, {@code false} otherwise.
     */
    private static boolean isLetter(char character) {
    	return ((character >= 'a') && (character <= 'z'))
    			|| ((character >= 'A') && (character <= 'Z'));
    }
    
    /**
     * Checks whether the passed character is a digit
     * out of the character set ['0'-'9']. Within the US-ASCII
     * character set, the digits are plasced in a closed sequence
     * beginning with '0' and ending with '9'. 
     * 
     * @param character The character to test
     * @return {@code true} if the passed character is a digit as defined above,
     *         {@codee false} otherwise.
     */
    private static boolean isDigit(char character) {
    	return (character >= '0') && (character <= '9');
    }
    
    /**
     * Checks whether the passed character is a letter, a digit or
     * an underbar. A letter is given out of the character set ['a'-'z','A'-'Z'],
     * a digit is given out of the character set ['0'-'9'] and an underbar is given
     * as the character '_'.
     * 
     * @param character The character to test
     * @return {@code true} if the passed character is a letter, a digit or an underbar
     *         as described above, {@code false} otherwise.
     */
    private static boolean isLetterOrDigitOrUnderbar(char character) {
    	return isLetter(character) || isDigit(character) || (character == '_');
    }
    
    /**
     * Delivers the identifier the value of the constant may start with.
     * 
     * <p>Given the constant definitions <code>const A=42</code> and
     * <code>const B=A+1</code>, a call of this method returns <code>null</code>
     * for constant <code>A</code> and <code>A</code> for constant <code>B</code>.
     *  
     * @return The identifier the value of the constant starts with or
     *         <code>null</code> in case the value does not start with
     *         an identifier.
     */
    private String getIdentifierValueStartsWith() {
    	if ((identifierValueStartsWith == null) && (trailingPartOfValue == null)) {
    		identifierValueStartsWith = detectIdentifier(value);
    		
    		if (identifierValueStartsWith == null) {
    			trailingPartOfValue = value;
    		} else {
    			trailingPartOfValue = value.substring(identifierValueStartsWith.length());
    		}
    	}
    	
    	return identifierValueStartsWith;
    }

    /**
     * Reference to the context this constant belongs to.
     */
    private final JrpcgenContext context;
    
    /**
     * Contains value (or identifier refering to another constant) of constant.
     */
    private String value;
    
    /**
     * Contains the identifier the value may start with.
     */
    private String identifierValueStartsWith;

    /**
     * Contains the trailing part behind the identifier
     * the value may start with.
     */
    private String trailingPartOfValue;
    
    /**
     * Specifies the enclosure (scope) within the identifier must be
     * addressed for a constant defined by an enumumeration.
     */
    private String enclosure;
    
    /**
     * Flag indicating whether this constant and its dependencies 
     * have already been declared.
     */
    private boolean alreadyDeclared = false;




}

// End of JrpcgenConst.java
