Browse Source

added source

Abhinav Sarkar 11 years ago
commit
b343d9c8da

+ 7
- 0
.classpath View File

@@ -0,0 +1,7 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<classpath>
3
+	<classpathentry kind="src" path="src/main/java"/>
4
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
5
+	<classpathentry kind="var" path="JYTHON_HOME/jython.jar" sourcepath="/JYTHON_HOME/Doc/javadoc"/>
6
+	<classpathentry kind="output" path="bin"/>
7
+</classpath>

+ 17
- 0
.project View File

@@ -0,0 +1,17 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<projectDescription>
3
+	<name>jywrapper</name>
4
+	<comment></comment>
5
+	<projects>
6
+	</projects>
7
+	<buildSpec>
8
+		<buildCommand>
9
+			<name>org.eclipse.jdt.core.javabuilder</name>
10
+			<arguments>
11
+			</arguments>
12
+		</buildCommand>
13
+	</buildSpec>
14
+	<natures>
15
+		<nature>org.eclipse.jdt.core.javanature</nature>
16
+	</natures>
17
+</projectDescription>

+ 0
- 0
README View File


+ 89
- 0
src/main/java/net/abhinavsarkar/jywrapper/JyWrapper.java View File

@@ -0,0 +1,89 @@
1
+package net.abhinavsarkar.jywrapper;
2
+
3
+import static net.abhinavsarkar.jywrapper.Messages._;
4
+
5
+
6
+import net.abhinavsarkar.jywrapper.annotation.Wraps;
7
+import net.abhinavsarkar.jywrapper.exception.PythonImportNotFoundException;
8
+
9
+import org.python.core.PyFunction;
10
+import org.python.core.PyModule;
11
+import org.python.core.PyObject;
12
+import org.python.core.PyType;
13
+
14
+/**
15
+ * @author Abhinav Sarkar <abhinav@abhinavsarkar.net>
16
+ *
17
+ * @param <T>	The type of the java class to wrap the Python class/module with.
18
+ */
19
+public final class JyWrapper {
20
+	
21
+	private JyWrapper() {
22
+	}
23
+
24
+	public static <T> T wrap(final Class<T> javaClass) {
25
+		final Wraps annotation = javaClass.getAnnotation(Wraps.class);
26
+		if (annotation == null) {
27
+			throw new PythonImportNotFoundException(_("JyWrapper.7", javaClass));  //$NON-NLS-1$
28
+		}
29
+		
30
+		return wrap(javaClass, annotation.value());
31
+	}
32
+
33
+	/**
34
+	 * @param pyImportName	The full import name of the Python class/module
35
+	 * 		to wrap.
36
+	 * @return	An instance of {@link UninitedPyObjectWrapper}, ready to be 
37
+	 * 		initialized.
38
+	 * @throws	IllegalStateException Thrown if the java Class to be used to
39
+	 * 		wrap the Python module/class, has not been supplied by earlier 
40
+	 * 		calling {@link JyWrapper#with(Class)}.
41
+	 * @throws IllegalArgumentException Thrown if the pyImportName parameter
42
+	 * 		is null.	
43
+	 */
44
+	public static <T> T wrap(final Class<T> javaClass, final String pyImportName) {
45
+		if (javaClass == null) {
46
+			throw new IllegalStateException(_("JyWrapper.6", "javaClass"));   //$NON-NLS-1$ //$NON-NLS-2$
47
+		}
48
+		if (pyImportName == null) {
49
+			throw new IllegalArgumentException(_("JyWrapper.6", "pyImportName"));   //$NON-NLS-1$ //$NON-NLS-2$
50
+		}
51
+		
52
+		final PyObject pyImport = PyImportLoader.loadPyImport(pyImportName);
53
+		if (!(pyImport instanceof PyType || pyImport instanceof PyModule)) {
54
+			throw new IllegalArgumentException(_("JyWrapper.5", pyImportName));  //$NON-NLS-1$
55
+		}
56
+		return Util.py2Java(pyImport, javaClass);
57
+	}
58
+	
59
+	/**
60
+	 * @param <T>			The return type of the {@link PyCallable} instance. 
61
+	 * @param pyImportName	The full import name of the Python function to wrap.
62
+	 * @param returnType	The class of the return type.
63
+	 * @return				An instance of {@link PyCallable} which wraps the
64
+	 * 		Python function given in parameter.
65
+	 * @throws	IllegalArgumentException Thrown if the any of the parameters 
66
+	 * 		supplied are null or if the pyImportName parameter supplied does not 
67
+	 * 		correspond to a Python function.
68
+	 */
69
+	public static <T> PyCallable<T> wrapPyFunction(
70
+			final String pyImportName, final Class<T> returnType) {
71
+		if (pyImportName == null) {
72
+			throw new IllegalArgumentException(_("JyWrapper.6", "pyImportName"));   //$NON-NLS-1$ //$NON-NLS-2$
73
+		}
74
+		if (returnType == null) {
75
+			throw new IllegalArgumentException(_("JyWrapper.6", "returnType"));   //$NON-NLS-1$ //$NON-NLS-2$
76
+		}
77
+		
78
+		final PyObject pyImport = PyImportLoader.loadPyImport(pyImportName);
79
+		if (!(pyImport instanceof PyFunction)) {
80
+			throw new IllegalArgumentException(_("JyWrapper.0", pyImportName));  //$NON-NLS-1$
81
+		}
82
+		
83
+		@SuppressWarnings("unchecked")
84
+		final PyCallable<T> newInstance = PyObjectProxy.newInstance(
85
+				pyImport, PyCallable.class);
86
+		return newInstance;
87
+	}
88
+	
89
+}

