/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/apps/jrpcgen/JrpcgenDeclaration.java,v 1.2 2003/08/14 08:08:34 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;

/**
 * The <code>JrpcgenDeclaration</code> class represents a single declaration
 * from an rpcgen "x"-file.
 *
 * @version $Revision: 1.2 $ $Date: 2003/08/14 08:08:34 $ $State: Exp $ $Locker:  $
 * @author Harald Albrecht
 */
public class JrpcgenDeclaration extends JrpcgenDocumentable implements JrpcgenItem, Cloneable {

	public static class Table extends JrpcgenItemTable<JrpcgenDeclaration> {}

	public enum Kind {
		/**
	     * Indicates that a scalar is declared.
	     */
	    SCALAR,

	    /**
	     * Indicates that a vector (an array) with fixed size is declared.
	     */
	    FIXEDVECTOR,

	    /**
	     * Indicates that a vector (an array) with dynamic (or unknown) size
	     * is declared.
	     */
	    DYNAMICVECTOR,

	    /**
	     * Indicates that an indirection (reference, pointer, whatever you like
	     * to call it nowadays) is declared.
	     */
	    INDIRECTION;
	}
	
    /**
     * Constructs a <code>JrpcgenDeclaration</code> and sets the identifier
     * and its data type. The {@link JrpcgenDeclaration#kind} of the
     * declaration is assumed to be {@link JrpcgenDeclaration#SCALAR}.
     *
     * @param identifier Identifier to be declared.
     * @param type Data type the identifier is declared of.
     */
    public JrpcgenDeclaration(String identifier, JrpcgenTypeMapping typeMapping) {
    	this(identifier, typeMapping, Kind.SCALAR, null);
    }

    /**
     * Constructs a <code>JrpcgenDeclaration</code> and sets the identifier,
     * its data type, kind and size of vector. This constructur is typically
     * used when declaring either fixed-size or dynamic arrays.
     *
     * @param identifier Identifier to be declared.
     * @param type Data type the identifier is declared of.
     * @param kind Kind of declaration (scalar, vector, indirection).
     * @param size Size of array (if fixed-sized or bounded, otherwise <code>null</code>).
     */
    public JrpcgenDeclaration(String identifier, JrpcgenTypeMapping typeMapping, Kind kind, String size) {
        this.identifier = identifier;
        this.typeMapping = typeMapping;
        this.kind = kind;
        this.size = size;
    }
    
    @Override
    final public String getIdentifier() {
    	return identifier;
    }
    
    final public String getType() {
    	return typeMapping.getDefinitionName();
    }
    
    final public String getJavaType() {
    	if (javaType == null) {
    		if (typeMapping.isStringType()) {
    			return typeMapping.getJavaName();
    		} else {
                switch(kind) {
                case SCALAR:
                	javaType = typeMapping.getJavaName();
                	break;
                	
                case FIXEDVECTOR:
                	javaType = typeMapping.getJavaName().concat("[]");
                	break;
                	
                case DYNAMICVECTOR:
                	if (typeMapping.isStringType()) {
                		javaType = typeMapping.getJavaName();
                	} else {
                		javaType = typeMapping.getJavaName().concat("[]");
                	}
                	break;
                	
                case INDIRECTION:
                	javaType = typeMapping.getJavaClass();
                	break;
                }
    		}
    	}
    	
    	return javaType;
    }
    
    final public JrpcgenTypeMapping getTypeMapping() {
    	return typeMapping;
    }
    
    final public Kind getKind() {
    	return kind;
    }
    
    final public String getSize() {
    	return size;
    }
    
    public void updateHash(JrpcgenSHA hash) {
    	hash.update(typeMapping.getDefinitionName());
    	hash.update(kind.ordinal());
    	hash.update(identifier);
    }
    
    public void writeMemberDeclaration(JrpcgenJavaFile javaFile, String access, boolean initString) {
        writeDocumentation(javaFile);
        javaFile.newLine().beginLine().append(access).space().append(getJavaType()).space().append(identifier);
        
        if ( initString
             && typeMapping.isStringType() ) {
        	javaFile.println(" = \"\";");
        } else {
        	javaFile.semicolon().newLine();
        }
    }
    
