[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