+ 28
- 0
src/main/java/net/abhinavsarkar/jywrapper/Messages.java View File

@@ -0,0 +1,28 @@
1
+package net.abhinavsarkar.jywrapper;
2
+
3
+import java.text.MessageFormat;
4
+import java.util.MissingResourceException;
5
+import java.util.ResourceBundle;
6
+
7
+final class Messages {
8
+	private static final String BUNDLE_NAME = "net.abhinavsarkar.jywrapper.messages"; //$NON-NLS-1$
9
+
10
+	private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
11
+			.getBundle(BUNDLE_NAME);
12
+
13
+	private Messages() {
14
+	}
15
+
16
+	public static String getString(String key) {
17
+		try {
18
+			return RESOURCE_BUNDLE.getString(key);
19
+		} catch (MissingResourceException e) {
20
+			return '!' + key + '!';
21
+		}
22
+	}
23
+
24
+	public static String _(final String messageKey, final Object... arguments) {
25
+		return new MessageFormat(getString(messageKey))
26
+			.format(arguments, new StringBuffer(), null).toString();
27
+	}
28
+}

+ 17
- 0
src/main/java/net/abhinavsarkar/jywrapper/PyAttributeType.java View File

@@ -0,0 +1,17 @@
1
+package net.abhinavsarkar.jywrapper;
2
+
3
+import net.abhinavsarkar.jywrapper.PyObjectProxy.MemberType;
4
+
5
+public enum PyAttributeType {
6
+	GETTER(MemberType.GETTER), SETTER(MemberType.SETTER), CONST(MemberType.CONST);
7
+	
8
+	private final MemberType memberType;
9
+
10
+	private PyAttributeType(MemberType memberType) {
11
+		this.memberType = memberType;
12
+	}
13
+
14
+	public MemberType getMemberType() {
15
+		return memberType;
16
+	}
17
+}

+ 16
- 0
src/main/java/net/abhinavsarkar/jywrapper/PyCallable.java View File

@@ -0,0 +1,16 @@
1
+package net.abhinavsarkar.jywrapper;
2
+
3
+/**
4
+ * @author AbhinavSarkar
5
+ *
6
+ * @param <T>
7
+ */
8
+public interface PyCallable<T> {
9
+	
10
+	/**
11
+	 * @param args
12
+	 * @return
13
+	 */
14
+	public abstract T call(Object... args); 
15
+
16
+}

+ 62
- 0
src/main/java/net/abhinavsarkar/jywrapper/PyImportLoader.java View File

@@ -0,0 +1,62 @@
1
+package net.abhinavsarkar.jywrapper;
2
+
3
+import static net.abhinavsarkar.jywrapper.Messages._;
4
+
5
+import java.util.concurrent.ConcurrentHashMap;
6
+
7
+import net.abhinavsarkar.jywrapper.exception.PythonImportNotFoundException;
8
+
9
+import org.python.core.Py;
10
+import org.python.core.PyException;
11
+import org.python.core.PyObject;
12
+import org.python.core.PySystemState;
13
+
14
+/**
15
+ * @author AbhinavSarkar
16
+ *
17
+ */
18
+public final class PyImportLoader {
19
+	
20
+	private static final PyObject importer = new PySystemState().getBuiltins()
21
+		.__getitem__(Py.newString("__import__")); //$NON-NLS-1$
22
+
23
+   private static final ConcurrentHashMap<String, PyObject> loadedPyImports = 
24
+		new ConcurrentHashMap<String, PyObject>();
25
+
26
+   private PyImportLoader() {
27
+	}
28
+   
29
+   /**
30
+     * @param fullImportName
31
+     * @return
32
+     * @throws PythonImportNotFoundException
33
+     */
34
+    public static PyObject loadPyImport(String fullImportName)
35
+            throws PythonImportNotFoundException {
36
+    	if (!loadedPyImports.containsKey(fullImportName)) {
37
+    		int i = fullImportName.lastIndexOf('.');
38
+            String errorMsg = _("PyImportLoader.1", fullImportName); //$NON-NLS-1$
39
+            PyObject pyImport;
40
+            if (i == -1) {
41
+                String pyModuleName = fullImportName;
42
+                try {
43
+                	pyImport = importer.__call__(Py.newString(pyModuleName));
44
+                } catch (PyException pye) {
45
+                    throw new PythonImportNotFoundException(errorMsg, pye);
46
+                }
47
+            } else {
48
+                String pyModuleName = fullImportName.substring(0, i);
49
+                String pyClassName = fullImportName.substring(i + 1);
50
+
51
+                try {
52
+                	pyImport = importer.__call__(Py.newString(pyModuleName))
53
+                		.__getattr__(pyClassName);
54
+                } catch (PyException pye) {
55
+                    throw new PythonImportNotFoundException(errorMsg, pye);
56
+                }
57
+            }
58
+			loadedPyImports.putIfAbsent(fullImportName, pyImport);
59
+    	}
60
+		return loadedPyImports.get(fullImportName);
61
+    }
62
+}

+ 20
- 0
src/main/java/net/abhinavsarkar/jywrapper/PyMethodType.java View File

@@ -0,0 +1,20 @@
1
+package net.abhinavsarkar.jywrapper;
2
+
3
+import net.abhinavsarkar.jywrapper.PyObjectProxy.MemberType;
4
+
5
+public enum PyMethodType {
6
+	INIT(MemberType.INIT), 
7
+	DIRECT(MemberType.DIRECT), 
8
+	UNDERSCORED(MemberType.UNDERSCORED), 
9
+	NUMERIC(MemberType.NUMERIC);
10
+	
11
+	private final MemberType memberType;
12
+
13
+	private PyMethodType(MemberType memberType) {
14
+		this.memberType = memberType;
15
+	}
16
+
17
+	public MemberType getMemberType() {
18
+		return memberType;
19
+	}
20
+}

