/*
 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.rmi.rmic.newrmic.jrmp;

import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.Type;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import sun.rmi.rmic.newrmic.BatchEnvironment;
import sun.rmi.rmic.newrmic.IndentingWriter;

import static sun.rmi.rmic.newrmic.Constants.*;
import static sun.rmi.rmic.newrmic.jrmp.Constants.*;

/**
 * Writes the source code for the stub class and (optionally) skeleton
 * class for a particular remote implementation class.
 *
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 *
 * @author Peter Jones
 **/
class StubSkeletonWriter {

    /** rmic environment for this object */
    private final BatchEnvironment env;

    /** the remote implementation class to generate code for */
    private final RemoteClass remoteClass;

    /** version of the JRMP stub protocol to generate code for */
    private final StubVersion version;

    /*
     * binary names of the stub and skeleton classes to generate for
     * the remote class
     */
    private final String stubClassName;
    private final String skeletonClassName;

    /* package name and simple names of the stub and skeleton classes */
    private final String packageName;
    private final String stubClassSimpleName;
    private final String skeletonClassSimpleName;

    /** remote methods of class, indexed by operation number */
    private final RemoteClass.Method[] remoteMethods;

    /**
     * Names to use for the java.lang.reflect.Method static fields in
     * the generated stub class corresponding to each remote method.
     **/
    private final String[] methodFieldNames;

    /**
     * Creates a StubSkeletonWriter instance for the specified remote
     * implementation class.  The generated code will implement the
     * specified JRMP stub protocol version.
     **/
    StubSkeletonWriter(BatchEnvironment env,
                       RemoteClass remoteClass,
                       StubVersion version)
    {
        this.env = env;
        this.remoteClass = remoteClass;
        this.version = version;

        stubClassName = Util.binaryNameOf(remoteClass.classDoc()) + "_Stub";
        skeletonClassName =
            Util.binaryNameOf(remoteClass.classDoc()) + "_Skel";

        int i = stubClassName.lastIndexOf('.');
        packageName = (i != -1 ? stubClassName.substring(0, i) : "");
        stubClassSimpleName = stubClassName.substring(i + 1);
        skeletonClassSimpleName = skeletonClassName.substring(i + 1);

        remoteMethods = remoteClass.remoteMethods();
        methodFieldNames = nameMethodFields(remoteMethods);
    }

    /**
     * Returns the binary name of the stub class to generate for the
     * remote implementation class.
     **/
    String stubClassName() {
        return stubClassName;
    }

    /**
     * Returns the binary name of the skeleton class to generate for
     * the remote implementation class.
     **/
    String skeletonClassName() {
        return skeletonClassName;
    }

