[Jython-checkins] jython: Proxymakers that subclass CustomMaker can now completely control the

jim.baker jython-checkins at python.org
Thu Sep 5 01:45:32 CEST 2013


http://hg.python.org/jython/rev/be402c429680
changeset:   7113:be402c429680
user:        Jim Baker <jim.baker at rackspace.com>
date:        Wed Sep 04 17:45:12 2013 -0600
summary:
  Proxymakers that subclass CustomMaker can now completely control the
proxymaking process, including saving bytecode.

files:
  Lib/test/bark.py                         |   30 ++
  Lib/test/clamp.py                        |   60 ++++
  Lib/test/test_java_integration.py        |  140 +++++++++-
  src/org/python/compiler/CustomMaker.java |   54 ++++
  src/org/python/core/MakeProxies.java     |   16 +-
  5 files changed, 276 insertions(+), 24 deletions(-)


diff --git a/Lib/test/bark.py b/Lib/test/bark.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/bark.py
@@ -0,0 +1,30 @@
+from __future__ import print_function
+
+from java.io import Serializable
+from java.util.concurrent import Callable
+
+from clamp import PackageProxy
+
+
+class Dog(Callable, Serializable):
+    # FIXME in a future branch, support the ability to call __init__
+    # via __initProxy__ To support this functionality, the test
+    # version of the clamp module in this directory needs to add a
+    # Java constructor with an Object... arg list; possibly introspect
+    # the arg list of __init__; or get some additional metadata from
+    # the initialization of PackageProxy.
+    __proxymaker__ = PackageProxy("org.python.test")
+                
+    def __init__(self):
+        self.name = "Rover"
+        self.number = 42
+        
+    def whoami(self):
+        return self.name
+
+    def call(self):
+        # Using print forces use of PySystemState and shows it's initialized
+        print("%s barks %s times" % (self.name, self.number))
+
+    def __eq__(self, other):
+        return self.name == other.name and self.number == other.number
diff --git a/Lib/test/clamp.py b/Lib/test/clamp.py
new file mode 100644
--- /dev/null
+++ b/Lib/test/clamp.py
@@ -0,0 +1,60 @@
+import java
+import os
+import os.path
+
+from java.lang.reflect import Modifier
+from org.python.util import CodegenUtils
+from org.python.compiler import CustomMaker, ProxyCodeHelpers
+
+
+__all__ = ["PackageProxy", "SerializableProxies"]
+
+
+class SerializableProxies(CustomMaker):
+
+    # NOTE: SerializableProxies is itself a java proxy, but it's not a custom one!
+
+    serialized_path = None
+    
+    def doConstants(self):
+        self.classfile.addField("serialVersionUID",
+                                CodegenUtils.ci(java.lang.Long.TYPE), Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL)
+        code = self.classfile.addMethod("<clinit>", ProxyCodeHelpers.makeSig("V"), Modifier.STATIC)
+        code.visitLdcInsn(java.lang.Long(1))
+        code.putstatic(self.classfile.name, "serialVersionUID", CodegenUtils.ci(java.lang.Long.TYPE))
+        code.return_()
+
+    def saveBytes(self, bytes):
+        if self.serialized_path:
+            path = os.path.join(self.serialized_path, os.path.join(*self.myClass.split(".")) + ".class")
+            parent = os.path.dirname(path)
+            try:
+                os.makedirs(parent)
+            except OSError:
+                pass  # Directory exists
+            with open(path, "wb") as f:
+                f.write(bytes.toByteArray())
+
+    def makeClass(self):
+        try:
+            # If already defined on CLASSPATH, simply return this class
+            cls = java.lang.Class.forName(self.myClass)
+            print "Class defined on CLASSPATH", cls
+        except:
+            # Otherwise build it
+            cls = CustomMaker.makeClass(self)
+        return cls
+
+
+class PackageProxy(object):
+
+    def __init__(self, package):
+        self.package = package
+    
+    def __call__(self, superclass, interfaces, className, pythonModuleName, fullProxyName, mapping):
+        """Constructs a usable proxy name that does not depend on ordering"""
+        if "." in pythonModuleName:
+            # get around that will be called differently from regrtest, as test.module instead of module
+            pythonModuleName = pythonModuleName.split(".")[-1]
+        return SerializableProxies(superclass, interfaces, className, pythonModuleName, self.package + "." + pythonModuleName + "." + className, mapping)
+
diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py
--- a/Lib/test/test_java_integration.py
+++ b/Lib/test/test_java_integration.py
@@ -1,5 +1,6 @@
 import copy
 import glob
+import importlib
 import operator
 import os
 import os.path
@@ -16,6 +17,7 @@
 from java.lang import (ClassCastException, ExceptionInInitializerError, String, Runnable, System,
         Runtime, Math, Byte)
 from java.math import BigDecimal, BigInteger
+from java.net import URI
 from java.io import (ByteArrayInputStream, ByteArrayOutputStream, File, FileInputStream,
                      FileNotFoundException, FileOutputStream, FileWriter, ObjectInputStream,
                      ObjectOutputStream, OutputStreamWriter, UnsupportedEncodingException)