+ 584
- 0
src/main/java/net/abhinavsarkar/jywrapper/PyObjectProxy.java View File

@@ -0,0 +1,584 @@
1
+/**
2
+ * 
3
+ */
4
+package net.abhinavsarkar.jywrapper;
5
+
6
+import static net.abhinavsarkar.jywrapper.Messages._;
7
+
8
+import java.lang.reflect.InvocationHandler;
9
+import java.lang.reflect.Method;
10
+import java.lang.reflect.Proxy;
11
+import java.util.concurrent.ConcurrentHashMap;
12
+
13
+import net.abhinavsarkar.jywrapper.annotation.PyAttribute;
14
+import net.abhinavsarkar.jywrapper.annotation.PyMethod;
15
+import net.abhinavsarkar.jywrapper.exception.PythonImportInstantiationError;
16
+
17
+import org.python.core.Py;
18
+import org.python.core.PyException;
19
+import org.python.core.PyFunction;
20
+import org.python.core.PyModule;
21
+import org.python.core.PyObject;
22
+import org.python.core.PyProperty;
23
+import org.python.core.PyProxy;
24
+import org.python.core.PyType;
25
+
26
+final class PyObjectProxy implements InvocationHandler {
27
+	
28
+	private static final String JAVA_COMPARE_METHOD_NAME = "compareTo";  //$NON-NLS-1$
29
+
30
+	private static final String GETTER_METHOD_PREFIX = "get";  //$NON-NLS-1$
31
+
32
+	private static final String SETTER_METHOD_PREFIX = "set";  //$NON-NLS-1$
33
+
34
+	private static final String CONST_METHOD_PREFIX = "const";  //$NON-NLS-1$
35
+	
36
+	private static enum JavaSpecialMethod {
37
+		equals("__eq__"), hashCode("__hash__"), toString("__str__");    //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
38
+		
39
+		private final String pyEquiv;
40
+
41
+		private JavaSpecialMethod(final String pyEquiv) {
42
+			this.pyEquiv = pyEquiv;
43
+		}
44
+
45
+		public static Object invoke(final PyObject pyObject, final Method method,
46
+				final Object[] args) throws NoSuchMethodException {
47
+			try {
48
+				return invokePyMethod(pyObject, method.getReturnType(),
49
+						JavaSpecialMethod.valueOf(method.getName()).pyEquiv, args);
50
+			} catch (final IllegalArgumentException ex) {
51
+				throw new NoSuchMethodException();
52
+			} 			
53
+		}
54
+	}
55
+	
56
+	private static enum NumericMethod {
57
+		add					("__add__"),   //$NON-NLS-1$
58
+		subtract			("__sub__"),   //$NON-NLS-1$
59
+		multiply			("__mul__"),   //$NON-NLS-1$
60
+		divide				("__div__"),   //$NON-NLS-1$
61
+		divideAndRemainder	("__divmod__"),   //$NON-NLS-1$
62
+		remainder			("__mod__"),   //$NON-NLS-1$
63
+		pow					("__pow__"),   //$NON-NLS-1$
64
+		negate				("__neg__"),   //$NON-NLS-1$
65
+		plus				("__pos__"),   //$NON-NLS-1$
66
+		abs					("__abs__"),  //$NON-NLS-1$
67
+		and					("__and__"),   //$NON-NLS-1$
68
+		or					("__or__"),   //$NON-NLS-1$
69
+		xor					("__xor__"),   //$NON-NLS-1$
70
+		invert				("__invert__"),  //$NON-NLS-1$
71
+		intValue			("__int__"),   //$NON-NLS-1$
72
+		longValue			("__long__"),   //$NON-NLS-1$
73
+		floatValue			("__float__");  //$NON-NLS-1$
74
+		
75
+		private final String pyEquiv;
76
+
77
+		private NumericMethod(final String pyEquiv) {
78
+			this.pyEquiv = pyEquiv;
79
+		}
80
+
81
+		public static Object invoke(final PyObject pyObject, final Method method,
82
+				final Object[] args) throws NoSuchMethodException {
83
+			try {
84
+				return findAndInvokePyMethod(pyObject, 
85
+						method.getReturnType(), 
86
+						NumericMethod.valueOf(method.getName()).pyEquiv, args);
87
+			} catch (final IllegalArgumentException ex) {
88
+				throw new NoSuchMethodException();
89
+			} 				
90
+		}
91
+	}
92
+	
93
+	private static enum PyProxyInterfaceMethod {
94
+		_setPyInstance, _setPySystemState, __initProxy__, _getPySystemState,
95
+		_getPyInstance {
96
+			@Override
97
+			public Object invoke(final PyObject pyObject) {
98
+				return new Throwable().getStackTrace()[4].getClassName()
99
+						.startsWith("org.python") ? pyObject : null;
100
+			}
101
+		};
102
+		
103
+		public Object invoke(final PyObject pyObject) { 
104
+			return Void.TYPE; 
105
+		}
106
+	}
107
+	
108
+	static enum MemberType {
109
+		INIT {
110
+			@Override
111
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
112
+					final Method method, final Object[] args, final String pyImportName) 
113
+				throws NoSuchMethodException {
114
+				final PyMethod annotation = method.getAnnotation(PyMethod.class);
115
+				
116
+				if (annotation != null 
117
+						&& annotation.type() == PyMethodType.INIT) {
118
+					synchronized (pyObject) {
119
+						return initialize(pyObject, pyImportName, javaClass, args);
120
+					}						
121
+				}					
122
+				throw new NoSuchMethodException(
123
+						_("JyWrapper.15", "initialization method",  //$NON-NLS-1$ //$NON-NLS-2$
124
+								method.getName(), pyImportName));  
125
+			}
126
+		}, 
127
+		CALL {
128
+			@Override
129
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
130
+					final Method method, final Object[] args, final String pyImportName) 
131
+				throws NoSuchMethodException {
132
+				if (javaClass.equals(PyCallable.class)
133
+						&& pyObject instanceof PyFunction
134
+						&& "call".equals(method.getName())) {  //$NON-NLS-1$
135
+					return invokePyMethod(pyObject, method.getReturnType(), 
136
+							"__call__", (Object[]) args[0]);  //$NON-NLS-1$
137
+				}
138
+				throw new NoSuchMethodException();
139
+			}
140
+		}, 
141
+		DIRECT {
142
+			@Override
143
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
144
+					final Method method, final Object[] args, final String pyImportName) 
145
+				throws NoSuchMethodException {
146
+				String methodName = method.getName();
147
+				final PyMethod annotation = method.getAnnotation(PyMethod.class);
148
+				if (annotation != null 
149
+						&& annotation.type() == PyMethodType.DIRECT) {
150
+					final String name = annotation.method();
151
+					if (!"".equals(name)) {  //$NON-NLS-1$
152
+						methodName = name;
153
+					}
154
+				}
155
+				try {
156
+					return findAndInvokePyMethod(pyObject, 
157
+							method.getReturnType(), methodName, args);
158
+				} catch (final NoSuchMethodException e) {
159
+					throw new NoSuchMethodException(
160
+							_("JyWrapper.15", "direct method",  //$NON-NLS-1$ //$NON-NLS-2$
161
+									methodName, pyImportName));  
162
+				}
163
+			}
164
+		}, 
165
+		UNDERSCORED {
166
+			@Override
167
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
168
+					final Method method, final Object[] args, final String pyImportName) 
169
+				throws NoSuchMethodException {
170
+				try {
171
+					return findAndInvokePyMethod(pyObject, 
172
+							method.getReturnType(), 
173
+							Util.camelCase2UnderScore(method.getName()), args);
174
+				} catch (final NoSuchMethodException e) {
175
+					throw new NoSuchMethodException(
176
+							_("JyWrapper.15", "underscored method",  //$NON-NLS-1$ //$NON-NLS-2$
177
+									method.getName(), pyImportName));  
178
+				}
179
+			}
180
+		}, 
181
+		COMPARE {
182
+			@Override
183
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
184
+					final Method method, final Object[] args, final String pyImportName) 
185
+				throws NoSuchMethodException {
186
+				for (final RichComparisonOperator compOp : RichComparisonOperator.values()) {
187
+					try {
188
+						if ((Boolean) findAndInvokePyMethod(pyObject, 
189
+								Boolean.class, compOp.name(), args)) {
190
+							return compOp.returnValue();
191
+						}
192
+					} catch (final PyException e) {
193
+						if (e.type.equals(Py.NotImplementedError)) {
194
+							// the comparison operator is not implemented. move on.
195
+							continue;
196
+						}
197
+						throw e;
198
+					}			
199
+				}
200
+				
201
+				try {
202
+					return (Integer) findAndInvokePyMethod(
203
+							pyObject, Integer.class, "__cmp__", args);  //$NON-NLS-1$
204
+				} catch (final PyException e) {
205
+					if (!e.type.equals(Py.NotImplementedError)) {
206
+						throw e;
207
+					}
208
+				}
209
+				throw new NoSuchMethodException(_("JyWrapper.11"));  //$NON-NLS-1$
210
+			}
211
+		}, 
212
+		SPECIAL {
213
+			@Override
214
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
215
+					final Method method, final Object[] args, final String pyImportName) 
216
+				throws NoSuchMethodException {
217
+				return JavaSpecialMethod.invoke(pyObject, method, args);					
218
+			}
219
+		}, 
220
+		PYPROXY_INTERFACE {
221
+			@Override
222
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
223
+					final Method method, final Object[] args, final String pyImportName) 
224
+				throws NoSuchMethodException {
225
+				try {
226
+					return PyProxyInterfaceMethod.valueOf(method.getName())
227
+							.invoke(pyObject);
228
+				} catch (final IllegalArgumentException ex) {
229
+					throw new NoSuchMethodException();
230
+				}
231
+			}
232
+		}, 
233
+		GETTER {
234
+			@Override
235
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
236
+					final Method method, final Object[] args, final String pyImportName) 
237
+				throws NoSuchMethodException {
238
+				final PyAttribute annotation = method.getAnnotation(PyAttribute.class);
239
+				final String methodName = method.getName();
240
+				PyObject attrValue = null;
241
+				String attrName = methodName;
242
+				
243
+				if (annotation != null 
244
+						&& annotation.type() == PyAttributeType.GETTER) {
245
+					attrName = annotation.attribute();
246
+					if (!"".equals(attrName)) {  //$NON-NLS-1$
247
+						synchronized (pyObject) {
248
+							attrValue = pyObject.__findattr__(attrName);	
249
+						}							
250
+					}	
251
+				}
252
+				
253
+				if (attrValue == null && methodName.startsWith(GETTER_METHOD_PREFIX)) {
254
+					attrName = Util.camelCase2UnderScore(methodName)
255
+						.substring(GETTER_METHOD_PREFIX.length() + 1);
256
+					synchronized (pyObject) {
257
+						attrValue = pyObject.__findattr__(attrName);	
258
+					}							
259
+				}					
260
+				
261
+				if (attrValue != null) {
262
+					if (PyProperty.class.isAssignableFrom(attrValue.getClass())) {
263
+						throw new IllegalArgumentException(_("JyWrapper.12", attrName));  //$NON-NLS-1$
264
+					}
265
+					return Util.py2Java(attrValue, method.getReturnType());
266
+				}
267
+				
268
+				throw new NoSuchMethodException(
269
+						_("JyWrapper.15", "instance attribute",  //$NON-NLS-1$ //$NON-NLS-2$
270
+								methodName, pyImportName));  
271
+			}
272
+		}, 
273
+		SETTER {
274
+			@Override
275
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
276
+					final Method method, final Object[] args, final String pyImportName) 
277
+				throws NoSuchMethodException, IllegalAccessException {
278
+				final String methodName = method.getName();
279
+				final PyAttribute annotation = method.getAnnotation(PyAttribute.class);
280
+				String attrName = null;
281
+				
282
+				if (annotation != null 
283
+						&& annotation.type() == PyAttributeType.SETTER) {
284
+					attrName = annotation.attribute();
285
+					if ("".equals(attrName)) {  //$NON-NLS-1$
286
+						attrName = null;
287
+					}	
288
+				}
289
+				
290
+				if (attrName == null
291
+						&& methodName.startsWith(SETTER_METHOD_PREFIX)
292
+						&& args.length == 1) {
293
+					attrName = Util.camelCase2UnderScore(methodName).substring(
294
+							SETTER_METHOD_PREFIX.length() + 1);
295
+				}
296
+				
297
+				if (attrName != null) {
298
+					PyObject attrValue;
299
+					synchronized (pyObject) {
300
+						attrValue = pyObject.__findattr__(attrName);	
301
+					}
302
+					if (attrValue != null) {
303
+						if (PyProperty.class.isAssignableFrom(attrValue.getClass())) {
304
+							throw new IllegalArgumentException(
305
+									_("JyWrapper.14", attrName));  //$NON-NLS-1$
306
+						}
307
+					}
308
+					try {
309
+						final PyObject pyArgs = Py.java2py(args[0]);
310
+						synchronized (pyObject) {
311
+							pyObject.__setattr__(attrName, pyArgs);									
312
+						}
313
+						return Void.TYPE;
314
+					} catch (final PyException e) {
315
+						if (e.type.equals(Py.AttributeError)
316
+								&& e.value.toString().equals(
317
+										"can't set attribute")) {  //$NON-NLS-1$
318
+							throw new IllegalAccessException(
319
+									_("JyWrapper.13", attrName));  //$NON-NLS-1$
320
+						}
321
+						throw e;
322
+					}
323
+				}
324
+				
325
+				throw new NoSuchMethodException(
326
+						_("JyWrapper.15", "instance attribute",  //$NON-NLS-1$ //$NON-NLS-2$
327
+								methodName, pyImportName));  
328
+			}
329
+		}, 
330
+		CONST {
331
+			@Override
332
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
333
+					final Method method, final Object[] args, final String pyImportName) 
334
+				throws NoSuchMethodException {
335
+				final PyAttribute annotation = method.getAnnotation(PyAttribute.class);
336
+				final String methodName = method.getName();
337
+				PyObject attrValue = null;
338
+				
339
+				if (annotation != null 
340
+						&& annotation.type() == PyAttributeType.CONST) {
341
+					final String attrName = annotation.attribute();
342
+					if (!"".equals(attrName)) {  //$NON-NLS-1$
343
+						synchronized (pyObject) {
344
+							attrValue = pyObject.__findattr__(attrName);	
345
+						}							
346
+					} else {
347
+						synchronized (pyObject) {
348
+							attrValue = pyObject.__findattr__(methodName);	
349
+						}
350
+					}
351
+				}
352
+				
353
+				if (attrValue == null
354
+						&& methodName.startsWith(CONST_METHOD_PREFIX)) {
355
+					final String attrName = methodName
356
+							.substring(CONST_METHOD_PREFIX.length());
357
+					synchronized (pyObject) {
358
+						attrValue = pyObject.__findattr__(attrName);
359
+					}
360
+				}			
361
+				
362
+				if (attrValue != null) {
363
+					return Util.py2Java(attrValue, method.getReturnType());
364
+				}
365
+				
366
+				throw new NoSuchMethodException(
367
+						_("JyWrapper.15", "constant",  //$NON-NLS-1$ //$NON-NLS-2$
368
+								methodName, pyImportName));  
369
+			}
370
+		}, 
371
+		NUMERIC {
372
+			@Override
373
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
374
+					final Method method, final Object[] args, final String pyImportName) 
375
+				throws NoSuchMethodException {
376
+				try {
377
+					return NumericMethod.invoke(pyObject, method, args);
378
+				} catch (final NoSuchMethodException e) {
379
+					throw new NoSuchMethodException(
380
+							_("JyWrapper.15", "eqivalent numeric method",  //$NON-NLS-1$ //$NON-NLS-2$
381
+									method.getName(), pyImportName));  
382
+				}					
383
+			}
384
+		}, 
385
+		NO_SUCH_METHOD {
386
+			@Override
387
+			public Object invoke(final PyObject pyObject, final Class<?> javaClass,
388
+					final Method method, final Object[] args, final String pyImportName) 
389
+				throws NoSuchMethodException {
390
+				throw new NoSuchMethodException(
391
+						_("JyWrapper.15", "method", method.getName(), pyImportName));   //$NON-NLS-1$ //$NON-NLS-2$
392
+			}
393
+		};
394
+		
395
+		public abstract Object invoke(PyObject pyObject, Class<?> javaClass, 
396
+				Method method, Object[] args, String pyImportName) 
397
+			throws NoSuchMethodException, IllegalAccessException;
398
+	}
399
+	
400
+	private static enum RichComparisonOperator {
401
+		__eq__(0), __lt__(-1), __gt__(1);
402
+		
403
+		private int returnValue;
404
+
405
+		public int returnValue() { return returnValue; }
406
+
407
+		private RichComparisonOperator(final int returnValue) {
408
+			this.returnValue = returnValue;
409
+		}
410
+	}
411
+	
412
+	private final PyObject pyObject;
413
+	
414
+	private final Class<?> javaClass;
415
+
416
+	private final String pyImportName;
417
+
418
+	private static final ConcurrentHashMap<Method, MemberType> METHOD_JUMP_TABLE =
419
+		new ConcurrentHashMap<Method, MemberType>();
420
+
421
+	private PyObjectProxy(final PyObject pyObject, final Class<?> javaClass) {
422
+		this.pyObject = pyObject;
423
+		this.javaClass = javaClass;
424
+		this.pyImportName = Util.getPyImportName(this.pyObject);
425
+	}
426
+
427
+	public static <T> T newInstance(final PyObject pyObject, 
428
+			final Class<T> javaClass) {
429
+		if (javaClass.isInterface()) {
430
+			ClassLoader classLoader = javaClass.getClassLoader();
431
+			if (classLoader == null) {
432
+				classLoader = ClassLoader.getSystemClassLoader();
433
+			}
434
+			
435
+			@SuppressWarnings("unchecked")
436
+			final T newProxyInstance = (T) Proxy.newProxyInstance(
437
+					classLoader,
438
+					new Class[] { javaClass, PyProxy.class }, 
439
+					new PyObjectProxy(pyObject, javaClass));
440
+			return newProxyInstance;
441
+		} else {
442
+			throw new IllegalArgumentException(_("JyWrapper.8", javaClass.getName())); //$NON-NLS-1$
443
+		}
444
+	}
445
+
446
+	public Object invoke(final Object proxy, final Method method, final Object[] args)
447
+			throws NoSuchMethodException, IllegalAccessException {
448
+		final PyMethod pyMethodAnnotation = method.getAnnotation(PyMethod.class);
449
+		if (pyMethodAnnotation != null) {
450
+			return pyMethodAnnotation.type().getMemberType()
451
+				.invoke(pyObject, javaClass, method, args, pyImportName);
452
+		}
453
+		
454
+		final PyAttribute pyAttributeAnnotation = method.getAnnotation(PyAttribute.class);
455
+		if (pyAttributeAnnotation != null) {
456
+			return pyAttributeAnnotation.type().getMemberType()
457
+				.invoke(pyObject, javaClass, method, args, pyImportName);
458
+		} 
459
+		
460
+		final String methodName = method.getName();
461
+		if (!METHOD_JUMP_TABLE.containsKey(method)) {
462
+			if (Comparable.class.isAssignableFrom(javaClass)
463
+					&& JAVA_COMPARE_METHOD_NAME.equals(methodName)) {
464
+				//check if the method is "compareTo"
465
+				METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.COMPARE);
466
+			} else {			
467
+				//check if the method is one of the special methods
468
+				try {
469
+					JavaSpecialMethod.valueOf(methodName);
470
+					METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.SPECIAL);
471
+				} catch (final IllegalArgumentException ex) {
472
+					//the method is not one of the special methods. move on.
473
+					
474
+					//check if the method is one of the PyProxy interface methods
475
+					try {
476
+						PyProxyInterfaceMethod.valueOf(methodName);
477
+						METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.PYPROXY_INTERFACE);
478
+					} catch (final IllegalArgumentException e) {
479
+						//the method is not one of the PyProxy interface methods.
480
+						//move on.
481
+					}
482
+				}
483
+			}
484
+		}
485
+		
486
+		//check if the method has already been registered in the jump table
487
+		//if so invoke it using the method type registered in the table
488
+		if (METHOD_JUMP_TABLE.containsKey(method)) {
489
+			return METHOD_JUMP_TABLE.get(method)
490
+					.invoke(pyObject, javaClass, method, args, pyImportName);
491
+		}
492
+		
493
+		// try each method types one by one to guess the correct one
494
+		for (final MemberType methodType : MemberType.values()) {
495
+			try {
496
+				final Object retVal = methodType.invoke(
497
+						pyObject, javaClass, method, args, pyImportName);
498
+				METHOD_JUMP_TABLE.putIfAbsent(method, methodType);
499
+				return retVal;
500
+			} catch (final NoSuchMethodException ex) {
501
+				continue;
502
+			}
503
+		}
504
+
505
+		//all checks failed. throw NoSuchMethodException
506
+		METHOD_JUMP_TABLE.putIfAbsent(method, MemberType.NO_SUCH_METHOD);
507
+		throw new NoSuchMethodException(
508
+				_("JyWrapper.15", "method", methodName, pyImportName));   //$NON-NLS-1$ //$NON-NLS-2$
509
+	}
510
+
511
+	/**
512
+	 * @param args	Arguments to initialize the underlying Python class.
513
+	 * @param pyImport 
514
+	 * @param pyImportName 
515
+	 * @param javaClass 
516
+	 * @return	An initialized (wrapped) Python class instance, ready to be used.
517
+	 */
518
+	private static <T> T initialize(final PyObject pyImport, final String pyImportName, 
519
+			final Class<T> javaClass, final Object... args) {
520
+		if (javaClass == null) {
521
+			throw new IllegalStateException(_("JyWrapper.6", "javaClass"));   //$NON-NLS-1$ //$NON-NLS-2$
522
+		}
523
+		if (pyImportName == null) {
524
+			throw new IllegalArgumentException(_("JyWrapper.6", "pyImportName"));   //$NON-NLS-1$ //$NON-NLS-2$
525
+		}
526
+		
527
+		if (pyImport instanceof PyModule) {
528
+			throw new PythonImportInstantiationError(_("JyWrapper.4", pyImportName));  //$NON-NLS-1$
529
+		}
530
+		if (!(pyImport instanceof PyType)) {
531
+			throw new PythonImportInstantiationError(_("JyWrapper.9"));  //$NON-NLS-1$
532
+		}
533
+		
534
+		try {
535
+			return Util.py2Java(pyImport.__call__(Util.convertArgs(args)), javaClass);
536
+		} catch (final PyException e) {
537
+			if (e.type.equals(Py.TypeError)) {
538
+				throw new PythonImportInstantiationError(
539
+						_("JyWrapper.3", pyImportName), e);  //$NON-NLS-1$
540
+			}
541
+			throw e;
542
+		} catch (final IllegalArgumentException e) {
543
+			throw new IllegalArgumentException(
544
+					_("JyWrapper.2", pyImportName, javaClass.getName()));  //$NON-NLS-1$
545
+		}			
546
+	}
547
+
548
+	private static Object findAndInvokePyMethod(final PyObject pyObject, 
549
+			final Class<?> javaReturnType, final String methodName, final Object[] args)
550
+		throws NoSuchMethodException {
551
+		PyObject pyAttr;
552
+		synchronized (pyObject) {
553
+			pyAttr = pyObject.__findattr__(methodName);					
554
+		}
555
+		if (pyAttr instanceof org.python.core.PyMethod || pyAttr instanceof PyFunction) {
556
+			return invokePyMethod(pyObject, javaReturnType, methodName, args);
557
+		} else {
558
+			throw new NoSuchMethodException();
559
+		}
560
+	}
561
+
562
+	private static Object invokePyMethod(final PyObject pyObject, 
563
+			final Class<?> javaReturnType, final String methodName, final Object[] args) {
564
+		PyObject pyReturnValue;
565
+		try {
566
+			final PyObject[] pyArgs = Util.convertArgs(args);
567
+			synchronized (pyObject) {
568
+				pyReturnValue = pyObject.invoke(methodName, pyArgs);	
569
+			}
570
+		} catch (final PyException e) {
571
+			if (e.type.equals(Py.TypeError)) {
572
+				throw new IllegalArgumentException(
573
+						_("JyWrapper.10", methodName), e);  //$NON-NLS-1$
574
+			}
575
+			throw e;
576
+		}
577
+		
578
+		if (pyReturnValue == Py.None) {
579
+			return "void".equals(javaReturnType.getName()) ? Void.TYPE : null; //$NON-NLS-1$
580
+		}
581
+		return Util.py2Java(pyReturnValue, javaReturnType);
582
+	}
583
+	
584
+}