    /**
     * Writes the stub class for the remote class to a stream.
     **/
    void writeStub(IndentingWriter p) throws IOException {

        /*
         * Write boiler plate comment.
         */
        p.pln("// Stub class generated by rmic, do not edit.");
        p.pln("// Contents subject to change without notice.");
        p.pln();

        /*
         * If remote implementation class was in a particular package,
         * declare the stub class to be in the same package.
         */
        if (!packageName.equals("")) {
            p.pln("package " + packageName + ";");
            p.pln();
        }

        /*
         * Declare the stub class; implement all remote interfaces.
         */
        p.plnI("public final class " + stubClassSimpleName);
        p.pln("extends " + REMOTE_STUB);
        ClassDoc[] remoteInterfaces = remoteClass.remoteInterfaces();
        if (remoteInterfaces.length > 0) {
            p.p("implements ");
            for (int i = 0; i < remoteInterfaces.length; i++) {
                if (i > 0) {
                    p.p(", ");
                }
                p.p(remoteInterfaces[i].qualifiedName());
            }
            p.pln();
        }
        p.pOlnI("{");

        if (version == StubVersion.V1_1 ||
            version == StubVersion.VCOMPAT)
        {
            writeOperationsArray(p);
            p.pln();
            writeInterfaceHash(p);
            p.pln();
        }

        if (version == StubVersion.VCOMPAT ||
            version == StubVersion.V1_2)
        {
            p.pln("private static final long serialVersionUID = " +
                STUB_SERIAL_VERSION_UID + ";");
            p.pln();

            /*
             * We only need to declare and initialize the static fields of
             * Method objects for each remote method if there are any remote
             * methods; otherwise, skip this code entirely, to avoid generating
             * a try/catch block for a checked exception that cannot occur
             * (see bugid 4125181).
             */
            if (methodFieldNames.length > 0) {
                if (version == StubVersion.VCOMPAT) {
                    p.pln("private static boolean useNewInvoke;");
                }
                writeMethodFieldDeclarations(p);
                p.pln();

                /*
                 * Initialize java.lang.reflect.Method fields for each remote
                 * method in a static initializer.
                 */
                p.plnI("static {");
                p.plnI("try {");
                if (version == StubVersion.VCOMPAT) {
                    /*
                     * Fat stubs must determine whether the API required for
                     * the JDK 1.2 stub protocol is supported in the current
                     * runtime, so that it can use it if supported.  This is
                     * determined by using the Reflection API to test if the
                     * new invoke method on RemoteRef exists, and setting the
                     * static boolean "useNewInvoke" to true if it does, or
                     * to false if a NoSuchMethodException is thrown.
                     */
                    p.plnI(REMOTE_REF + ".class.getMethod(\"invoke\",");
                    p.plnI("new java.lang.Class[] {");
                    p.pln(REMOTE + ".class,");
                    p.pln("java.lang.reflect.Method.class,");
                    p.pln("java.lang.Object[].class,");
                    p.pln("long.class");
                    p.pOln("});");
                    p.pO();
                    p.pln("useNewInvoke = true;");
                }
                writeMethodFieldInitializers(p);
                p.pOlnI("} catch (java.lang.NoSuchMethodException e) {");
                if (version == StubVersion.VCOMPAT) {
                    p.pln("useNewInvoke = false;");
                } else {
                    p.plnI("throw new java.lang.NoSuchMethodError(");
                    p.pln("\"stub class initialization failed\");");
                    p.pO();
                }
                p.pOln("}");            // end try/catch block
                p.pOln("}");            // end static initializer
                p.pln();
            }
        }

        writeStubConstructors(p);
        p.pln();

        /*
         * Write each stub method.
         */
        if (remoteMethods.length > 0) {
            p.pln("// methods from remote interfaces");
            for (int i = 0; i < remoteMethods.length; ++i) {
                p.pln();
                writeStubMethod(p, i);
            }
        }

        p.pOln("}");                    // end stub class
    }

    /**
     * Writes the constructors for the stub class.
     **/
    private void writeStubConstructors(IndentingWriter p)
        throws IOException
    {
        p.pln("// constructors");

        /*
         * Only stubs compatible with the JDK 1.1 stub protocol need
         * a no-arg constructor; later versions use reflection to find
         * the constructor that directly takes a RemoteRef argument.
         */
        if (version == StubVersion.V1_1 ||
            version == StubVersion.VCOMPAT)
        {
            p.plnI("public " + stubClassSimpleName + "() {");
            p.pln("super();");
            p.pOln("}");
        }

        p.plnI("public " + stubClassSimpleName + "(" + REMOTE_REF + " ref) {");
        p.pln("super(ref);");
        p.pOln("}");
    }

