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.Method;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collection;
35  import java.util.HashMap;
36  import java.util.HashSet;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Set;
40  import java.util.TreeSet;
41  import java.util.concurrent.ConcurrentHashMap;
42  import java.util.concurrent.ConcurrentMap;
43  
44  import org.openrdf.elmo.ElmoBehaviourFactory;
45  import org.openrdf.elmo.ElmoEntityResolver;
46  import org.openrdf.elmo.ElmoMapperResolver;
47  import org.openrdf.elmo.annotations.factory;
48  import org.openrdf.elmo.dynacode.ClassFactory;
49  import org.openrdf.elmo.dynacode.ClassTemplate;
50  import org.openrdf.elmo.dynacode.CodeBuilder;
51  import org.openrdf.elmo.exceptions.ElmoCompositionException;
52  
53  /**
54   * This class takes a collection of roles (interfaces or classes) and uses
55   * composition to combine this into a single class.
56   * 
57   * @author James Leigh
58   * 
59   */
60  public class ElmoEntityCompositor implements ElmoEntityResolver {
61  	private static final String _$INTERCEPTED = "_$intercepted";
62  	private static final String PKG_PREFIX = "elmobeans.proxies._$";
63  	private static final String CLASS_PREFIX = "_$EntityProxy";
64  
65  	private ElmoMapperResolver loader;
66  
67  	private ClassFactory cp;
68  
69  	private Map<List<Class<?>>, Class<?>> constructors;
70  
71  	private Map<Class<?>, ElmoBehaviourFactory<?>> factories;
72  
73  	private ConcurrentMap<List<Class<?>>, Class<?>> classes;
74  
75  	public ElmoEntityCompositor() {
76  		constructors = new HashMap<List<Class<?>>, Class<?>>();
77  		factories = new HashMap<Class<?>, ElmoBehaviourFactory<?>>();
78  		classes = new ConcurrentHashMap<List<Class<?>>, Class<?>>();
79  	}
80  
81  	public void setBehaviourClassResolver(ElmoMapperResolver loader) {
82  		this.loader = loader;
83  	}
84  
85  	public void setClassDefiner(ClassFactory definer) {
86  		this.cp = definer;
87  	}
88  
89  	public void addFactoryClass(Class<?> factory, Class<?>... constructor) {
90  		constructors.put(Arrays.asList(constructor), factory);
91  	}
92  
93  	public void setFactoryClasses(Map<List<Class<?>>, Class<?>> factories) {
94  		this.constructors.putAll(factories);
95  	}
96  
97  	public void addBehaviourFactory(ElmoBehaviourFactory<?> factory) {
98  		factories.put(factory.getBehaviourClass(), factory);
99  	}
100 
101 	public Class<?> resolveRoles(Class<?>[] roles) {
102 		List<Class<?>> list = Arrays.asList(roles);
103 		Class<?> type = classes.get(list);
104 		if (type == null) {
105 			type = createClass(list);
106 			Class<?> o = classes.putIfAbsent(list, type);
107 			if (o != null) {
108 				type = o;
109 			}
110 		}
111 		return type;
112 	}
113 
114 	private Class<?> createClass(List<Class<?>> roles) {
115 		try {
116 			List<Class<?>> types = new ArrayList<Class<?>>(roles);
117 			types = removeSuperClasses(types);
118 			Set<Class<?>> interfaces = new HashSet<Class<?>>(types.size());
119 			Set<Class<?>> behaviours = new HashSet<Class<?>>(types.size());
120 			Map<Class<?>, ElmoBehaviourFactory<?>> map = new HashMap();
121 			map.putAll(factories);
122 			for (Class<?> role : types) {
123 				if (role.isAnnotationPresent(factory.class)) {
124 					for (Method m : role.getMethods()) {
125 						if (m.isAnnotationPresent(factory.class)) {
126 							map.put(m.getReturnType(), new MethodFactory(m,
127 									role));
128 						}
129 					}
130 				}
131 			}
132 			for (Class<?> role : types) {
133 				if (role.isInterface()) {
134 					interfaces.add(role);
135 				}
136 				if (role.isAnnotationPresent(factory.class))
137 					continue;
138 				if (!role.isInterface() || map.containsKey(role)) {
139 					behaviours.add(role);
140 				}
141 			}
142 			behaviours.addAll(loader.findMappers(interfaces));
143 			String className = getJavaClassName(roles);
144 			return getComposedBehaviours(className, interfaces, behaviours, map);
145 		} catch (Exception e) {
146 			List<String> roleNames = new ArrayList<String>();
147 			for (Class<?> f : roles) {
148 				roleNames.add(f.getSimpleName());
149 			}
150 			throw new ElmoCompositionException(e.getMessage()
151 					+ " for entity with roles: " + roleNames, e);
152 		}
153 	}
154 
155 	@SuppressWarnings("unchecked")
156 	private List<Class<?>> removeSuperClasses(List<Class<?>> classes) {
157 		for (int i = classes.size() - 1; i >= 0; i--) {
158 			Class<?> c = classes.get(i);
159 			for (int j = classes.size() - 1; j >= 0; j--) {
160 				Class<?> d = classes.get(j);
161 				if (i != j && c.isAssignableFrom(d)) {
162 					classes.remove(i);
163 					break;
164 				}
165 			}
166 		}
167 		return classes;
168 	}
169 
170 	private Class<?> getComposedBehaviours(String className, Set<Class<?>> interfaces,
171 			Set<Class<?>> javaClasses, Map<Class<?>, ElmoBehaviourFactory<?>> factories) throws Exception {
172 		try {
173 			return Class.forName(className, true, cp);
174 		} catch (ClassNotFoundException e) {
175 			synchronized (cp) {
176 				try {
177 					return Class.forName(className, true, cp);
178 				} catch (ClassNotFoundException e1) {
179 					return composeBehaviours(className, interfaces, javaClasses, factories);
180 				}
181 			}
182 		}
183 	}
184 
185 	private Class<?> composeBehaviours(String className,
186 			Set<Class<?>> interfaces, Set<Class<?>> javaClasses,
187 			Map<Class<?>, ElmoBehaviourFactory<?>> factories) throws Exception {
188 		List<Behaviour> behaviours = new ArrayList<Behaviour>();
189 		ClassTemplate cc = cp.createClassTemplate(className);
190 		for (Class<?> clazz : javaClasses) {
191 			addInterfaces(clazz, interfaces);
192 		}
193 		for (Class<?> face : interfaces) {
194 			cc.addInterface(face);
195 		}
196 		for (Class<?> clazz : javaClasses) {
197 			Behaviour behaviour = new Behaviour();
198 			behaviour.setClassDefiner(cp);
199 			behaviour.setJavaClass(clazz);
200 			behaviour.setDeclaring(cc);
201 			behaviour.setConstructorFactories(constructors);
202 			behaviour.setFactories(factories);
203 			behaviour.init();
204 			behaviours.add(behaviour);
205 		}
206 		for (Method method : getMethods(javaClasses)) {
207 			if (!method.getName().startsWith("_$")) {
208 				Class<?> face = findInterface(method, cc.getInterfaces());
209 				if (face == null)
210 					face = method.getDeclaringClass();
211 				List<Behaviour> incepts = getInterceptors(behaviours, method,
212 						face, cc);
213 				if (incepts.size() > 0) {
214 					String name = _$INTERCEPTED + method.getName();
215 					if (implementMethod(behaviours, method, name, cc)) {
216 						interceptMethod(incepts, method, name, face, cc);
217 					}
218 				} else {
219 					implementMethod(behaviours, method, method.getName(), cc);
220 				}
221 			}
222 		}
223 		return cp.createClass(cc);
224 	}
225 
226 	private Collection<Method> getMethods(Set<Class<?>> javaClasses) {
227 		Map map = new HashMap();
228 		for (Class<?> jc : javaClasses) {
229 			for (Method m : jc.getMethods()) {
230 				if (m.getDeclaringClass().equals(Object.class))
231 					continue;
232 				List list = new ArrayList(m.getParameterTypes().length + 1);
233 				list.add(m.getName());
234 				list.addAll(Arrays.asList(m.getParameterTypes()));
235 				if (!map.containsKey(list)) {
236 					map.put(list, m);
237 				}
238 			}
239 		}
240 		return map.values();
241 	}
242 
243 	private Set<Class<?>> addInterfaces(Class<?> clazz, Set<Class<?>> interfaces) {
244 		if (interfaces.contains(clazz))
245 			return interfaces;
246 		if (clazz.isInterface()) {
247 			interfaces.add(clazz);
248 		}
249 		Class<?> superclass = clazz.getSuperclass();
250 		if (superclass != null) {
251 			addInterfaces(superclass, interfaces);
252 		}
253 		for (Class<?> face : clazz.getInterfaces()) {
254 			addInterfaces(face, interfaces);
255 		}
256 		return interfaces;
257 	}
258 
259 	private String getJavaClassName(Collection<Class<?>> javaClasses) {
260 		String phex = packagesToHexString(javaClasses);
261 		String chex = classesToHexString(javaClasses);
262 		return PKG_PREFIX + phex + "." + CLASS_PREFIX + chex;
263 	}
264 
265 	private String packagesToHexString(Collection<Class<?>> javaClasses) {
266 		TreeSet<String> names = new TreeSet<String>();
267 		for (Class<?> clazz : javaClasses) {
268 			names.add(clazz.getPackage().getName());
269 		}
270 		return toHexString(names);
271 	}
272 
273 	private String classesToHexString(Collection<Class<?>> javaClasses) {
274 		TreeSet<String> names = new TreeSet<String>();
275 		for (Class<?> clazz : javaClasses) {
276 			names.add(clazz.getName());
277 		}
278 		return toHexString(names);
279 	}
280 
281 	private String toHexString(TreeSet<String> names) {
282 		long hashCode = 0;
283 		for (String name : names) {
284 			hashCode = 31 * hashCode + name.hashCode();
285 		}
286 		return Long.toHexString(hashCode);
287 	}
288 
289 	private boolean implementMethod(List<Behaviour> behaviours,
290 			Method method, String name, ClassTemplate cc) throws Exception {
291 		Class<?> type = method.getReturnType();
292 		boolean voidReturnType = type.equals(Void.TYPE);
293 		boolean booleanReturnType = type.equals(Boolean.TYPE);
294 		boolean primitiveReturnType = type.isPrimitive();
295 		StringBuilder body = new StringBuilder();
296 		if (!voidReturnType && primitiveReturnType) {
297 			body.append(type.getName()).append(" result;");
298 		} else if (!voidReturnType) {
299 			body.append("Object result;");
300 		}
301 		int implemented = 0;
302 		StringBuilder eval = null;
303 		for (Behaviour behaviour : behaviours) {
304 			if (behaviour.isMethodPresent(method)) {
305 				implemented++;
306 				if (!voidReturnType)
307 					body.append("result = ");
308 				eval = new StringBuilder();
309 				eval.append(behaviour.getGetterName()).append("()");
310 				eval.append(".").append(method.getName()).append("($$);");
311 				body.append(eval);
312 				if (booleanReturnType) {
313 					body.append("if (result) return result;");
314 				} else if (!voidReturnType && primitiveReturnType) {
315 					body.append("if (result != 0) return ($r) result;");
316 				} else if (!voidReturnType) {
317 					body.append("if (result != null) return ($r) result;");
318 				}
319 			}
320 		}
321 		if (!voidReturnType)
322 			body.append("return ($r) result;");
323 		if (implemented == 1 && eval != null) {
324 			cc.createMethod(method.getReturnType(), name, method.getParameterTypes()).code("return ($r) " + eval.toString()).end();
325 			return true;
326 		} else if (implemented > 1) {
327 			cc.createMethod(method.getReturnType(), name, method.getParameterTypes()).code(body.toString()).end();
328 			return true;
329 		}
330 		return false;
331 	}
332 
333 	private List<Behaviour> getInterceptors(List<Behaviour> behaviours,
334 			Method method, Class<?> face, ClassTemplate cc) throws Exception {
335 		List<Behaviour> result = new ArrayList<Behaviour>(behaviours.size());
336 		for (Behaviour behaviour : behaviours) {
337 			if (behaviour.invokeCondition(method, face)) {
338 				result.add(behaviour);
339 			}
340 		}
341 		return result;
342 	}
343 
344 	private void interceptMethod(List<Behaviour> interceptors, Method method, String name,
345 			Class<?> face, ClassTemplate cc) throws Exception {
346 		CodeBuilder body = cc.overrideMethod(method);
347 		body.code("return ($r) new ").code(
348 				InvocationContextImpl.class.getName());
349 		body.code("($0, ");
350 		Method declaredMethod = face.getDeclaredMethod(method.getName(), method.getParameterTypes());
351 		body.insert(declaredMethod);
352 		body.code(", $args, ");
353 		body.insertMethod(name, method.getParameterTypes()).code(")");
354 		for (Behaviour behaviour : interceptors) {
355 			for (Method m : behaviour.getAroundInvoke(method, face, cc)) {
356 				body.code(".appendInvocation(");
357 				body.code(behaviour.getGetterName()).code("(), ");
358 				body.insert(m);
359 				body.code(")");
360 			}
361 		}
362 		body.code(".proceed();");
363 		body.end();
364 	}
365 
366 	private Class<?> findInterface(Method method, Class<?>[] interfaces) throws Exception {
367 		Class<?> declaring = null;
368 		for (Class<?> face : interfaces) {
369 			try {
370 				face.getDeclaredMethod(method.getName(), method.getParameterTypes());
371 				declaring = face;
372 			} catch (NoSuchMethodException e) {
373 				declaring = findInterface(method, face.getInterfaces());
374 			}
375 			if (declaring != null)
376 				return declaring;
377 		}
378 		return declaring;
379 	}
380 }