+ 75
- 0
src/main/java/net/abhinavsarkar/jywrapper/Util.java View File

@@ -0,0 +1,75 @@
1
+package net.abhinavsarkar.jywrapper;
2
+
3
+import static net.abhinavsarkar.jywrapper.Messages._;
4
+
5
+import org.python.core.Py;
6
+import org.python.core.PyFunction;
7
+import org.python.core.PyModule;
8
+import org.python.core.PyObject;
9
+import org.python.core.PyString;
10
+import org.python.core.PyType;
11
+
12
+final class Util {
13
+	
14
+	private Util() {
15
+	}
16
+
17
+	static <T> T py2Java(final PyObject pyObject, final Class<T> javaClass) {
18
+		final Object javaWrapper = pyObject.__tojava__(javaClass);	
19
+		if (javaWrapper == Py.NoConversion) {
20
+			if (javaClass.isInterface()) {
21
+				return PyObjectProxy.newInstance(pyObject, javaClass);
22
+			} else {
23
+				throw new IllegalArgumentException(
24
+						_("JyWrapper.1", pyObject, javaClass.getName()));  //$NON-NLS-1$
25
+			}
26
+		} else {
27
+			@SuppressWarnings("unchecked")
28
+			final T t = (T) javaWrapper;
29
+			return t;
30
+		}
31
+	}
32
+
33
+	static PyObject[] convertArgs(final Object[] args) {
34
+		if (args == null || args.length == 0) {
35
+			return new PyObject[0];
36
+		}
37
+		if (args.length == 1) {
38
+			return new PyObject[] { Py.java2py(args[0]) };
39
+		}
40
+		
41
+		final PyObject[] pyArgs = new PyObject[args.length];
42
+		for (int i = args.length; --i >= 0;) {
43
+			pyArgs[i] = Py.java2py(args[i]);
44
+		}
45
+		return pyArgs;
46
+	}
47
+
48
+	static String getPyImportName(final PyObject pyObject) {
49
+		synchronized (pyObject) {
50
+			if (pyObject instanceof PyType) {
51
+				final PyType pyType = (PyType) pyObject;
52
+				final PyObject module = pyType.getModule();
53
+		        if (module instanceof PyString 
54
+		        		&& !module.toString().equals("__builtin__")) {  //$NON-NLS-1$
55
+		            return String.format("%s.%s", module.toString(),   //$NON-NLS-1$
56
+		            		pyType.getName());
57
+		        }
58
+		        return pyType.getName();
59
+			} else if (pyObject instanceof PyModule) {
60
+				return ((PyModule) pyObject).toString();
61
+			} else if (pyObject instanceof PyFunction) {
62
+				final PyFunction pyFunction = (PyFunction) pyObject;
63
+				return String.format("%s.%s", pyFunction.__module__,   //$NON-NLS-1$
64
+						pyFunction.__name__); 
65
+			} else {
66
+				return getPyImportName(pyObject.getType());
67
+			}	
68
+		}			
69
+	}
70
+
71
+	static String camelCase2UnderScore(final String word) {
72
+		return word.replaceAll("([A-Z])", "_$1").toLowerCase();   //$NON-NLS-1$ //$NON-NLS-2$
73
+	}
74
+
75
+}

