/*
 * Copyright (c) 1998, 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.
 */

/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 */

package sun.rmi.rmic.iiop;

import java.io.File;
import java.io.IOException;
import java.io.SerializablePermission;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import sun.tools.java.Identifier;
import sun.tools.java.ClassNotFound;
import sun.tools.java.ClassDefinition;
import sun.tools.java.ClassDeclaration;
import sun.tools.java.CompilerError;
import sun.rmi.rmic.IndentingWriter;
import java.util.HashSet;
import java.util.Arrays;
import com.sun.corba.se.impl.util.Utility;
import com.sun.corba.se.impl.util.PackagePrefixChecker;
import sun.rmi.rmic.Main;


/**
 * An IIOP stub/tie generator for rmic.
 *
 * @author      Bryan Atsatt
 * @author      Anil Vijendran
 * @author      M. Mortazavi
 */

public class StubGenerator extends sun.rmi.rmic.iiop.Generator {

    private static final String DEFAULT_STUB_CLASS = "javax.rmi.CORBA.Stub";
    private static final String DEFAULT_TIE_CLASS = "org.omg.CORBA_2_3.portable.ObjectImpl";
    private static final String DEFAULT_POA_TIE_CLASS = "org.omg.PortableServer.Servant";

    protected boolean reverseIDs = false;
    protected boolean localStubs = true;
    protected boolean standardPackage = false;
    protected boolean useHash = true;
    protected String stubBaseClass = DEFAULT_STUB_CLASS;
    protected String tieBaseClass = DEFAULT_TIE_CLASS;
    protected HashSet namesInUse = new HashSet();
    protected Hashtable classesInUse = new Hashtable();
    protected Hashtable imports = new Hashtable();
    protected int importCount = 0;
    protected String currentPackage = null;
    protected String currentClass = null;
    protected boolean castArray = false;
    protected Hashtable transactionalObjects = new Hashtable() ;
    protected boolean POATie = false ;
    protected boolean emitPermissionCheck = false;

    /**
     * Default constructor for Main to use.
     */
    public StubGenerator() {
    }

    /**
     * Overridden in order to set the standardPackage flag.
     */
    public void generate(
            sun.rmi.rmic.BatchEnvironment env,
            ClassDefinition cdef, File destDir) {
        ((sun.rmi.rmic.iiop.BatchEnvironment)env).
                setStandardPackage(standardPackage);
        super.generate(env, cdef, destDir);
    }

    /**
     * Return true if a new instance should be created for each
     * class on the command line. Subclasses which return true
     * should override newInstance() to return an appropriately
     * constructed instance.
     */
    protected boolean requireNewInstance() {
        return false;
    }

    /**
     * Return true if non-conforming types should be parsed.
     * @param stack The context stack.
     */
    protected boolean parseNonConforming(ContextStack stack) {

        // We let the environment setting decide so that
        // another generator (e.g. IDLGenerator) can change
        // it and we will just go with the flow...

        return stack.getEnv().getParseNonConforming();
    }

    /**
     * Create and return a top-level type.
     * @param cdef The top-level class definition.
     * @param stack The context stack.
     * @return The compound type or null if is non-conforming.
     */
    protected CompoundType getTopType(ClassDefinition cdef, ContextStack stack) {

        CompoundType result = null;

        // Do we have an interface?

        if (cdef.isInterface()) {

            // Yes, so first try Abstract...

            result = AbstractType.forAbstract(cdef,stack,true);

            if (result == null) {

                // Then try Remote...

                result = RemoteType.forRemote(cdef,stack,false);
            }
        } else {

            // Not an interface, so try Implementation...

            result = ImplementationType.forImplementation(cdef,stack,false);
        }

        return result;
    }

    /**
     * Examine and consume command line arguments.
     * @param argv The command line arguments. Ignore null
     * and unknown arguments. Set each consumed argument to null.
     * @param error Report any errors using the main.error() methods.
     * @return true if no errors, false otherwise.
     */
    public boolean parseArgs(String argv[], Main main) {
        Object marker = new Object() ;

        // Reset any cached options...

        reverseIDs = false;
        localStubs = true;
        useHash = true;
        stubBaseClass = DEFAULT_STUB_CLASS;
        //       tieBaseClass = DEFAULT_TIE_CLASS;
        transactionalObjects = new Hashtable() ;

        // Parse options...

        boolean result = super.parseArgs(argv,main);
        if (result) {
            for (int i = 0; i < argv.length; i++) {
                if (argv[i] != null) {
                    String arg = argv[i].toLowerCase();
                    if (arg.equals("-iiop")) {
                        argv[i] = null;
                    } else if (arg.equals("-xreverseids")) {
                        reverseIDs = true;
                        argv[i] = null;
                    } else if (arg.equals("-nolocalstubs")) {
                        localStubs = false;
                        argv[i] = null;
                    } else if (arg.equals("-xnohash")) {
                        useHash = false;
                        argv[i] = null;
                    } else if (argv[i].equals("-standardPackage")) {
                        standardPackage = true;
                        argv[i] = null;
                    } else if (argv[i].equals("-emitPermissionCheck")) {
                        emitPermissionCheck = true;
                        argv[i] = null;
                    } else if (arg.equals("-xstubbase")) {
                        argv[i] = null;
                        if (++i < argv.length && argv[i] != null && !argv[i].startsWith("-")) {
                            stubBaseClass = argv[i];
                            argv[i] = null;
                        } else {
                            main.error("rmic.option.requires.argument", "-Xstubbase");
                            result = false;
                        }
                    } else if (arg.equals("-xtiebase")) {
                        argv[i] = null;
                        if (++i < argv.length && argv[i] != null && !argv[i].startsWith("-")) {
                            tieBaseClass = argv[i];
                            argv[i] = null;
                        } else {
                            main.error("rmic.option.requires.argument", "-Xtiebase");
                            result = false;
                        }
                    } else if (arg.equals("-transactional" )) {
                        // Scan for the next non-flag argument.
                        // Assume that it is a class name and add it
                        // to the list of transactional classes.
                        for ( int ctr=i+1; ctr<argv.length; ctr++ ) {
                            if (argv[ctr].charAt(1) != '-') {
                                transactionalObjects.put( argv[ctr], marker ) ;
                                break ;
                            }
                        }
                        argv[i] = null;
                    } else if (arg.equals( "-poa" )) {
                        POATie = true ;
                        argv[i] = null;
                    }
                }
            }
        }
        if(POATie){
            tieBaseClass = DEFAULT_POA_TIE_CLASS;
        } else {
            tieBaseClass = DEFAULT_TIE_CLASS;
        }
        return result;
    }

    /**
     * Return an array containing all the file names and types that need to be
     * generated for the given top-level type.  The file names must NOT have an
     * extension (e.g. ".java").
     * @param topType The type returned by getTopType().
     * @param alreadyChecked A set of Types which have already been checked.
     *  Intended to be passed to topType.collectMatching(filter,alreadyChecked).
     */
    protected OutputType[] getOutputTypesFor(CompoundType topType,
                                             HashSet alreadyChecked) {

        // We want to generate stubs for all remote and implementation types,
        // so collect them up.
        //
        // We use the form of collectMatching which allows us to pass in a set of
        // types which have previously been checked. By doing so, we ensure that if
        // the command line contains Hello and HelloImpl, we will only generate
        // output for Hello once...

        int filter = TYPE_REMOTE | TYPE_IMPLEMENTATION;
        Type[] genTypes = topType.collectMatching(filter,alreadyChecked);
        int count = genTypes.length;
        Vector list = new Vector(count+5);
        BatchEnvironment theEnv = topType.getEnv();

        // Now walk all types...

        for (int i = 0; i < genTypes.length; i++) {

            Type type = genTypes[i];
            String typeName = type.getName();
            boolean createStub = true;

            // Is it an implementation type?

            if (type instanceof ImplementationType) {

                // Yes, so add a tie for it...

                list.addElement(new OutputType(Utility.tieNameForCompiler(typeName), type));

                // Does it have more than 1 remote interface?  If so, we
                // want to create a stub for it...

                int remoteInterfaceCount = 0;
                InterfaceType[] interfaces = ((CompoundType)type).getInterfaces();
                for (int j = 0; j < interfaces.length; j++) {
                    if (interfaces[j].isType(TYPE_REMOTE) &&
                        !interfaces[j].isType(TYPE_ABSTRACT)) {
                        remoteInterfaceCount++;
                    }
                }

                if (remoteInterfaceCount <= 1) {

                    // No, so do not create a stub for this type...

                    createStub = false;
                }
            }

            // Is it an abstract interface type?

            if (type instanceof AbstractType) {

                // Do not create a stub for this type...

                createStub = false;  // d11141
            }

            if (createStub) {

                // Add a stub for the type...

                list.addElement(new OutputType(Utility.stubNameForCompiler(typeName), type));
            }
        }

        // Copy list into array..

        OutputType[] outputTypes = new OutputType[list.size()];
        list.copyInto(outputTypes);
        return outputTypes;
    }

