/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/apps/jrpcgen/JrpcgenUnion.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;
import java.util.Vector;

/**
 * The <code>JrpcgenUnion</code> class represents a single union 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 JrpcgenUnion extends JrpcgenComplexType {

    /**
     * Returns just the identifier.
     */
    public String toString() {
        return "union " + getIdentifier() + "(" + discriminant + "){" + elements + "}";
    }

    /**
     * Constructs a <code>JrpcgenUnion</code> and sets the identifier, the
     * descrimant element as well as all attribute elements.
     *
     * @param context The context the nwe union belongs to.
     * @param identifier Identifier to be declared.
     * @param discriminant Discriminant element of class
     *   {@link JrpcgenDeclaration}.
     * @param elements Vector of atrribute elements of class
     *   {@link JrpcgenDeclaration}.
     */
    public JrpcgenUnion(JrpcgenContext context, String identifier, JrpcgenDeclaration discriminant, Vector<JrpcgenUnionArm> elements) {
    	super(identifier, Type.UNION);
        this.context = context;
        this.discriminant = discriminant;
        this.elements = elements;
    }

    final public JrpcgenDeclaration getDiscriminant() {
    	return discriminant;
    }
    
    final public Vector<JrpcgenUnionArm> getElements() {
    	return elements;
    }
    
    @Override
    public void generateJavaFile() {
        //
        // Create new source code file containing a Java class representing
        // the XDR struct.
        //
    	JrpcgenOptions options = context.options();
        
        try (JrpcgenJavaFile javaFile = JrpcgenJavaFile.open(getIdentifier(), context)) {
        	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(" {");
            
            //
            // Note that the descriminant can not be of an array type, string, etc.
            // so we don't have to handle all the special cases here.
            //
            discriminant.writeMemberDeclaration(javaFile.newLine(), "public", context.options().initStrings);

            JrpcgenSHA hash = context.createSHA(getIdentifier());
            
            for (JrpcgenUnionArm arm : elements) {
            	arm.updateHash(hash);
            	arm.writeMemberDeclaration(javaFile, "public", context.options().initStrings);
            }

            //
            // Generate serial version unique identifier
            //
            if ( options.makeSerializable ) {
            	javaFile.newLine().beginLine().append("private static final long serialVersionUID = ")
            		.append(hash.toString()).println("L;");
            }

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

            javaFile.newLine().beginPublicConstructor(getIdentifier()).parameter("XdrDecodingStream", "xdr")
            	.exceptions("OncRpcException, IOException").endSignature()
            	.beginLine().println("xdrDecode(xdr);");
            javaFile.endMethod();

            if(options.generateValueConstructor())
            {
            	final String valueName = ("value".equals(discriminant.getIdentifier()) ? "value_" : "value");

            	javaFile.newLine().beginPublicConstructor(getIdentifier())
            		.parameter(discriminant.getTypeMapping().getJavaName(), discriminant.getIdentifier()).parameter("Object", valueName)
            		.endSignature();

            	javaFile.beginLine().append("this.").append(discriminant.getIdentifier())
            		.append(" = ").append(discriminant.getIdentifier()).println(';');

            	writeElements(javaFile, new ElementWriter() {
            		@Override
            		public void write(JrpcgenUnionArm arm, JrpcgenJavaFile javaFile) {
            			arm.writeConstructorArm(javaFile, valueName, context);
            		}
            	});

            	javaFile.endMethod();
            }

            javaFile.newLine().beginLine().println("@Override");
            javaFile.beginPublicMethod().resultType("void").name("xdrEncode").parameter("XdrEncodingStream", "xdr")
            	.exceptions("OncRpcException", "IOException").endSignature();
            
            discriminant.writeEncodingPart(javaFile, getIdentifier(), context);
            
            writeElements(javaFile, new ElementWriter() {
            	@Override
            	public void write(JrpcgenUnionArm arm, JrpcgenJavaFile javaFile) {
            		arm.writeEncodingPart(javaFile, getIdentifier(), context);
            	}
            });
            
            javaFile.endMethod();

            javaFile.newLine().beginLine().println("@Override");
            javaFile.beginPublicMethod().resultType("void").name("xdrDecode").parameter("XdrDecodingStream", "xdr")
            	.exceptions("OncRpcException", "IOException").endSignature();

            discriminant.writeDecodingPart(javaFile, getIdentifier(), context);
            
            writeElements(javaFile, new ElementWriter() {
            	@Override
            	public void write(JrpcgenUnionArm arm, JrpcgenJavaFile javaFile) {
            		arm.writeDecodingPart(javaFile, getIdentifier(), context);
            	}
            });

            javaFile.endMethod();
     
            if(options.generateToStringMethod())
            {
            	javaFile.newLine().beginLine().println("@Override");
            	javaFile.beginPublicMethod().resultType("String").name("toString").endSignature();
            	javaFile.beginLine().append("String ret = \"").append(getIdentifier()).println("=\";");
            	
            	writeElements(javaFile, new ElementWriter() {
            		@Override
            		public void write(JrpcgenUnionArm arm, JrpcgenJavaFile javaFile) {
            			arm.writeToStringPart(javaFile, context);
            		}
            	});

            	javaFile.beginLine().println("return ret;");
            	javaFile.endMethod();
            }

            if(options.generateEqualsMethod())
            {
            	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;");
            	discriminant.writeEqualsPart(javaFile, context);              
            	javaFile.beginLine().println("boolean ret = true;");

            	writeElements(javaFile, new ElementWriter() {
            		@Override
            		public void write(JrpcgenUnionArm arm, JrpcgenJavaFile javaFile) {
            			arm.writeEqualsPart(javaFile, context);
            		}
            	});

            	javaFile.beginLine().println("return ret;");
            	javaFile.endMethod();

            	javaFile.newLine().beginLine().println("@Override");
            	javaFile.beginPublicMethod().resultType("int").name("hashCode").endSignature()
            		.beginLine().println("return XdrHashCode.hashCode(this);");
            	javaFile.endMethod();
            }
           
            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());
        }
    } 
    
    /**
     * Dumps the union together with its attribute elements end the
     * descriminant to <code>System.out</code>.
     */
    public void dump() {
    	dump(System.out).println();
    }
    
    public <T extends Appendable> T dump(T appendable) {
    	try {
    		appendable.append(Type.UNION.name()).append(' ').append(getIdentifier()).append(':').append(JrpcgenContext.newline())
    			.append("  switch (").append(discriminant.getType()).append(' ').append(discriminant.getIdentifier()).append(')')
    			.append(JrpcgenContext.newline());
    	
    		for (JrpcgenUnionArm element : elements) {
    			element.dump(appendable.append("    ")).append(JrpcgenContext.newline());
    		}
    	} catch (IOException ioException) {
    		// Ignored at this point
    	}
    	
    	return appendable;
    }
    
    private interface ElementWriter {
    	void write(JrpcgenUnionArm arm, JrpcgenJavaFile javaFile);
    }

    private void writeElements(JrpcgenJavaFile javaFile, ElementWriter elementWriter) {
    	if (discriminant.getTypeMapping().isBooleanType()) {
    		javaFile.beginBlock().append("if ( ").append(discriminant.getIdentifier())
    			.println(" == true ) {");
    		elementWriter.write(elements.get(0), javaFile);
    		
    		if (elements.get(1).hasPayload()) {
        		javaFile.elseBlock().println("} else {");
        		elementWriter.write(elements.get(1), javaFile);
    		}
    		
    		javaFile.endBlock().println("}");
    	} else {
    		javaFile.beginBlock().append("switch ( ").append(discriminant.getIdentifier())
    			.println(" ) {");

    		for (JrpcgenUnionArm arm : elements) {
    			arm.startCase(javaFile, getIdentifier(), context);
    			elementWriter.write(arm, javaFile);
    			arm.endCase(javaFile);
    		}

    		javaFile.endBlock().println("}");
    	} /* endif (Is the discriminator of boolean type?) */
    }
    
    /**
     * A reference to the context the union belongs to.
     */
    private final JrpcgenContext context;
    
    /**
     * {@link JrpcgenDeclaration} of descriminant element (containing its
     * identifier and data type).
     */
    private final JrpcgenDeclaration discriminant;

    /**
     * Contains arms of union. The arms are of class
     * {@link JrpcgenDeclaration}. The keys are the descriminant values.
     */
    private final Vector<JrpcgenUnionArm> elements;


}

// End of JrpcgenUnion.java