/*
 * $Header: /home/harald/repos/remotetea.sf.net/remotetea/src/org/acplt/oncrpc/apps/jrpcgen/JrpcgenProcedureInfo.java,v 1.3 2003/08/14 11:26:50 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.Iterator;

/**
 * The <code>JrpcgenProcedureInfo</code> class contains information about a
 * specific version of an ONC/RPC program as defined in an rpcgen "x"-file.
 *
 * @version $Revision: 1.3 $ $Date: 2003/08/14 11:26:50 $ $State: Exp $ $Locker:  $
 * @author Harald Albrecht
 */
class JrpcgenProcedureInfo extends JrpcgenDocumentable implements JrpcgenItem {

	public static class Table extends JrpcgenItemTable<JrpcgenProcedureInfo> {}
	
    /**
     * Procedure number assigned to the procedure of a particular verions of
     * an ONC/RPC program. This attribute contains either an integer literal
     * or an identifier (which must resolve to an integer).
     */
    public String procedureNumber;

    /**
     * Identifier assigned to the procedure number of an a particular
     * procedure of a particular version of an ONC/RPC program.
     */
    public String procedureId;

    /**
     * Parameter(s) to the remote procedure.
     */
    public JrpcgenParamInfo.Table parameters;

    /**
     * Constructs a new <code>JrpcgenProcedureInfo</code> object containing
     * information about a programs' version and a set of procedures
     * defined by this program version.
     *
     * @param jdoc Java Doc comment out of .x file
     * @param procedureId Identifier assigned to the procedure of a particular
     *   version of an ONC/RPC program.
     * @param procedureNumber Procedure number assigned to remote procedure.
     * @param resultType Type specifier of result returned by remote procedure.
     * @param parameters Type specifier of parameter to the remote procedure.
     */
    public JrpcgenProcedureInfo(String jdoc, String procedureId, String procedureNumber,
                                JrpcgenTypeInfo resultTypeInfo, JrpcgenParamInfo.Table parameters) {
        super(jdoc);
        this.procedureId = procedureId;
        this.procedureNumber = procedureNumber;
        this.parameters = parameters;
        this.resultTypeInfo = resultTypeInfo;
    }
    
    public JrpcgenProcedureInfo(String procedureId, String procedureNumber,
    		JrpcgenTypeInfo resultTypeInfo, JrpcgenParamInfo.Table parameters) {
    	this(null, procedureId, procedureNumber, resultTypeInfo, parameters);
    }
    
    public String getResultType() {
    	return resultTypeInfo.getDefinitionName();
    }
    
    public void setConstant(JrpcgenConst constant) {
    	this.constant = constant;
    }
    
    public JrpcgenConst getConstant() {
    	return constant;
    }
    
    public void writeDocumentation(JrpcgenJavaFile javaFile) {
    	/*
    	 * In case no documentation is given, a default documentation
    	 * is written.
    	 */
    	if (documentationIsProvided()) {
    		/*
    		 * The x-file provided a documentation for this
    		 * procedure. This one will be written to the file.
    		 * The call is passed to the base implementation
    		 * of this method.
    		 */
    		super.writeDocumentation(javaFile);
    	} else {
    		/*
    		 * The x-file did not provide a documentation for this
    		 * method. A default documentation is written to the
    		 * Java file.
    		 */
    		javaFile.beginLine().println("/**");
    		javaFile.beginLine().append(" * Call remote procedure ").append(procedureId).println('.');
    		
            //
            // If there are no parameters, skip the parameter documentation
            // section, otherwise dump javadoc @param entries for every
            // parameter encountered.
            //
            if ( parameters != null ) {
                for ( JrpcgenParamInfo param : parameters ) {
                	javaFile.beginLine().append(" * @param ").append(param.getName())
                		.append(" parameter (of type ").append(param.getTypeInfo().getJavaName())
                		.println(") to the remote procedure call.");
                }
            } /* endif (Are there parameters passed to the remote procedure?) */
            
            //
            // Only generate javadoc for result, when it is non-void.
            //
            if (JrpcgenBaseType.VOID.getJavaName().compareTo(resultTypeInfo.getJavaName()) != 0) {
            	javaFile.beginLine().append(" * @return Result from remote procedure call (of type ")
            		.append(getResultType()).println(").");
            } /* endif (The result type is differen from 'void') */
            

            javaFile.beginLine().println(" * @throws OncRpcException if an ONC/RPC error occurs.");
            javaFile.beginLine().println(" * @throws IOException if an I/O error occurs.");
            javaFile.beginLine().append(" */");
            appendDeprecatedOrNothing(javaFile);
    	} /* endif (Does the x-file provided a documentation?) */
    }
    