@@ -24,17 +26,24 @@
 from java.awt import Dimension, Color, Component, Container
 from java.awt.event import ComponentEvent
 from javax.swing.tree import TreePath
+from javax.tools import SimpleJavaFileObject, JavaFileObject, ToolProvider
 
 from org.python.core.util import FileUtil
+from org.python.compiler import CustomMaker
 from org.python.tests import (BeanImplementation, Child, Child2,
                               CustomizableMapHolder, Listenable, ToUnicode)
 from org.python.tests.mro import (ConfusedOnGetitemAdd, FirstPredefinedGetitem, GetitemAdder)
 from org.python.util import PythonInterpreter
+
+import java
 import org.python.core.Options
 
 from javatests import Issue1833
 from javatests.ProxyTests import NullToString, Person
 
+from clamp import SerializableProxies
+
+
 
 class InstantiationTest(unittest.TestCase):
     def test_cant_instantiate_abstract(self):
@@ -561,6 +570,39 @@
     return jars
 
 
+
+
+class JavaSource(SimpleJavaFileObject):
+
+    def __init__(self, name, source):
+        self._name = name
+        self._source = source
+        SimpleJavaFileObject.__init__(
+            self, 
+            URI.create("string:///" + name.replace(".", "/") + JavaFileObject.Kind.SOURCE.extension),
+            JavaFileObject.Kind.SOURCE)
+
+    def getName(self):
+        return self._name
+
+    def getCharContent(self, ignore):
+        return self._source
+
+
+def compile_java_source(options, class_name, source):
+    """Compiles a single java source "file" contained in the string source
+    
+    Use options, specifically -d DESTDIR, to control where the class
+    file is emitted. Note that we could use forwarding managers to
+    avoid tempdirs, but this is overkill here given that we still need
+    to run the emitted Java class.
+    """
+    f = JavaSource(class_name, source)
+    compiler = ToolProvider.getSystemJavaCompiler()
+    task = compiler.getTask(None, None, None, options, None, [f])
+    task.call()
+
+
 class SerializationTest(unittest.TestCase):
 
     def test_java_serialization(self):
@@ -611,13 +653,72 @@
             jars = find_jython_jars()
             jars.append(proxies_jar_path)
             classpath = ":".join(jars)
+            env = dict(os.environ)
+            env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, "..")))
             cmd = [os.path.join(System.getProperty("java.home"), "bin/java"),
                    "-classpath", classpath, "ProxyDeserialization", cat_path]
-            self.assertEqual(subprocess.check_output(cmd), "meow\n")
+            self.assertEqual(subprocess.check_output(cmd, env=env), "meow\n")
         finally:
             org.python.core.Options.proxyDebugDirectory = old_proxy_debug_dir
             shutil.rmtree(tempdir)
 
+    def test_custom_proxymaker(self):
+        """Verify custom proxymaker supports direct usage of Python code in Java"""
+        tempdir = tempfile.mkdtemp()
+        try:
+            SerializableProxies.serialized_path = tempdir
+            import bark #importlib.import_module("bark")
+            dog = bark.Dog()
+            self.assertEqual(dog.whoami(), "Rover")
+            self.assertEqual(dog.serialVersionUID, 1)
+            self.assertEqual(dog, roundtrip_serialization(dog))
+
+            # Create a jar file containing the org.python.test.Dog proxy
+            proxies_jar_path = os.path.join(tempdir, "proxies.jar")
+            subprocess.check_call(["jar", "cf", proxies_jar_path, "-C", tempdir, "org/"])
+
+            # Build a Java class importing Dog
+            source = """
+import org.python.test.bark.Dog;  // yes, it's that simple
+
+public class BarkTheDog {
+    public static void main(String[] args) {
+        Dog dog = new Dog();
+        try {
+            dog.call();
+        }
+        catch(Exception e) {
+            System.err.println(e);
+        }
+    }
+}
+"""
+            jars = find_jython_jars()
+            jars.append(proxies_jar_path)
+            classpath = ":".join(jars)
+            compile_java_source(
+                ["-classpath", classpath, "-d", tempdir],
+                "BarkTheDog", source)           
+
+            # Then in a completely different JVM running our
+            # BarkTheDog code, verify we get an appropriate bark
+            # message printed to stdout, which in turn ensures that
+            # PySystemState (and Jython runtime) is initialized for
+            # the proxy
+            classpath += ":" + tempdir
+            cmd = [os.path.join(System.getProperty("java.home"), "bin/java"),
+                   "-classpath", classpath, "BarkTheDog"]
+            env = dict(os.environ)
+            env.update(JYTHONPATH=os.path.normpath(os.path.join(__file__, "..")))
+            self.assertEqual(
+                subprocess.check_output(cmd, env=env),
+                "Class defined on CLASSPATH <type 'org.python.test.bark.Dog'>\n"
+                "Rover barks 42 times\n")
+        finally:
+            pass
+            # print "Will not remove", tempdir
+            #shutil.rmtree(tempdir)
+
 
 class CopyTest(unittest.TestCase):
     