    public void writeGettersAndSetters(JrpcgenJavaFile javaFile) {
		String jbName = getIdentifier().substring(0,1).toUpperCase() + getIdentifier().substring(1);
		String getterName = "get".concat(jbName);
		String setterName = "set".concat(jbName);

		if ( isArray() ) {
			//
			// Generate the setter(s)
			//
			javaFile.beginPublicMethod().resultType("void").name(setterName).parameter(getJavaType(), identifier).endSignature()
				.beginLine().append("this").dot().append(identifier).append(" = ").append(identifier).semicolon().newLine().endMethod();
			
			javaFile.beginPublicMethod().resultType("void").name(setterName).parameter("int", "index")
				.parameter(typeMapping.getJavaName(), identifier.concat("Element")).endSignature()
				.beginLine().append("this").dot().append(identifier).append("[index] = ")
				.append(identifier).append("Element").semicolon().newLine().endMethod();
			
			//
			// Generate the getter(s)
			//
			javaFile.beginPublicMethod().resultType(getJavaType()).name(getterName).endSignature()
				.beginLine().keywordReturn().space().append("this").dot().append(identifier).semicolon().newLine().endMethod();
		
			javaFile.beginPublicMethod().resultType(typeMapping.getJavaName()).name(getterName).parameter("int", "index").endSignature()
				.beginLine().keywordReturn().space().append("this").dot().append(identifier).println("[index];").endMethod();

		} else {
			//
			// Generate the setter
			//
			javaFile.beginPublicMethod().resultType("void").name(setterName).parameter(getJavaType(), identifier).endSignature()
				.beginLine().append("this").dot().append(identifier).append(" = ").append(identifier).semicolon().newLine().endMethod();
			
			//
			// Generate the getter
			//
			javaFile.beginPublicMethod().resultType(getJavaType()).name(getterName).endSignature()
				.beginLine().keywordReturn().space().append("this").dot().append(identifier).semicolon().newLine().endMethod();
		}
    }
    
    public void writeEncodingPart(JrpcgenJavaFile javaFile, String enclosure, JrpcgenContext context) {
    	writeEncodingPart(javaFile, enclosure, null, context);
    }
    
    public void writeEncodingPart(JrpcgenJavaFile javaFile, String enclosure, String oref, JrpcgenContext context) {
        //
        // Skip entries for void arms etc...
        //
    	if (! typeMapping.isVoid())  {
        	final String variable = (oref == null ? identifier : "" + oref + "." + identifier);
        	final String xdrStream = "xdr";
        	
            switch (kind) {
            case SCALAR:
            	typeMapping.writeXdrEncodingCall(javaFile.beginLine(), xdrStream, variable);
            	javaFile.semicolon().newLine();
            	break;
            	
            case FIXEDVECTOR:
            	typeMapping.writeXdrFixedVectorEncodingCall(javaFile.beginLine(), xdrStream, variable, JrpcgenConst.getAsRValue(size, context));
            	javaFile.semicolon().newLine();
            	break;
            	
            case DYNAMICVECTOR:
            	typeMapping.writeXdrDynamicVectorEncodingCall(javaFile.beginLine(), xdrStream, variable);
            	javaFile.semicolon().newLine();
            	break;
            	
            case INDIRECTION:
            	JrpcgenBaseType.BOOL.writeXdrEncodingCall(javaFile.beginLine(), xdrStream, new JrpcgenJavaFile.Expression() {
					
					@Override
					public void writeTo(JrpcgenJavaFile javaFile) {
						javaFile.append(variable).append(" != null");
					}
				});
            	
            	javaFile.semicolon().beginNewLine().append("if (").append(variable).append(" != null) ");
            	typeMapping.writeXdrEncodingCall(javaFile, xdrStream, variable);
            	javaFile.semicolon().newLine();
            	break;
            }
    	}
    }
    
    public void writeDecodingPart(JrpcgenJavaFile javaFile, String enclosure, JrpcgenContext context) {
    	writeDecodingPart(javaFile, enclosure, null, context);
    }
    