    public void writeClientStubSignature(JrpcgenJavaFile javaFile) {
        //
        // Now emit the method signature, checking each argument for
        // specials, like enumerations. Also take care of no parameters
        // at all... Fortunately, this is relatively easy as we do not
        // need to care about parameter wrapping/unwrapping here.
        //            
        writeDocumentation(javaFile);
        writeSignature(javaFile.newLine().beginPublicMethod(), /* withCallInfo */ false, /* finalParameters */ true)
        	.exceptions("OncRpcException", "IOException").endSignature();
    }
    
    public void writeServerStubDeclaration(JrpcgenJavaFile javaFile, JrpcgenContext context) {
        //
        // Now emit the method signature, checking each argument for
        // specials, like enumerations. Also take care of no parameters
        // at all... Fortunately, this is relatively easy as we do not
        // need to care about parameter wrapping/unwrapping here.
        //            
        writeDocumentation(javaFile);
        writeSignature(javaFile.newLine().beginPublicAbstractMethod(), context.options().withCallInfo, /* finalParameters */ false)
        	.endSignature().newLine();
    }
    
    public void writeServerStubMethodCall(JrpcgenJavaFile javaFile, final JrpcgenContext context) {
    	JrpcgenJavaFile.Expression procedureCall;
    	
    	javaFile.elseBlock().append("case ").append(this.constant.getValueAsRValue(null)).println(": {");
    	
    	if (parameters == null) {
    		javaFile.beginLine().append("call.retrieveCall(");
    		JrpcgenBaseType.VOID.writeXdrConstructorCall(javaFile, (String)null);
    		javaFile.rightParenthesis().semicolon().newLine();
    		
    		procedureCall = new JrpcgenJavaFile.Expression() {
				
				@Override
				public void writeTo(JrpcgenJavaFile javaFile) {
					javaFile.append(procedureId).leftParenthesis();
					
					if (context.options().withCallInfo) {
						javaFile.append("call");
					}
					
					javaFile.rightParenthesis();
				}
			};
    	} else {
        	writeXdrRetrieveArgument(javaFile, "args$", context);
        	javaFile.beginLine().println("call.retrieveCall(args$);");
        	
        	procedureCall = new JrpcgenJavaFile.Expression() {
    			
    			@Override
    			public void writeTo(JrpcgenJavaFile javaFile) {
    				boolean parametersStarted = false;
    				
    				javaFile.append(procedureId).leftParenthesis();
    				
    		    	if (context.options().withCallInfo) {
    		    		javaFile.append("call");
    		    		parametersStarted = true;
    		    	}
    		    	
    	    		if (parameters.size() == 1) {
    	    			JrpcgenParamInfo parameter = parameters.getFirstItem();
    	    			if (parametersStarted) {
    	        			javaFile.append(", ");
    	    			}
    	    			
    	    			parameter.getTypeInfo().writeXdrToJava(javaFile, "args$");
    	    		} else {
    	            	for (JrpcgenParamInfo parameter : parameters) {
    	            		if (parametersStarted) {
    	            			javaFile.append(", args$.").append(parameter.getName());
    	            		} else {
    	            			javaFile.append("args$.").append(parameter.getName());
    	            			parametersStarted = true;
    	            		}
    	            	}
    	    		}
    		    	
    		    	javaFile.rightParenthesis();
    			}
    		};

    	}
    	
    	/*
    	 * Does the procedure return something?
    	 */
    	if (resultTypeInfo.isVoid()) {
    		javaFile.beginLine().expression(procedureCall).semicolon()
    			.beginNewLine().append("call.reply(");
   			resultTypeInfo.writeXdrConstructorCall(javaFile, (String)null);
    		javaFile.rightParenthesis().semicolon().newLine();
    	} else {
    		javaFile.beginLine().append(resultTypeInfo.getXdrClass()).space().append("result$ = ");
    		resultTypeInfo.writeJavaToXdr(javaFile, procedureCall);    		
        	javaFile.semicolon().beginNewLine().println("call.reply(result$);");
    	}
    	
    	javaFile.beginLine().println("break;").elseBlock().println('}');
    }
    