    /**
     * Writes the stub method for the remote method with the given
     * operation number.
     **/
    private void writeStubMethod(IndentingWriter p, int opnum)
        throws IOException
    {
        RemoteClass.Method method = remoteMethods[opnum];
        MethodDoc methodDoc = method.methodDoc();
        String methodName = methodDoc.name();
        Type[] paramTypes = method.parameterTypes();
        String paramNames[] = nameParameters(paramTypes);
        Type returnType = methodDoc.returnType();
        ClassDoc[] exceptions = method.exceptionTypes();

        /*
         * Declare stub method; throw exceptions declared in remote
         * interface(s).
         */
        p.pln("// implementation of " +
              Util.getFriendlyUnqualifiedSignature(methodDoc));
        p.p("public " + returnType.toString() + " " + methodName + "(");
        for (int i = 0; i < paramTypes.length; i++) {
            if (i > 0) {
                p.p(", ");
            }
            p.p(paramTypes[i].toString() + " " + paramNames[i]);
        }
        p.plnI(")");
        if (exceptions.length > 0) {
            p.p("throws ");
            for (int i = 0; i < exceptions.length; i++) {
                if (i > 0) {
                    p.p(", ");
                }
                p.p(exceptions[i].qualifiedName());
            }
            p.pln();
        }
        p.pOlnI("{");

        /*
         * The RemoteRef.invoke methods throw Exception, but unless
         * this stub method throws Exception as well, we must catch
         * Exceptions thrown from the invocation.  So we must catch
         * Exception and rethrow something we can throw:
         * UnexpectedException, which is a subclass of
         * RemoteException.  But for any subclasses of Exception that
         * we can throw, like RemoteException, RuntimeException, and
         * any of the exceptions declared by this stub method, we want
         * them to pass through unmodified, so first we must catch any
         * such exceptions and rethrow them directly.
         *
         * We have to be careful generating the rethrowing catch
         * blocks here, because javac will flag an error if there are
         * any unreachable catch blocks, i.e. if the catch of an
         * exception class follows a previous catch of it or of one of
         * its superclasses.  The following method invocation takes
         * care of these details.
         */
        List<ClassDoc> catchList = computeUniqueCatchList(exceptions);

        /*
         * If we need to catch any particular exceptions (i.e. this method
         * does not declare java.lang.Exception), put the entire stub
         * method in a try block.
         */
        if (catchList.size() > 0) {
            p.plnI("try {");
        }

        if (version == StubVersion.VCOMPAT) {
            p.plnI("if (useNewInvoke) {");
        }
        if (version == StubVersion.VCOMPAT ||
            version == StubVersion.V1_2)
        {
            if (!Util.isVoid(returnType)) {
                p.p("Object $result = ");               // REMIND: why $?
            }
            p.p("ref.invoke(this, " + methodFieldNames[opnum] + ", ");
            if (paramTypes.length > 0) {
                p.p("new java.lang.Object[] {");
                for (int i = 0; i < paramTypes.length; i++) {
                    if (i > 0)
                        p.p(", ");
                    p.p(wrapArgumentCode(paramTypes[i], paramNames[i]));
                }
                p.p("}");
            } else {
                p.p("null");
            }
            p.pln(", " + method.methodHash() + "L);");
            if (!Util.isVoid(returnType)) {
                p.pln("return " +
                    unwrapArgumentCode(returnType, "$result") + ";");
            }
        }
        if (version == StubVersion.VCOMPAT) {
            p.pOlnI("} else {");
        }
        if (version == StubVersion.V1_1 ||
            version == StubVersion.VCOMPAT)
        {
            p.pln(REMOTE_CALL + " call = ref.newCall((" + REMOTE_OBJECT +
                ") this, operations, " + opnum + ", interfaceHash);");

            if (paramTypes.length > 0) {
                p.plnI("try {");
                p.pln("java.io.ObjectOutput out = call.getOutputStream();");
                writeMarshalArguments(p, "out", paramTypes, paramNames);
                p.pOlnI("} catch (java.io.IOException e) {");
                p.pln("throw new " + MARSHAL_EXCEPTION +
                    "(\"error marshalling arguments\", e);");
                p.pOln("}");
            }

            p.pln("ref.invoke(call);");

            if (Util.isVoid(returnType)) {
                p.pln("ref.done(call);");
            } else {
                p.pln(returnType.toString() + " $result;");
                                                        // REMIND: why $?
                p.plnI("try {");
                p.pln("java.io.ObjectInput in = call.getInputStream();");
                boolean objectRead =
                    writeUnmarshalArgument(p, "in", returnType, "$result");
                p.pln(";");
                p.pOlnI("} catch (java.io.IOException e) {");
                p.pln("throw new " + UNMARSHAL_EXCEPTION +
                    "(\"error unmarshalling return\", e);");
                /*
                 * If any only if readObject has been invoked, we must catch
                 * ClassNotFoundException as well as IOException.
                 */
                if (objectRead) {
                    p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
                    p.pln("throw new " + UNMARSHAL_EXCEPTION +
                        "(\"error unmarshalling return\", e);");
                }
                p.pOlnI("} finally {");
                p.pln("ref.done(call);");
                p.pOln("}");
                p.pln("return $result;");
            }
        }
        if (version == StubVersion.VCOMPAT) {
            p.pOln("}");                // end if/else (useNewInvoke) block
        }

        /*
         * If we need to catch any particular exceptions, finally write
         * the catch blocks for them, rethrow any other Exceptions with an
         * UnexpectedException, and end the try block.
         */
        if (catchList.size() > 0) {
            for (ClassDoc catchClass : catchList) {
                p.pOlnI("} catch (" + catchClass.qualifiedName() + " e) {");
                p.pln("throw e;");
            }
            p.pOlnI("} catch (java.lang.Exception e) {");
            p.pln("throw new " + UNEXPECTED_EXCEPTION +
                "(\"undeclared checked exception\", e);");
            p.pOln("}");                // end try/catch block
        }

        p.pOln("}");                    // end stub method
    }

