gnu.classpath.tools.rmic.ClassRmicCompiler.java Source code

Java tutorial

Introduction

Here is the source code for gnu.classpath.tools.rmic.ClassRmicCompiler.java

Source

/* ClassRmicCompiler.java --
   Copyright (c) 1996, 1997, 1998, 1999, 2001, 2002, 2003, 2004, 2005
   Free Software Foundation, Inc.
    
This file is part of GNU Classpath.
    
GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
    
GNU Classpath 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 for more details.
    
You should have received a copy of the GNU General Public License
along with GNU Classpath; see the file COPYING.  If not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA. */

package gnu.classpath.tools.rmic;

import gnu.java.rmi.server.RMIHashes;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.rmi.MarshalException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.UnexpectedException;
import java.rmi.UnmarshalException;
import java.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteRef;
import java.rmi.server.RemoteStub;
import java.rmi.server.Skeleton;
import java.rmi.server.SkeletonMismatchException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;

public class ClassRmicCompiler implements RmicBackend {
    private String[] args;
    private int next;
    private List errors = new ArrayList();
    private boolean keep = false;
    private boolean need11Stubs = true;
    private boolean need12Stubs = true;
    private boolean compile = true;
    private boolean verbose;
    private boolean noWrite;
    private String destination;
    private String classpath;
    private ClassLoader loader;
    private int errorCount = 0;

    private Class clazz;
    private String classname;
    private String classInternalName;
    private String fullclassname;
    private MethodRef[] remotemethods;
    private String stubname;
    private String skelname;
    private List mRemoteInterfaces;

    /**
     * @return true if run was successful
     */
    public boolean run(String[] inputFiles) {
        args = inputFiles;

        if (next >= args.length)
            return false;

        for (int i = next; i < args.length; i++) {
            try {
                if (verbose)
                    System.out.println("[Processing class " + args[i] + ".class]");
                processClass(args[i].replace(File.separatorChar, '.'));
            } catch (IOException e) {
                errors.add(e);
            } catch (RMICException e) {
                errors.add(e);
            }
        }
        if (errors.size() > 0) {
            for (Iterator it = errors.iterator(); it.hasNext();) {
                Exception ex = (Exception) it.next();
                logError(ex);
            }
        }

        return errorCount == 0;
    }

    private void processClass(String cls) throws IOException, RMICException {
        // reset class specific vars
        clazz = null;
        classname = null;
        classInternalName = null;
        fullclassname = null;
        remotemethods = null;
        stubname = null;
        skelname = null;
        mRemoteInterfaces = new ArrayList();

        analyzeClass(cls);
        generateStub();
        if (need11Stubs)
            generateSkel();
    }

    private void analyzeClass(String cname) throws RMICException {
        if (verbose)
            System.out.println("[analyze class " + cname + "]");
        int p = cname.lastIndexOf('.');
        if (p != -1)
            classname = cname.substring(p + 1);
        else
            classname = cname;
        fullclassname = cname;

        findClass();
        findRemoteMethods();
    }

    /**
     * @deprecated
     */
    public Exception getException() {
        return errors.size() == 0 ? null : (Exception) errors.get(0);
    }

    private void findClass() throws RMICException {
        ClassLoader cl = (loader == null ? ClassLoader.getSystemClassLoader() : loader);
        try {
            clazz = Class.forName(fullclassname, false, cl);
        } catch (ClassNotFoundException cnfe) {
            throw new RMICException("Class " + fullclassname + " not found in classpath", cnfe);
        }

        if (!Remote.class.isAssignableFrom(clazz)) {
            throw new RMICException("Class " + clazz.getName() + " does not implement a remote interface.");
        }
    }

    private static Type[] typeArray(Class[] cls) {
        Type[] t = new Type[cls.length];
        for (int i = 0; i < cls.length; i++) {
            t[i] = Type.getType(cls[i]);
        }

        return t;
    }

    private static String[] internalNameArray(Type[] t) {
        String[] s = new String[t.length];
        for (int i = 0; i < t.length; i++) {
            s[i] = t[i].getInternalName();
        }

        return s;
    }

    private static String[] internalNameArray(Class[] c) {
        return internalNameArray(typeArray(c));
    }

    private static final String forName = "class$";

    private static Object param(Method m, int argIndex) {
        List l = new ArrayList();
        l.add(m);
        l.add(new Integer(argIndex));
        return l;
    }

