/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/apps/jrpcgen/JrpcgenStruct.java,v 1.1 2003/08/13 12:03:47 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>JrpcgenStruct</code> class represents a single structure defined
 * in an rpcgen "x"-file.
 *
 * @version $Revision: 1.1 $ $Date: 2003/08/13 12:03:47 $ $State: Exp $ $Locker:  $
 * @author Harald Albrecht
 */
public class JrpcgenStruct extends JrpcgenComplexType {

    /**
     * Constructs a <code>JrpcgenStruct</code> and sets the identifier and all
     * its attribute elements.
     *
     * @param context The context the new struct belongs to.
     * @param identifier Identifier to be declared.
     * @param elements Table of declarations based on class
     *   {@link JrpcgenDeclaration}.
     */
    public JrpcgenStruct(JrpcgenContext context, String identifier, JrpcgenDeclaration.Table elements)
    {
    	super(getIdentifier(identifier), Type.STRUCT);
    	this.context = context;
        this.elements = elements;
    }

    private static String getIdentifier(String identifier) {
    	return identifier != null ? identifier
    			: "unnamed_struct_" + NEXT_UNNAMED_STRUCT_ID++;
    }

    /**
     * Returns the elements of this structure.
     * 
     * @return The elements of this structure.
     */
    final public JrpcgenDeclaration.Table getElements() {
    	return elements;
    }
    