    /**
     * Computes the exceptions that need to be caught and rethrown in
     * a stub method before wrapping Exceptions in
     * UnexpectedExceptions, given the exceptions declared in the
     * throws clause of the method.  Returns a list containing the
     * exception to catch.  Each exception is guaranteed to be unique,
     * i.e. not a subclass of any of the other exceptions in the list,
     * so the catch blocks for these exceptions may be generated in
     * any order relative to each other.
     *
     * RemoteException and RuntimeException are each automatically
     * placed in the returned list (unless any of their superclasses
     * are already present), since those exceptions should always be
     * directly rethrown by a stub method.
     *
     * The returned list will be empty if java.lang.Exception or one
     * of its superclasses is in the throws clause of the method,
     * indicating that no exceptions need to be caught.
     **/
    private List<ClassDoc> computeUniqueCatchList(ClassDoc[] exceptions) {
        List<ClassDoc> uniqueList = new ArrayList<ClassDoc>();

        uniqueList.add(env.docRuntimeException());
        uniqueList.add(env.docRemoteException()); // always catch/rethrow these

        /* For each exception declared by the stub method's throws clause: */
    nextException:
        for (ClassDoc ex : exceptions) {
            if (env.docException().subclassOf(ex)) {
                /*
                 * If java.lang.Exception (or a superclass) was declared
                 * in the throws clause of this stub method, then we don't
                 * have to bother catching anything; clear the list and
                 * return.
                 */
                uniqueList.clear();
                break;
            } else if (!ex.subclassOf(env.docException())) {
                /*
                 * Ignore other Throwables that do not extend Exception,
                 * because they cannot be thrown by the invoke methods.
                 */
                continue;
            }
            /*
             * Compare this exception against the current list of
             * exceptions that need to be caught:
             */
            for (Iterator<ClassDoc> i = uniqueList.iterator(); i.hasNext();) {
                ClassDoc ex2 = i.next();
                if (ex.subclassOf(ex2)) {
                    /*
                     * If a superclass of this exception is already on
                     * the list to catch, then ignore this one and continue;
                     */
                    continue nextException;
                } else if (ex2.subclassOf(ex)) {
                    /*
                     * If a subclass of this exception is on the list
                     * to catch, then remove it;
                     */
                    i.remove();
                }
            }
            /* This exception is unique: add it to the list to catch. */
            uniqueList.add(ex);
        }
        return uniqueList;
    }

    /**
     * Writes the skeleton for the remote class to a stream.
     **/
    void writeSkeleton(IndentingWriter p) throws IOException {
        if (version == StubVersion.V1_2) {
            throw new AssertionError(
                "should not generate skeleton for version " + version);
        }

        /*
         * Write boiler plate comment.
         */
        p.pln("// Skeleton class generated by rmic, do not edit.");
        p.pln("// Contents subject to change without notice.");
        p.pln();

        /*
         * If remote implementation class was in a particular package,
         * declare the skeleton class to be in the same package.
         */
        if (!packageName.equals("")) {
            p.pln("package " + packageName + ";");
            p.pln();
        }

        /*
         * Declare the skeleton class.
         */
        p.plnI("public final class " + skeletonClassSimpleName);
        p.pln("implements " + SKELETON);
        p.pOlnI("{");

        writeOperationsArray(p);
        p.pln();

        writeInterfaceHash(p);
        p.pln();

        /*
         * Define the getOperations() method.
         */
        p.plnI("public " + OPERATION + "[] getOperations() {");
        p.pln("return (" + OPERATION + "[]) operations.clone();");
        p.pOln("}");
        p.pln();

        /*
         * Define the dispatch() method.
         */
        p.plnI("public void dispatch(" + REMOTE + " obj, " +
            REMOTE_CALL + " call, int opnum, long hash)");
        p.pln("throws java.lang.Exception");
        p.pOlnI("{");

        if (version == StubVersion.VCOMPAT) {
            p.plnI("if (opnum < 0) {");
            if (remoteMethods.length > 0) {
                for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
                    if (opnum > 0)
                        p.pO("} else ");
                    p.plnI("if (hash == " +
                        remoteMethods[opnum].methodHash() + "L) {");
                    p.pln("opnum = " + opnum + ";");
                }
                p.pOlnI("} else {");
            }
            /*
             * Skeleton throws UnmarshalException if it does not recognize
             * the method hash; this is what UnicastServerRef.dispatch()
             * would do.
             */
            p.pln("throw new " +
                UNMARSHAL_EXCEPTION + "(\"invalid method hash\");");
            if (remoteMethods.length > 0) {
                p.pOln("}");
            }
            /*
             * Ignore the validation of the interface hash if the
             * operation number was negative, since it is really a
             * method hash instead.
             */
            p.pOlnI("} else {");
        }

