// Copyright (c) Corporation for National Research Initiatives package org.python.compiler; import java.io.OutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Map; import java.util.Set; import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.python.core.Py; import org.python.core.PyMethod; import org.python.core.PyObject; import org.python.core.PyProxy; import org.python.core.PyReflectedFunction; import org.python.util.Generic; public class ProxyMaker implements ClassConstants, Opcodes { public static final int tBoolean=0; public static final int tByte=1; public static final int tShort=2; public static final int tInteger=3; public static final int tLong=4; public static final int tFloat=5; public static final int tDouble=6; public static final int tCharacter=7; public static final int tVoid=8; public static final int tOther=9; public static final int tNone=10; public static Map<Class<?>, Integer> types = fillTypes(); public static Map<Class<?>, Integer> fillTypes() { Map<Class<?>, Integer> typeMap = Generic.map(); typeMap.put(Boolean.TYPE, tBoolean); typeMap.put(Byte.TYPE, tByte); typeMap.put(Short.TYPE, tShort); typeMap.put(Integer.TYPE, tInteger); typeMap.put(Long.TYPE, tLong); typeMap.put(Float.TYPE, tFloat); typeMap.put(Double.TYPE, tDouble); typeMap.put(Character.TYPE, tCharacter); typeMap.put(Void.TYPE, tVoid); return typeMap; } public static int getType(Class<?> c) { if (c == null) { return tNone; } Object i = types.get(c); if (i == null) { return tOther; } else { return ((Integer)i); } } /** * Retrieves <code>name</code> from the PyObject in <code>proxy</code> if it's defined in * Python. This is a specialized helper function for internal PyProxy use. */ public static PyObject findPython(PyProxy proxy, String name) { PyObject o = proxy._getPyInstance(); if (o == null) { proxy.__initProxy__(new Object[0]); o = proxy._getPyInstance(); } PyObject ret = o.__findattr__(name); if (ret instanceof PyMethod) { PyMethod meth = ((PyMethod)ret); if (meth.im_func instanceof PyReflectedFunction) { PyReflectedFunction func = (PyReflectedFunction)meth.im_func; if (func.nargs > 0 && proxy.getClass() == func.argslist[0].declaringClass) { // This function is the default return for the proxy type if the Python instance // hasn't returned something of its own from __findattr__, so do the standard // Java call on this return null; } } } Py.setSystemState(proxy._getPySystemState()); return ret; } Class<?> superclass; Class<?>[] interfaces; Set<String> names; Set<String> supernames = Generic.set(); public ClassFile classfile; /** The name of the class to build. */ public String myClass; /** * Creates a proxy class maker that produces classes named * <code>org.python.proxies.(superclassName)</code> with <code>superclass</code> as an * implemented interface or extended class, depending on the its type. * * @deprecated - Use {@link ProxyMaker#ProxyMaker(String, Class, Class[]) */ @Deprecated public ProxyMaker(String superclassName, Class<?> superclass) { this("org.python.proxies." + superclassName, superclass.isInterface() ? Object.class : superclass, superclass.isInterface() ? new Class<?>[] { superclass} : new Class<?>[0]); } /** * Creates a proxy class maker that produces classes named <code>proxyClassName</code> that * extends <code>superclass</code> and implements the interfaces in <code>interfaces</code>. */ public ProxyMaker(String proxyClassName, Class<?> superclass, Class<?>... interfaces) { this.myClass = proxyClassName; if (superclass == null) { superclass = Object.class; } if (superclass.isInterface()) { throw new IllegalArgumentException("Given an interface, " + superclass.getName() + ", for a proxy superclass"); } this.superclass = superclass; if (interfaces == null) { interfaces = new Class[0]; } for (Class<?> interfac : interfaces) { if (!interfac.isInterface()) { throw new IllegalArgumentException( "All classes in the interfaces array must be interfaces, unlike " + interfac.getName()); } } this.interfaces = interfaces; } public static String mapClass(Class<?> c) { String name = c.getName(); int index = name.indexOf("."); if (index == -1) { return name; } StringBuffer buf = new StringBuffer(name.length()); int last_index = 0; while (index != -1) { buf.append(name.substring(last_index, index)); buf.append("/"); last_index = index+1; index = name.indexOf(".", last_index); } buf.append(name.substring(last_index, name.length())); return buf.toString(); } public static String mapType(Class<?> type) { if (type.isArray()) return "["+mapType(type.getComponentType()); switch (getType(type)) { case tByte: return "B"; case tCharacter: return "C"; case tDouble: return "D"; case tFloat: return "F"; case tInteger: return "I"; case tLong: return "J"; case tShort: return "S"; case tBoolean: return "Z"; case tVoid: return "V"; default: return "L"+mapClass(type)+";"; } } public static String makeSig(Class<?> ret, Class<?>... sig) { String[] mapped = new String[sig.length]; for (int i = 0; i < mapped.length; i++) { mapped[i] = mapType(sig[i]); } return makeSig(mapType(ret), mapped); } public static String makeSig(String returnType, String... parameterTypes) { StringBuilder buf = new StringBuilder("("); for (String param : parameterTypes) { buf.append(param); } return buf.append(')').append(returnType).toString(); } public void doConstants() throws Exception { Code code = classfile.addMethod("<clinit>", makeSig("V"), Modifier.STATIC); code.return_(); } public static void doReturn(Code code, Class<?> type) throws Exception { switch (getType(type)) { case tNone: break; case tCharacter: case tBoolean: case tByte: case tShort: case tInteger: code.ireturn(); break; case tLong: code.lreturn(); break; case tFloat: code.freturn(); break; case tDouble: code.dreturn(); break; case tVoid: code.return_(); break; default: code.areturn(); break; } } public static void doNullReturn(Code code, Class<?> type) throws Exception { switch (getType(type)) { case tNone: break; case tCharacter: case tBoolean: case tByte: case tShort: case tInteger: code.iconst_0(); code.ireturn(); break; case tLong: code.lconst_0(); code.lreturn(); break; case tFloat: code.fconst_0(); code.freturn(); break; case tDouble: code.dconst_0(); code.dreturn(); break; case tVoid: code.return_(); break; default: code.aconst_null(); code.areturn(); break; } } public void callSuper(Code code, String name, String superclass, Class<?>[] parameters, Class<?> ret, String sig) throws Exception { code.aload(0); int local_index; int i; for (i=0, local_index=1; i<parameters.length; i++) { switch(getType(parameters[i])) { case tCharacter: case tBoolean: case tByte: case tShort: case tInteger: code.iload(local_index); local_index += 1; break; case tLong: code.lload(local_index); local_index += 2; break; case tFloat: code.fload(local_index); local_index += 1; break; case tDouble: code.dload(local_index); local_index += 2; break; default: code.aload(local_index); local_index += 1; break; } } code.invokespecial(superclass, name, sig); doReturn(code, ret); } public void doJavaCall(Code code, String name, String type, String jcallName) throws Exception { code.invokevirtual("org/python/core/PyObject", jcallName, makeSig($pyObj, $objArr)); code.invokestatic("org/python/core/Py", "py2"+name, makeSig(type, $pyObj)); } public void getArgs(Code code, Class<?>[] parameters) throws Exception { if (parameters.length == 0) { code.getstatic("org/python/core/Py", "EmptyObjects", $pyObjArr); } else { code.iconst(parameters.length); code.anewarray("java/lang/Object"); int array = code.getLocal("[org/python/core/PyObject"); code.astore(array); int local_index; int i; for (i=0, local_index=1; i<parameters.length; i++) { code.aload(array); code.iconst(i); switch (getType(parameters[i])) { case tBoolean: case tByte: case tShort: case tInteger: code.iload(local_index); local_index += 1; code.invokestatic("org/python/core/Py", "newInteger", "(I)" + $pyInteger); break; case tLong: code.lload(local_index); local_index += 2; code.invokestatic("org/python/core/Py", "newInteger", "(J)" + $pyObj); break; case tFloat: code.fload(local_index); local_index += 1; code.invokestatic("org/python/core/Py", "newFloat", "(F)" + $pyFloat); break; case tDouble: code.dload(local_index); local_index += 2; code.invokestatic("org/python/core/Py", "newFloat", "(D)" + $pyFloat); break; case tCharacter: code.iload(local_index); local_index += 1; code.invokestatic("org/python/core/Py", "newString", "(C)" + $pyStr); break; default: code.aload(local_index); local_index += 1; break; } code.aastore(); } code.aload(array); } } public void callMethod(Code code, String name, Class<?>[] parameters, Class<?> ret, Class<?>[] exceptions) throws Exception { Label start = null; Label end = null; String jcallName = "_jcall"; int instLocal = 0; if (exceptions.length > 0) { start = new Label(); end = new Label(); jcallName = "_jcallexc"; instLocal = code.getLocal("org/python/core/PyObject"); code.astore(instLocal); code.label(start); code.aload(instLocal); } getArgs(code, parameters); switch (getType(ret)) { case tCharacter: doJavaCall(code, "char", "C", jcallName); break; case tBoolean: doJavaCall(code, "boolean", "Z", jcallName); break; case tByte: case tShort: case tInteger: doJavaCall(code, "int", "I", jcallName); break; case tLong: doJavaCall(code, "long", "J", jcallName); break; case tFloat: doJavaCall(code, "float", "F", jcallName); break; case tDouble: doJavaCall(code, "double", "D", jcallName); break; case tVoid: doJavaCall(code, "void", "V", jcallName); break; default: code.invokevirtual("org/python/core/PyObject", jcallName, makeSig($pyObj, $objArr)); code.ldc(ret.getName()); code.invokestatic("java/lang/Class","forName", makeSig($clss, $str)); code.invokestatic("org/python/core/Py", "tojava", makeSig($obj, $pyObj, $clss)); // I guess I need this checkcast to keep the verifier happy code.checkcast(mapClass(ret)); break; } if (end != null) { code.label(end); } doReturn(code, ret); if (exceptions.length > 0) { boolean throwableFound = false; Label handlerStart = null; for (Class<?> exception : exceptions) { handlerStart = new Label(); code.label(handlerStart); int excLocal = code.getLocal("java/lang/Throwable"); code.astore(excLocal); code.aload(excLocal); code.athrow(); code.visitTryCatchBlock(start, end, handlerStart, mapClass(exception)); doNullReturn(code, ret); code.freeLocal(excLocal); if (exception == Throwable.class) throwableFound = true; } if (!throwableFound) { // The final catch (Throwable) handlerStart = new Label(); code.label(handlerStart); int excLocal = code.getLocal("java/lang/Throwable"); code.astore(excLocal); code.aload(instLocal); code.aload(excLocal); code.invokevirtual("org/python/core/PyObject", "_jthrow", makeSig("V", $throwable)); code.visitTryCatchBlock(start, end, handlerStart, "java/lang/Throwable"); code.freeLocal(excLocal); doNullReturn(code, ret); } code.freeLocal(instLocal); } } public void addMethod(Method method, int access) throws Exception { boolean isAbstract = false; if (Modifier.isAbstract(access)) { access = access & ~Modifier.ABSTRACT; isAbstract = true; } Class<?>[] parameters = method.getParameterTypes(); Class<?> ret = method.getReturnType(); String sig = makeSig(ret, parameters); String name = method.getName(); names.add(name); Code code = classfile.addMethod(name, sig, access); code.aload(0); code.ldc(name); if (!isAbstract) { int tmp = code.getLocal("org/python/core/PyObject"); code.invokestatic("org/python/compiler/ProxyMaker", "findPython", makeSig($pyObj, $pyProxy, $str)); code.astore(tmp); code.aload(tmp); Label callPython = new Label(); code.ifnonnull(callPython); String superClass = mapClass(method.getDeclaringClass()); callSuper(code, name, superClass, parameters, ret, sig); code.label(callPython); code.aload(tmp); callMethod(code, name, parameters, ret, method.getExceptionTypes()); addSuperMethod("super__"+name, name, superClass, parameters, ret, sig, access); } else { code.invokestatic("org/python/compiler/ProxyMaker", "findPython", makeSig($pyObj, $pyProxy, $str)); code.dup(); Label returnNull = new Label(); code.ifnull(returnNull); callMethod(code, name, parameters, ret, method.getExceptionTypes()); code.label(returnNull); code.pop(); doNullReturn(code, ret); } } private String methodString(Method m) { StringBuffer buf = new StringBuffer(m.getName()); buf.append(":"); Class<?>[] params = m.getParameterTypes(); for (Class<?> param : params) { buf.append(param.getName()); buf.append(","); } return buf.toString(); } protected void addMethods(Class<?> c, Set<String> t) throws Exception { Method[] methods = c.getDeclaredMethods(); for (Method method : methods) { if (!t.add(methodString(method))) { continue; } int access = method.getModifiers(); if (Modifier.isStatic(access) || Modifier.isPrivate(access)) { continue; } if (Modifier.isNative(access)) { access = access & ~Modifier.NATIVE; } if (Modifier.isProtected(access)) { access = (access & ~Modifier.PROTECTED) | Modifier.PUBLIC; if (Modifier.isFinal(access)) { addSuperMethod(method, access); continue; } } else if (Modifier.isFinal(access)) { continue; } else if (!Modifier.isPublic(access)) { continue; // package protected by process of elimination; we can't override } addMethod(method, access); } Class<?> sc = c.getSuperclass(); if (sc != null) { addMethods(sc, t); } for (Class<?> iface : c.getInterfaces()) { addMethods(iface, t); } } public void addConstructor(String name, Class<?>[] parameters, Class<?> ret, String sig, int access) throws Exception { Code code = classfile.addMethod("<init>", sig, access); callSuper(code, "<init>", name, parameters, Void.TYPE, sig); } public void addConstructors(Class<?> c) throws Exception { Constructor<?>[] constructors = c.getDeclaredConstructors(); String name = mapClass(c); for (Constructor<?> constructor : constructors) { int access = constructor.getModifiers(); if (Modifier.isPrivate(access)) { continue; } if (Modifier.isNative(access)) { access = access & ~Modifier.NATIVE; } if (Modifier.isProtected(access)) { access = access & ~Modifier.PROTECTED | Modifier.PUBLIC; } Class<?>[] parameters = constructor.getParameterTypes(); addConstructor(name, parameters, Void.TYPE, makeSig(Void.TYPE, parameters), access); } } // Super methods are added for the following three reasons: // // 1) for a protected non-final method add a public method with no // super__ prefix. This gives needed access to this method for // subclasses // // 2) for protected final methods, add a public method with the // super__ prefix. This avoids the danger of trying to override a // final method // // 3) For any other method that is overridden, add a method with the // super__ prefix. This gives access to super. version or the // method. // public void addSuperMethod(Method method, int access) throws Exception { Class<?>[] parameters = method.getParameterTypes(); Class<?> ret = method.getReturnType(); String superClass = mapClass(method.getDeclaringClass()); String superName = method.getName(); String methodName = superName; if (Modifier.isFinal(access)) { methodName = "super__" + superName; access &= ~Modifier.FINAL; } addSuperMethod(methodName, superName, superClass, parameters, ret, makeSig(ret, parameters), access); } public void addSuperMethod(String methodName, String superName, String declClass, Class<?>[] parameters, Class<?> ret, String sig, int access) throws Exception { if (methodName.startsWith("super__")) { /* rationale: JC java-class, P proxy-class subclassing JC in order to avoid infinite recursion P should define super__foo only if no class between P and JC in the hierarchy defines it yet; this means that the python class needing P is the first that redefines the JC method foo. */ try { superclass.getMethod(methodName, parameters); return; } catch (NoSuchMethodException e) { // OK, no one else defines it, so we need to } catch (SecurityException e) { return; } } supernames.add(methodName); Code code = classfile.addMethod(methodName, sig, access); callSuper(code, superName, declClass, parameters, ret, sig); } public void addProxy() throws Exception { // implement PyProxy interface classfile.addField("__proxy", $pyObj, Modifier.PROTECTED); // setProxy methods Code code = classfile.addMethod("_setPyInstance", makeSig("V", $pyObj), Modifier.PUBLIC); code.aload(0); code.aload(1); code.putfield(classfile.name, "__proxy", $pyObj); code.return_(); // getProxy method code = classfile.addMethod("_getPyInstance", makeSig($pyObj), Modifier.PUBLIC); code.aload(0); code.getfield(classfile.name, "__proxy", $pyObj); code.areturn(); String pySys = "Lorg/python/core/PySystemState;"; // implement PyProxy interface classfile.addField("__systemState", pySys, Modifier.PROTECTED | Modifier.TRANSIENT); // setProxy method code = classfile.addMethod("_setPySystemState", makeSig("V", pySys), Modifier.PUBLIC); code.aload(0); code.aload(1); code.putfield(classfile.name, "__systemState", pySys); code.return_(); // getProxy method code = classfile.addMethod("_getPySystemState", makeSig(pySys), Modifier.PUBLIC); code.aload(0); code.getfield(classfile.name, "__systemState", pySys); code.areturn(); } public void addClassDictInit() throws Exception { // classDictInit method classfile.addInterface(mapClass(org.python.core.ClassDictInit.class)); Code code = classfile.addMethod("classDictInit", makeSig("V", $pyObj), Modifier.PUBLIC | Modifier.STATIC); code.aload(0); code.ldc("__supernames__"); int strArray = CodeCompiler.makeStrings(code, supernames); code.aload(strArray); code.freeLocal(strArray); code.invokestatic("org/python/core/Py", "java2py", makeSig($pyObj, $obj)); code.invokevirtual("org/python/core/PyObject", "__setitem__", makeSig("V", $str, $pyObj)); code.return_(); } /** * Builds this proxy and writes its bytecode to <code>out</code>. */ public void build(OutputStream out) throws Exception { build(); classfile.write(out); } public void build() throws Exception { names = Generic.set(); int access = superclass.getModifiers(); if ((access & Modifier.FINAL) != 0) { throw new InstantiationException("can't subclass final class"); } access = Modifier.PUBLIC | Modifier.SYNCHRONIZED; classfile = new ClassFile(myClass, mapClass(superclass), access); addProxy(); addConstructors(superclass); classfile.addInterface("org/python/core/PyProxy"); Set<String> seenmethods = Generic.set(); addMethods(superclass, seenmethods); for (Class<?> iface : interfaces) { if (iface.isAssignableFrom(superclass)) { Py.writeWarning("compiler", "discarding redundant interface: " + iface.getName()); continue; } classfile.addInterface(mapClass(iface)); addMethods(iface, seenmethods); } doConstants(); addClassDictInit(); } }