    /**
     * Generates a Java file defining the class, which will represent an XDR struct.
     * The generated class will implement the interface {@link XdrAble}.
     * 
     * <p>At least, the generated class will offer members for the structure elements,
     * a default constructor, a decoding constructor as well as an encoding and a
     * decoding method. If the structure is used in a fixed and/or dynamic vector context,
     * static methods providing encoding and decoding of a vector of the structure are
     * additionally written. Dependant on the options the generated class may vary:
     * <ul>
     * <li>The option {@code -ser} (serializable) is given: The generated class will
     * implement the interface {@link Serializable}</li>
     * <li>The option {@code -bean} is given: The generated class will provide
     * getters and setters to access the member fields. The member fields
     * will have a reduced visibility allowing direct access to the struct and
     * its extending classes, only.</li>
     * <li>The option {@code -noToString} is <strong><em>not</em></strong> given:
     * The generated class will provide an own implementation of the
     * {@code toString()}-method.</li>
     * <li>The option {@code -noEquals} is <strong><em>not</em></strong> given:
     * The generated class will provide own implementations of the
     * {@code equlas()}- and {@code hash()}-method.</li>
     * <li>The option {@code -noValueCtor} is <strong><em>not</em></strong> given:
     * The generated class will provide a value constructor, which offers a
     * parameter list corresponding to the elements of the structure.</li>
     * </ul>
     */
    @Override
    public void generateJavaFile() {
        //
        // Create new source code file containing a Java class representing
        // the XDR struct.
        //
    	JrpcgenOptions options = context.options();
    	
    	/*
    	 * Define the visibility of the member fields dependant on whether the generated
    	 * class gets bean character or not. With bean character the member fields will be visible to the
    	 * inheritance chain only. Otherwise the member fields will be world accessible. 
    	 */
        String access = options.makeBean ? "protected" : "public";
        
        try (JrpcgenJavaFile javaFile = JrpcgenJavaFile.open(getIdentifier(), context)) {
        	/*
        	 * Write the header including standard imports from the ONC/RPC runtime
        	 * library.
        	 */
        	javaFile.writeHeader(true);

            writeDocumentation(javaFile);
            
            javaFile.newLine().beginTypedefinition("public class ").append(getIdentifier())
            	.append(" implements XdrAble");
            
            if ( options.makeSerializable ) {
                javaFile.append(", java.io.Serializable");
            }
            
            javaFile.append(" {").newLine();

            //
            // Generate declarations of all members of this XDR struct. This the
            // perfect place to also update the hash function using the elements
            // together with their type.
            //
            boolean useIteration = false;
            JrpcgenSHA hash = context.createSHA(getIdentifier());
            
            for ( JrpcgenDeclaration declaration : elements.getItemList() ) {
            	declaration.updateHash(hash);
            	declaration.writeMemberDeclaration(javaFile, access, options.initStrings);

                //
                // If the last element in the XDR struct is a reference to
                // the type of the XDR struct (that is, a linked list), then
                // we can convert this tail recursion into an iteration,
                // avoiding deep recursions for large lists.
                //
                if ( (declaration == elements.getLastItem())
                     && JrpcgenDeclaration.Kind.INDIRECTION.equals(declaration.getKind())
                     && (declaration.getType().equals(getIdentifier())) ) {
                    useIteration = true;
                }
            }

            //
            // Generate serial version unique identifier
            //
            if ( options.makeSerializable ) {
            	javaFile.newLine().beginLine().append("private static final long serialVersionUID = ")
            		.append(hash.toString()).println("L;");
            	
                if ( options.makeBean ) {
                	//
                	// Also generate accessors (getters and setters) so that
                	// class can be used as a bean.
                	//
                	for ( JrpcgenDeclaration declaration : elements.getItemList() ) {
                    	declaration.writeGettersAndSetters(javaFile.newLine());
                	}
                }
            }

            //
            // Now generate code for encoding and decoding this class (structure).
            //
            /*
             * Generate the default constructor.
             */
            javaFile.newLine().beginPublicConstructor(getIdentifier()).endSignature().endMethod();

            /*
             * Generate the 'decoding' constructor, which will construct a concrete
             * instance out of an XDR decoding stream.
             */
            javaFile.newLine().beginPublicConstructor(getIdentifier()).parameter("XdrDecodingStream", "xdr")
            	.exceptions("OncRpcException", "IOException").endSignature();
            javaFile.beginLine().println("xdrDecode(xdr);");
            javaFile.endMethod();
            
            /*
             * If specified write a value constructor, which offers a parameter list
             * corresponding to the member field list.
             */
            if(options.generateValueConstructor()) {
            	writeValueConstructor(javaFile);
            }

            /*
             * Write the XDR encoding method. In case the last member field of the struct
             * is an indirection to another instance of the generated struct, the encoding sequence
             * will be written as a loop instead of a recursive call chain. 
             */
            javaFile.newLine().beginLine().println("@Override");
            javaFile.beginPublicMethod().resultType("void").name("xdrEncode").parameter("XdrEncodingStream", "xdr")
            	.exceptions("OncRpcException", "IOException").endSignature();
            
            if ( useIteration ) {
            	javaFile.beginBlock().append("for (").append(getIdentifier()).space().append("$current = this; $current != null; $current = $current.")
            		.append(elements.getLastItem().getIdentifier()).println(") {");
            	
                for (final JrpcgenDeclaration declaration : elements.getItemList()) {
                	if (declaration != elements.getLastItem()) {
                        declaration.writeEncodingPart(javaFile, getIdentifier(), "$current", context);
                	} else {
                		JrpcgenBaseType.BOOL.writeXdrEncodingCall(javaFile.beginLine(), "xdr", new JrpcgenJavaFile.Expression() {
							
							@Override
							public void writeTo(JrpcgenJavaFile javaFile) {
								javaFile.append("$current.").append(declaration.getIdentifier()).append(" != null");
							}
						});
                		javaFile.semicolon().newLine();
                	}
                }
                
                javaFile.endBlock().println('}');
            } else {
                for ( JrpcgenDeclaration declaration : elements.getItemList() ) {
                	declaration.writeEncodingPart(javaFile, getIdentifier(), context);
                }
            }
            javaFile.endMethod();

            /*
             * Write the XDR decoding method. In case the last member field of the struct
             * is an indirection to another instance of the generated struct, the decoding
             * sequence will be written as a loop instead of a recursive call chain.
             */
            javaFile.newLine().beginLine().println("@Override");
            javaFile.beginPublicMethod().resultType("void").name("xdrDecode").parameter("XdrDecodingStream", "xdr")
            	.exceptions("OncRpcException", "IOException").endSignature();
            
            if ( useIteration ) {
            	javaFile.beginBlock().append("for (").append(getIdentifier()).space().append("$current = this; $current != null; $current = $current.")
            		.append(elements.getLastItem().getIdentifier()).println(") {");
            	
            	for (JrpcgenDeclaration declaration : elements) {
            		if (declaration != elements.getLastItem()) {
            			declaration.writeDecodingPart(javaFile, getIdentifier(), "$current", context);
            		} else {
                		javaFile.beginLine().append("$current.").append(declaration.getIdentifier()).append(" = ");
                		JrpcgenBaseType.BOOL.writeXdrDecodingCall(javaFile, "xdr");
                		javaFile.append(" ? ");
                		writeXdrConstructorCall(javaFile, (String)null);
                		javaFile.println(" : null;");
            		}
            	}
            	
            	javaFile.endBlock().println('}');
            } else {
                for ( JrpcgenDeclaration declaration : elements.getItemList() ) {
                	declaration.writeDecodingPart(javaFile, getIdentifier(), context);
                }
            }
            javaFile.endMethod();
            
            if (options.generateToStringMethod()) {
                writeToStringMethod(javaFile);
            }
            
            if (options.generateEqualsMethod()) {
            	writeEqualsMethod(javaFile);
            }

            /*
             * If the structure will be used in a fixed and/or dynamic vector
             * context, the following call will write the appropriate
             * static encoding and decoding methods.
             */
            writeXdrVectorCodingMethods(javaFile, context);
            
            //
            // Close class...
            //
            javaFile.newLine().endTypedefinition();
        } catch (IOException ioException) {
    		/*
    		 * The IOexception is thrown by the close()-method
    		 * of the Java source file only.
    		 */
    		System.err.println("Cannot close source code file: "
    				+ ioException.getLocalizedMessage());
        }
    }
    