        p.plnI("if (hash != interfaceHash)");
        p.pln("throw new " +
            SKELETON_MISMATCH_EXCEPTION + "(\"interface hash mismatch\");");
        p.pO();

        if (version == StubVersion.VCOMPAT) {
            p.pOln("}");                // end if/else (opnum < 0) block
        }
        p.pln();

        /*
         * Cast remote object reference to the remote implementation
         * class, if it's not private.  We don't use the binary name
         * of the class like previous implementations did because that
         * would not compile with javac (since 1.4.1).  If the remote
         * implementation class is private, then we can't cast to it
         * like previous implementations did because that also would
         * not compile with javac-- so instead, we'll have to try to
         * cast to the remote interface for each remote method.
         */
        if (!remoteClass.classDoc().isPrivate()) {
            p.pln(remoteClass.classDoc().qualifiedName() + " server = (" +
                  remoteClass.classDoc().qualifiedName() + ") obj;");
        }

        /*
         * Process call according to the operation number.
         */
        p.plnI("switch (opnum) {");
        for (int opnum = 0; opnum < remoteMethods.length; opnum++) {
            writeSkeletonDispatchCase(p, opnum);
        }
        p.pOlnI("default:");
        /*
         * Skeleton throws UnmarshalException if it does not recognize
         * the operation number; this is consistent with the case of an
         * unrecognized method hash.
         */
        p.pln("throw new " + UNMARSHAL_EXCEPTION +
            "(\"invalid method number\");");
        p.pOln("}");                    // end switch statement

        p.pOln("}");                    // end dispatch() method

