commit b343d9c8da7754812161d52fe84f8f7da6bb25d8 Author: git Date: Thu Oct 22 22:51:58 2009 +0530 added source diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..834f40a --- /dev/null +++ b/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..13b2083 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + jywrapper + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/src/main/java/net/abhinavsarkar/jywrapper/JyWrapper.java b/src/main/java/net/abhinavsarkar/jywrapper/JyWrapper.java new file mode 100644 index 0000000..bf4de37 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/JyWrapper.java @@ -0,0 +1,89 @@ +package net.abhinavsarkar.jywrapper; + +import static net.abhinavsarkar.jywrapper.Messages._; + + +import net.abhinavsarkar.jywrapper.annotation.Wraps; +import net.abhinavsarkar.jywrapper.exception.PythonImportNotFoundException; + +import org.python.core.PyFunction; +import org.python.core.PyModule; +import org.python.core.PyObject; +import org.python.core.PyType; + +/** + * @author Abhinav Sarkar + * + * @param The type of the java class to wrap the Python class/module with. + */ +public final class JyWrapper { + + private JyWrapper() { + } + + public static T wrap(final Class javaClass) { + final Wraps annotation = javaClass.getAnnotation(Wraps.class); + if (annotation == null) { + throw new PythonImportNotFoundException(_("JyWrapper.7", javaClass)); //$NON-NLS-1$ + } + + return wrap(javaClass, annotation.value()); + } + + /** + * @param pyImportName The full import name of the Python class/module + * to wrap. + * @return An instance of {@link UninitedPyObjectWrapper}, ready to be + * initialized. + * @throws IllegalStateException Thrown if the java Class to be used to + * wrap the Python module/class, has not been supplied by earlier + * calling {@link JyWrapper#with(Class)}. + * @throws IllegalArgumentException Thrown if the pyImportName parameter + * is null. + */ + public static T wrap(final Class javaClass, final String pyImportName) { + if (javaClass == null) { + throw new IllegalStateException(_("JyWrapper.6", "javaClass")); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (pyImportName == null) { + throw new IllegalArgumentException(_("JyWrapper.6", "pyImportName")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + final PyObject pyImport = PyImportLoader.loadPyImport(pyImportName); + if (!(pyImport instanceof PyType || pyImport instanceof PyModule)) { + throw new IllegalArgumentException(_("JyWrapper.5", pyImportName)); //$NON-NLS-1$ + } + return Util.py2Java(pyImport, javaClass); + } + + /** + * @param The return type of the {@link PyCallable} instance. + * @param pyImportName The full import name of the Python function to wrap. + * @param returnType The class of the return type. + * @return An instance of {@link PyCallable} which wraps the + * Python function given in parameter. + * @throws IllegalArgumentException Thrown if the any of the parameters + * supplied are null or if the pyImportName parameter supplied does not + * correspond to a Python function. + */ + public static PyCallable wrapPyFunction( + final String pyImportName, final Class returnType) { + if (pyImportName == null) { + throw new IllegalArgumentException(_("JyWrapper.6", "pyImportName")); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (returnType == null) { + throw new IllegalArgumentException(_("JyWrapper.6", "returnType")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + final PyObject pyImport = PyImportLoader.loadPyImport(pyImportName); + if (!(pyImport instanceof PyFunction)) { + throw new IllegalArgumentException(_("JyWrapper.0", pyImportName)); //$NON-NLS-1$ + } + + @SuppressWarnings("unchecked") + final PyCallable newInstance = PyObjectProxy.newInstance( + pyImport, PyCallable.class); + return newInstance; + } + +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/Messages.java b/src/main/java/net/abhinavsarkar/jywrapper/Messages.java new file mode 100644 index 0000000..b51c668 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/Messages.java @@ -0,0 +1,28 @@ +package net.abhinavsarkar.jywrapper; + +import java.text.MessageFormat; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +final class Messages { + private static final String BUNDLE_NAME = "net.abhinavsarkar.jywrapper.messages"; //$NON-NLS-1$ + + private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle + .getBundle(BUNDLE_NAME); + + private Messages() { + } + + public static String getString(String key) { + try { + return RESOURCE_BUNDLE.getString(key); + } catch (MissingResourceException e) { + return '!' + key + '!'; + } + } + + public static String _(final String messageKey, final Object... arguments) { + return new MessageFormat(getString(messageKey)) + .format(arguments, new StringBuffer(), null).toString(); + } +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/PyAttributeType.java b/src/main/java/net/abhinavsarkar/jywrapper/PyAttributeType.java new file mode 100644 index 0000000..497bf12 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/PyAttributeType.java @@ -0,0 +1,17 @@ +package net.abhinavsarkar.jywrapper; + +import net.abhinavsarkar.jywrapper.PyObjectProxy.MemberType; + +public enum PyAttributeType { + GETTER(MemberType.GETTER), SETTER(MemberType.SETTER), CONST(MemberType.CONST); + + private final MemberType memberType; + + private PyAttributeType(MemberType memberType) { + this.memberType = memberType; + } + + public MemberType getMemberType() { + return memberType; + } +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/PyCallable.java b/src/main/java/net/abhinavsarkar/jywrapper/PyCallable.java new file mode 100644 index 0000000..97571a4 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/PyCallable.java @@ -0,0 +1,16 @@ +package net.abhinavsarkar.jywrapper; + +/** + * @author AbhinavSarkar + * + * @param + */ +public interface PyCallable { + + /** + * @param args + * @return + */ + public abstract T call(Object... args); + +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/PyImportLoader.java b/src/main/java/net/abhinavsarkar/jywrapper/PyImportLoader.java new file mode 100644 index 0000000..8298af9 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/PyImportLoader.java @@ -0,0 +1,62 @@ +package net.abhinavsarkar.jywrapper; + +import static net.abhinavsarkar.jywrapper.Messages._; + +import java.util.concurrent.ConcurrentHashMap; + +import net.abhinavsarkar.jywrapper.exception.PythonImportNotFoundException; + +import org.python.core.Py; +import org.python.core.PyException; +import org.python.core.PyObject; +import org.python.core.PySystemState; + +/** + * @author AbhinavSarkar + * + */ +public final class PyImportLoader { + + private static final PyObject importer = new PySystemState().getBuiltins() + .__getitem__(Py.newString("__import__")); //$NON-NLS-1$ + + private static final ConcurrentHashMap loadedPyImports = + new ConcurrentHashMap(); + + private PyImportLoader() { + } + + /** + * @param fullImportName + * @return + * @throws PythonImportNotFoundException + */ + public static PyObject loadPyImport(String fullImportName) + throws PythonImportNotFoundException { + if (!loadedPyImports.containsKey(fullImportName)) { + int i = fullImportName.lastIndexOf('.'); + String errorMsg = _("PyImportLoader.1", fullImportName); //$NON-NLS-1$ + PyObject pyImport; + if (i == -1) { + String pyModuleName = fullImportName; + try { + pyImport = importer.__call__(Py.newString(pyModuleName)); + } catch (PyException pye) { + throw new PythonImportNotFoundException(errorMsg, pye); + } + } else { + String pyModuleName = fullImportName.substring(0, i); + String pyClassName = fullImportName.substring(i + 1); + + try { + pyImport = importer.__call__(Py.newString(pyModuleName)) + .__getattr__(pyClassName); + } catch (PyException pye) { + throw new PythonImportNotFoundException(errorMsg, pye); + } + } + loadedPyImports.putIfAbsent(fullImportName, pyImport); + } + return loadedPyImports.get(fullImportName); + } +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/PyMethodType.java b/src/main/java/net/abhinavsarkar/jywrapper/PyMethodType.java new file mode 100644 index 0000000..0782b06 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/PyMethodType.java @@ -0,0 +1,20 @@ +package net.abhinavsarkar.jywrapper; + +import net.abhinavsarkar.jywrapper.PyObjectProxy.MemberType; + +public enum PyMethodType { + INIT(MemberType.INIT), + DIRECT(MemberType.DIRECT), + UNDERSCORED(MemberType.UNDERSCORED), + NUMERIC(MemberType.NUMERIC); + + private final MemberType memberType; + + private PyMethodType(MemberType memberType) { + this.memberType = memberType; + } + + public MemberType getMemberType() { + return memberType; + } +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/PyObjectProxy.java b/src/main/java/net/abhinavsarkar/jywrapper/PyObjectProxy.java new file mode 100644 index 0000000..70bafc3 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/PyObjectProxy.java @@ -0,0 +1,584 @@ +/** + * + */ +package net.abhinavsarkar.jywrapper; + +import static net.abhinavsarkar.jywrapper.Messages._; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.concurrent.ConcurrentHashMap; + +import net.abhinavsarkar.jywrapper.annotation.PyAttribute; +import net.abhinavsarkar.jywrapper.annotation.PyMethod; +import net.abhinavsarkar.jywrapper.exception.PythonImportInstantiationError; + +import org.python.core.Py; +import org.python.core.PyException; +import org.python.core.PyFunction; +import org.python.core.PyModule; +import org.python.core.PyObject; +import org.python.core.PyProperty; +import org.python.core.PyProxy; +import org.python.core.PyType; + +final class PyObjectProxy implements InvocationHandler { + + private static final String JAVA_COMPARE_METHOD_NAME = "compareTo"; //$NON-NLS-1$ + + private static final String GETTER_METHOD_PREFIX = "get"; //$NON-NLS-1$ + + private static final String SETTER_METHOD_PREFIX = "set"; //$NON-NLS-1$ + + private static final String CONST_METHOD_PREFIX = "const"; //$NON-NLS-1$ + + private static enum JavaSpecialMethod { + equals("__eq__"), hashCode("__hash__"), toString("__str__"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + + private final String pyEquiv; + + private JavaSpecialMethod(final String pyEquiv) { + this.pyEquiv = pyEquiv; + } + + public static Object invoke(final PyObject pyObject, final Method method, + final Object[] args) throws NoSuchMethodException { + try { + return invokePyMethod(pyObject, method.getReturnType(), + JavaSpecialMethod.valueOf(method.getName()).pyEquiv, args); + } catch (final IllegalArgumentException ex) { + throw new NoSuchMethodException(); + } + } + } + + private static enum NumericMethod { + add ("__add__"), //$NON-NLS-1$ + subtract ("__sub__"), //$NON-NLS-1$ + multiply ("__mul__"), //$NON-NLS-1$ + divide ("__div__"), //$NON-NLS-1$ + divideAndRemainder ("__divmod__"), //$NON-NLS-1$ + remainder ("__mod__"), //$NON-NLS-1$ + pow ("__pow__"), //$NON-NLS-1$ + negate ("__neg__"), //$NON-NLS-1$ + plus ("__pos__"), //$NON-NLS-1$ + abs ("__abs__"), //$NON-NLS-1$ + and ("__and__"), //$NON-NLS-1$ + or ("__or__"), //$NON-NLS-1$ + xor ("__xor__"), //$NON-NLS-1$ + invert ("__invert__"), //$NON-NLS-1$ + intValue ("__int__"), //$NON-NLS-1$ + longValue ("__long__"), //$NON-NLS-1$ + floatValue ("__float__"); //$NON-NLS-1$ + + private final String pyEquiv; + + private NumericMethod(final String pyEquiv) { + this.pyEquiv = pyEquiv; + } + + public static Object invoke(final PyObject pyObject, final Method method, + final Object[] args) throws NoSuchMethodException { + try { + return findAndInvokePyMethod(pyObject, + method.getReturnType(), + NumericMethod.valueOf(method.getName()).pyEquiv, args); + } catch (final IllegalArgumentException ex) { + throw new NoSuchMethodException(); + } + } + } + + private static enum PyProxyInterfaceMethod { + _setPyInstance, _setPySystemState, __initProxy__, _getPySystemState, + _getPyInstance { + @Override + public Object invoke(final PyObject pyObject) { + return new Throwable().getStackTrace()[4].getClassName() + .startsWith("org.python") ? pyObject : null; + } + }; + + public Object invoke(final PyObject pyObject) { + return Void.TYPE; + } + } + + static enum MemberType { + INIT { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + final PyMethod annotation = method.getAnnotation(PyMethod.class); + + if (annotation != null + && annotation.type() == PyMethodType.INIT) { + synchronized (pyObject) { + return initialize(pyObject, pyImportName, javaClass, args); + } + } + throw new NoSuchMethodException( + _("JyWrapper.15", "initialization method", //$NON-NLS-1$ //$NON-NLS-2$ + method.getName(), pyImportName)); + } + }, + CALL { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + if (javaClass.equals(PyCallable.class) + && pyObject instanceof PyFunction + && "call".equals(method.getName())) { //$NON-NLS-1$ + return invokePyMethod(pyObject, method.getReturnType(), + "__call__", (Object[]) args[0]); //$NON-NLS-1$ + } + throw new NoSuchMethodException(); + } + }, + DIRECT { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + String methodName = method.getName(); + final PyMethod annotation = method.getAnnotation(PyMethod.class); + if (annotation != null + && annotation.type() == PyMethodType.DIRECT) { + final String name = annotation.method(); + if (!"".equals(name)) { //$NON-NLS-1$ + methodName = name; + } + } + try { + return findAndInvokePyMethod(pyObject, + method.getReturnType(), methodName, args); + } catch (final NoSuchMethodException e) { + throw new NoSuchMethodException( + _("JyWrapper.15", "direct method", //$NON-NLS-1$ //$NON-NLS-2$ + methodName, pyImportName)); + } + } + }, + UNDERSCORED { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + try { + return findAndInvokePyMethod(pyObject, + method.getReturnType(), + Util.camelCase2UnderScore(method.getName()), args); + } catch (final NoSuchMethodException e) { + throw new NoSuchMethodException( + _("JyWrapper.15", "underscored method", //$NON-NLS-1$ //$NON-NLS-2$ + method.getName(), pyImportName)); + } + } + }, + COMPARE { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + for (final RichComparisonOperator compOp : RichComparisonOperator.values()) { + try { + if ((Boolean) findAndInvokePyMethod(pyObject, + Boolean.class, compOp.name(), args)) { + return compOp.returnValue(); + } + } catch (final PyException e) { + if (e.type.equals(Py.NotImplementedError)) { + // the comparison operator is not implemented. move on. + continue; + } + throw e; + } + } + + try { + return (Integer) findAndInvokePyMethod( + pyObject, Integer.class, "__cmp__", args); //$NON-NLS-1$ + } catch (final PyException e) { + if (!e.type.equals(Py.NotImplementedError)) { + throw e; + } + } + throw new NoSuchMethodException(_("JyWrapper.11")); //$NON-NLS-1$ + } + }, + SPECIAL { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + return JavaSpecialMethod.invoke(pyObject, method, args); + } + }, + PYPROXY_INTERFACE { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + try { + return PyProxyInterfaceMethod.valueOf(method.getName()) + .invoke(pyObject); + } catch (final IllegalArgumentException ex) { + throw new NoSuchMethodException(); + } + } + }, + GETTER { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + final PyAttribute annotation = method.getAnnotation(PyAttribute.class); + final String methodName = method.getName(); + PyObject attrValue = null; + String attrName = methodName; + + if (annotation != null + && annotation.type() == PyAttributeType.GETTER) { + attrName = annotation.attribute(); + if (!"".equals(attrName)) { //$NON-NLS-1$ + synchronized (pyObject) { + attrValue = pyObject.__findattr__(attrName); + } + } + } + + if (attrValue == null && methodName.startsWith(GETTER_METHOD_PREFIX)) { + attrName = Util.camelCase2UnderScore(methodName) + .substring(GETTER_METHOD_PREFIX.length() + 1); + synchronized (pyObject) { + attrValue = pyObject.__findattr__(attrName); + } + } + + if (attrValue != null) { + if (PyProperty.class.isAssignableFrom(attrValue.getClass())) { + throw new IllegalArgumentException(_("JyWrapper.12", attrName)); //$NON-NLS-1$ + } + return Util.py2Java(attrValue, method.getReturnType()); + } + + throw new NoSuchMethodException( + _("JyWrapper.15", "instance attribute", //$NON-NLS-1$ //$NON-NLS-2$ + methodName, pyImportName)); + } + }, + SETTER { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException, IllegalAccessException { + final String methodName = method.getName(); + final PyAttribute annotation = method.getAnnotation(PyAttribute.class); + String attrName = null; + + if (annotation != null + && annotation.type() == PyAttributeType.SETTER) { + attrName = annotation.attribute(); + if ("".equals(attrName)) { //$NON-NLS-1$ + attrName = null; + } + } + + if (attrName == null + && methodName.startsWith(SETTER_METHOD_PREFIX) + && args.length == 1) { + attrName = Util.camelCase2UnderScore(methodName).substring( + SETTER_METHOD_PREFIX.length() + 1); + } + + if (attrName != null) { + PyObject attrValue; + synchronized (pyObject) { + attrValue = pyObject.__findattr__(attrName); + } + if (attrValue != null) { + if (PyProperty.class.isAssignableFrom(attrValue.getClass())) { + throw new IllegalArgumentException( + _("JyWrapper.14", attrName)); //$NON-NLS-1$ + } + } + try { + final PyObject pyArgs = Py.java2py(args[0]); + synchronized (pyObject) { + pyObject.__setattr__(attrName, pyArgs); + } + return Void.TYPE; + } catch (final PyException e) { + if (e.type.equals(Py.AttributeError) + && e.value.toString().equals( + "can't set attribute")) { //$NON-NLS-1$ + throw new IllegalAccessException( + _("JyWrapper.13", attrName)); //$NON-NLS-1$ + } + throw e; + } + } + + throw new NoSuchMethodException( + _("JyWrapper.15", "instance attribute", //$NON-NLS-1$ //$NON-NLS-2$ + methodName, pyImportName)); + } + }, + CONST { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + final PyAttribute annotation = method.getAnnotation(PyAttribute.class); + final String methodName = method.getName(); + PyObject attrValue = null; + + if (annotation != null + && annotation.type() == PyAttributeType.CONST) { + final String attrName = annotation.attribute(); + if (!"".equals(attrName)) { //$NON-NLS-1$ + synchronized (pyObject) { + attrValue = pyObject.__findattr__(attrName); + } + } else { + synchronized (pyObject) { + attrValue = pyObject.__findattr__(methodName); + } + } + } + + if (attrValue == null + && methodName.startsWith(CONST_METHOD_PREFIX)) { + final String attrName = methodName + .substring(CONST_METHOD_PREFIX.length()); + synchronized (pyObject) { + attrValue = pyObject.__findattr__(attrName); + } + } + + if (attrValue != null) { + return Util.py2Java(attrValue, method.getReturnType()); + } + + throw new NoSuchMethodException( + _("JyWrapper.15", "constant", //$NON-NLS-1$ //$NON-NLS-2$ + methodName, pyImportName)); + } + }, + NUMERIC { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + try { + return NumericMethod.invoke(pyObject, method, args); + } catch (final NoSuchMethodException e) { + throw new NoSuchMethodException( + _("JyWrapper.15", "eqivalent numeric method", //$NON-NLS-1$ //$NON-NLS-2$ + method.getName(), pyImportName)); + } + } + }, + NO_SUCH_METHOD { + @Override + public Object invoke(final PyObject pyObject, final Class javaClass, + final Method method, final Object[] args, final String pyImportName) + throws NoSuchMethodException { + throw new NoSuchMethodException( + _("JyWrapper.15", "method", method.getName(), pyImportName)); //$NON-NLS-1$ //$NON-NLS-2$ + } + }; + + public abstract Object invoke(PyObject pyObject, Class javaClass, + Method method, Object[] args, String pyImportName) + throws NoSuchMethodException, IllegalAccessException; + } + + private static enum RichComparisonOperator { + __eq__(0), __lt__(-1), __gt__(1); + + private int returnValue; + + public int returnValue() { return returnValue; } + + private RichComparisonOperator(final int returnValue) { + this.returnValue = returnValue; + } + } + + private final PyObject pyObject; + + private final Class javaClass; + + private final String pyImportName; + + private static final ConcurrentHashMap METHOD_JUMP_TABLE = + new ConcurrentHashMap(); + + private PyObjectProxy(final PyObject pyObject, final Class javaClass) { + this.pyObject = pyObject; + this.javaClass = javaClass; + this.pyImportName = Util.getPyImportName(this.pyObject); + } + + public static T newInstance(final PyObject pyObject, + final Class javaClass) { + if (javaClass.isInterface()) { + ClassLoader classLoader = javaClass.getClassLoader(); + if (classLoader == null) { + classLoader = ClassLoader.getSystemClassLoader(); + } + + @SuppressWarnings("unchecked") + final T newProxyInstance = (T) Proxy.newProxyInstance( + classLoader, + new Class[] { javaClass, PyProxy.class }, + new PyObjectProxy(pyObject, javaClass)); + return newProxyInstance; + } else { + throw new IllegalArgumentException(_("JyWrapper.8", javaClass.getName())); //$NON-NLS-1$ + } + } + + public Object invoke(final Object proxy, final Method method, final Object[] args) + throws NoSuchMethodException, IllegalAccessException { + final PyMethod pyMethodAnnotation = method.getAnnotation(PyMethod.class); + if (pyMethodAnnotation != null) { + return pyMethodAnnotation.type().getMemberType() + .invoke(pyObject, javaClass, method, args, pyImportName); + } + + final PyAttribute pyAttributeAnnotation = method.getAnnotation(PyAttribute.class); + if (pyAttributeAnnotation != null) { + return pyAttributeAnnotation.type().getMemberType() + .invoke(pyObject, javaClass, method, args, pyImportName); + } + + final String methodName = method.getName(); + if (!METHOD_JUMP_TABLE.containsKey(method)) { + if (Comparable.class.isAssignableFrom(javaClass) + && JAVA_COMPARE_METHOD_NAME.equals(methodName)) { + //check if the method is "compareTo" + METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.COMPARE); + } else { + //check if the method is one of the special methods + try { + JavaSpecialMethod.valueOf(methodName); + METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.SPECIAL); + } catch (final IllegalArgumentException ex) { + //the method is not one of the special methods. move on. + + //check if the method is one of the PyProxy interface methods + try { + PyProxyInterfaceMethod.valueOf(methodName); + METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.PYPROXY_INTERFACE); + } catch (final IllegalArgumentException e) { + //the method is not one of the PyProxy interface methods. + //move on. + } + } + } + } + + //check if the method has already been registered in the jump table + //if so invoke it using the method type registered in the table + if (METHOD_JUMP_TABLE.containsKey(method)) { + return METHOD_JUMP_TABLE.get(method) + .invoke(pyObject, javaClass, method, args, pyImportName); + } + + // try each method types one by one to guess the correct one + for (final MemberType methodType : MemberType.values()) { + try { + final Object retVal = methodType.invoke( + pyObject, javaClass, method, args, pyImportName); + METHOD_JUMP_TABLE.putIfAbsent(method, methodType); + return retVal; + } catch (final NoSuchMethodException ex) { + continue; + } + } + + //all checks failed. throw NoSuchMethodException + METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.NO_SUCH_METHOD); + throw new NoSuchMethodException( + _("JyWrapper.15", "method", methodName, pyImportName)); //$NON-NLS-1$ //$NON-NLS-2$ + } + + /** + * @param args Arguments to initialize the underlying Python class. + * @param pyImport + * @param pyImportName + * @param javaClass + * @return An initialized (wrapped) Python class instance, ready to be used. + */ + private static T initialize(final PyObject pyImport, final String pyImportName, + final Class javaClass, final Object... args) { + if (javaClass == null) { + throw new IllegalStateException(_("JyWrapper.6", "javaClass")); //$NON-NLS-1$ //$NON-NLS-2$ + } + if (pyImportName == null) { + throw new IllegalArgumentException(_("JyWrapper.6", "pyImportName")); //$NON-NLS-1$ //$NON-NLS-2$ + } + + if (pyImport instanceof PyModule) { + throw new PythonImportInstantiationError(_("JyWrapper.4", pyImportName)); //$NON-NLS-1$ + } + if (!(pyImport instanceof PyType)) { + throw new PythonImportInstantiationError(_("JyWrapper.9")); //$NON-NLS-1$ + } + + try { + return Util.py2Java(pyImport.__call__(Util.convertArgs(args)), javaClass); + } catch (final PyException e) { + if (e.type.equals(Py.TypeError)) { + throw new PythonImportInstantiationError( + _("JyWrapper.3", pyImportName), e); //$NON-NLS-1$ + } + throw e; + } catch (final IllegalArgumentException e) { + throw new IllegalArgumentException( + _("JyWrapper.2", pyImportName, javaClass.getName())); //$NON-NLS-1$ + } + } + + private static Object findAndInvokePyMethod(final PyObject pyObject, + final Class javaReturnType, final String methodName, final Object[] args) + throws NoSuchMethodException { + PyObject pyAttr; + synchronized (pyObject) { + pyAttr = pyObject.__findattr__(methodName); + } + if (pyAttr instanceof org.python.core.PyMethod || pyAttr instanceof PyFunction) { + return invokePyMethod(pyObject, javaReturnType, methodName, args); + } else { + throw new NoSuchMethodException(); + } + } + + private static Object invokePyMethod(final PyObject pyObject, + final Class javaReturnType, final String methodName, final Object[] args) { + PyObject pyReturnValue; + try { + final PyObject[] pyArgs = Util.convertArgs(args); + synchronized (pyObject) { + pyReturnValue = pyObject.invoke(methodName, pyArgs); + } + } catch (final PyException e) { + if (e.type.equals(Py.TypeError)) { + throw new IllegalArgumentException( + _("JyWrapper.10", methodName), e); //$NON-NLS-1$ + } + throw e; + } + + if (pyReturnValue == Py.None) { + return "void".equals(javaReturnType.getName()) ? Void.TYPE : null; //$NON-NLS-1$ + } + return Util.py2Java(pyReturnValue, javaReturnType); + } + +} \ No newline at end of file diff --git a/src/main/java/net/abhinavsarkar/jywrapper/Util.java b/src/main/java/net/abhinavsarkar/jywrapper/Util.java new file mode 100644 index 0000000..a61db5c --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/Util.java @@ -0,0 +1,75 @@ +package net.abhinavsarkar.jywrapper; + +import static net.abhinavsarkar.jywrapper.Messages._; + +import org.python.core.Py; +import org.python.core.PyFunction; +import org.python.core.PyModule; +import org.python.core.PyObject; +import org.python.core.PyString; +import org.python.core.PyType; + +final class Util { + + private Util() { + } + + static T py2Java(final PyObject pyObject, final Class javaClass) { + final Object javaWrapper = pyObject.__tojava__(javaClass); + if (javaWrapper == Py.NoConversion) { + if (javaClass.isInterface()) { + return PyObjectProxy.newInstance(pyObject, javaClass); + } else { + throw new IllegalArgumentException( + _("JyWrapper.1", pyObject, javaClass.getName())); //$NON-NLS-1$ + } + } else { + @SuppressWarnings("unchecked") + final T t = (T) javaWrapper; + return t; + } + } + + static PyObject[] convertArgs(final Object[] args) { + if (args == null || args.length == 0) { + return new PyObject[0]; + } + if (args.length == 1) { + return new PyObject[] { Py.java2py(args[0]) }; + } + + final PyObject[] pyArgs = new PyObject[args.length]; + for (int i = args.length; --i >= 0;) { + pyArgs[i] = Py.java2py(args[i]); + } + return pyArgs; + } + + static String getPyImportName(final PyObject pyObject) { + synchronized (pyObject) { + if (pyObject instanceof PyType) { + final PyType pyType = (PyType) pyObject; + final PyObject module = pyType.getModule(); + if (module instanceof PyString + && !module.toString().equals("__builtin__")) { //$NON-NLS-1$ + return String.format("%s.%s", module.toString(), //$NON-NLS-1$ + pyType.getName()); + } + return pyType.getName(); + } else if (pyObject instanceof PyModule) { + return ((PyModule) pyObject).toString(); + } else if (pyObject instanceof PyFunction) { + final PyFunction pyFunction = (PyFunction) pyObject; + return String.format("%s.%s", pyFunction.__module__, //$NON-NLS-1$ + pyFunction.__name__); + } else { + return getPyImportName(pyObject.getType()); + } + } + } + + static String camelCase2UnderScore(final String word) { + return word.replaceAll("([A-Z])", "_$1").toLowerCase(); //$NON-NLS-1$ //$NON-NLS-2$ + } + +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/annotation/PyAttribute.java b/src/main/java/net/abhinavsarkar/jywrapper/annotation/PyAttribute.java new file mode 100644 index 0000000..60543a8 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/annotation/PyAttribute.java @@ -0,0 +1,17 @@ +package net.abhinavsarkar.jywrapper.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import net.abhinavsarkar.jywrapper.PyAttributeType; + +@Documented +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface PyAttribute { + PyAttributeType type(); + String attribute() default ""; +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/annotation/PyMethod.java b/src/main/java/net/abhinavsarkar/jywrapper/annotation/PyMethod.java new file mode 100644 index 0000000..8f9dc3d --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/annotation/PyMethod.java @@ -0,0 +1,17 @@ +package net.abhinavsarkar.jywrapper.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import net.abhinavsarkar.jywrapper.PyMethodType; + +@Documented +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface PyMethod { + PyMethodType type(); + String method() default ""; +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/annotation/Wraps.java b/src/main/java/net/abhinavsarkar/jywrapper/annotation/Wraps.java new file mode 100644 index 0000000..aea57f4 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/annotation/Wraps.java @@ -0,0 +1,14 @@ +package net.abhinavsarkar.jywrapper.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Wraps { + String value(); +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportInstantiationError.java b/src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportInstantiationError.java new file mode 100644 index 0000000..bf44ede --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportInstantiationError.java @@ -0,0 +1,26 @@ +package net.abhinavsarkar.jywrapper.exception; + +/** + * @author AbhinavSarkar + * + */ +public class PythonImportInstantiationError extends RuntimeException { + + private static final long serialVersionUID = -6419405475631226539L; + + public PythonImportInstantiationError() { + } + + public PythonImportInstantiationError(String message) { + super(message); + } + + public PythonImportInstantiationError(Throwable cause) { + super(cause); + } + + public PythonImportInstantiationError(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportNotFoundException.java b/src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportNotFoundException.java new file mode 100644 index 0000000..d4e0338 --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportNotFoundException.java @@ -0,0 +1,26 @@ +package net.abhinavsarkar.jywrapper.exception; + +/** + * @author AbhinavSarkar + * + */ +public class PythonImportNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 3287534701588858305L; + + public PythonImportNotFoundException() { + } + + public PythonImportNotFoundException(String message) { + super(message); + } + + public PythonImportNotFoundException(Throwable cause) { + super(cause); + } + + public PythonImportNotFoundException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/net/abhinavsarkar/jywrapper/messages.properties b/src/main/java/net/abhinavsarkar/jywrapper/messages.properties new file mode 100644 index 0000000..b1a4bab --- /dev/null +++ b/src/main/java/net/abhinavsarkar/jywrapper/messages.properties @@ -0,0 +1,17 @@ +JyWrapper.0="{0}" is not a Python Function +JyWrapper.1=Cannot convert Python object: {0} to a Java object of class {1} +JyWrapper.2=Cannot convert Python Type {0} to Java Class {1} +JyWrapper.3=Cannot not instantiate the Python type: {0} +JyWrapper.4=Python module {0} can be instantiated statically only +JyWrapper.5={0} is not a Python Type or Module +JyWrapper.6=parameter "{0}" is null +JyWrapper.7=No Python import name annotated on the Java Class {0} +JyWrapper.8={0} is not an Interface +JyWrapper.9=Cannot instantiate a Python non type +JyWrapper.10=Instance method "{0}" called on a Python Type +JyWrapper.11=No comparison methods found in the backing Python object +JyWrapper.12=Trying to get instance attribute "{0}" from a Python Type +JyWrapper.13=Can''t set attribute: {0} +JyWrapper.14=Trying to set instance attribute "{0}" in a python class +JyWrapper.15=No {0} named "{1}" in the backing Python Type {2} +PyImportLoader.1=Python Import "{0}" not found in pythonpath