    public void writeInterfaceDeclaration(JrpcgenJavaFile javaFile, JrpcgenContext context) {
        //
        // Now emit the method signature, checking each argument for
        // specials, like enumerations. Also take care of no parameters
        // at all... Fortunately, this is relatively easy as we do not
        // need to care about parameter wrapping/unwrapping here.
        //            
        writeDocumentation(javaFile);
        writeSignature(javaFile.newLine().beginInterfaceMethod(), /* callinfo */ false, /* finalParameters */ false)
        	.exceptions("OncRpcException", "IOException").endSignature().newLine();
    }
    
    public JrpcgenJavaFile.MethodSignature writeSignature(JrpcgenJavaFile.MethodSignature methodSignature, boolean withCallInfo, boolean finalParameters) {
    	methodSignature.resultType(resultTypeInfo.getJavaName()).name(procedureId);
    	
    	/*
    	 * If desired the call information is passed as
    	 * first parameter.
    	 */
    	if (withCallInfo) {
    		methodSignature.parameter("OncRpcCallInformation", "call$");
    	}
    	
    	/*
    	 * Either parameters is 'null' or the table contains
    	 * at least one parameter. The latter case is considered
    	 * in here.
    	 */
    	if (parameters != null) {
 			if (parameters.size() == 1) {
 				parameters.getFirstItem().writeMethodParameter(methodSignature);
 			} else {
 	 			for (JrpcgenParamInfo parameter : parameters) {
 	 				if (finalParameters) {
 	 	 				parameter.writeFinalMethodParameter(methodSignature);
 	 				} else {
 	 					parameter.writeMethodParameter(methodSignature);
 	 				}
 	    		}
 			}
    	}
    	
    	return methodSignature;
    }
    
    public String writeXdrCallArgument(JrpcgenJavaFile javaFile, JrpcgenContext context) {
    	String argumentName = null;
    	
    	if (parameters == null) {
    		JrpcgenBaseType voidType = JrpcgenBaseType.VOID;
        	argumentName = "args$";
        	javaFile.beginLine().append(voidType.getXdrClass()).space().append(argumentName).append(" = ");
        	voidType.writeXdrConstructorCall(javaFile, (String)null);
        	javaFile.semicolon().newLine();
        } else if (parameters.size() == 1){
        	JrpcgenParamInfo parameter = parameters.getFirstItem();
        	argumentName = parameter.writeXdrCallArgument(javaFile, context);
        } else {
        	argumentName = "args$";
        	javaFile.beginBlock().append("XdrAble ").append(argumentName).println(" = new XdrAble() {");
        	javaFile.beginPublicMethod().resultType("void").name("xdrEncode").parameter("XdrEncodingStream", "xdr")
        		.exceptions("OncRpcException", "IOException").endSignature();
        	
        	for (JrpcgenParamInfo parameter : parameters) {
        		parameter.writeXdrEncodingCall(javaFile, "xdr");
        	}
        	
        	javaFile.endMethod();
        	
        	javaFile.beginPublicMethod().resultType("void").name("xdrDecode").parameter("XdrDecodingStream", "xdr")
    			.exceptions("OncRpcException", "IOException").endSignature();
        	javaFile.endMethod();
        	
        	javaFile.endBlock().println("};");
        }
    	
    	return argumentName;
    }
    