@@ -691,24 +792,25 @@
 
 
 def test_main():
-    test_support.run_unittest(InstantiationTest,
-                              BeanTest,
-                              SysIntegrationTest,
-                              IOTest,
-                              JavaReservedNamesTest,
-                              PyReservedNamesTest,
-                              ImportTest,
-                              ColorTest,
-                              TreePathTest,
-                              BigNumberTest,
-                              JavaStringTest,
-                              JavaDelegationTest,
-                              SecurityManagerTest,
-                              JavaWrapperCustomizationTest,
-                              SerializationTest,
-                              CopyTest,
-                              UnicodeTest,
-                              BeanPropertyTest)
+    test_support.run_unittest(
+        BeanPropertyTest,
+        BeanTest,
+        BigNumberTest,
+        ColorTest,
+        CopyTest,
+        IOTest,
+        ImportTest,
+        InstantiationTest,
+        JavaDelegationTest,
+        JavaReservedNamesTest,
+        JavaStringTest,
+        JavaWrapperCustomizationTest,
+        PyReservedNamesTest,
+        SecurityManagerTest,
+        SerializationTest,
+        SysIntegrationTest,
+        TreePathTest,
+        UnicodeTest)
 
 if __name__ == "__main__":
     test_main()
diff --git a/src/org/python/compiler/CustomMaker.java b/src/org/python/compiler/CustomMaker.java
new file mode 100644
--- /dev/null
+++ b/src/org/python/compiler/CustomMaker.java
@@ -0,0 +1,54 @@
+package org.python.compiler;
+
+import org.python.core.BytecodeLoader;
+import org.python.core.Py;
+import org.python.core.PyObject;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Arrays;
+
+
+public class CustomMaker extends JavaMaker {
+
+    public CustomMaker(Class<?> superclass,
+                     Class<?>[] interfaces,
+                     String pythonClass,
+                     String pythonModule,
+                     String myClass,
+                     PyObject methods) {
+        super(superclass, interfaces, pythonClass, pythonModule, myClass, methods);
+    }
+
+    // Override to save bytes
+    public void saveBytes(ByteArrayOutputStream bytes) {
+    }
+
+    // By default makeClass will have the same behavior as MakeProxies calling JavaMaker,
+    // other than the debug behavior of saving the classfile (as controlled by
+    // Options.ProxyDebugDirectory; users of CustomMaker simply need to save it themselves).
+    //
+    // Override this method to get custom classes built from any desired source.
+    public Class<?> makeClass() {
+        try {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            build(bytes); // Side effect of writing to bytes
+            saveBytes(bytes);
+            List<Class<?>> secondary = new LinkedList(Arrays.asList(interfaces));
+            List<Class<?>> referents = null;
+            if (secondary != null) {
+                if (superclass != null) {
+                    secondary.add(0, superclass);
+                }
+                referents = secondary;
+            } else if (superclass != null) {
+                referents = new ArrayList<Class<?>>(1);
+                referents.add(superclass);
+            }
+            return BytecodeLoader.makeClass(myClass, referents, bytes.toByteArray());
+        } catch (Exception exc) {
+            throw Py.JavaError(exc);
+        }
+    }
+}
diff --git a/src/org/python/core/MakeProxies.java b/src/org/python/core/MakeProxies.java
--- a/src/org/python/core/MakeProxies.java
+++ b/src/org/python/core/MakeProxies.java
@@ -8,6 +8,7 @@
 
 import org.python.compiler.APIVersion;
 import org.python.compiler.AdapterMaker;
+import org.python.compiler.CustomMaker;
 import org.python.compiler.JavaMaker;
 
 class MakeProxies {
@@ -65,16 +66,21 @@
         
         // Grab the proxy maker from the class if it exists, and if it does, use the proxy class
         // name from the maker
-        PyObject customProxyMaker = dict.__finditem__("__proxymaker__");
-        if (customProxyMaker != null) {
+        PyObject userDefinedProxyMaker = dict.__finditem__("__proxymaker__");
+        if (userDefinedProxyMaker != null) {
             if (module == null) {
                 throw Py.TypeError("Classes using __proxymaker__ must define __module__");
             }
             PyObject[] args = Py.javas2pys(superclass, interfaces, className, pythonModuleName, fullProxyName, dict);
-            javaMaker = Py.tojava(customProxyMaker.__call__(args), JavaMaker.class);
-            // TODO Full proxy name
+            javaMaker = Py.tojava(userDefinedProxyMaker.__call__(args), JavaMaker.class);
+            if (javaMaker instanceof CustomMaker) {
+                // This hook hack is necessary because of the divergent behavior of how classes
+                // are made - we want to allow CustomMaker complete freedom in constructing the
+                // class, including saving any bytes.
+                CustomMaker customMaker = (CustomMaker) javaMaker;
+                return customMaker.makeClass();
+            }
         }
-        
         if (javaMaker == null) {
             javaMaker = new JavaMaker(superclass,
                         interfaces,

-- 
Repository URL: http://hg.python.org/jython


More information about the Jython-checkins mailing list