    /**
     * Return the file name extension for the given file name (e.g. ".java").
     * All files generated with the ".java" extension will be compiled. To
     * change this behavior for ".java" files, override the compileJavaSourceFile
     * method to return false.
     * @param outputType One of the items returned by getOutputTypesFor(...)
     */
    protected String getFileNameExtensionFor(OutputType outputType) {
        return SOURCE_FILE_EXTENSION;
    }

    /**
     * Write the output for the given OutputFileName into the output stream.
     * @param name One of the items returned by getOutputTypesFor(...)
     * @param alreadyChecked A set of Types which have already been checked.
     *  Intended to be passed to Type.collectMatching(filter,alreadyChecked).
     * @param writer The output stream.
     */
    protected void writeOutputFor(      OutputType outputType,
                                        HashSet alreadyChecked,
                                        IndentingWriter writer) throws IOException {

        String fileName = outputType.getName();
        CompoundType theType = (CompoundType) outputType.getType();

        // Are we doing a Stub or Tie?

        if (fileName.endsWith(Utility.RMI_STUB_SUFFIX)) {

            // Stub.

            writeStub(outputType,writer);

        } else {

            // Tie

            writeTie(outputType,writer);
        }
    }

    /**
     * Write a stub for the specified type.
     */
    protected void writeStub(OutputType outputType,
                             IndentingWriter p) throws IOException {

        CompoundType theType = (CompoundType) outputType.getType();
        RemoteType[] remoteInterfaces = getDirectRemoteInterfaces(theType);

        // Write comment.

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

        // Set our standard classes...

        setStandardClassesInUse(theType,true);

        // Add classes for this type...

        addClassesInUse(theType,remoteInterfaces);

        // Write package and import statements...

        writePackageAndImports(p);

//        generate
//        import java.security.AccessController;
//        import java.security.PrivilegedAction;
//        import java.io.SerializablePermission;
        if (emitPermissionCheck) {
            p.pln("import java.security.AccessController;");
            p.pln("import java.security.PrivilegedAction;");
            p.pln("import java.io.SerializablePermission;");
            p.pln();
            p.pln();
        }

        // Declare the stub class; implement all remote interfaces.

        p.p("public class " + currentClass);

        p.p(" extends " + getName(stubBaseClass));
        p.p(" implements ");
        if (remoteInterfaces.length > 0) {
            for(int i = 0; i < remoteInterfaces.length; i++) {
                if (i > 0) {
                    p.pln(",");
                }
                String objName = testUtil(getName(remoteInterfaces[i]), theType);
                p.p(objName);
            }
        }

        // Add java.rmi.Remote if this type does not implement it.
        // This allows stubs for Abstract interfaces to be treated
        // uniformly...

        if (!implementsRemote(theType)) {
            p.pln(",");
            p.p(getName("java.rmi.Remote"));
        }

        p.plnI(" {");
        p.pln();

        // Write the ids...

        writeIds( p, theType, false );
        p.pln();

        if (emitPermissionCheck) {

            // produce the following generated code
            //
            // private transient boolean _instantiated = false;
            //
            // private static Void checkPermission() {
            // SecurityManager sm = System.getSecurityManager();
            // if (sm != null) {
            //     sm.checkPermission(new SerializablePermission(
            // "enableSubclassImplementation")); // testing
            // }
            // return null;
            // }
            //
            // private _XXXXXX_Stub(Void ignore) {
            // }
            //
            // public _XXXXXX_Stub() {
            // this(checkPermission());
            // _instantiated = true;
            // }
            //
            // private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
            //    checkPermission();
            //    s.defaultReadObject();
            //    _instantiated = true;
            // }
            //
            // where XXXXXX is the name of the remote interface

                p.pln();
                p.plnI("private transient boolean _instantiated = false;");
                p.pln();
                p.pO();
                p.plnI("private static Void checkPermission() {");
                p.plnI("SecurityManager sm = System.getSecurityManager();");
                p.pln("if (sm != null) {");
                p.pI();
                p.plnI("sm.checkPermission(new SerializablePermission(");
                p.plnI("\"enableSubclassImplementation\"));");
                p.pO();
                p.pO();
                p.pOln("}");
                p.pln("return null;");
                p.pO();
                p.pOln("}");
                p.pln();
                p.pO();

                p.pI();
                p.plnI("private " + currentClass + "(Void ignore) {  }");
                p.pln();
                p.pO();

                p.plnI("public " + currentClass + "() {");
                p.pln("this(checkPermission());");
                p.pln("_instantiated = true;");
                p.pOln("}");
                p.pln();
                p.plnI("private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {");
                p.plnI("checkPermission();");
                p.pO();
                p.pln("s.defaultReadObject();");
                p.pln("_instantiated = true;");
                p.pOln("}");
                p.pln();
                //p.pO();
        }

       if (!emitPermissionCheck) {
            p.pI();
       }

        // Write the _ids() method...

        p.plnI("public String[] _ids() { ");
        p.pln("return (String[]) _type_ids.clone();");
        p.pOln("}");

        // Get all the methods and write each stub method...

        CompoundType.Method[] remoteMethods = theType.getMethods();
        int methodCount = remoteMethods.length;
        if (methodCount > 0) {
            boolean writeHeader = true;
            for(int i = 0; i < methodCount; i++) {
                if (!remoteMethods[i].isConstructor()) {
                    if (writeHeader) {
                        writeHeader = false;
                    }
                    p.pln();
                    writeStubMethod(p, remoteMethods[i], theType);
                }
            }
        }

        // Write the cast array hack...

        writeCastArray(p);

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

    void addClassInUse(String qualifiedName) {
        String unqualifiedName = qualifiedName;
        String packageName = null;
        int index = qualifiedName.lastIndexOf('.');
        if (index > 0) {
            unqualifiedName = qualifiedName.substring(index+1);
            packageName = qualifiedName.substring(0,index);
        }
        addClassInUse(unqualifiedName,qualifiedName,packageName);
    }

    void addClassInUse(Type type) {
        if (!type.isPrimitive()) {
            Identifier id = type.getIdentifier();
            String name = IDLNames.replace(id.getName().toString(),". ",".");
            String packageName = type.getPackageName();
            String qualifiedName;
            if (packageName != null) {
                qualifiedName = packageName+"."+name;
            } else {
                qualifiedName = name;
            }
            addClassInUse(name,qualifiedName,packageName);
        }
    }

    void addClassInUse(Type[] types) {
        for (int i = 0; i < types.length; i++) {
            addClassInUse(types[i]);
        }
    }

    void addStubInUse(Type type) {
        if (type.getIdentifier() != idCorbaObject &&
            type.isType(TYPE_CORBA_OBJECT)) {
            String stubName = getStubNameFor(type,false);
            String packageName = type.getPackageName();
            String fullName;
            if (packageName == null) {
                fullName = stubName;
            } else {
                fullName = packageName + "." + stubName;
            }
            addClassInUse(stubName,fullName,packageName);
        }
        if (type.isType(TYPE_REMOTE) ||
            type.isType(TYPE_JAVA_RMI_REMOTE)) {
            addClassInUse("javax.rmi.PortableRemoteObject");
        }
    }

    String getStubNameFor(Type type, boolean qualified) {
        String stubName;
        String className;
        if (qualified) {
            className = type.getQualifiedName();
        } else {
            className = type.getName();
        }
        if (((CompoundType)type).isCORBAObject()) {
            stubName = Utility.idlStubName(className);
        } else {
            stubName = Utility.stubNameForCompiler(className);
        }
        return stubName;
    }

    void addStubInUse(Type[] types) {
        for (int i = 0; i < types.length; i++) {
            addStubInUse(types[i]);
        }
    }

    private static final String NO_IMPORT = new String();

    void addClassInUse(String unqualifiedName, String qualifiedName, String packageName) {

        // Have we already got an entry for this qualifiedName?

        String currentName = (String)classesInUse.get(qualifiedName);

        if (currentName == null) {

            // No, never seen it before. Grab any existing import
            // name and then decide what to do...

            String importName = (String) imports.get(unqualifiedName);
            String nameToUse = null;

            if (packageName == null) {

                // Default package, so doesn't matter which name to use...

                nameToUse = unqualifiedName;

            } else if (packageName.equals("java.lang")) {

                // java.lang.*, so use unqualified name...

                nameToUse = unqualifiedName;

                // unless you want to be able to import things from the right place :--)

                if(nameToUse.endsWith("_Stub")) nameToUse = Util.packagePrefix()+qualifiedName;

            } else if (currentPackage != null && packageName.equals(currentPackage)) {

                // Class in currentPackage, so use unqualified name...

                nameToUse = unqualifiedName;

                // Do we already have a previous import under this
                // unqualified name?

                if (importName != null) {

                    // Yes, so we use qualifiedName...

                    nameToUse = qualifiedName;

                }

            } else if (importName != null) {

                // It is in some package for which we normally
                // would import, but we have a previous import
                // under this unqualified name. We must use
                // the qualified name...

                nameToUse = qualifiedName;

                /*
                  // Is the currentPackage the default package?

                  if (currentPackage == null) {

                  // Yes, so undo the import so that all
                  // uses for this name will be qualified...

                  String old = (String)imports.remove(unqualifiedName);
                  classesInUse.put(old,old);
                  importCount--;

                  // Note that this name is in use but should
                  // not be imported...

                  imports.put(nameToUse,NO_IMPORT);
                  }
                */
            } else if (qualifiedName.equals("org.omg.CORBA.Object")) {

                // Always qualify this quy to avoid confusion...

                nameToUse = qualifiedName;

            } else {

                // Default to using unqualified name, and add
                // this guy to the imports...

                // Check for nested class in which case we use
                // the fully qualified name instead of imports
                if (unqualifiedName.indexOf('.') != -1) {
                    nameToUse = qualifiedName;
                } else {
                    nameToUse = unqualifiedName;
                    imports.put(unqualifiedName,qualifiedName);
                    importCount++;
                }
            }

            // Now add the name...

            classesInUse.put(qualifiedName,nameToUse);
        }
    }

    String getName(Type type) {
        if (type.isPrimitive()) {
            return type.getName() + type.getArrayBrackets();
        }
        Identifier id = type.getIdentifier();
        String name = IDLNames.replace(id.toString(),". ",".");
        return getName(name) + type.getArrayBrackets();
    }

    // Added for Bug 4818753
    String getExceptionName(Type type) {
        Identifier id = type.getIdentifier();
        return IDLNames.replace(id.toString(),". ",".");
    }

    String getName(String qualifiedName) {
        return (String)classesInUse.get(qualifiedName);
    }

    String getName(Identifier id) {
        return getName(id.toString());
    }

    String getStubName(Type type) {
        String stubName = getStubNameFor(type,true);
        return getName(stubName);
    }

    void setStandardClassesInUse(CompoundType type,
                                 boolean stub) throws IOException {

        // Reset our state...

        currentPackage = type.getPackageName();
        imports.clear();
        classesInUse.clear();
        namesInUse.clear();
        importCount = 0;
        castArray = false;

        // Add the top-level type...

        addClassInUse(type);

        // Set current class name...

        if (stub) {
            currentClass = Utility.stubNameForCompiler(type.getName());
        } else {
            currentClass = Utility.tieNameForCompiler(type.getName());
        }

        // Add current class...

        if (currentPackage == null) {
            addClassInUse(currentClass,currentClass,currentPackage);
        } else {
            addClassInUse(currentClass,(currentPackage+"."+currentClass),currentPackage);
        }

        // Add standard classes...

        addClassInUse("javax.rmi.CORBA.Util");
        addClassInUse(idRemote.toString());
        addClassInUse(idRemoteException.toString());
        addClassInUse(idOutputStream.toString());
        addClassInUse(idInputStream.toString());
        addClassInUse(idSystemException.toString());
        addClassInUse(idJavaIoSerializable.toString());
        addClassInUse(idCorbaORB.toString());
        addClassInUse(idReplyHandler.toString());

        // Add stub/tie specific imports...

        if (stub) {
            addClassInUse(stubBaseClass);
            addClassInUse("java.rmi.UnexpectedException");
            addClassInUse(idRemarshalException.toString());
            addClassInUse(idApplicationException.toString());
            if (localStubs) {
                addClassInUse("org.omg.CORBA.portable.ServantObject");
            }
        } else {
            addClassInUse(type);
            addClassInUse(tieBaseClass);
            addClassInUse(idTieInterface.toString());
            addClassInUse(idBadMethodException.toString());
            addClassInUse(idPortableUnknownException.toString());
            addClassInUse(idJavaLangThrowable.toString());
        }
    }

    void addClassesInUse(CompoundType type, RemoteType[] interfaces) {

        // Walk all methods and add types in use...

        CompoundType.Method[] methods = type.getMethods();
        for (int i = 0; i < methods.length; i++) {
            addClassInUse(methods[i].getReturnType());
            addStubInUse(methods[i].getReturnType());
            addClassInUse(methods[i].getArguments());
            addStubInUse(methods[i].getArguments());
            addClassInUse(methods[i].getExceptions());
            // bug 4473859: Also include narrower subtypes for use
            addClassInUse(methods[i].getImplExceptions());
        }

        // If this is a stub, add all interfaces...

        if (interfaces != null) {
            addClassInUse(interfaces);
        }
    }

    void writePackageAndImports(IndentingWriter p) throws IOException {

        // Write package declaration...

        if (currentPackage != null) {
            p.pln("package " +
                     Util.correctPackageName(
                          currentPackage, false, standardPackage)
                   + ";");
            p.pln();
        }

        // Get imports into an array and sort them...

        String[] names = new String[importCount];
        int index = 0;
        for (Enumeration e = imports.elements() ; e.hasMoreElements() ;) {
            String it = (String) e.nextElement();
            if (it != NO_IMPORT) {
                names[index++] = it;
            }
        }

        Arrays.sort(names,new StringComparator());

        // Now dump them out...

        for (int i = 0; i < importCount; i++) {
            if(
               Util.isOffendingPackage(names[i])
               && names[i].endsWith("_Stub")
               && String.valueOf(names[i].charAt(names[i].lastIndexOf(".")+1)).equals("_")
               ){
                p.pln("import " + PackagePrefixChecker.packagePrefix()+names[i]+";");
            } else{
                p.pln("import " + names[i] + ";");
            }
        }
        p.pln();

        // Include offending packages . . .
        if ( currentPackage!=null && Util.isOffendingPackage(currentPackage) ){
            p.pln("import " + currentPackage +".*  ;");
        }
        p.pln();

    }

    boolean implementsRemote(CompoundType theType) {
        boolean result = theType.isType(TYPE_REMOTE) && !theType.isType(TYPE_ABSTRACT);

        // If theType is not remote, look at all the interfaces
        // until we find one that is...

        if (!result) {
            InterfaceType[] interfaces = theType.getInterfaces();
            for (int i = 0; i < interfaces.length; i++) {
                result = implementsRemote(interfaces[i]);
                if (result) {
                    break;
                }
            }
        }

        return result;
    }

    void writeStubMethod (  IndentingWriter p,
                            CompoundType.Method method,
                            CompoundType theType) throws IOException {

        // Wtite the method declaration and opening brace...
        String methodName = method.getName();
        String methodIDLName = method.getIDLName();

        Type paramTypes[] = method.getArguments();
        String paramNames[] = method.getArgumentNames();
        Type returnType = method.getReturnType();
        ValueType[] exceptions = getStubExceptions(method,false);
        boolean hasIOException = false;

        addNamesInUse(method);
        addNameInUse("_type_ids");

        String objName = testUtil(getName(returnType), returnType);
        p.p("public " + objName + " " + methodName + "(");
        for(int i = 0; i < paramTypes.length; i++) {
            if (i > 0)
                p.p(", ");
            p.p(getName(paramTypes[i]) + " " + paramNames[i]);
        }

        p.p(")");
        if (exceptions.length > 0) {
            p.p(" throws ");
            for(int i = 0; i < exceptions.length; i++) {
                if (i > 0) {
                    p.p(", ");
                }
                // Added for Bug 4818753
                p.p(getExceptionName(exceptions[i]));
            }
        }

        p.plnI(" {");

        // Now create the method body...
        if (emitPermissionCheck) {
            p.pln("if ((System.getSecurityManager() != null) && (!_instantiated)) {");
            p.plnI("    throw new java.io.IOError(new java.io.IOException(\"InvalidObject \"));");
            p.pOln("}");
            p.pln();
        }


        if (localStubs) {
            writeLocalStubMethodBody(p,method,theType);
        } else {
            writeNonLocalStubMethodBody(p,method,theType);
        }

        // Close out the method...

        p.pOln("}");
    }


    void writeLocalStubMethodBody (IndentingWriter p,
                                   CompoundType.Method method,
                                   CompoundType theType) throws IOException {

        String objName;
        String paramNames[] = method.getArgumentNames();
        Type returnType = method.getReturnType();
        ValueType[] exceptions = getStubExceptions(method,false);
        String methodName = method.getName();
        String methodIDLName = method.getIDLName();

        p.plnI("if (!Util.isLocal(this)) {");
        writeNonLocalStubMethodBody(p,method,theType);
        p.pOlnI("} else {");
        String so = getVariableName("so");

        p.pln("ServantObject "+so+" = _servant_preinvoke(\""+methodIDLName+"\","+getName(theType)+".class);");
        p.plnI("if ("+so+" == null) {");
        if (!returnType.isType(TYPE_VOID)) {
            p.p("return ");
        }
        p.p(methodName+"(");
        for (int i = 0; i < paramNames.length; i++) {
            if (i > 0)
                p.p(", ");
            p.p(paramNames[i]);
        }
        p.pln(");");
        if (returnType.isType(TYPE_VOID)) {
            p.pln( "return ;" ) ;
        }

        p.pOln("}");
        p.plnI("try {");

        // Generate code to copy required arguments, and
        // get back the names by which all arguments are known...

        String[] argNames = writeCopyArguments(method,p);

        // Now write the method...

        boolean copyReturn = mustCopy(returnType);
        String resultName = null;
        if (!returnType.isType(TYPE_VOID)) {
            if (copyReturn) {
                resultName = getVariableName("result");
                objName = testUtil(getName(returnType), returnType);
                p.p(objName+" "+resultName + " = ");
            } else {
                p.p("return ");
            }
        }
        objName = testUtil(getName(theType), theType);
        p.p("(("+objName+")"+so+".servant)."+methodName+"(");

        for (int i = 0; i < argNames.length; i++) {
            if (i > 0)
                p.p(", ");
            p.p(argNames[i]);
        }

        if (copyReturn) {
            p.pln(");");
            objName = testUtil(getName(returnType), returnType);
            p.pln("return ("+objName+")Util.copyObject("+resultName+",_orb());");
        } else {
            p.pln(");");
        }

        String e1 = getVariableName("ex");
        String e2 = getVariableName("exCopy");
        p.pOlnI("} catch (Throwable "+e1+") {");

        p.pln("Throwable "+e2+" = (Throwable)Util.copyObject("+e1+",_orb());");
        for(int i = 0; i < exceptions.length; i++) {
            if (exceptions[i].getIdentifier() != idRemoteException &&
                exceptions[i].isType(TYPE_VALUE)) {
                // Added for Bug 4818753
                p.plnI("if ("+e2+" instanceof "+getExceptionName(exceptions[i])+") {");
                p.pln("throw ("+getExceptionName(exceptions[i])+")"+e2+";");
                p.pOln("}");
            }
        }

        p.pln("throw Util.wrapException("+e2+");");
        p.pOlnI("} finally {");
        p.pln("_servant_postinvoke("+so+");");
        p.pOln("}");
        p.pOln("}");
    }


    void writeNonLocalStubMethodBody (  IndentingWriter p,
                                        CompoundType.Method method,
                                        CompoundType theType) throws IOException {

        String methodName = method.getName();
        String methodIDLName = method.getIDLName();

        Type paramTypes[] = method.getArguments();
        String paramNames[] = method.getArgumentNames();
        Type returnType = method.getReturnType();
        ValueType[] exceptions = getStubExceptions(method,true);

        String in = getVariableName("in");
        String out = getVariableName("out");
        String ex = getVariableName("ex");

        // Decide if we need to use the new streams for
        // any of the read calls...

        boolean needNewReadStreamClass = false;
        for (int i = 0; i < exceptions.length; i++) {
            if (exceptions[i].getIdentifier() != idRemoteException &&
                exceptions[i].isType(TYPE_VALUE) &&
                needNewReadStreamClass(exceptions[i])) {
                needNewReadStreamClass = true;
                break;
            }
        }
        if (!needNewReadStreamClass) {
            for (int i = 0; i < paramTypes.length; i++) {
                if (needNewReadStreamClass(paramTypes[i])) {
                    needNewReadStreamClass = true;
                    break;
                }
            }
        }
        if (!needNewReadStreamClass) {
            needNewReadStreamClass = needNewReadStreamClass(returnType);
        }

        // Decide if we need to use the new streams for
        // any of the write calls...

        boolean needNewWriteStreamClass = false;
        for (int i = 0; i < paramTypes.length; i++) {
            if (needNewWriteStreamClass(paramTypes[i])) {
                needNewWriteStreamClass = true;
                break;
            }
        }

        // Now write the method, inserting casts where needed...

        p.plnI("try {");
        if (needNewReadStreamClass) {
            p.pln(idExtInputStream + " "+in+" = null;");
        } else {
            p.pln(idInputStream + " "+in+" = null;");
        }
        p.plnI("try {");

        String argStream = "null";

        if (needNewWriteStreamClass) {
            p.plnI(idExtOutputStream + " "+out+" = ");
            p.pln("(" + idExtOutputStream + ")");
            p.pln("_request(\"" + methodIDLName + "\", true);");
            p.pO();
        } else {
            p.pln("OutputStream "+out+" = _request(\"" + methodIDLName + "\", true);");
        }

        if (paramTypes.length > 0) {
            writeMarshalArguments(p, out, paramTypes, paramNames);
            p.pln();
        }
        argStream = out;

        if (returnType.isType(TYPE_VOID)) {
            p.pln("_invoke(" + argStream + ");" );
        } else {
            if (needNewReadStreamClass) {
                p.plnI(in+" = (" + idExtInputStream + ")_invoke(" + argStream + ");");
                p.pO();
            } else {
                p.pln(in+" = _invoke(" + argStream + ");");
            }
            p.p("return ");
            writeUnmarshalArgument(p, in, returnType, null);
            p.pln();
        }

        // Handle ApplicationException...

        p.pOlnI("} catch ("+getName(idApplicationException)+" "+ex+") {");
        if (needNewReadStreamClass) {
            p.pln(in + " = (" + idExtInputStream + ") "+ex+".getInputStream();");
        } else {
            p.pln(in + " = "+ex+".getInputStream();");
        }

        boolean idRead = false;
        boolean idAllocated = false;
        for(int i = 0; i < exceptions.length; i++) {
            if (exceptions[i].getIdentifier() != idRemoteException) {

                // Is this our special-case IDLEntity exception?

                if (exceptions[i].isIDLEntityException() && !exceptions[i].isCORBAUserException()) {

                    // Yes.

                    if (!idAllocated && !idRead) {
                        p.pln("String $_id = "+ex+".getId();");
                        idAllocated = true;
                    }

                    String helperName = IDLNames.replace(exceptions[i].getQualifiedIDLName(false),"::",".");
                    helperName += "Helper";
                    p.plnI("if ($_id.equals("+helperName+".id())) {");
                    p.pln("throw "+helperName+".read("+in+");");

                } else {

                    // No.

                    if (!idAllocated && !idRead) {
        p.pln("String $_id = "+in+".read_string();");
                        idAllocated = true;
                        idRead = true;
                    } else if (idAllocated && !idRead) {
                        p.pln("$_id = "+in+".read_string();");
                        idRead = true;
                    }
                    p.plnI("if ($_id.equals(\""+getExceptionRepositoryID(exceptions[i])+"\")) {");
                    // Added for Bug 4818753
                    p.pln("throw ("+getExceptionName(exceptions[i])+") "+in+".read_value(" + getExceptionName(exceptions[i]) + ".class);");
                }
                p.pOln("}");
            }
        }
        if (!idAllocated && !idRead) {
            p.pln("String $_id = "+in+".read_string();");
            idAllocated = true;
            idRead = true;
        } else if (idAllocated && !idRead) {
            p.pln("$_id = "+in+".read_string();");
            idRead = true;
        }
        p.pln("throw new UnexpectedException($_id);");

        // Handle RemarshalException...

        p.pOlnI("} catch ("+getName(idRemarshalException)+" "+ex+") {");
        if (!returnType.isType(TYPE_VOID)) {
            p.p("return ");
        }
        p.p(methodName + "(");
        for(int i = 0; i < paramTypes.length; i++) {
            if (i > 0) {
                p.p(",");
            }
            p.p(paramNames[i]);
        }
        p.pln(");");

        // Ensure that we release the reply...

        p.pOlnI("} finally {");
        p.pln("_releaseReply("+in+");");

        p.pOln("}");

        // Handle SystemException...

        p.pOlnI("} catch (SystemException "+ex+") {");
        p.pln("throw Util.mapSystemException("+ex+");");
        p.pOln("}");

        // returnResult(p,returnType);
    }

    void allocateResult (IndentingWriter p,
                         Type returnType) throws IOException {
        if (!returnType.isType(TYPE_VOID)) {
            String objName = testUtil(getName(returnType), returnType);
            p.p(objName + " result = ");
        }
    }

    int getTypeCode(Type type) {

        int typeCode = type.getTypeCode();

        // Handle late-breaking special case for
        // abstract IDL entities...

        if ((type instanceof CompoundType) &&
            ((CompoundType)type).isAbstractBase()) {
            typeCode = TYPE_ABSTRACT;
        }

        return typeCode;
    }


    /**
     * Write a snippet of Java code to marshal a value named "name" of
     * type "type" to the java.io.ObjectOutput stream named "stream".
     */
    void writeMarshalArgument(IndentingWriter p,
                              String streamName,
                              Type type, String name) throws IOException {

        int typeCode = getTypeCode(type);

        switch (typeCode) {
        case TYPE_BOOLEAN:
            p.p(streamName + ".write_boolean(" + name + ");");
            break;
        case TYPE_BYTE:
            p.p(streamName + ".write_octet(" + name + ");");
            break;
        case TYPE_CHAR:
            p.p(streamName + ".write_wchar(" + name + ");");
            break;
        case TYPE_SHORT:
            p.p(streamName + ".write_short(" + name + ");");
            break;
        case TYPE_INT:
            p.p(streamName + ".write_long(" + name + ");");
            break;
        case TYPE_LONG:
            p.p(streamName + ".write_longlong(" + name + ");");
            break;
        case TYPE_FLOAT:
            p.p(streamName + ".write_float(" + name + ");");
            break;
        case TYPE_DOUBLE:
            p.p(streamName + ".write_double(" + name + ");");
            break;
        case TYPE_STRING:
            p.p(streamName + ".write_value(" + name + "," + getName(type) + ".class);");
            break;
        case TYPE_ANY:
            p.p("Util.writeAny("+ streamName + "," + name + ");");
            break;
        case TYPE_CORBA_OBJECT:
            p.p(streamName + ".write_Object(" + name + ");");
            break;
        case TYPE_REMOTE:
            p.p("Util.writeRemoteObject("+ streamName + "," + name + ");");
            break;
        case TYPE_ABSTRACT:
            p.p("Util.writeAbstractObject("+ streamName + "," + name + ");");
            break;
        case TYPE_NC_INTERFACE:
            p.p(streamName + ".write_value((Serializable)" + name + "," + getName(type) + ".class);");
            break;
        case TYPE_VALUE:
            p.p(streamName + ".write_value(" + name + "," + getName(type) + ".class);");
            break;
        case TYPE_IMPLEMENTATION:
            p.p(streamName + ".write_value((Serializable)" + name + "," + getName(type) + ".class);");
            break;
        case TYPE_NC_CLASS:
            p.p(streamName + ".write_value((Serializable)" + name + "," + getName(type) + ".class);");
            break;
        case TYPE_ARRAY:
            castArray = true;
            p.p(streamName + ".write_value(cast_array(" + name + ")," + getName(type) + ".class);");
            break;
        case TYPE_JAVA_RMI_REMOTE:
            p.p("Util.writeRemoteObject("+ streamName + "," + name + ");");
            break;
        default:
            throw new Error("unexpected type code: " + typeCode);
        }
    }

    /**
     * Write 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 in unmarshalled and
     * discarded).
     */
    void writeUnmarshalArgument(IndentingWriter p,
                                String streamName,
                                Type type,
                                String name) throws IOException {

        int typeCode = getTypeCode(type);

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

        switch (typeCode) {
        case TYPE_BOOLEAN:
            p.p(streamName + ".read_boolean();");
            break;
        case TYPE_BYTE:
            p.p(streamName + ".read_octet();");
            break;
        case TYPE_CHAR:
            p.p(streamName + ".read_wchar();");
            break;
        case TYPE_SHORT:
            p.p(streamName + ".read_short();");
            break;
        case TYPE_INT:
            p.p(streamName + ".read_long();");
            break;
        case TYPE_LONG:
            p.p(streamName + ".read_longlong();");
            break;
        case TYPE_FLOAT:
            p.p(streamName + ".read_float();");
            break;
        case TYPE_DOUBLE:
            p.p(streamName + ".read_double();");
            break;
        case TYPE_STRING:
            p.p("(String) " + streamName + ".read_value(" + getName(type) + ".class);");
            break;
        case TYPE_ANY:
            if (type.getIdentifier() != idJavaLangObject) {
                p.p("(" + getName(type) + ") ");
            }
            p.p("Util.readAny(" + streamName + ");");
            break;
        case TYPE_CORBA_OBJECT:
            if (type.getIdentifier() == idCorbaObject) {
                p.p("(" + getName(type) + ") " + streamName + ".read_Object();");
            } else {
                p.p("(" + getName(type) + ") " + streamName + ".read_Object(" + getStubName(type) + ".class);");
            }
            break;
        case TYPE_REMOTE:
            String objName = testUtil(getName(type), type);
            p.p("(" + objName + ") " +
                "PortableRemoteObject.narrow(" + streamName + ".read_Object(), " + objName + ".class);");
            break;
        case TYPE_ABSTRACT:
            p.p("(" + getName(type) + ") " + streamName + ".read_abstract_interface();");
            break;
        case TYPE_NC_INTERFACE:
            p.p("(" + getName(type) + ") " + streamName + ".read_value(" + getName(type) + ".class);");
            break;
        case TYPE_VALUE:
            p.p("(" + getName(type) + ") " + streamName + ".read_value(" + getName(type) + ".class);");
            break;
        case TYPE_IMPLEMENTATION:
            p.p("(" + getName(type) + ") " + streamName + ".read_value(" + getName(type) + ".class);");
            break;
        case TYPE_NC_CLASS:
            p.p("(" + getName(type) + ") " + streamName + ".read_value(" + getName(type) + ".class);");
            break;
        case TYPE_ARRAY:
            p.p("(" + getName(type) + ") " + streamName + ".read_value(" + getName(type) + ".class);");
            break;
        case TYPE_JAVA_RMI_REMOTE:
            p.p("(" + getName(type) + ") " +
                "PortableRemoteObject.narrow(" + streamName + ".read_Object(), " + getName(type) + ".class);");
            //      p.p("(" + getName(type) + ") " + streamName + ".read_Object(" + getStubName(type) + ".class);");
            break;
        default:
            throw new Error("unexpected type code: " + typeCode);
        }
    }

    /**
     * Get a list of all the RepositoryIDs for interfaces
     * implemented directly or indirectly by theType. In the
     * case of an  ImplementationType which implements 2 or
     * more remote interfaces, this list will begin with the
     * Identifier for the implementation (see section 5.9 in
     * the Java -> IDL mapping). Ensures that the most derived
     * type is first in the list because the IOR is generated
     * using that entry in the _ids array.
     */
    String[] getAllRemoteRepIDs (CompoundType theType) {

        String[] result;

        // Collect up all the (inherited) remote interfaces
        // (ignores all the 'special' interfaces: Remote,
        // Serializable, Externalizable)...

        Type[] types = collectAllRemoteInterfaces(theType);

        int length = types.length;
        boolean haveImpl = theType instanceof ImplementationType;
        InterfaceType[] interfaces = theType.getInterfaces();
        int remoteCount = countRemote(interfaces,false);
        int offset = 0;

        // Do we have an implementation type that implements
        // more than one remote interface?

        if (haveImpl && remoteCount > 1) {

            // Yes, so we need to insert it at the beginning...

            result = new String[length + 1];
            result[0] = getRepositoryID(theType);
            offset = 1;

        } else {

            // No.

            result = new String[length];

            // Here we need to ensure that the most derived
            // interface ends up being first in the list. If
            // there is only one, we're done.

            if (length > 1) {

                // First, decide what the most derived type is...

                String mostDerived = null;

                if (haveImpl) {

                    // If we get here, we know that there is only one
                    // direct remote interface, so just find it...

                    for (int i = 0; i < interfaces.length; i++) {
                        if (interfaces[i].isType(TYPE_REMOTE)) {
                            mostDerived = interfaces[i].getRepositoryID();
                            break;
                        }
                    }
                } else {

                    // If we get here we know that theType is a RemoteType
                    // so just use its id...

                    mostDerived = theType.getRepositoryID();
                }

                // Now search types list and make sure mostDerived is
                // at index zero...

                for (int i = 0; i < length; i++) {
                    if (types[i].getRepositoryID() == mostDerived) {

                        // Found it. Swap it if we need to...

                        if (i > 0) {
                            Type temp = types[0];
                            types[0] = types[i];
                            types[i] = temp;
                        }

                        break;
                    }
                }
            }
        }

        // Now copy contents of the types array...

        for (int i = 0; i < types.length; i++) {
            result[offset++] = getRepositoryID(types[i]);
        }

        // If we're supposed to, reverse the array. This
        // is only done when the -testReverseIDs flag is
        // passed, and that should ONLY be done for test
        // cases. This is an undocumented feature.

        if (reverseIDs) {
            int start = 0;
            int end = result.length -1;
            while (start < end) {
                String temp = result[start];
                result[start++] = result[end];
                result[end--] = temp;
            }
        }

        return result;
    }

    /**
     * Collect all the inherited remote interfaces.
     */
    Type[] collectAllRemoteInterfaces (CompoundType theType) {
        Vector list = new Vector();

        // Collect up all the Remote interfaces, and get an instance
        // for java.rmi.Remote...

        addRemoteInterfaces(list,theType);

        // Create and return our results...

        Type[] result = new Type[list.size()];
        list.copyInto(result);

        return result;
    }

    /**
     * Add all the inherited remote interfaces to list.
     */
    void addRemoteInterfaces(Vector list, CompoundType theType) {

        if (theType != null) {
            if (theType.isInterface() && !list.contains(theType)) {
                list.addElement(theType);
            }

            InterfaceType[] interfaces = theType.getInterfaces();
            for (int i = 0; i < interfaces.length; i++) {

                if (interfaces[i].isType(TYPE_REMOTE)) {
                    addRemoteInterfaces(list,interfaces[i]);
                }
            }

            addRemoteInterfaces(list,theType.getSuperclass());
        }
    }

    /**
     * Get a list of all the remote interfaces which this stub
     * should declare.
     */
    RemoteType[] getDirectRemoteInterfaces (CompoundType theType) {

        RemoteType[] result;
        InterfaceType[] interfaces = theType.getInterfaces();

        // First, get a list of all the interfaces...

        InterfaceType[] list;

        // Because we can be passed either an ImplementationType
        // (which has interfaces) or a RemoteType (which is an
        // interface and may have interfaces) we must handle each
        // separately...

        // Do we have an implementation type?

        if (theType instanceof ImplementationType) {

            // Yes, so list is exactly what this type
            // implements and is correct already.

            list = interfaces;

        } else {

            // No, so list is just theType...

            list = new InterfaceType[1];
            list[0] = (InterfaceType) theType;
        }

        // Ok, now count up the remote interfaces, allocate
        // our result and fill it in...

        int remoteCount = countRemote(list,false);

        if (remoteCount == 0) {
            throw new CompilerError("iiop.StubGenerator: No remote interfaces!");
        }

        result = new RemoteType[remoteCount];
        int offset = 0;
        for (int i = 0; i < list.length; i++) {
            if (list[i].isType(TYPE_REMOTE)) {
                result[offset++] = (RemoteType)list[i];
            }
        }

        return result;
    }

    int countRemote (Type[] list, boolean includeAbstract) {
        int remoteCount = 0;
        for (int i = 0; i < list.length; i++) {
            if (list[i].isType(TYPE_REMOTE) &&
                (includeAbstract || !list[i].isType(TYPE_ABSTRACT))) {
                remoteCount++;
            }
        }

        return remoteCount;
    }

    void writeCastArray(IndentingWriter p) throws IOException {
        if (castArray) {
            p.pln();
            p.pln("// This method is required as a work-around for");
            p.pln("// a bug in the JDK 1.1.6 verifier.");
            p.pln();
            p.plnI("private "+getName(idJavaIoSerializable)+" cast_array(Object obj) {");
            p.pln("return ("+getName(idJavaIoSerializable)+")obj;");
            p.pOln("}");
        }
    }
    void writeIds(IndentingWriter p, CompoundType theType, boolean isTie
                  ) throws IOException {
        p.plnI("private static final String[] _type_ids = {");

        String[] ids = getAllRemoteRepIDs(theType);

        if (ids.length >0 ) {
            for(int i = 0; i < ids.length; i++) {
                if (i > 0)
                    p.pln(", ");
                p.p("\"" + ids[i] + "\"");
            }
        } else {
           // Must be an implementation which only implements Remote...
           p.pln("\"\"");
        }
        String qname = theType.getQualifiedName() ;
        boolean isTransactional = isTie && transactionalObjects.containsKey( qname ) ;
        // Add TransactionalObject if needed.
        if (isTransactional) {
            // Have already written an id.
            p.pln( ", " ) ;
            p.pln( "\"IDL:omg.org/CosTransactions/TransactionalObject:1.0\"" ) ;
        } else if (ids.length > 0) {
            p.pln();
        }
        p.pOln("};");
    }


    /**
     * Write the Tie for the remote class to a stream.
     */
    protected void writeTie(OutputType outputType,
                            IndentingWriter p) throws IOException
    {
        CompoundType theType = (CompoundType) outputType.getType();
        RemoteType[] remoteInterfaces = null;

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

        // Set our standard classes...
        setStandardClassesInUse(theType,false);

        // Add classes for this type...
        addClassesInUse(theType,remoteInterfaces);

        // Write package and import statements...
        writePackageAndImports(p);

        // Declare the tie class.
        p.p("public class " + currentClass + " extends " +
            getName(tieBaseClass) + " implements Tie");

        // Add java.rmi.Remote if this type does not implement it.
        // This allows stubs for Abstract interfaces to be treated
        // uniformly...
        if (!implementsRemote(theType)) {
            p.pln(",");
            p.p(getName("java.rmi.Remote"));
        }

        p.plnI(" {");

        // Write data members...
        p.pln();
        p.pln("volatile private " + getName(theType) + " target = null;");
        p.pln();

        // Write the ids...
        writeIds( p, theType, true ) ;

        // Write setTarget method...
        p.pln();
        p.plnI("public void setTarget(Remote target) {");
        p.pln("this.target = (" + getName(theType) + ") target;");
        p.pOln("}");

        // Write getTarget method...
        p.pln();
        p.plnI("public Remote getTarget() {");
        p.pln("return target;");
        p.pOln("}");

        // Write thisObject method...
        p.pln();
        write_tie_thisObject_method(p,idCorbaObject);

        // Write deactivate method...
        p.pln();
        write_tie_deactivate_method(p);

        // Write get orb method...
        p.pln();
        p.plnI("public ORB orb() {");
        p.pln("return _orb();");
        p.pOln("}");

        // Write set orb method...
        p.pln();
        write_tie_orb_method(p);

        // Write the _ids() method...
        p.pln();
        write_tie__ids_method(p);

        // Get all the methods...
        CompoundType.Method[] remoteMethods = theType.getMethods();

        // Register all the argument names used, plus our
        // data member names...

        addNamesInUse(remoteMethods);
        addNameInUse("target");
        addNameInUse("_type_ids");

        // Write the _invoke method...
        p.pln();

        String in = getVariableName("in");
        String _in = getVariableName("_in");
        String ex = getVariableName("ex");
        String method = getVariableName("method");
        String reply = getVariableName("reply");

        p.plnI("public OutputStream  _invoke(String "+method+", InputStream "+_in+", " +
               "ResponseHandler "+reply+") throws SystemException {");

        if (remoteMethods.length > 0) {
            p.plnI("try {");
            p.pln(getName(theType) + " target = this.target;");
            p.plnI("if (target == null) {");
            p.pln("throw new java.io.IOException();");
            p.pOln("}");
            p.plnI(idExtInputStream + " "+in+" = ");
            p.pln("(" + idExtInputStream + ") "+_in+";");
            p.pO();

            // See if we should use a hash table style
            // comparison...

            StaticStringsHash hash = getStringsHash(remoteMethods);

            if (hash != null) {
                p.plnI("switch ("+method+"."+hash.method+") {");
                for (int i = 0; i < hash.buckets.length; i++) {
                    p.plnI("case "+hash.keys[i]+": ");
                    for (int j = 0; j < hash.buckets[i].length; j++) {
                        CompoundType.Method current = remoteMethods[hash.buckets[i][j]];
                        if (j > 0) {
                            p.pO("} else ");
                        }
                        p.plnI("if ("+method+".equals(\""+ current.getIDLName() +"\")) {");
                        writeTieMethod(p, theType,current);
                    }
                    p.pOln("}");
                    p.pO();
                }
            } else {
                for(int i = 0; i < remoteMethods.length; i++) {
                CompoundType.Method current = remoteMethods[i];
                if (i > 0) {
                    p.pO("} else ");
                }

                p.plnI("if ("+method+".equals(\""+ current.getIDLName() +"\")) {");
                writeTieMethod(p, theType, current);
            }
            }

            if (hash != null) {
                p.pI();
                //        p.plnI("default:");
            } else {
                //   p.pOlnI("} else {");
            }
            //              p.pln("throw new "+getName(idBadMethodException)+"();");

            if (hash != null) {
                p.pO();
            }
            p.pOln("}");
            p.pln("throw new "+getName(idBadMethodException)+"();");

            p.pOlnI("} catch ("+getName(idSystemException)+" "+ex+") {");
            p.pln("throw "+ex+";");

            p.pOlnI("} catch ("+getName(idJavaLangThrowable)+" "+ex+") {");
            p.pln("throw new " + getName(idPortableUnknownException) + "("+ex+");");
            p.pOln("}");
        } else {
            // No methods...

            p.pln("throw new " + getName(idBadMethodException) + "();");
        }

        p.pOln("}");            // end invoke

        // Write the cast array hack...

        writeCastArray(p);

        // End tie class...
        p.pOln("}");
    }
    public void catchWrongPolicy(IndentingWriter p) throws IOException {
        p.pln("");
    }
    public void catchServantNotActive(IndentingWriter p) throws IOException {
        p.pln("");
    }
    public void catchObjectNotActive(IndentingWriter p) throws IOException {
        p.pln("");
    }

    public void write_tie_thisObject_method(IndentingWriter p,
                                            Identifier idCorbaObject)
        throws IOException
    {
        if(POATie){
            p.plnI("public " + idCorbaObject + " thisObject() {");
            /*
            p.pln("org.omg.CORBA.Object objref = null;");
            p.pln("try{");
            p.pln("objref = _poa().servant_to_reference(this);");
            p.pln("}catch (org.omg.PortableServer.POAPackage.WrongPolicy exception){");
            catchWrongPolicy(p);
            p.pln("}catch (org.omg.PortableServer.POAPackage.ServantNotActive exception){");
            catchServantNotActive(p);
            p.pln("}");
            p.pln("return objref;");
            */
            p.pln("return _this_object();");
            p.pOln("}");
        } else {
            p.plnI("public " + idCorbaObject + " thisObject() {");
            p.pln("return this;");
            p.pOln("}");
        }
    }

    public void write_tie_deactivate_method(IndentingWriter p)
        throws IOException
    {
        if(POATie){
            p.plnI("public void deactivate() {");
            p.pln("try{");
            p.pln("_poa().deactivate_object(_poa().servant_to_id(this));");
            p.pln("}catch (org.omg.PortableServer.POAPackage.WrongPolicy exception){");
            catchWrongPolicy(p);
            p.pln("}catch (org.omg.PortableServer.POAPackage.ObjectNotActive exception){");
            catchObjectNotActive(p);
            p.pln("}catch (org.omg.PortableServer.POAPackage.ServantNotActive exception){");
            catchServantNotActive(p);
            p.pln("}");
            p.pOln("}");
        } else {
            p.plnI("public void deactivate() {");
            p.pln("_orb().disconnect(this);");
            p.pln("_set_delegate(null);");
            p.pln("target = null;");
            p.pOln("}");
        }
    }

    public void write_tie_orb_method(IndentingWriter p)
        throws IOException
    {
        if(POATie){
        p.plnI("public void orb(ORB orb) {");
        /*
        p.pln("try{");
        p.pln("orb.connect(_poa().servant_to_reference(this));");
        p.pln("}catch (org.omg.PortableServer.POAPackage.WrongPolicy exception){");
        catchWrongPolicy(p);
        p.pln("}catch (org.omg.PortableServer.POAPackage.ServantNotActive exception){");
        catchServantNotActive(p);
        p.pln("}");
        */
        p.pln("try {");
        p.pln("    ((org.omg.CORBA_2_3.ORB)orb).set_delegate(this);");
        p.pln("}");
        p.pln("catch(ClassCastException e) {");
        p.pln("    throw new org.omg.CORBA.BAD_PARAM");
        p.pln("        (\"POA Servant requires an instance of org.omg.CORBA_2_3.ORB\");");
        p.pln("}");
        p.pOln("}");
        } else {
        p.plnI("public void orb(ORB orb) {");
        p.pln("orb.connect(this);");
        p.pOln("}");
        }
    }

    public void write_tie__ids_method(IndentingWriter p)
        throws IOException
    {
        if(POATie){
        p.plnI("public String[] _all_interfaces(org.omg.PortableServer.POA poa, byte[] objectId){");
        p.pln("return (String[]) _type_ids.clone();");
        p.pOln("}");
        } else {
        p.plnI("public String[] _ids() { ");
        p.pln("return (String[]) _type_ids.clone();");
        p.pOln("}");
        }
    }


    StaticStringsHash getStringsHash (CompoundType.Method[] methods) {
        if (useHash && methods.length > 1) {
            String[] methodNames = new String[methods.length];
            for (int i = 0; i < methodNames.length; i++) {
                methodNames[i] = methods[i].getIDLName();
            }
            return new StaticStringsHash(methodNames);
        }
        return null;
    }

    static boolean needNewReadStreamClass(Type type) {
        if (type.isType(TYPE_ABSTRACT)) {
            return true;
        }
        // Handle late-breaking special case for
        // abstract IDL entities...
        if ((type instanceof CompoundType) &&
            ((CompoundType)type).isAbstractBase()) {
            return true;
        }
        return needNewWriteStreamClass(type);
    }

    static boolean needNewWriteStreamClass(Type type) {
        switch (type.getTypeCode()) {
        case TYPE_VOID:
        case TYPE_BOOLEAN:
        case TYPE_BYTE:
        case TYPE_CHAR:
        case TYPE_SHORT:
        case TYPE_INT:
        case TYPE_LONG:
        case TYPE_FLOAT:
        case TYPE_DOUBLE:           return false;

        case TYPE_STRING:           return true;
        case TYPE_ANY:              return false;
        case TYPE_CORBA_OBJECT:     return false;
        case TYPE_REMOTE:           return false;
        case TYPE_ABSTRACT:         return false;
        case TYPE_NC_INTERFACE:     return true;
        case TYPE_VALUE:            return true;
        case TYPE_IMPLEMENTATION:   return true;
        case TYPE_NC_CLASS:         return true;
        case TYPE_ARRAY:            return true;
        case TYPE_JAVA_RMI_REMOTE:  return false;

        default: throw new Error("unexpected type code: " + type.getTypeCode());
        }
    }

    /*
     * Decide which arguments need to be copied and write
     * the copy code. Returns an array of argument names to
     * use to refer to either the copy or the original.
     */
    String[] writeCopyArguments(CompoundType.Method method,
                                IndentingWriter p) throws IOException {

        Type[] args = method.getArguments();
        String[] origNames = method.getArgumentNames();

        // Copy the current parameter names to a result array...

        String[] result = new String[origNames.length];
        for (int i = 0; i < result.length; i++) {
            result[i] = origNames[i];
        }

        // Decide which arguments must be copied, if any. If
        // any of the arguments are types for which a 'real' copy
        // will be done, rather than just an autoConnect, set
        // realCopy = true. Note that abstract types may only
        // need autoConnect, but we cannot know that at compile
        // time...

        boolean realCopy = false;
        boolean[] copyArg = new boolean[args.length];
        int copyCount = 0;
        int firstCopiedArg = 0; // Only used in single copy case.  It is only the first arg that
                                // needs copying IF copyCount == 1.

        for (int i = 0; i < args.length; i++) {
            if (mustCopy(args[i])) {
                copyArg[i] = true;
                copyCount++;
                firstCopiedArg = i;
                if (args[i].getTypeCode() != TYPE_REMOTE &&
                    args[i].getTypeCode() != TYPE_IMPLEMENTATION) {
                    realCopy = true;
                }
            } else {
                copyArg[i] = false;
            }
        }

        // Do we have any types which must be copied?
        if (copyCount > 0) {
            // Yes. Are we only doing the copy to ensure
            // that autoConnect occurs?
            if (realCopy) {
                // Nope. We need to go back thru the list and
                // mark any strings so that they will be copied
                // to preserve any shared references...
                for (int i = 0; i < args.length; i++) {
                    if (args[i].getTypeCode() == TYPE_STRING) {
                        copyArg[i] = true;
                        copyCount++;
                    }
                }
            }

            // We're ready to generate code. Do we have more than
            // one to copy?
            if (copyCount > 1) {
                // Generate a call to copyObjects...
                String arrayName = getVariableName("copies");
                p.p("Object[] " + arrayName + " = Util.copyObjects(new Object[]{");
                boolean first = true;
                for (int i = 0; i < args.length; i++) {
                    if (copyArg[i]) {
                        if (!first) {
                            p.p(",");
                        }
                        first = false;
                        p.p(origNames[i]);
                    }
                }
                p.pln("},_orb());");

                // For each of the types which was copied, create
                // a local temporary for it, updating the result
                // array with the new local parameter name...
                int copyIndex = 0 ;
                for (int i = 0; i < args.length; i++) {
                    if (copyArg[i]) {
                        result[i] = getVariableName(result[i]+"Copy");
                        p.pln( getName(args[i]) + " " + result[i] + " = (" + getName(args[i]) + ") " +
                               arrayName + "[" + copyIndex++ +"];");
                    }
                }
            } else {
                // Generate a call to copyObject, updating the result
                // with the new local parameter name...
                result[firstCopiedArg] = getVariableName(result[firstCopiedArg]+"Copy");
                p.pln( getName(args[firstCopiedArg]) + " " + result[firstCopiedArg] + " = (" +
                       getName(args[firstCopiedArg]) + ") Util.copyObject(" +
                       origNames[firstCopiedArg] + ",_orb());");
            }
        }

        return result;
    }

    static final String SINGLE_SLASH = "\\";
    static final String DOUBLE_SLASH = SINGLE_SLASH + SINGLE_SLASH;

    String getRepositoryID(Type type) {
        return IDLNames.replace(type.getRepositoryID(), SINGLE_SLASH, DOUBLE_SLASH);
    }

    String getExceptionRepositoryID(Type type) {
        ClassType theType = (ClassType) type;
        return IDLNames.getIDLRepositoryID(theType.getQualifiedIDLExceptionName(false));
    }

    String getVariableName(String proposed) {
        while (namesInUse.contains(proposed)) {
            proposed = "$" + proposed;
        }

        return proposed;
    }

    void addNamesInUse(CompoundType.Method[] methods) {
        for (int i = 0; i < methods.length; i++) {
            addNamesInUse(methods[i]);
        }
    }

    void addNamesInUse(CompoundType.Method method) {
        String paramNames[] = method.getArgumentNames();
        for (int i = 0; i < paramNames.length; i++) {
            addNameInUse(paramNames[i]);
        }
    }

    void addNameInUse(String name) {
        namesInUse.add(name);
    }

    static boolean mustCopy(Type type) {
        switch (type.getTypeCode()) {
        case TYPE_VOID:
        case TYPE_BOOLEAN:
        case TYPE_BYTE:
        case TYPE_CHAR:
        case TYPE_SHORT:
        case TYPE_INT:
        case TYPE_LONG:
        case TYPE_FLOAT:
        case TYPE_DOUBLE:
        case TYPE_STRING:           return false;

        case TYPE_ANY:              return true;

        case TYPE_CORBA_OBJECT:     return false;

        case TYPE_REMOTE:
        case TYPE_ABSTRACT:
        case TYPE_NC_INTERFACE:
        case TYPE_VALUE:
        case TYPE_IMPLEMENTATION:
        case TYPE_NC_CLASS:
        case TYPE_ARRAY:
        case TYPE_JAVA_RMI_REMOTE:  return true;

        default: throw new Error("unexpected type code: " + type.getTypeCode());
        }
    }

    ValueType[] getStubExceptions (CompoundType.Method method, boolean sort) {

        ValueType[] list = method.getFilteredStubExceptions(method.getExceptions());

        // Sort the list so that all org.omg.CORBA.UserException
        // subtypes are at the beginning of the list.  This ensures
        // that the stub will not call read_string() before calling
        // XXHelper.read().

        if (sort) {
            Arrays.sort(list,new UserExceptionComparator());
            }

        return list;
                }

    ValueType[] getTieExceptions (CompoundType.Method method) {
        return method.getUniqueCatchList(method.getImplExceptions());
    }

    void writeTieMethod(IndentingWriter p, CompoundType type,
                        CompoundType.Method method) throws IOException {
        String methodName = method.getName();
        Type paramTypes[] = method.getArguments();
        String paramNames[] = method.getArgumentNames();
        Type returnType = method.getReturnType();
        ValueType[] exceptions = getTieExceptions(method);
        String in = getVariableName("in");
        String ex = getVariableName("ex");
        String out = getVariableName("out");
        String reply = getVariableName("reply");

        for (int i = 0; i < paramTypes.length; i++) {
            p.p(getName(paramTypes[i])+" "+paramNames[i]+" = ");
            writeUnmarshalArgument(p, in, paramTypes[i], null);
            p.pln();
        }

        boolean handleExceptions = exceptions != null;
        boolean doReturn = !returnType.isType(TYPE_VOID);

        if (handleExceptions && doReturn) {
            String objName = testUtil(getName(returnType), returnType);
            p.pln(objName+" result;");
        }

        if (handleExceptions)
            p.plnI("try {");

        if (doReturn) {
            if (handleExceptions) {
                p.p("result = ");
            } else {
                p.p(getName(returnType)+" result = ");
            }
        }

        p.p("target."+methodName+"(");
        for(int i = 0; i < paramNames.length; i++) {
            if (i > 0)
                p.p(", ");
            p.p(paramNames[i]);
        }
        p.pln(");");

        if (handleExceptions) {
            for(int i = 0; i < exceptions.length; i++) {
                p.pOlnI("} catch ("+getName(exceptions[i])+" "+ex+") {");

                // Is this our IDLEntity Exception special case?

                if (exceptions[i].isIDLEntityException() && !exceptions[i].isCORBAUserException()) {

                                // Yes...

                    String helperName = IDLNames.replace(exceptions[i].getQualifiedIDLName(false),"::",".");
                    helperName += "Helper";
                    p.pln(idOutputStream+" "+out +" = "+reply+".createExceptionReply();");
                    p.pln(helperName+".write("+out+","+ex+");");

                } else {

                                // No...

                    p.pln("String id = \"" + getExceptionRepositoryID(exceptions[i]) + "\";");
                p.plnI(idExtOutputStream + " "+out+" = ");
                p.pln("(" + idExtOutputStream + ") "+reply+".createExceptionReply();");
                p.pOln(out+".write_string(id);");
                    p.pln(out+".write_value("+ex+"," + getName(exceptions[i]) + ".class);");
                }

                p.pln("return "+out+";");
            }
            p.pOln("}");
        }

        if (needNewWriteStreamClass(returnType)) {
            p.plnI(idExtOutputStream + " "+out+" = ");
            p.pln("(" + idExtOutputStream + ") "+reply+".createReply();");
            p.pO();
        } else {
            p.pln("OutputStream "+out+" = "+reply+".createReply();");
        }

        if (doReturn) {
            writeMarshalArgument(p, out, returnType, "result");
            p.pln();
        }

        p.pln("return "+out+";");
    }


    /**
     * Write 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".
     */
    void writeMarshalArguments(IndentingWriter p,
                               String streamName,
                               Type[] types, String[] names)
        throws IOException
    {
        if (types.length != names.length) {
            throw new Error("paramter type and name arrays different sizes");
        }

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

    /**
     * Added for IASRI 4987274. Remote classes named "Util" were
     * getting confused with javax.rmi.CORBA.Util and the
     * unqualifiedName "Util".
     */
    String testUtil(String objectName, Type ttype) {
        if (objectName.equals("Util")) {
                String correctedName = (String)ttype.getPackageName() + "." + objectName;
                return correctedName;
        } else {
                return objectName;
        }
    }
}

class StringComparator implements java.util.Comparator {
    public int compare(Object o1, Object o2) {
        String s1 = (String)o1;
        String s2 = (String)o2;
        return s1.compareTo(s2);
    }
}


class UserExceptionComparator implements java.util.Comparator {
    public int compare(Object o1, Object o2) {
        ValueType v1 = (ValueType)o1;
        ValueType v2 = (ValueType)o2;
        int result = 0;
        if (isUserException(v1)) {
            if (!isUserException(v2)) {
                result = -1;
            }
        } else if (isUserException(v2)) {
            if (!isUserException(v1)) {
                result = 1;
            }
        }
        return result;
    }

    final boolean isUserException(ValueType it) {
        return it.isIDLEntityException() && !it.isCORBAUserException();
    }
}