+ 17
- 0
src/main/java/net/abhinavsarkar/jywrapper/annotation/PyAttribute.java View File

@@ -0,0 +1,17 @@
1
+package net.abhinavsarkar.jywrapper.annotation;
2
+
3
+import java.lang.annotation.Documented;
4
+import java.lang.annotation.ElementType;
5
+import java.lang.annotation.Retention;
6
+import java.lang.annotation.RetentionPolicy;
7
+import java.lang.annotation.Target;
8
+
9
+import net.abhinavsarkar.jywrapper.PyAttributeType;
10
+
11
+@Documented
12
+@Target({ ElementType.METHOD })
13
+@Retention(RetentionPolicy.RUNTIME)
14
+public @interface PyAttribute {
15
+	PyAttributeType type();
16
+	String attribute() default "";
17
+}

+ 17
- 0
src/main/java/net/abhinavsarkar/jywrapper/annotation/PyMethod.java View File

@@ -0,0 +1,17 @@
1
+package net.abhinavsarkar.jywrapper.annotation;
2
+
3
+import java.lang.annotation.Documented;
4
+import java.lang.annotation.ElementType;
5
+import java.lang.annotation.Retention;
6
+import java.lang.annotation.RetentionPolicy;
7
+import java.lang.annotation.Target;
8
+
9
+import net.abhinavsarkar.jywrapper.PyMethodType;
10
+
11
+@Documented
12
+@Target({ ElementType.METHOD })
13
+@Retention(RetentionPolicy.RUNTIME)
14
+public @interface PyMethod {
15
+	PyMethodType type();
16
+	String method() default "";
17
+}

