View Javadoc

1   /*
2    * Copyright (c) 2007, James Leigh All rights reserved.
3    * 
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    * 
7    * - Redistributions of source code must retain the above copyright notice, this
8    *   list of conditions and the following disclaimer.
9    * - Redistributions in binary form must reproduce the above copyright notice,
10   *   this list of conditions and the following disclaimer in the documentation
11   *   and/or other materials provided with the distribution. 
12   * - Neither the name of the openrdf.org nor the names of its contributors may
13   *   be used to endorse or promote products derived from this software without
14   *   specific prior written permission.
15   * 
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26   * POSSIBILITY OF SUCH DAMAGE.
27   * 
28   */
29  package org.openrdf.elmo.impl;
30  
31  import java.lang.reflect.Constructor;
32  import java.lang.reflect.Field;
33  import java.lang.reflect.Method;
34  import java.util.ArrayList;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  import java.util.regex.Pattern;
39  
40  import javassist.CannotCompileException;
41  
42  import org.openrdf.elmo.ElmoBehaviourFactory;
43  import org.openrdf.elmo.Entity;
44  import org.openrdf.elmo.annotations.intercepts;
45  import org.openrdf.elmo.dynacode.ClassFactory;
46  import org.openrdf.elmo.dynacode.ClassTemplate;
47  import org.openrdf.elmo.dynacode.CodeBuilder;
48  import org.openrdf.elmo.exceptions.ElmoCompositionException;
49  
50  class Behaviour {
51  	private static final String FACTORY_SUFFIX = "Factory";
52  
53  	private static final String BEHAVIOUR_DELEGATE_FIELD = "_$staticDelegate";
54  
55  	private static final String NEW_INSTANCE = "newInstance";
56  
57  	private static final String CLASS_PREFIX = "elmobeans.factories.";
58  
59  	private static final String CREATE_BEHAVIOUR = "createBehaviour";
60  
61  	private ClassFactory cp;
62  
63  	private Class<?> javaClass;
64  
65  	private ClassTemplate declaring;
66  
67  	private String getterName;
68  
69  	private Map<List<Class<?>>, Class<?>> constructors;
70  
71  	private Map<Class<?>, ElmoBehaviourFactory<?>> factories;
72  
73  	public void setDeclaring(ClassTemplate declaring) {
74  		this.declaring = declaring;
75  	}
76  
77  	public void setClassDefiner(ClassFactory definer) {
78  		this.cp = definer;
79  	}
80  
81  	public void setConstructorFactories(Map<List<Class<?>>, Class<?>> factories) {
82  		this.constructors = factories;
83  	}
84  
85  	public void setFactories(Map<Class<?>, ElmoBehaviourFactory<?>> factories) {
86  		this.factories = factories;
87  	}
88  
89  	public void setJavaClass(Class<?> javaClass) {
90  		this.javaClass = javaClass;
91  	}
92  
93  	public List<Method> getAroundInvoke(Method method, Class<?> face,
94  			ClassTemplate cc) throws Exception {
95  		List<Method> list = new ArrayList<Method>();
96  		Method jm = findInterfaceMethod(method, face);
97  		for (Method im : javaClass.getMethods()) {
98  			if (im.isAnnotationPresent(intercepts.class)) {
99  				intercepts it = im.getAnnotation(intercepts.class);
100 				if (!nameMatches(method.getName(), it))
101 					continue;
102 				if (!argcMatch(it.argc(), jm.getParameterTypes().length))
103 					continue;
104 				if (!argsMatch(it.parameters(), jm.getParameterTypes()))
105 					continue;
106 				if (!returnTypeMatches(jm, it))
107 					continue;
108 				if (!declaredInMatches(jm, it))
109 					continue;
110 				if (isEnabled(jm, im.getDeclaringClass(), it))
111 					list.add(im);
112 			}
113 		}
114 		return list;
115 	}
116 
117 	public String getGetterName() {
118 		return getterName;
119 	}
120 
121 	public Class<?> getJavaClass() {
122 		return javaClass;
123 	}
124 
125 	public boolean isMethodPresent(Method method) throws Exception {
126 		try {
127 			Class<?>[] types = method.getParameterTypes();
128 			Method m = javaClass.getMethod(method.getName(), types);
129 			return !isObjectMethod(m);
130 		} catch (NoSuchMethodException e) {
131 			return false;
132 		}
133 	}
134 
135 	public boolean invokeCondition(Method jm, Class<?> face)
136 			throws Exception {
137 		for (Method im : javaClass.getMethods()) {
138 			if (im.isAnnotationPresent(intercepts.class)) {
139 				intercepts it = im.getAnnotation(intercepts.class);
140 				if (!nameMatches(jm.getName(), it))
141 					continue;
142 				if (!argcMatch(it.argc(), jm.getParameterTypes().length))
143 					continue;
144 				if (!argsMatch(it.parameters(), jm.getParameterTypes()))
145 					continue;
146 				if (!returnTypeMatches(jm, it))
147 					continue;
148 				if (!declaredInMatches(jm, it))
149 					continue;
150 				if (isEnabled(jm, im.getDeclaringClass(), it))
151 					return true;
152 			}
153 		}
154 		return false;
155 	}
156 
157 	public void init() throws Exception {
158 		getterName = "_$get" + javaClass.getSimpleName()
159 				+ Integer.toHexString(javaClass.hashCode());
160 		String fieldName = "_$" + getterName.substring(5);
161 		declaring.createField(javaClass, fieldName);
162 		CodeBuilder code = declaring.createMethod(javaClass, getterName);
163 		code.code("if (").code(fieldName).code(" != null){");
164 		code.code("return ").code(fieldName).code(";} else {");
165 		code.code("return ").code(fieldName).code(" = ($r) ");
166 		code.code(createFactoryField()).code(".");
167 		code.code(CREATE_BEHAVIOUR).code("($0);");
168 		code.code("}");
169 		code.end();
170 	}
171 
172 	private List<Class<?>> getConstructorLevel(Set<List<Class<?>>> constructors)
173 			throws NoSuchMethodException {
174 		for (List<Class<?>> constructor : constructors) {
175 			try {
176 				javaClass.getConstructor(constructor
177 						.toArray(new Class[constructor.size()]));
178 				return constructor;
179 			} catch (NoSuchMethodException e) {
180 			}
181 		}
182 		List<Class<?>> result = new ArrayList<Class<?>>();
183 		result.add(Entity.class);
184 		assert constructors.contains(result);
185 		return result;
186 	}
187 
188 	private String createFactoryField() throws Exception {
189 		Class<?> fc = getFactoryClass();
190 		String name = "_$" + javaClass.getSimpleName()
191 				+ Integer.toHexString(javaClass.getName().hashCode())
192 				+ FACTORY_SUFFIX;
193 		Class<?> class1 = ElmoBehaviourFactory.class;
194 		CodeBuilder code = declaring.assignStaticField(class1, name);
195 		if (factories.get(javaClass) instanceof MethodFactory) {
196 			MethodFactory mf = (MethodFactory) factories.get(javaClass);
197 			Class<?> factory = mf.getFactoryClass();
198 			code.construct(MethodFactory.class, mf.getMethod(), factory).end();
199 		} else {
200 			code.construct(fc, javaClass).end();
201 		}
202 		return name;
203 	}
204 
205 	private Class<?> getFactoryClass() throws Exception {
206 		Class<?> factory = getFactorySuperClass();
207 		String cn = javaClass.getName();
208 		String name = CLASS_PREFIX + cn + FACTORY_SUFFIX;
209 		try {
210 			return Class.forName(name, true, cp);
211 		} catch (ClassNotFoundException e) {
212 			if (factories.get(javaClass) instanceof MethodFactory) {
213 				MethodFactory mf = (MethodFactory) factories.get(javaClass);
214 				return mf.getFactoryClass();
215 			}
216 			ClassTemplate fc = cp.createClassTemplate(name, factory);
217 			if (factories.containsKey(javaClass)) {
218 				createDelegateFactoryField(fc, BEHAVIOUR_DELEGATE_FIELD);
219 			} else {
220 				overrideFactoryClassBody(fc);
221 			}
222 			Class<?> definedClass = cp.createClass(fc);
223 			if (factories.containsKey(javaClass)) {
224 				Field field = definedClass.getField(BEHAVIOUR_DELEGATE_FIELD);
225 				field.set(null, factories.get(javaClass));
226 			}
227 			return definedClass;
228 		}
229 	}
230 
231 	private void createDelegateFactoryField(ClassTemplate fc, String fieldName)
232 			throws Exception {
233 		Class<?> type = ElmoBehaviourFactory.class;
234 		fc.assignStaticField(type, fieldName).code(fieldName).end();
235 		Class<?>[] param = new Class<?>[] { Class.class };
236 		String body = "super(" + fieldName + ");";
237 		fc.addConstructor(param, body);
238 	}
239 
240 	private void overrideFactoryClassBody(ClassTemplate fc) throws Exception {
241 		Class<?>[] param = new Class<?>[] { Class.class };
242 		fc.addConstructor(param, "super($1);");
243 		List<Class<?>> constructor = getConstructorLevel(constructors.keySet());
244 		try {
245 			overrideNewInstanceMethod(constructor, fc);
246 		} catch (CannotCompileException e) {
247 			throw new ElmoCompositionException(e.getMessage() + ' '
248 					+ javaClass.getSimpleName() + constructor, e);
249 		}
250 	}
251 
252 	private Class<?> getFactorySuperClass() throws Exception {
253 		if (factories.containsKey(javaClass)) {
254 			return ElmoBehaviourFactoryWrapper.class;
255 		}
256 		List<Class<?>> constructor = getConstructorLevel(constructors.keySet());
257 		return constructors.get(constructor);
258 	}
259 
260 	private void overrideNewInstanceMethod(List<Class<?>> constructor,
261 			ClassTemplate fc) throws Exception {
262 		Class<?>[] param = new Class<?>[constructor.size()];
263 		for (int i = 0, n = constructor.size(); i < n; i++) {
264 			param[i] = constructor.get(i);
265 		}
266 		CodeBuilder sb = fc.createMethod(Object.class, NEW_INSTANCE, param);
267 		sb.code("return new ").code(javaClass.getName()).code("(");
268 		for (int i = 1, n = constructor.size(); i <= n; i++) {
269 			if (n == 1 && constructor.get(0).equals(Entity.class)) {
270 				String face = getConstructorParameterType();
271 				if (face != null) {
272 					sb.code("(").code(face).code(")");
273 				}
274 			}
275 			sb.code("$").code(Integer.toString(i));
276 			if (i < n) {
277 				sb.code(",");
278 			}
279 		}
280 		sb.code(");").end();
281 	}
282 
283 	private String getConstructorParameterType() throws Exception {
284 		for (Constructor<?> c : javaClass.getConstructors()) {
285 			Class<?>[] param = c.getParameterTypes();
286 			if (param.length == 1 && param[0].isInterface()) {
287 				for (Class<?> f : declaring.getInterfaces()) {
288 					if (param[0].isAssignableFrom(f)) {
289 						return param[0].getName();
290 					}
291 				}
292 			}
293 		}
294 		return null;
295 	}
296 
297 	private boolean nameMatches(String method, intercepts it) {
298 		if (it.method().length() == 0)
299 			return true;
300 		return Pattern.matches(it.method(), method);
301 	}
302 
303 	private boolean argcMatch(int argc1, int argc2) {
304 		return argc1 < 0 || argc2 < 0 || argc1 == argc2;
305 	}
306 
307 	private boolean argsMatch(Class<?>[] pattern, Class<?>[] type) {
308 		if (pattern.length == 1 && pattern[0] == intercepts.class)
309 			return true;
310 		if (pattern.length != type.length)
311 			return false;
312 		for (int i = 0; i < pattern.length; i++) {
313 			if (!isAssignableFrom(pattern[i], type[i]))
314 				return false;
315 			if (!pattern[i].equals(type[i]))
316 				return false;
317 		}
318 		return true;
319 	}
320 
321 	private boolean returnTypeMatches(Method jm, intercepts it) {
322 		if (it.returns() == intercepts.class)
323 			return true;
324 		return isAssignableFrom(jm.getReturnType(), it.returns());
325 	}
326 
327 	private boolean declaredInMatches(Method jm, intercepts it) {
328 		if (it.declaring() == intercepts.class)
329 			return true;
330 		try {
331 			it.declaring().getMethod(jm.getName(), jm.getParameterTypes());
332 			return true;
333 		} catch (NoSuchMethodException e) {
334 			return false;
335 		}
336 	}
337 
338 	private boolean isEnabled(Method jm, Class<?> interceptor, intercepts it)
339 			throws Exception {
340 		if (it.conditional().length() == 0)
341 			return true;
342 		Method cm = interceptor.getDeclaredMethod(it.conditional(),
343 				new Class[] { Method.class });
344 		assert cm != null : it.conditional();
345 		return Boolean.TRUE.equals(cm.invoke(null, new Object[] { jm }));
346 	}
347 
348 	private boolean isAssignableFrom(Class<?> t1, Class<?> t2) {
349 		if (t1.isAssignableFrom(t2))
350 			return true;
351 		if (t2.isPrimitive())
352 			return getWrapperClass(t2).equals(t1);
353 		if (t1.isPrimitive())
354 			return getWrapperClass(t1).equals(t2);
355 		return false;
356 	}
357 
358 	private Class<?> getWrapperClass(Class<?> primitiveClass) {
359 		if (primitiveClass.equals(Boolean.TYPE))
360 			return Boolean.class;
361 		if (primitiveClass.equals(Byte.TYPE))
362 			return Byte.class;
363 		if (primitiveClass.equals(Character.TYPE))
364 			return Character.class;
365 		if (primitiveClass.equals(Short.TYPE))
366 			return Short.class;
367 		if (primitiveClass.equals(Integer.TYPE))
368 			return Integer.class;
369 		if (primitiveClass.equals(Long.TYPE))
370 			return Long.class;
371 		if (primitiveClass.equals(Float.TYPE))
372 			return Float.class;
373 		if (primitiveClass.equals(Double.TYPE))
374 			return Double.class;
375 		if (primitiveClass.equals(Void.TYPE))
376 			return Void.class;
377 		return primitiveClass;
378 	}
379 
380 	private boolean isObjectMethod(Method m) {
381 		return m.getDeclaringClass().getName().equals(Object.class.getName());
382 	}
383 
384 	private Method findInterfaceMethod(Method method, Class<?> face)
385 			throws Exception {
386 		String name = method.getName();
387 		Class<?>[] types = method.getParameterTypes();
388 		Class<?> clazz = face;
389 		return clazz.getDeclaredMethod(name, types);
390 	}
391 }