    public void writeDecodingPart(JrpcgenJavaFile javaFile, String enclosure, String oref, JrpcgenContext context) {
    	//
        // Skip entries for void arms etc...
        //
    	if (! typeMapping.isVoid()) {
    		final String variable = (oref == null ? identifier : "" + oref + "." + identifier);
        	final String xdrStream = "xdr";

        	switch(kind) {
        	case SCALAR:
        		javaFile.beginLine().append(variable).append(" = ");
        		typeMapping.writeXdrDecodingCall(javaFile, xdrStream);
        		javaFile.semicolon().newLine();
        		break;
        		
        	case FIXEDVECTOR:
        		javaFile.beginLine().append(variable).append(" = ");
        		typeMapping.writeXdrFixedVectorDecodingCall(javaFile, xdrStream, JrpcgenConst.getAsRValue(size, context));
        		javaFile.semicolon().newLine();
        		break;
        		
        	case DYNAMICVECTOR:
        		javaFile.beginLine().append(variable).append(" = ");
        		typeMapping.writeXdrDynamicVectorDecodingCall(javaFile, xdrStream);
        		javaFile.semicolon().newLine();
        		break;
        		
        	case INDIRECTION:
        		javaFile.beginLine().append(variable).append(" = ");
        		JrpcgenBaseType.BOOL.writeXdrDecodingCall(javaFile, xdrStream);
        		javaFile.append(" ? ");
        		typeMapping.writeXdrDecodingCall(javaFile, xdrStream);
        		javaFile.println(" : null;");
        	}
    	}
    }

    public void writeToStringPart(JrpcgenJavaFile javaFile) {
        if ( isArray() ) {
        	javaFile.beginLine().append("+ \"").append(identifier)
        		.append("=\" + java.util.Arrays.toString(").append(identifier).append(')');
        } else {
        	javaFile.beginLine().append("+ \"").append(identifier)
        		.append("=\" + ").append(identifier);
         }
    }
    
    public void writeEqualsPart(JrpcgenJavaFile javaFile, JrpcgenContext context) {
    	javaFile.beginLine().append("if (");
    	writeEqualsExpression(javaFile, /* negate */ true, context);
    	javaFile.append(") return false;").newLine();
    }
    
    public void writeEqualsExpression(JrpcgenJavaFile javaFile, boolean negate, JrpcgenContext context) {
    	if ( isArray() ) {
    		javaFile.append(negate ? "! " : "")
    			.append("java.util.Arrays.equals(this.").append(identifier)
    			.append(", other.").append(identifier).append(')');
    	} else if (Kind.INDIRECTION.equals(kind)) {
    		javaFile.append(negate ? "! " : "").append("java.util.Objects.equals(this.").append(identifier)
    			.append(", other.").append(identifier).rightParenthesis();
    	} else {
    		typeMapping.writeEqualsExpression(javaFile, "this.".concat(identifier), "other.".concat(identifier), negate);
    	} 
    }
    
    /**
     * Returns the identifier.
     */
    public String toString() {
    	return dump(new StringBuilder()).toString();
    }


    /**
     * Dumps the declaration to <code>System.out</code>.
     */
    public void dump() {
    	dump(System.out).println();
    }
    
    public <T extends Appendable> T dump(T appendable) {
    	try {
    		appendable
    			.append(typeMapping.getDefinitionName())
    			.append(Kind.INDIRECTION.equals(kind) ? " *" : " ")
    			.append(identifier);
    		
        	switch (kind) {
        	case FIXEDVECTOR:
        		appendable.append('[').append(size).append(']');
        		break;
        		
        	case DYNAMICVECTOR:
        		if (size == null) {
        			appendable.append("<>");
        		} else {
        			appendable.append('<').append(size).append('>');
        		}
        		break;
        		
        	default:
        		break;
        	}
    	} catch (IOException ioException) {
    		// Ignored at this place.
    	}
    	
    	return appendable;
    }

    /**
     * Clones declaration object.
     */
    public Object clone()
           throws CloneNotSupportedException {
        return super.clone();
    }

    /**
     * @return true if the type of this declaration is a java array (string is no java array).
     */
    public boolean isArray() {
      return (Kind.FIXEDVECTOR.equals(kind) || Kind.DYNAMICVECTOR.equals(kind))
          && (! typeMapping.isStringType()); 
    }

    /**
     * Identifier.
     */
    private final String identifier;
   
    /**
     * Type specifier.
     */
    private final JrpcgenTypeMapping typeMapping;

    /**
     * Kind of declaration (scalar, fixed size vector, dynamic vector).
     *
     * @see JrpcgenDeclaration#Kind
     */
    private final Kind kind;

    /**
     * Fixed size or upper limit for size of vector.
     */
    private final String size;

    private String javaType;

}

// End of JrpcgenDeclaration.java