    /**
     * Returns a string representation of the structure.
     * 
     * @return A string describing the structure.
     */
    public String toString() {
    	return dump(new StringBuilder()).toString();
    }

    /**
     * Dumps the structure together with its attribute elements to
     * <code>System.out</code>.
     */
    public void dump() {
    	dump(System.out).println();
    }

    /**
     * Dumps a description of the structure to the passed target object,
     * which implements the appendable interface {@link Appendable}.
     * 
     * @param <T> A type extending or implementing the interface {@code java.lang.Appendable}.
     * @param appendable An object of type {@code T} 
     * @return The passed object with a dump of this structure appended.
     */
    public <T extends Appendable> T dump(T appendable) {
    	try {
        	appendable.append(Type.STRUCT.name()).append(' ').append(getIdentifier()).append(':').append(JrpcgenContext.newline());
        	
        	for (JrpcgenDeclaration element : elements.getItemList()) {
        		element.dump(appendable.append("  ")).append(JrpcgenContext.newline());
        	}
    	} catch (IOException ioException) {
    		// Ignored at this place.
    	}
    	
    	return appendable;
    }
    
    private void writeValueConstructor(JrpcgenJavaFile javaFile)
    {
    	JrpcgenJavaFile.MethodSignature signature = javaFile.newLine().beginPublicConstructor(getIdentifier());
    	
    	for ( JrpcgenDeclaration declaration : elements ) {
    		signature.parameterLineBreak(declaration.getJavaType(), declaration.getIdentifier(), 80);
    	}
    	
    	signature.endSignature();

    	for ( JrpcgenDeclaration declaration : elements.getItemList() ) {
    		javaFile.beginLine().append("this.").append(declaration.getIdentifier())
    			.append(" = ").append(declaration.getIdentifier()).println(";");
    	} 
    	
    	javaFile.endMethod();
    }

    private void writeToStringMethod(JrpcgenJavaFile javaFile) {
    	javaFile.newLine().beginLine().println("@Override");
    	javaFile.beginPublicMethod().resultType("String").name("toString").endSignature();
        javaFile.beginLine().append("return \"").append(getIdentifier()).println(" [\"");
        
        for ( JrpcgenDeclaration declaration : elements.getItemList() ) {
        	declaration.writeToStringPart(javaFile);
            
            if(declaration != elements.getLastItem())
            	javaFile.append(" + \", \"").newLine();
            else
            	javaFile.newLine();
        }     
        
        javaFile.beginLine().println("+ \"]\";");
        javaFile.endMethod();
    }
    
    private void writeEqualsMethod(JrpcgenJavaFile javaFile) {
        javaFile.newLine().beginLine().println("@Override");
        javaFile.beginPublicMethod().resultType("boolean").name("equals").parameter("Object", "obj").endSignature();
        javaFile.beginLine().println("if (this == obj) return true;");
        javaFile.beginLine().println("if (obj == null) return false;");
        javaFile.beginLine().println("if (getClass() != obj.getClass()) return false;");
        javaFile.beginLine().append(getIdentifier()).append(" other = (").append(getIdentifier()).println(")obj;");

        for ( JrpcgenDeclaration declaration : elements.getItemList() ) {
        	declaration.writeEqualsPart(javaFile, context);
        }
        
        javaFile.beginLine().println("return true;");
        javaFile.endMethod();
        
        // generate hashCode() also
        javaFile.newLine().beginLine().println("@Override");
        javaFile.beginPublicMethod().resultType("int").name("hashCode").endSignature()
        	.beginLine().println("return XdrHashCode.hashCode(this);");
        javaFile.endMethod();
    }
    
    /**
     * Global next available id for an unnamed inner structure.
     */
    private static long NEXT_UNNAMED_STRUCT_ID = 1;
    
    /**
     * A reference to the context the struct belongs to.
     */
    private final JrpcgenContext context;
    
    /**
     * Contains elements of structure. The elements are of class
     * {@link JrpcgenDeclaration}.
     */
    private final JrpcgenDeclaration.Table elements;


}

// End of JrpcgenStruct.java