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