+ 14
- 0
src/main/java/net/abhinavsarkar/jywrapper/annotation/Wraps.java View File

@@ -0,0 +1,14 @@
1
+package net.abhinavsarkar.jywrapper.annotation;
2
+
3
+import java.lang.annotation.Documented;
4
+import java.lang.annotation.ElementType;
5
+import java.lang.annotation.Retention;
6
+import java.lang.annotation.RetentionPolicy;
7
+import java.lang.annotation.Target;
8
+
9
+@Documented
10
+@Target({ ElementType.TYPE })
11
+@Retention(RetentionPolicy.RUNTIME)
12
+public @interface Wraps {
13
+	String value();
14
+}

+ 26
- 0
src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportInstantiationError.java View File

@@ -0,0 +1,26 @@
1
+package net.abhinavsarkar.jywrapper.exception;
2
+
3
+/**
4
+ * @author AbhinavSarkar
5
+ *
6
+ */
7
+public class PythonImportInstantiationError extends RuntimeException {
8
+
9
+	private static final long serialVersionUID = -6419405475631226539L;
10
+
11
+	public PythonImportInstantiationError() {
12
+	}
13
+
14
+	public PythonImportInstantiationError(String message) {
15
+		super(message);
16
+	}
17
+
18
+	public PythonImportInstantiationError(Throwable cause) {
19
+		super(cause);
20
+	}
21
+
22
+	public PythonImportInstantiationError(String message, Throwable cause) {
23
+		super(message, cause);
24
+	}
25
+
26
+}