    public void writeXdrRetrieveArgument(JrpcgenJavaFile javaFile, String argumentName, JrpcgenContext context) {
        if (parameters.size() == 1){
        	JrpcgenTypeInfo typeInfo = parameters.getFirstItem().getTypeInfo();
        	javaFile.beginLine().append(typeInfo.getXdrClass()).space().append(argumentName).append(" = ");
        	typeInfo.writeXdrConstructorCall(javaFile, (String)null);
        	javaFile.semicolon().newLine();
        } else {
        	javaFile.beginBlock().println("class XdrAble$ implements XdrAble {");
        	
        	/*
        	 * Declare the arguments as public class members.
        	 */
        	for (JrpcgenParamInfo parameter : parameters) {
        		parameter.appendAsDeclaration(javaFile.beginLine().append("public "));
        	}
        	
        	javaFile.beginPublicMethod().resultType("void").name("xdrEncode").parameter("XdrEncodingStream", "xdr")
        		.exceptions("OncRpcException", "IOException").endSignature().endMethod();
        	
        	javaFile.beginPublicMethod().resultType("void").name("xdrDecode").parameter("XdrDecodingStream", "xdr")
    			.exceptions("OncRpcException", "IOException").endSignature();
        	
        	for (JrpcgenParamInfo parameter : parameters) {
        		parameter.writeXdrDecodingCall(javaFile, "xdr");
        	}
        	
        	javaFile.endMethod();
        	
        	javaFile.endBlock().println("};");
        	
        	javaFile.beginLine().append("XdrAble$ ").append(argumentName).println(" = new XdrAble$();");
        }
    }
    
    public void writeXdrResultArgument(JrpcgenJavaFile javaFile, String resultName) {
    	javaFile.beginLine().append(resultTypeInfo.getXdrClass()).space().append(resultName).append(" = ");
    	resultTypeInfo.writeXdrConstructorCall(javaFile, (String)null);
    	javaFile.semicolon().newLine();
    }
        
    public void writeReturnStatement(JrpcgenJavaFile javaFile, String resultName) {
    	if (! resultTypeInfo.isVoid()) {
	    	javaFile.beginLine().keywordReturn().space();
	    	resultTypeInfo.writeXdrToJava(javaFile, resultName);
	    	javaFile.semicolon().newLine();
    	}
    }
    
    @Override
    public String getIdentifier() {
    	return procedureId;
    }
    
    public <T extends Appendable> T dump(T appendable) {
    	try {
    		appendable.append("PROCEDURE ").append(resultTypeInfo.getDefinitionName())
    			.append(" ").append(procedureId).append('(');
    		
    		if ((parameters == null) || parameters.isEmpty()) {
    			appendable.append(JrpcgenBaseType.VOID.getDefinitionName());
    		} else {
    			Iterator<JrpcgenParamInfo> parameterIterator = parameters.iterator();
    			    			
    			parameterIterator.next().dump(appendable);
    			
    			while (parameterIterator.hasNext()) {
    				parameterIterator.next().dump(appendable.append(", "));
    			}
    		}
    		
    		appendable.append(") = ").append(procedureNumber);
    	} catch (IOException ioException) {
    		// Ignored at this place.
    	}
    	
    	return appendable;
    }
    
    private JrpcgenConst constant;
    private JrpcgenTypeInfo resultTypeInfo;
}

// End of JrpcgenProcedureInfo.java