    private static void generateClassForNamer(ClassVisitor cls) {
        MethodVisitor cv = cls.visitMethod(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC,
                forName,
                Type.getMethodDescriptor(Type.getType(Class.class), new Type[] { Type.getType(String.class) }),
                null, null);

        Label start = new Label();
        cv.visitLabel(start);
        cv.visitVarInsn(Opcodes.ALOAD, 0);
        cv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Class.class), "forName",
                Type.getMethodDescriptor(Type.getType(Class.class), new Type[] { Type.getType(String.class) }));
        cv.visitInsn(Opcodes.ARETURN);

        Label handler = new Label();
        cv.visitLabel(handler);
        cv.visitVarInsn(Opcodes.ASTORE, 1);
        cv.visitTypeInsn(Opcodes.NEW, typeArg(NoClassDefFoundError.class));
        cv.visitInsn(Opcodes.DUP);
        cv.visitVarInsn(Opcodes.ALOAD, 1);
        cv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(ClassNotFoundException.class), "getMessage",
                Type.getMethodDescriptor(Type.getType(String.class), new Type[] {}));
        cv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(NoClassDefFoundError.class), "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(String.class) }));
        cv.visitInsn(Opcodes.ATHROW);
        cv.visitTryCatchBlock(start, handler, handler, Type.getInternalName(ClassNotFoundException.class));
        cv.visitMaxs(-1, -1);
    }

    private void generateClassConstant(MethodVisitor cv, Class cls) {
        if (cls.isPrimitive()) {
            Class boxCls;
            if (cls.equals(Boolean.TYPE))
                boxCls = Boolean.class;
            else if (cls.equals(Character.TYPE))
                boxCls = Character.class;
            else if (cls.equals(Byte.TYPE))
                boxCls = Byte.class;
            else if (cls.equals(Short.TYPE))
                boxCls = Short.class;
            else if (cls.equals(Integer.TYPE))
                boxCls = Integer.class;
            else if (cls.equals(Long.TYPE))
                boxCls = Long.class;
            else if (cls.equals(Float.TYPE))
                boxCls = Float.class;
            else if (cls.equals(Double.TYPE))
                boxCls = Double.class;
            else if (cls.equals(Void.TYPE))
                boxCls = Void.class;
            else
                throw new IllegalArgumentException("unknown primitive type " + cls);

            cv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(boxCls), "TYPE",
                    Type.getDescriptor(Class.class));
            return;
        }
        cv.visitLdcInsn(cls.getName());
        cv.visitMethodInsn(Opcodes.INVOKESTATIC, classInternalName, forName,
                Type.getMethodDescriptor(Type.getType(Class.class), new Type[] { Type.getType(String.class) }));
    }

    private void generateClassArray(MethodVisitor code, Class[] classes) {
        code.visitLdcInsn(new Integer(classes.length));
        code.visitTypeInsn(Opcodes.ANEWARRAY, typeArg(Class.class));
        for (int i = 0; i < classes.length; i++) {
            code.visitInsn(Opcodes.DUP);
            code.visitLdcInsn(new Integer(i));
            generateClassConstant(code, classes[i]);
            code.visitInsn(Opcodes.AASTORE);
        }
    }

    private void fillOperationArray(MethodVisitor clinit) {
        // Operations array
        clinit.visitLdcInsn(new Integer(remotemethods.length));
        clinit.visitTypeInsn(Opcodes.ANEWARRAY, typeArg(Operation.class));
        clinit.visitFieldInsn(Opcodes.PUTSTATIC, classInternalName, "operations",
                Type.getDescriptor(Operation[].class));

        for (int i = 0; i < remotemethods.length; i++) {
            Method m = remotemethods[i].meth;

            StringBuilder desc = new StringBuilder();
            desc.append(getPrettyName(m.getReturnType()) + " ");
            desc.append(m.getName() + "(");

            // signature
            Class[] sig = m.getParameterTypes();
            for (int j = 0; j < sig.length; j++) {
                desc.append(getPrettyName(sig[j]));
                if (j + 1 < sig.length)
                    desc.append(", ");
            }

            // push operations array
            clinit.visitFieldInsn(Opcodes.GETSTATIC, classInternalName, "operations",
                    Type.getDescriptor(Operation[].class));

            // push array index
            clinit.visitLdcInsn(new Integer(i));

            // instantiate operation and leave a copy on the stack
            clinit.visitTypeInsn(Opcodes.NEW, typeArg(Operation.class));
            clinit.visitInsn(Opcodes.DUP);
            clinit.visitLdcInsn(desc.toString());
            clinit.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Operation.class), "<init>",
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(String.class) }));

            // store in operations array
            clinit.visitInsn(Opcodes.AASTORE);
        }
    }

    private void generateStaticMethodObjs(MethodVisitor clinit) {
        for (int i = 0; i < remotemethods.length; i++) {
            Method m = remotemethods[i].meth;

            /*
             * $method_<i>m.getName()</i>_<i>i</i> =
             *   <i>m.getDeclaringClass()</i>.class.getMethod
             *     (m.getName(), m.getParameterType())
             */
            String methodVar = "$method_" + m.getName() + "_" + i;
            generateClassConstant(clinit, m.getDeclaringClass());
            clinit.visitLdcInsn(m.getName());
            generateClassArray(clinit, m.getParameterTypes());
            clinit.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Class.class), "getMethod",
                    Type.getMethodDescriptor(Type.getType(Method.class),
                            new Type[] { Type.getType(String.class), Type.getType(Class[].class) }));

            clinit.visitFieldInsn(Opcodes.PUTSTATIC, classInternalName, methodVar,
                    Type.getDescriptor(Method.class));
        }
    }

    private void generateStub() throws IOException {
        stubname = fullclassname + "_Stub";
        String stubclassname = classname + "_Stub";
        File file = new File((destination == null ? "." : destination) + File.separator
                + stubname.replace('.', File.separatorChar) + ".class");

        if (verbose)
            System.out.println("[Generating class " + stubname + "]");

        final ClassWriter stub = new ClassWriter(true);
        classInternalName = stubname.replace('.', '/');
        final String superInternalName = Type.getType(RemoteStub.class).getInternalName();

        String[] remoteInternalNames = internalNameArray((Class[]) mRemoteInterfaces.toArray(new Class[] {}));
        stub.visit(Opcodes.V1_2, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, classInternalName, null, superInternalName,
                remoteInternalNames);

        if (need12Stubs) {
            stub.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "serialVersionUID",
                    Type.LONG_TYPE.getDescriptor(), null, new Long(2L));
        }

        if (need11Stubs) {
            stub.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "interfaceHash",
                    Type.LONG_TYPE.getDescriptor(), null, new Long(RMIHashes.getInterfaceHash(clazz)));

            if (need12Stubs) {
                stub.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC, "useNewInvoke",
                        Type.BOOLEAN_TYPE.getDescriptor(), null, null);
            }

            stub.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "operations",
                    Type.getDescriptor(Operation[].class), null, null);
        }

        // Set of method references.
        if (need12Stubs) {
            for (int i = 0; i < remotemethods.length; i++) {
                Method m = remotemethods[i].meth;
                String slotName = "$method_" + m.getName() + "_" + i;
                stub.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC, slotName,
                        Type.getDescriptor(Method.class), null, null);
            }
        }

        MethodVisitor clinit = stub.visitMethod(Opcodes.ACC_STATIC, "<clinit>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}), null, null);

        if (need11Stubs) {
            fillOperationArray(clinit);
            if (!need12Stubs)
                clinit.visitInsn(Opcodes.RETURN);
        }

        if (need12Stubs) {
            // begin of try
            Label begin = new Label();

            // beginning of catch
            Label handler = new Label();
            clinit.visitLabel(begin);

            // Initialize the methods references.
            if (need11Stubs) {
                /*
                 * RemoteRef.class.getMethod("invoke", new Class[] {
                 *   Remote.class, Method.class, Object[].class, long.class })
                 */
                generateClassConstant(clinit, RemoteRef.class);
                clinit.visitLdcInsn("invoke");
                generateClassArray(clinit, new Class[] { Remote.class, Method.class, Object[].class, long.class });
                clinit.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Class.class), "getMethod",
                        Type.getMethodDescriptor(Type.getType(Method.class),
                                new Type[] { Type.getType(String.class), Type.getType(Class[].class) }));

                // useNewInvoke = true
                clinit.visitInsn(Opcodes.ICONST_1);
                clinit.visitFieldInsn(Opcodes.PUTSTATIC, classInternalName, "useNewInvoke",
                        Type.BOOLEAN_TYPE.getDescriptor());
            }

            generateStaticMethodObjs(clinit);

            // jump past handler
            clinit.visitInsn(Opcodes.RETURN);
            clinit.visitLabel(handler);
            if (need11Stubs) {
                // useNewInvoke = false
                clinit.visitInsn(Opcodes.ICONST_0);
                clinit.visitFieldInsn(Opcodes.PUTSTATIC, classInternalName, "useNewInvoke",
                        Type.BOOLEAN_TYPE.getDescriptor());
                clinit.visitInsn(Opcodes.RETURN);
            } else {
                // throw NoSuchMethodError
                clinit.visitTypeInsn(Opcodes.NEW, typeArg(NoSuchMethodError.class));
                clinit.visitInsn(Opcodes.DUP);
                clinit.visitLdcInsn("stub class initialization failed");
                clinit.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(NoSuchMethodError.class),
                        "<init>",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(String.class) }));
                clinit.visitInsn(Opcodes.ATHROW);
            }

            clinit.visitTryCatchBlock(begin, handler, handler, Type.getInternalName(NoSuchMethodException.class));

        }

        clinit.visitMaxs(-1, -1);

        generateClassForNamer(stub);

        // Constructors
        if (need11Stubs) {
            // no arg public constructor
            MethodVisitor code = stub.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}), null, null);
            code.visitVarInsn(Opcodes.ALOAD, 0);
            code.visitMethodInsn(Opcodes.INVOKESPECIAL, superInternalName, "<init>",
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
            code.visitInsn(Opcodes.RETURN);

            code.visitMaxs(-1, -1);
        }

        // public RemoteRef constructor
        MethodVisitor constructor = stub.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(RemoteRef.class) }), null, null);
        constructor.visitVarInsn(Opcodes.ALOAD, 0);
        constructor.visitVarInsn(Opcodes.ALOAD, 1);
        constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, superInternalName, "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(RemoteRef.class) }));
        constructor.visitInsn(Opcodes.RETURN);
        constructor.visitMaxs(-1, -1);

        // Method implementations
        for (int i = 0; i < remotemethods.length; i++) {
            Method m = remotemethods[i].meth;
            Class[] sig = m.getParameterTypes();
            Class returntype = m.getReturnType();
            Class[] except = sortExceptions((Class[]) remotemethods[i].exceptions.toArray(new Class[0]));

            MethodVisitor code = stub.visitMethod(Opcodes.ACC_PUBLIC, m.getName(),
                    Type.getMethodDescriptor(Type.getType(returntype), typeArray(sig)), null,
                    internalNameArray(typeArray(except)));

            final Variables var = new Variables();

            // this and parameters are the declared vars
            var.declare("this");
            for (int j = 0; j < sig.length; j++)
                var.declare(param(m, j), size(sig[j]));

            Label methodTryBegin = new Label();
            code.visitLabel(methodTryBegin);

            if (need12Stubs) {
                Label oldInvoke = new Label();
                if (need11Stubs) {
                    // if not useNewInvoke jump to old invoke
                    code.visitFieldInsn(Opcodes.GETSTATIC, classInternalName, "useNewInvoke",
                            Type.getDescriptor(boolean.class));
                    code.visitJumpInsn(Opcodes.IFEQ, oldInvoke);
                }

                // this.ref
                code.visitVarInsn(Opcodes.ALOAD, var.get("this"));
                code.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(RemoteObject.class), "ref",
                        Type.getDescriptor(RemoteRef.class));

                // "this" is first arg to invoke
                code.visitVarInsn(Opcodes.ALOAD, var.get("this"));

                // method object is second arg to invoke
                String methName = "$method_" + m.getName() + "_" + i;
                code.visitFieldInsn(Opcodes.GETSTATIC, classInternalName, methName,
                        Type.getDescriptor(Method.class));

                // args to remote method are third arg to invoke
                if (sig.length == 0)
                    code.visitInsn(Opcodes.ACONST_NULL);
                else {
                    // create arg Object[] (with boxed primitives) and push it
                    code.visitLdcInsn(new Integer(sig.length));
                    code.visitTypeInsn(Opcodes.ANEWARRAY, typeArg(Object.class));

                    var.allocate("argArray");
                    code.visitVarInsn(Opcodes.ASTORE, var.get("argArray"));

                    for (int j = 0; j < sig.length; j++) {
                        int size = size(sig[j]);
                        int insn = loadOpcode(sig[j]);
                        Class box = sig[j].isPrimitive() ? box(sig[j]) : null;

                        code.visitVarInsn(Opcodes.ALOAD, var.get("argArray"));
                        code.visitLdcInsn(new Integer(j));

                        // put argument on stack
                        if (box != null) {
                            code.visitTypeInsn(Opcodes.NEW, typeArg(box));
                            code.visitInsn(Opcodes.DUP);
                            code.visitVarInsn(insn, var.get(param(m, j)));
                            code.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(box), "<init>",
                                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(sig[j]) }));
                        } else
                            code.visitVarInsn(insn, var.get(param(m, j)));

                        code.visitInsn(Opcodes.AASTORE);
                    }

                    code.visitVarInsn(Opcodes.ALOAD, var.deallocate("argArray"));
                }

                // push remote operation opcode
                code.visitLdcInsn(new Long(remotemethods[i].hash));
                code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteRef.class), "invoke",
                        Type.getMethodDescriptor(Type.getType(Object.class),
                                new Type[] { Type.getType(Remote.class), Type.getType(Method.class),
                                        Type.getType(Object[].class), Type.LONG_TYPE }));

                if (!returntype.equals(Void.TYPE)) {
                    int retcode = returnOpcode(returntype);
                    Class boxCls = returntype.isPrimitive() ? box(returntype) : null;
                    code.visitTypeInsn(Opcodes.CHECKCAST, typeArg(boxCls == null ? returntype : boxCls));
                    if (returntype.isPrimitive()) {
                        // unbox
                        code.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getType(boxCls).getInternalName(),
                                unboxMethod(returntype),
                                Type.getMethodDescriptor(Type.getType(returntype), new Type[] {}));
                    }

                    code.visitInsn(retcode);
                } else
                    code.visitInsn(Opcodes.RETURN);

                if (need11Stubs)
                    code.visitLabel(oldInvoke);
            }

            if (need11Stubs) {

                // this.ref.newCall(this, operations, index, interfaceHash)
                code.visitVarInsn(Opcodes.ALOAD, var.get("this"));
                code.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(RemoteObject.class), "ref",
                        Type.getDescriptor(RemoteRef.class));

                // "this" is first arg to newCall
                code.visitVarInsn(Opcodes.ALOAD, var.get("this"));

                // operations is second arg to newCall
                code.visitFieldInsn(Opcodes.GETSTATIC, classInternalName, "operations",
                        Type.getDescriptor(Operation[].class));

                // method index is third arg
                code.visitLdcInsn(new Integer(i));

                // interface hash is fourth arg
                code.visitFieldInsn(Opcodes.GETSTATIC, classInternalName, "interfaceHash",
                        Type.LONG_TYPE.getDescriptor());

                code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteRef.class), "newCall",
                        Type.getMethodDescriptor(Type.getType(RemoteCall.class),
                                new Type[] { Type.getType(RemoteObject.class), Type.getType(Operation[].class),
                                        Type.INT_TYPE, Type.LONG_TYPE }));

                // store call object on stack and leave copy on stack
                var.allocate("call");
                code.visitInsn(Opcodes.DUP);
                code.visitVarInsn(Opcodes.ASTORE, var.get("call"));

                Label beginArgumentTryBlock = new Label();
                code.visitLabel(beginArgumentTryBlock);

                // ObjectOutput out = call.getOutputStream();
                code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteCall.class),
                        "getOutputStream",
                        Type.getMethodDescriptor(Type.getType(ObjectOutput.class), new Type[] {}));

                for (int j = 0; j < sig.length; j++) {
                    // dup the ObjectOutput
                    code.visitInsn(Opcodes.DUP);

                    // get j'th arg to remote method
                    code.visitVarInsn(loadOpcode(sig[j]), var.get(param(m, j)));

                    Class argCls = sig[j].isPrimitive() ? sig[j] : Object.class;

                    // out.writeFoo
                    code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectOutput.class),
                            writeMethod(sig[j]),
                            Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(argCls) }));
                }

                // pop ObjectOutput
                code.visitInsn(Opcodes.POP);

                Label iohandler = new Label();
                Label endArgumentTryBlock = new Label();
                code.visitJumpInsn(Opcodes.GOTO, endArgumentTryBlock);
                code.visitLabel(iohandler);

                // throw new MarshalException(msg, ioexception);
                code.visitVarInsn(Opcodes.ASTORE, var.allocate("exception"));
                code.visitTypeInsn(Opcodes.NEW, typeArg(MarshalException.class));
                code.visitInsn(Opcodes.DUP);
                code.visitLdcInsn("error marshalling arguments");
                code.visitVarInsn(Opcodes.ALOAD, var.deallocate("exception"));
                code.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(MarshalException.class), "<init>",
                        Type.getMethodDescriptor(Type.VOID_TYPE,
                                new Type[] { Type.getType(String.class), Type.getType(Exception.class) }));
                code.visitInsn(Opcodes.ATHROW);

                code.visitLabel(endArgumentTryBlock);
                code.visitTryCatchBlock(beginArgumentTryBlock, iohandler, iohandler,
                        Type.getInternalName(IOException.class));

                // this.ref.invoke(call)
                code.visitVarInsn(Opcodes.ALOAD, var.get("this"));
                code.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(RemoteObject.class), "ref",
                        Type.getDescriptor(RemoteRef.class));
                code.visitVarInsn(Opcodes.ALOAD, var.get("call"));
                code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteRef.class), "invoke",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(RemoteCall.class) }));

                // handle return value
                boolean needcastcheck = false;

                Label beginReturnTryCatch = new Label();
                code.visitLabel(beginReturnTryCatch);

                int returncode = returnOpcode(returntype);

                if (!returntype.equals(Void.TYPE)) {
                    // call.getInputStream()
                    code.visitVarInsn(Opcodes.ALOAD, var.get("call"));
                    code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteCall.class),
                            "getInputStream",
                            Type.getMethodDescriptor(Type.getType(ObjectInput.class), new Type[] {}));

                    Class readCls = returntype.isPrimitive() ? returntype : Object.class;
                    code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectInput.class),
                            readMethod(returntype), Type.getMethodDescriptor(Type.getType(readCls), new Type[] {}));

                    boolean castresult = false;

                    if (!returntype.isPrimitive()) {
                        if (!returntype.equals(Object.class))
                            castresult = true;
                        else
                            needcastcheck = true;
                    }

                    if (castresult)
                        code.visitTypeInsn(Opcodes.CHECKCAST, typeArg(returntype));

                    // leave result on stack for return
                }

                // this.ref.done(call)
                code.visitVarInsn(Opcodes.ALOAD, var.get("this"));
                code.visitFieldInsn(Opcodes.GETFIELD, Type.getInternalName(RemoteObject.class), "ref",
                        Type.getDescriptor(RemoteRef.class));
                code.visitVarInsn(Opcodes.ALOAD, var.deallocate("call"));
                code.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteRef.class), "done",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(RemoteCall.class) }));

                // return; or return result;
                code.visitInsn(returncode);

                // exception handler
                Label handler = new Label();
                code.visitLabel(handler);
                code.visitVarInsn(Opcodes.ASTORE, var.allocate("exception"));

                // throw new UnmarshalException(msg, e)
                code.visitTypeInsn(Opcodes.NEW, typeArg(UnmarshalException.class));
                code.visitInsn(Opcodes.DUP);
                code.visitLdcInsn("error unmarshalling return");
                code.visitVarInsn(Opcodes.ALOAD, var.deallocate("exception"));
                code.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(UnmarshalException.class),
                        "<init>", Type.getMethodDescriptor(Type.VOID_TYPE,
                                new Type[] { Type.getType(String.class), Type.getType(Exception.class) }));
                code.visitInsn(Opcodes.ATHROW);

                Label endReturnTryCatch = new Label();

                // catch IOException
                code.visitTryCatchBlock(beginReturnTryCatch, handler, handler,
                        Type.getInternalName(IOException.class));

                if (needcastcheck) {
                    // catch ClassNotFoundException
                    code.visitTryCatchBlock(beginReturnTryCatch, handler, handler,
                            Type.getInternalName(ClassNotFoundException.class));
                }
            }

            Label rethrowHandler = new Label();
            code.visitLabel(rethrowHandler);
            // rethrow declared exceptions
            code.visitInsn(Opcodes.ATHROW);

            boolean needgeneral = true;
            for (int j = 0; j < except.length; j++) {
                if (except[j] == Exception.class)
                    needgeneral = false;
            }

            for (int j = 0; j < except.length; j++) {
                code.visitTryCatchBlock(methodTryBegin, rethrowHandler, rethrowHandler,
                        Type.getInternalName(except[j]));
            }

            if (needgeneral) {
                // rethrow unchecked exceptions
                code.visitTryCatchBlock(methodTryBegin, rethrowHandler, rethrowHandler,
                        Type.getInternalName(RuntimeException.class));

                Label generalHandler = new Label();
                code.visitLabel(generalHandler);
                String msg = "undeclared checked exception";

                // throw new java.rmi.UnexpectedException(msg, e)
                code.visitVarInsn(Opcodes.ASTORE, var.allocate("exception"));
                code.visitTypeInsn(Opcodes.NEW, typeArg(UnexpectedException.class));
                code.visitInsn(Opcodes.DUP);
                code.visitLdcInsn(msg);
                code.visitVarInsn(Opcodes.ALOAD, var.deallocate("exception"));
                code.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(UnexpectedException.class),
                        "<init>", Type.getMethodDescriptor(Type.VOID_TYPE,
                                new Type[] { Type.getType(String.class), Type.getType(Exception.class) }));
                code.visitInsn(Opcodes.ATHROW);

                code.visitTryCatchBlock(methodTryBegin, rethrowHandler, generalHandler,
                        Type.getInternalName(Exception.class));
            }

            code.visitMaxs(-1, -1);
        }

        stub.visitEnd();
        byte[] classData = stub.toByteArray();
        if (!noWrite) {
            if (file.exists())
                file.delete();
            if (file.getParentFile() != null)
                file.getParentFile().mkdirs();
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(classData);
            fos.flush();
            fos.close();
        }
    }

    private void generateSkel() throws IOException {
        skelname = fullclassname + "_Skel";
        String skelclassname = classname + "_Skel";
        File file = new File(destination == null ? ""
                : destination + File.separator + skelname.replace('.', File.separatorChar) + ".class");
        if (verbose)
            System.out.println("[Generating class " + skelname + "]");

        final ClassWriter skel = new ClassWriter(true);
        classInternalName = skelname.replace('.', '/');
        skel.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, classInternalName,
                Type.getInternalName(Object.class), null,
                new String[] { Type.getType(Skeleton.class).getInternalName() });

        skel.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "interfaceHash",
                Type.LONG_TYPE.getDescriptor(), null, new Long(RMIHashes.getInterfaceHash(clazz)));

        skel.visitField(Opcodes.ACC_PRIVATE + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL, "operations",
                Type.getDescriptor(Operation[].class), null, null);

        MethodVisitor clinit = skel.visitMethod(Opcodes.ACC_STATIC, "<clinit>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}), null, null);

        fillOperationArray(clinit);
        clinit.visitInsn(Opcodes.RETURN);

        clinit.visitMaxs(-1, -1);

        // no arg public constructor
        MethodVisitor init = skel.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}), null, null);
        init.visitVarInsn(Opcodes.ALOAD, 0);
        init.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
        init.visitInsn(Opcodes.RETURN);
        init.visitMaxs(-1, -1);

        /*
         * public Operation[] getOperations()
         * returns a clone of the operations array
         */
        MethodVisitor getOp = skel.visitMethod(Opcodes.ACC_PUBLIC, "getOperations",
                Type.getMethodDescriptor(Type.getType(Operation[].class), new Type[] {}), null, null);
        getOp.visitFieldInsn(Opcodes.GETSTATIC, classInternalName, "operations",
                Type.getDescriptor(Operation[].class));
        getOp.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Object.class), "clone",
                Type.getMethodDescriptor(Type.getType(Object.class), new Type[] {}));
        getOp.visitTypeInsn(Opcodes.CHECKCAST, typeArg(Operation[].class));
        getOp.visitInsn(Opcodes.ARETURN);
        getOp.visitMaxs(-1, -1);

        // public void dispatch(Remote, RemoteCall, int opnum, long hash)
        MethodVisitor dispatch = skel.visitMethod(Opcodes.ACC_PUBLIC, "dispatch",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(Remote.class),
                        Type.getType(RemoteCall.class), Type.INT_TYPE, Type.LONG_TYPE }),
                null, new String[] { Type.getInternalName(Exception.class) });

        Variables var = new Variables();
        var.declare("this");
        var.declare("remoteobj");
        var.declare("remotecall");
        var.declare("opnum");
        var.declareWide("hash");

        /*
         * if opnum >= 0
         * XXX it is unclear why there is handling of negative opnums
         */
        dispatch.visitVarInsn(Opcodes.ILOAD, var.get("opnum"));
        Label nonNegativeOpnum = new Label();
        Label opnumSet = new Label();
        dispatch.visitJumpInsn(Opcodes.IFGE, nonNegativeOpnum);

        for (int i = 0; i < remotemethods.length; i++) {
            // assign opnum if hash matches supplied hash
            dispatch.visitVarInsn(Opcodes.LLOAD, var.get("hash"));
            dispatch.visitLdcInsn(new Long(remotemethods[i].hash));
            Label notIt = new Label();
            dispatch.visitInsn(Opcodes.LCMP);
            dispatch.visitJumpInsn(Opcodes.IFNE, notIt);

            // opnum = <opnum>
            dispatch.visitLdcInsn(new Integer(i));
            dispatch.visitVarInsn(Opcodes.ISTORE, var.get("opnum"));
            dispatch.visitJumpInsn(Opcodes.GOTO, opnumSet);
            dispatch.visitLabel(notIt);
        }

        // throw new SkeletonMismatchException
        Label mismatch = new Label();
        dispatch.visitJumpInsn(Opcodes.GOTO, mismatch);

        dispatch.visitLabel(nonNegativeOpnum);

        // if opnum is already set, check that the hash matches the interface
        dispatch.visitVarInsn(Opcodes.LLOAD, var.get("hash"));
        dispatch.visitFieldInsn(Opcodes.GETSTATIC, classInternalName, "interfaceHash",
                Type.LONG_TYPE.getDescriptor());
        dispatch.visitInsn(Opcodes.LCMP);
        dispatch.visitJumpInsn(Opcodes.IFEQ, opnumSet);

        dispatch.visitLabel(mismatch);
        dispatch.visitTypeInsn(Opcodes.NEW, typeArg(SkeletonMismatchException.class));
        dispatch.visitInsn(Opcodes.DUP);
        dispatch.visitLdcInsn("interface hash mismatch");
        dispatch.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(SkeletonMismatchException.class),
                "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(String.class) }));
        dispatch.visitInsn(Opcodes.ATHROW);

        // opnum has been set
        dispatch.visitLabel(opnumSet);

        dispatch.visitVarInsn(Opcodes.ALOAD, var.get("remoteobj"));
        dispatch.visitTypeInsn(Opcodes.CHECKCAST, typeArg(clazz));
        dispatch.visitVarInsn(Opcodes.ASTORE, var.get("remoteobj"));

        Label deflt = new Label();
        Label[] methLabels = new Label[remotemethods.length];
        for (int i = 0; i < methLabels.length; i++)
            methLabels[i] = new Label();

        // switch on opnum
        dispatch.visitVarInsn(Opcodes.ILOAD, var.get("opnum"));
        dispatch.visitTableSwitchInsn(0, remotemethods.length - 1, deflt, methLabels);

        // Method dispatch
        for (int i = 0; i < remotemethods.length; i++) {
            dispatch.visitLabel(methLabels[i]);
            Method m = remotemethods[i].meth;
            generateMethodSkel(dispatch, m, var);
        }

        dispatch.visitLabel(deflt);
        dispatch.visitTypeInsn(Opcodes.NEW, typeArg(UnmarshalException.class));
        dispatch.visitInsn(Opcodes.DUP);
        dispatch.visitLdcInsn("invalid method number");
        dispatch.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(UnmarshalException.class), "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(String.class) }));
        dispatch.visitInsn(Opcodes.ATHROW);

        dispatch.visitMaxs(-1, -1);

        skel.visitEnd();
        byte[] classData = skel.toByteArray();
        if (!noWrite) {
            if (file.exists())
                file.delete();
            if (file.getParentFile() != null)
                file.getParentFile().mkdirs();
            FileOutputStream fos = new FileOutputStream(file);
            fos.write(classData);
            fos.flush();
            fos.close();
        }
    }

    private void generateMethodSkel(MethodVisitor cv, Method m, Variables var) {
        Class[] sig = m.getParameterTypes();

        Label readArgs = new Label();
        cv.visitLabel(readArgs);

        boolean needcastcheck = false;

        // ObjectInput in = call.getInputStream();
        cv.visitVarInsn(Opcodes.ALOAD, var.get("remotecall"));
        cv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteCall.class), "getInputStream",
                Type.getMethodDescriptor(Type.getType(ObjectInput.class), new Type[] {}));
        cv.visitVarInsn(Opcodes.ASTORE, var.allocate("objectinput"));

        for (int i = 0; i < sig.length; i++) {
            // dup input stream
            cv.visitVarInsn(Opcodes.ALOAD, var.get("objectinput"));

            Class readCls = sig[i].isPrimitive() ? sig[i] : Object.class;

            // in.readFoo()
            cv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectInput.class), readMethod(sig[i]),
                    Type.getMethodDescriptor(Type.getType(readCls), new Type[] {}));

            if (!sig[i].isPrimitive() && !sig[i].equals(Object.class)) {
                needcastcheck = true;
                cv.visitTypeInsn(Opcodes.CHECKCAST, typeArg(sig[i]));
            }

            // store arg in variable
            cv.visitVarInsn(storeOpcode(sig[i]), var.allocate(param(m, i), size(sig[i])));
        }

        var.deallocate("objectinput");

        Label doCall = new Label();
        Label closeInput = new Label();

        cv.visitJumpInsn(Opcodes.JSR, closeInput);
        cv.visitJumpInsn(Opcodes.GOTO, doCall);

        // throw new UnmarshalException
        Label handler = new Label();
        cv.visitLabel(handler);
        cv.visitVarInsn(Opcodes.ASTORE, var.allocate("exception"));
        cv.visitTypeInsn(Opcodes.NEW, typeArg(UnmarshalException.class));
        cv.visitInsn(Opcodes.DUP);
        cv.visitLdcInsn("error unmarshalling arguments");
        cv.visitVarInsn(Opcodes.ALOAD, var.deallocate("exception"));
        cv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(UnmarshalException.class), "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE,
                        new Type[] { Type.getType(String.class), Type.getType(Exception.class) }));
        cv.visitVarInsn(Opcodes.ASTORE, var.allocate("toThrow"));
        cv.visitJumpInsn(Opcodes.JSR, closeInput);
        cv.visitVarInsn(Opcodes.ALOAD, var.get("toThrow"));
        cv.visitInsn(Opcodes.ATHROW);

        cv.visitTryCatchBlock(readArgs, handler, handler, Type.getInternalName(IOException.class));
        if (needcastcheck) {
            cv.visitTryCatchBlock(readArgs, handler, handler, Type.getInternalName(ClassCastException.class));
        }

        // finally block
        cv.visitLabel(closeInput);
        cv.visitVarInsn(Opcodes.ASTORE, var.allocate("retAddress"));
        cv.visitVarInsn(Opcodes.ALOAD, var.get("remotecall"));
        cv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteCall.class), "releaseInputStream",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
        cv.visitVarInsn(Opcodes.RET, var.deallocate("retAddress"));
        var.deallocate("toThrow");

        // do the call using args stored as variables
        cv.visitLabel(doCall);
        cv.visitVarInsn(Opcodes.ALOAD, var.get("remoteobj"));
        for (int i = 0; i < sig.length; i++)
            cv.visitVarInsn(loadOpcode(sig[i]), var.deallocate(param(m, i)));
        cv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(clazz), m.getName(),
                Type.getMethodDescriptor(m));

        Class returntype = m.getReturnType();
        if (!returntype.equals(Void.TYPE)) {
            cv.visitVarInsn(storeOpcode(returntype), var.allocate("result", size(returntype)));
        }

        // write result to result stream
        Label writeResult = new Label();
        cv.visitLabel(writeResult);
        cv.visitVarInsn(Opcodes.ALOAD, var.get("remotecall"));
        cv.visitInsn(Opcodes.ICONST_1);
        cv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(RemoteCall.class), "getResultStream",
                Type.getMethodDescriptor(Type.getType(ObjectOutput.class), new Type[] { Type.BOOLEAN_TYPE }));

        if (!returntype.equals(Void.TYPE)) {
            // out.writeFoo(result)
            cv.visitVarInsn(loadOpcode(returntype), var.deallocate("result"));
            Class writeCls = returntype.isPrimitive() ? returntype : Object.class;
            cv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(ObjectOutput.class),
                    writeMethod(returntype),
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(writeCls) }));
        }

        cv.visitInsn(Opcodes.RETURN);

        // throw new MarshalException
        Label marshalHandler = new Label();
        cv.visitLabel(marshalHandler);
        cv.visitVarInsn(Opcodes.ASTORE, var.allocate("exception"));
        cv.visitTypeInsn(Opcodes.NEW, typeArg(MarshalException.class));
        cv.visitInsn(Opcodes.DUP);
        cv.visitLdcInsn("error marshalling return");
        cv.visitVarInsn(Opcodes.ALOAD, var.deallocate("exception"));
        cv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(MarshalException.class), "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE,
                        new Type[] { Type.getType(String.class), Type.getType(Exception.class) }));
        cv.visitInsn(Opcodes.ATHROW);
        cv.visitTryCatchBlock(writeResult, marshalHandler, marshalHandler, Type.getInternalName(IOException.class));
    }

    private static String typeArg(Class cls) {
        if (cls.isArray())
            return Type.getDescriptor(cls);

        return Type.getInternalName(cls);
    }

    private static String readMethod(Class cls) {
        if (cls.equals(Void.TYPE))
            throw new IllegalArgumentException("can not read void");

        String method;
        if (cls.equals(Boolean.TYPE))
            method = "readBoolean";
        else if (cls.equals(Byte.TYPE))
            method = "readByte";
        else if (cls.equals(Character.TYPE))
            method = "readChar";
        else if (cls.equals(Short.TYPE))
            method = "readShort";
        else if (cls.equals(Integer.TYPE))
            method = "readInt";
        else if (cls.equals(Long.TYPE))
            method = "readLong";
        else if (cls.equals(Float.TYPE))
            method = "readFloat";
        else if (cls.equals(Double.TYPE))
            method = "readDouble";
        else
            method = "readObject";

        return method;
    }

    private static String writeMethod(Class cls) {
        if (cls.equals(Void.TYPE))
            throw new IllegalArgumentException("can not read void");

        String method;
        if (cls.equals(Boolean.TYPE))
            method = "writeBoolean";
        else if (cls.equals(Byte.TYPE))
            method = "writeByte";
        else if (cls.equals(Character.TYPE))
            method = "writeChar";
        else if (cls.equals(Short.TYPE))
            method = "writeShort";
        else if (cls.equals(Integer.TYPE))
            method = "writeInt";
        else if (cls.equals(Long.TYPE))
            method = "writeLong";
        else if (cls.equals(Float.TYPE))
            method = "writeFloat";
        else if (cls.equals(Double.TYPE))
            method = "writeDouble";
        else
            method = "writeObject";

        return method;
    }

    private static int returnOpcode(Class cls) {
        int returncode;
        if (cls.equals(Boolean.TYPE))
            returncode = Opcodes.IRETURN;
        else if (cls.equals(Byte.TYPE))
            returncode = Opcodes.IRETURN;
        else if (cls.equals(Character.TYPE))
            returncode = Opcodes.IRETURN;
        else if (cls.equals(Short.TYPE))
            returncode = Opcodes.IRETURN;
        else if (cls.equals(Integer.TYPE))
            returncode = Opcodes.IRETURN;
        else if (cls.equals(Long.TYPE))
            returncode = Opcodes.LRETURN;
        else if (cls.equals(Float.TYPE))
            returncode = Opcodes.FRETURN;
        else if (cls.equals(Double.TYPE))
            returncode = Opcodes.DRETURN;
        else if (cls.equals(Void.TYPE))
            returncode = Opcodes.RETURN;
        else
            returncode = Opcodes.ARETURN;

        return returncode;
    }

    private static int loadOpcode(Class cls) {
        if (cls.equals(Void.TYPE))
            throw new IllegalArgumentException("can not load void");

        int loadcode;
        if (cls.equals(Boolean.TYPE))
            loadcode = Opcodes.ILOAD;
        else if (cls.equals(Byte.TYPE))
            loadcode = Opcodes.ILOAD;
        else if (cls.equals(Character.TYPE))
            loadcode = Opcodes.ILOAD;
        else if (cls.equals(Short.TYPE))
            loadcode = Opcodes.ILOAD;
        else if (cls.equals(Integer.TYPE))
            loadcode = Opcodes.ILOAD;
        else if (cls.equals(Long.TYPE))
            loadcode = Opcodes.LLOAD;
        else if (cls.equals(Float.TYPE))
            loadcode = Opcodes.FLOAD;
        else if (cls.equals(Double.TYPE))
            loadcode = Opcodes.DLOAD;
        else
            loadcode = Opcodes.ALOAD;

        return loadcode;
    }

    private static int storeOpcode(Class cls) {
        if (cls.equals(Void.TYPE))
            throw new IllegalArgumentException("can not load void");

        int storecode;
        if (cls.equals(Boolean.TYPE))
            storecode = Opcodes.ISTORE;
        else if (cls.equals(Byte.TYPE))
            storecode = Opcodes.ISTORE;
        else if (cls.equals(Character.TYPE))
            storecode = Opcodes.ISTORE;
        else if (cls.equals(Short.TYPE))
            storecode = Opcodes.ISTORE;
        else if (cls.equals(Integer.TYPE))
            storecode = Opcodes.ISTORE;
        else if (cls.equals(Long.TYPE))
            storecode = Opcodes.LSTORE;
        else if (cls.equals(Float.TYPE))
            storecode = Opcodes.FSTORE;
        else if (cls.equals(Double.TYPE))
            storecode = Opcodes.DSTORE;
        else
            storecode = Opcodes.ASTORE;

        return storecode;
    }

    private static String unboxMethod(Class primitive) {
        if (!primitive.isPrimitive())
            throw new IllegalArgumentException("can not unbox nonprimitive");

        String method;
        if (primitive.equals(Boolean.TYPE))
            method = "booleanValue";
        else if (primitive.equals(Byte.TYPE))
            method = "byteValue";
        else if (primitive.equals(Character.TYPE))
            method = "charValue";
        else if (primitive.equals(Short.TYPE))
            method = "shortValue";
        else if (primitive.equals(Integer.TYPE))
            method = "intValue";
        else if (primitive.equals(Long.TYPE))
            method = "longValue";
        else if (primitive.equals(Float.TYPE))
            method = "floatValue";
        else if (primitive.equals(Double.TYPE))
            method = "doubleValue";
        else
            throw new IllegalStateException("unknown primitive class " + primitive);

        return method;
    }

    public static Class box(Class cls) {
        if (!cls.isPrimitive())
            throw new IllegalArgumentException("can only box primitive");

        Class box;
        if (cls.equals(Boolean.TYPE))
            box = Boolean.class;
        else if (cls.equals(Byte.TYPE))
            box = Byte.class;
        else if (cls.equals(Character.TYPE))
            box = Character.class;
        else if (cls.equals(Short.TYPE))
            box = Short.class;
        else if (cls.equals(Integer.TYPE))
            box = Integer.class;
        else if (cls.equals(Long.TYPE))
            box = Long.class;
        else if (cls.equals(Float.TYPE))
            box = Float.class;
        else if (cls.equals(Double.TYPE))
            box = Double.class;
        else
            throw new IllegalStateException("unknown primitive type " + cls);

        return box;
    }

    private static int size(Class cls) {
        if (cls.equals(Long.TYPE) || cls.equals(Double.TYPE))
            return 2;
        else
            return 1;
    }

    /**
     * Sort exceptions so the most general go last.
     */
    private Class[] sortExceptions(Class[] except) {
        for (int i = 0; i < except.length; i++) {
            for (int j = i + 1; j < except.length; j++) {
                if (except[i].isAssignableFrom(except[j])) {
                    Class tmp = except[i];
                    except[i] = except[j];
                    except[j] = tmp;
                }
            }
        }
        return (except);
    }

    public void setup(boolean keep, boolean need11Stubs, boolean need12Stubs, boolean iiop, boolean poa,
            boolean debug, boolean warnings, boolean noWrite, boolean verbose, boolean force, String classpath,
            String bootclasspath, String extdirs, String outputDirectory) {
        this.keep = keep;
        this.need11Stubs = need11Stubs;
        this.need12Stubs = need12Stubs;
        this.verbose = verbose;
        this.noWrite = noWrite;

        // Set up classpath.
        this.classpath = classpath;
        StringTokenizer st = new StringTokenizer(classpath, File.pathSeparator);
        URL[] u = new URL[st.countTokens()];
        for (int i = 0; i < u.length; i++) {
            String path = st.nextToken();
            File f = new File(path);
            try {
                u[i] = f.toURL();
            } catch (java.net.MalformedURLException mue) {
                logError("malformed classpath component " + path);
                return;
            }
        }
        loader = new URLClassLoader(u);

        destination = outputDirectory;
    }

    private void findRemoteMethods() throws RMICException {
        List rmeths = new ArrayList();
        for (Class cur = clazz; cur != null; cur = cur.getSuperclass()) {
            Class[] interfaces = cur.getInterfaces();
            for (int i = 0; i < interfaces.length; i++) {
                if (java.rmi.Remote.class.isAssignableFrom(interfaces[i])) {
                    Class remoteInterface = interfaces[i];
                    if (verbose)
                        System.out.println("[implements " + remoteInterface.getName() + "]");

                    // check if the methods declare RemoteExceptions
                    Method[] meths = remoteInterface.getMethods();
                    for (int j = 0; j < meths.length; j++) {
                        Method m = meths[j];
                        Class[] exs = m.getExceptionTypes();

                        boolean throwsRemote = false;
                        for (int k = 0; k < exs.length; k++) {
                            if (exs[k].isAssignableFrom(RemoteException.class))
                                throwsRemote = true;
                        }

                        if (!throwsRemote) {
                            throw new RMICException("Method " + m + " in interface " + remoteInterface
                                    + " does not throw a RemoteException");
                        }

                        rmeths.add(m);
                    }

                    mRemoteInterfaces.add(remoteInterface);
                }
            }
        }

        // intersect exceptions for doubly inherited methods
        boolean[] skip = new boolean[rmeths.size()];
        for (int i = 0; i < skip.length; i++)
            skip[i] = false;
        List methrefs = new ArrayList();
        for (int i = 0; i < rmeths.size(); i++) {
            if (skip[i])
                continue;
            Method current = (Method) rmeths.get(i);
            MethodRef ref = new MethodRef(current);
            for (int j = i + 1; j < rmeths.size(); j++) {
                Method other = (Method) rmeths.get(j);
                if (ref.isMatch(other)) {
                    ref.intersectExceptions(other);
                    skip[j] = true;
                }
            }
            methrefs.add(ref);
        }

        // Convert into a MethodRef array and sort them
        remotemethods = (MethodRef[]) methrefs.toArray(new MethodRef[methrefs.size()]);
        Arrays.sort(remotemethods);
    }

    /**
     * Prints an error to System.err and increases the error count.
     */
    private void logError(Exception theError) {
        logError(theError.getMessage());
        if (verbose)
            theError.printStackTrace(System.err);
    }

    /**
     * Prints an error to System.err and increases the error count.
     */
    private void logError(String theError) {
        errorCount++;
        System.err.println("error: " + theError);
    }

    private static String getPrettyName(Class cls) {
        StringBuilder str = new StringBuilder();
        for (int count = 0;; count++) {
            if (!cls.isArray()) {
                str.append(cls.getName());
                for (; count > 0; count--)
                    str.append("[]");
                return (str.toString());
            }
            cls = cls.getComponentType();
        }
    }

    private static class MethodRef implements Comparable {
        Method meth;
        long hash;
        List exceptions;
        private String sig;

        MethodRef(Method m) {
            meth = m;
            sig = Type.getMethodDescriptor(meth);
            hash = RMIHashes.getMethodHash(m);
            // add exceptions removing subclasses
            exceptions = removeSubclasses(m.getExceptionTypes());
        }

        public int compareTo(Object obj) {
            MethodRef that = (MethodRef) obj;
            int name = this.meth.getName().compareTo(that.meth.getName());
            if (name == 0) {
                return this.sig.compareTo(that.sig);
            }
            return name;
        }

        public boolean isMatch(Method m) {
            if (!meth.getName().equals(m.getName()))
                return false;

            Class[] params1 = meth.getParameterTypes();
            Class[] params2 = m.getParameterTypes();
            if (params1.length != params2.length)
                return false;

            for (int i = 0; i < params1.length; i++)
                if (!params1[i].equals(params2[i]))
                    return false;

            return true;
        }

        private static List removeSubclasses(Class[] classes) {
            List list = new ArrayList();
            for (int i = 0; i < classes.length; i++) {
                Class candidate = classes[i];
                boolean add = true;
                for (int j = 0; j < classes.length; j++) {
                    if (classes[j].equals(candidate))
                        continue;
                    else if (classes[j].isAssignableFrom(candidate))
                        add = false;
                }
                if (add)
                    list.add(candidate);
            }

            return list;
        }

        public void intersectExceptions(Method m) {
            List incoming = removeSubclasses(m.getExceptionTypes());

            List updated = new ArrayList();

            for (int i = 0; i < exceptions.size(); i++) {
                Class outer = (Class) exceptions.get(i);
                boolean addOuter = false;
                for (int j = 0; j < incoming.size(); j++) {
                    Class inner = (Class) incoming.get(j);

                    if (inner.equals(outer) || inner.isAssignableFrom(outer))
                        addOuter = true;
                    else if (outer.isAssignableFrom(inner))
                        updated.add(inner);
                }

                if (addOuter)
                    updated.add(outer);
            }

            exceptions = updated;
        }
    }
}