+ 26
- 0
src/main/java/net/abhinavsarkar/jywrapper/exception/PythonImportNotFoundException.java View File

@@ -0,0 +1,26 @@
1
+package net.abhinavsarkar.jywrapper.exception;
2
+
3
+/**
4
+ * @author AbhinavSarkar
5
+ *
6
+ */
7
+public class PythonImportNotFoundException extends RuntimeException {
8
+
9
+	private static final long serialVersionUID = 3287534701588858305L;
10
+
11
+	public PythonImportNotFoundException() {
12
+	}
13
+
14
+	public PythonImportNotFoundException(String message) {
15
+		super(message);
16
+	}
17
+
18
+	public PythonImportNotFoundException(Throwable cause) {
19
+		super(cause);
20
+	}
21
+
22
+	public PythonImportNotFoundException(String message, Throwable cause) {
23
+		super(message, cause);
24
+	}
25
+
26
+}

+ 17
- 0
src/main/java/net/abhinavsarkar/jywrapper/messages.properties View File

@@ -0,0 +1,17 @@
1
+JyWrapper.0="{0}" is not a Python Function
2
+JyWrapper.1=Cannot convert Python object: {0} to a Java object of class {1}
3
+JyWrapper.2=Cannot convert Python Type {0} to Java Class {1}
4
+JyWrapper.3=Cannot not instantiate the Python type: {0}
5
+JyWrapper.4=Python module {0} can be instantiated statically only
6
+JyWrapper.5={0} is not a Python Type or Module
7
+JyWrapper.6=parameter "{0}" is null
8
+JyWrapper.7=No Python import name annotated on the Java Class {0}
9
+JyWrapper.8={0} is not an Interface
10
+JyWrapper.9=Cannot instantiate a Python non type
11
+JyWrapper.10=Instance method "{0}" called on a Python Type
12
+JyWrapper.11=No comparison methods found in the backing Python object
13
+JyWrapper.12=Trying to get instance attribute "{0}" from a Python Type
14
+JyWrapper.13=Can''t set attribute: {0}
15
+JyWrapper.14=Trying to set instance attribute "{0}" in a python class
16
+JyWrapper.15=No {0} named "{1}" in the backing Python Type {2}
17
+PyImportLoader.1=Python Import "{0}" not found in pythonpath

Loading…
Cancel
Save