        p.pOln("}");                    // end skeleton class
    }

    /**
     * Writes the case block for the skeleton's dispatch method for
     * the remote method with the given "opnum".
     **/
    private void writeSkeletonDispatchCase(IndentingWriter p, int opnum)
        throws IOException
    {
        RemoteClass.Method method = remoteMethods[opnum];
        MethodDoc methodDoc = method.methodDoc();
        String methodName = methodDoc.name();
        Type paramTypes[] = method.parameterTypes();
        String paramNames[] = nameParameters(paramTypes);
        Type returnType = methodDoc.returnType();

        p.pOlnI("case " + opnum + ": // " +
            Util.getFriendlyUnqualifiedSignature(methodDoc));
        /*
         * Use nested block statement inside case to provide an independent
         * namespace for local variables used to unmarshal parameters for
         * this remote method.
         */
        p.pOlnI("{");

        if (paramTypes.length > 0) {
            /*
             * Declare local variables to hold arguments.
             */
            for (int i = 0; i < paramTypes.length; i++) {
                p.pln(paramTypes[i].toString() + " " + paramNames[i] + ";");
            }

            /*
             * Unmarshal arguments from call stream.
             */
            p.plnI("try {");
            p.pln("java.io.ObjectInput in = call.getInputStream();");
            boolean objectsRead = writeUnmarshalArguments(p, "in",
                paramTypes, paramNames);
            p.pOlnI("} catch (java.io.IOException e) {");
            p.pln("throw new " + UNMARSHAL_EXCEPTION +
                "(\"error unmarshalling arguments\", e);");
            /*
             * If any only if readObject has been invoked, we must catch
             * ClassNotFoundException as well as IOException.
             */
            if (objectsRead) {
                p.pOlnI("} catch (java.lang.ClassNotFoundException e) {");
                p.pln("throw new " + UNMARSHAL_EXCEPTION +
                    "(\"error unmarshalling arguments\", e);");
            }
            p.pOlnI("} finally {");
            p.pln("call.releaseInputStream();");
            p.pOln("}");
        } else {
            p.pln("call.releaseInputStream();");
        }

        if (!Util.isVoid(returnType)) {
            /*
             * Declare variable to hold return type, if not void.
             */
            p.p(returnType.toString() + " $result = ");
                                                        // REMIND: why $?
        }

        /*
         * Invoke the method on the server object.  If the remote
         * implementation class is private, then we don't have a
         * reference cast to it, and so we try to cast to the remote
         * object reference to the method's declaring interface here.
         */
        String target = remoteClass.classDoc().isPrivate() ?
            "((" + methodDoc.containingClass().qualifiedName() + ") obj)" :
            "server";
        p.p(target + "." + methodName + "(");
        for (int i = 0; i < paramNames.length; i++) {
            if (i > 0)
                p.p(", ");
            p.p(paramNames[i]);
        }
        p.pln(");");

        /*
         * Always invoke getResultStream(true) on the call object to send
         * the indication of a successful invocation to the caller.  If
         * the return type is not void, keep the result stream and marshal
         * the return value.
         */
        p.plnI("try {");
        if (!Util.isVoid(returnType)) {
            p.p("java.io.ObjectOutput out = ");
        }
        p.pln("call.getResultStream(true);");
        if (!Util.isVoid(returnType)) {
            writeMarshalArgument(p, "out", returnType, "$result");
            p.pln(";");
        }
        p.pOlnI("} catch (java.io.IOException e) {");
        p.pln("throw new " +
            MARSHAL_EXCEPTION + "(\"error marshalling return\", e);");
        p.pOln("}");

        p.pln("break;");                // break from switch statement

        p.pOlnI("}");                   // end nested block statement
        p.pln();
    }

    /**
     * Writes declaration and initializer for "operations" static array.
     **/
    private void writeOperationsArray(IndentingWriter p)
        throws IOException
    {
        p.plnI("private static final " + OPERATION + "[] operations = {");
        for (int i = 0; i < remoteMethods.length; i++) {
            if (i > 0)
                p.pln(",");
            p.p("new " + OPERATION + "(\"" +
                remoteMethods[i].operationString() + "\")");
        }
        p.pln();
        p.pOln("};");
    }

    /**
     * Writes declaration and initializer for "interfaceHash" static field.
     **/
    private void writeInterfaceHash(IndentingWriter p)
        throws IOException
    {
        p.pln("private static final long interfaceHash = " +
            remoteClass.interfaceHash() + "L;");
    }

    /**
     * Writes declaration for java.lang.reflect.Method static fields
     * corresponding to each remote method in a stub.
     **/
    private void writeMethodFieldDeclarations(IndentingWriter p)
        throws IOException
    {
        for (String name : methodFieldNames) {
            p.pln("private static java.lang.reflect.Method " + name + ";");
        }
    }

    /**
     * Writes code to initialize the static fields for each method
     * using the Java Reflection API.
     **/
    private void writeMethodFieldInitializers(IndentingWriter p)
        throws IOException
    {
        for (int i = 0; i < methodFieldNames.length; i++) {
            p.p(methodFieldNames[i] + " = ");
            /*
             * Look up the Method object in the somewhat arbitrary
             * interface that we find in the Method object.
             */
            RemoteClass.Method method = remoteMethods[i];
            MethodDoc methodDoc = method.methodDoc();
            String methodName = methodDoc.name();
            Type paramTypes[] = method.parameterTypes();

            p.p(methodDoc.containingClass().qualifiedName() + ".class.getMethod(\"" +
                methodName + "\", new java.lang.Class[] {");
            for (int j = 0; j < paramTypes.length; j++) {
                if (j > 0)
                    p.p(", ");
                p.p(paramTypes[j].toString() + ".class");
            }
            p.pln("});");
        }
    }


    /*
     * Following are a series of static utility methods useful during
     * the code generation process:
     */

    /**
     * Generates an array of names for fields correspondins to the
     * given array of remote methods.  Each name in the returned array
     * is guaranteed to be unique.
     *
     * The name of a method is included in its corresponding field
     * name to enhance readability of the generated code.
     **/
    private static String[] nameMethodFields(RemoteClass.Method[] methods) {
        String[] names = new String[methods.length];
        for (int i = 0; i < names.length; i++) {
            names[i] = "$method_" + methods[i].methodDoc().name() + "_" + i;
        }
        return names;
    }

    /**
     * Generates an array of names for parameters corresponding to the
     * given array of types for the parameters.  Each name in the
     * returned array is guaranteed to be unique.
     *
     * A representation of the type of a parameter is included in its
     * corresponding parameter name to enhance the readability of the
     * generated code.
     **/
    private static String[] nameParameters(Type[] types) {
        String[] names = new String[types.length];
        for (int i = 0; i < names.length; i++) {
            names[i] = "$param_" +
                generateNameFromType(types[i]) + "_" + (i + 1);
        }
        return names;
    }

    /**
     * Generates a readable string representing the given type
     * suitable for embedding within a Java identifier.
     **/
    private static String generateNameFromType(Type type) {
        String name = type.typeName().replace('.', '$');
        int dimensions = type.dimension().length() / 2;
        for (int i = 0; i < dimensions; i++) {
            name = "arrayOf_" + name;
        }
        return name;
    }

    /**
     * Writes a snippet of Java code to marshal a value named "name"
     * of type "type" to the java.io.ObjectOutput stream named
     * "stream".
     *
     * Primitive types are marshalled with their corresponding methods
     * in the java.io.DataOutput interface, and objects (including
     * arrays) are marshalled using the writeObject method.
     **/
    private static void writeMarshalArgument(IndentingWriter p,
                                             String streamName,
                                             Type type, String name)
        throws IOException
    {
        if (type.dimension().length() > 0 || type.asClassDoc() != null) {
            p.p(streamName + ".writeObject(" + name + ")");
        } else if (type.typeName().equals("boolean")) {
            p.p(streamName + ".writeBoolean(" + name + ")");
        } else if (type.typeName().equals("byte")) {
            p.p(streamName + ".writeByte(" + name + ")");
        } else if (type.typeName().equals("char")) {
            p.p(streamName + ".writeChar(" + name + ")");
        } else if (type.typeName().equals("short")) {
            p.p(streamName + ".writeShort(" + name + ")");
        } else if (type.typeName().equals("int")) {
            p.p(streamName + ".writeInt(" + name + ")");
        } else if (type.typeName().equals("long")) {
            p.p(streamName + ".writeLong(" + name + ")");
        } else if (type.typeName().equals("float")) {
            p.p(streamName + ".writeFloat(" + name + ")");
        } else if (type.typeName().equals("double")) {
            p.p(streamName + ".writeDouble(" + name + ")");
        } else {
            throw new AssertionError(type);
        }
    }

    /**
     * Writes Java statements to marshal a series of values in order
     * as named in the "names" array, with types as specified in the
     * "types" array, to the java.io.ObjectOutput stream named
     * "stream".
     **/
    private static void writeMarshalArguments(IndentingWriter p,
                                              String streamName,
                                              Type[] types, String[] names)
        throws IOException
    {
        assert types.length == names.length;

        for (int i = 0; i < types.length; i++) {
            writeMarshalArgument(p, streamName, types[i], names[i]);
            p.pln(";");
        }
    }

    /**
     * Writes a snippet of Java code to unmarshal a value of type
     * "type" from the java.io.ObjectInput stream named "stream" into
     * a variable named "name" (if "name" is null, the value is
     * unmarshalled and discarded).
     *
     * Primitive types are unmarshalled with their corresponding
     * methods in the java.io.DataInput interface, and objects
     * (including arrays) are unmarshalled using the readObject
     * method.
     *
     * Returns true if code to invoke readObject was written, and
     * false otherwise.
     **/
    private static boolean writeUnmarshalArgument(IndentingWriter p,
                                                  String streamName,
                                                  Type type, String name)
        throws IOException
    {
        boolean readObject = false;

        if (name != null) {
            p.p(name + " = ");
        }

        if (type.dimension().length() > 0 || type.asClassDoc() != null) {
            p.p("(" + type.toString() + ") " + streamName + ".readObject()");
            readObject = true;
        } else if (type.typeName().equals("boolean")) {
            p.p(streamName + ".readBoolean()");
        } else if (type.typeName().equals("byte")) {
            p.p(streamName + ".readByte()");
        } else if (type.typeName().equals("char")) {
            p.p(streamName + ".readChar()");
        } else if (type.typeName().equals("short")) {
            p.p(streamName + ".readShort()");
        } else if (type.typeName().equals("int")) {
            p.p(streamName + ".readInt()");
        } else if (type.typeName().equals("long")) {
            p.p(streamName + ".readLong()");
        } else if (type.typeName().equals("float")) {
            p.p(streamName + ".readFloat()");
        } else if (type.typeName().equals("double")) {
            p.p(streamName + ".readDouble()");
        } else {
            throw new AssertionError(type);
        }

        return readObject;
    }

    /**
     * Writes Java statements to unmarshal a series of values in order
     * of types as in the "types" array from the java.io.ObjectInput
     * stream named "stream" into variables as named in "names" (for
     * any element of "names" that is null, the corresponding value is
     * unmarshalled and discarded).
     **/
    private static boolean writeUnmarshalArguments(IndentingWriter p,
                                                   String streamName,
                                                   Type[] types,
                                                   String[] names)
        throws IOException
    {
        assert types.length == names.length;

        boolean readObject = false;
        for (int i = 0; i < types.length; i++) {
            if (writeUnmarshalArgument(p, streamName, types[i], names[i])) {
                readObject = true;
            }
            p.pln(";");
        }
        return readObject;
    }

    /**
     * Returns a snippet of Java code to wrap a value named "name" of
     * type "type" into an object as appropriate for use by the Java
     * Reflection API.
     *
     * For primitive types, an appropriate wrapper class is
     * instantiated with the primitive value.  For object types
     * (including arrays), no wrapping is necessary, so the value is
     * named directly.
     **/
    private static String wrapArgumentCode(Type type, String name) {
        if (type.dimension().length() > 0 || type.asClassDoc() != null) {
            return name;
        } else if (type.typeName().equals("boolean")) {
            return ("(" + name +
                    " ? java.lang.Boolean.TRUE : java.lang.Boolean.FALSE)");
        } else if (type.typeName().equals("byte")) {
            return "new java.lang.Byte(" + name + ")";
        } else if (type.typeName().equals("char")) {
            return "new java.lang.Character(" + name + ")";
        } else if (type.typeName().equals("short")) {
            return "new java.lang.Short(" + name + ")";
        } else if (type.typeName().equals("int")) {
            return "new java.lang.Integer(" + name + ")";
        } else if (type.typeName().equals("long")) {
            return "new java.lang.Long(" + name + ")";
        } else if (type.typeName().equals("float")) {
            return "new java.lang.Float(" + name + ")";
        } else if (type.typeName().equals("double")) {
            return "new java.lang.Double(" + name + ")";
        } else {
            throw new AssertionError(type);
        }
    }

    /**
     * Returns a snippet of Java code to unwrap a value named "name"
     * into a value of type "type", as appropriate for the Java
     * Reflection API.
     *
     * For primitive types, the value is assumed to be of the
     * corresponding wrapper class, and a method is called on the
     * wrapper to retrieve the primitive value.  For object types
     * (include arrays), no unwrapping is necessary; the value is
     * simply cast to the expected real object type.
     **/
    private static String unwrapArgumentCode(Type type, String name) {
        if (type.dimension().length() > 0 || type.asClassDoc() != null) {
            return "((" + type.toString() + ") " + name + ")";
        } else if (type.typeName().equals("boolean")) {
            return "((java.lang.Boolean) " + name + ").booleanValue()";
        } else if (type.typeName().equals("byte")) {
            return "((java.lang.Byte) " + name + ").byteValue()";
        } else if (type.typeName().equals("char")) {
            return "((java.lang.Character) " + name + ").charValue()";
        } else if (type.typeName().equals("short")) {
            return "((java.lang.Short) " + name + ").shortValue()";
        } else if (type.typeName().equals("int")) {
            return "((java.lang.Integer) " + name + ").intValue()";
        } else if (type.typeName().equals("long")) {
            return "((java.lang.Long) " + name + ").longValue()";
        } else if (type.typeName().equals("float")) {
            return "((java.lang.Float) " + name + ").floatValue()";
        } else if (type.typeName().equals("double")) {
            return "((java.lang.Double) " + name + ").doubleValue()";
        } else {
            throw new AssertionError(type);
        }
    }
}
