1 package org.openrdf.elmo.impl;
2
3 import static java.lang.reflect.Modifier.isAbstract;
4 import static java.lang.reflect.Modifier.isFinal;
5 import static java.lang.reflect.Modifier.isProtected;
6
7 import java.lang.reflect.Constructor;
8 import java.lang.reflect.Method;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Collection;
12 import java.util.HashMap;
13 import java.util.List;
14 import java.util.Map;
15
16 import org.openrdf.elmo.Entity;
17 import org.openrdf.elmo.EntitySupport;
18 import org.openrdf.elmo.ImplementationResolver;
19 import org.openrdf.elmo.dynacode.ClassFactory;
20 import org.openrdf.elmo.dynacode.ClassTemplate;
21 import org.openrdf.elmo.dynacode.CodeBuilder;
22 import org.openrdf.elmo.exceptions.ElmoCompositionException;
23
24 public class AbstractBehaviourClassFactory implements ImplementationResolver {
25 private static final String GET_ENTITY_METHOD = "getSupportedElmoEntity";
26 public static final String CLASS_PREFIX = "elmobeans.behaviours.";
27 private static final String BEAN_FIELD_NAME = "_$elmoBean";
28 private ClassFactory cp;
29
30 public void setClassDefiner(ClassFactory definer) {
31 this.cp = definer;
32 }
33
34 public Collection<Class<?>> findImplementations(Collection<Class<?>> classes) {
35 try {
36 List<Class<?>> result = new ArrayList<Class<?>>();
37 for (Class<?> c : classes) {
38 result.add(findClass(c));
39 }
40 return result;
41 } catch (ElmoCompositionException e) {
42 throw e;
43 } catch (Exception e) {
44 throw new ElmoCompositionException(e);
45 }
46 }
47
48 private Class<?> findClass(Class<?> c) throws Exception {
49 String name = getClassName(c);
50 try {
51 return Class.forName(name, true, cp);
52 } catch (ClassNotFoundException e1) {
53 synchronized (cp) {
54 try {
55 return Class.forName(name, true, cp);
56 } catch (ClassNotFoundException e2) {
57 return createClass(name, c);
58 }
59 }
60 }
61
62 }
63
64 private Class<?> createClass(String name, Class<?> c) throws Exception {
65 ClassTemplate cc = cp.createClassTemplate(name, c);
66 cc.addInterface(EntitySupport.class);
67 cc.createField(Entity.class, BEAN_FIELD_NAME);
68 addConstructor(c, cc);
69 addEntitySupportMethod(cc);
70 for (Method m : getMethods(c)) {
71 if (isFinal(m.getModifiers()))
72 continue;
73 if (!isAbstract(m.getModifiers()))
74 continue;
75 Class<?> r = m.getReturnType();
76 Class<?>[] types = m.getParameterTypes();
77 CodeBuilder code = cc.createTransientMethod(r, m.getName(), types);
78 if (!Void.TYPE.equals(r)) {
79 code.code("return ($r) ");
80 }
81 if (m.getDeclaringClass().isInterface()) {
82 code.code("(").castObject(BEAN_FIELD_NAME, m.getDeclaringClass());
83 code.code(").").code(m.getName()).code("($$);").end();
84 } else {
85 code.code(BEAN_FIELD_NAME).code(".getClass().getMethod(");
86 code.insert(m.getName()).code(", ").insert(types).code(")").code(".invoke(");
87 code.code(BEAN_FIELD_NAME).code(", $args);").end();
88 }
89 }
90 return cp.createClass(cc);
91 }
92
93 private void addEntitySupportMethod(ClassTemplate cc) {
94 CodeBuilder method = cc.createMethod(Entity.class, GET_ENTITY_METHOD);
95 method.code("return ").code(BEAN_FIELD_NAME).code(";").end();
96 }
97
98 private Collection<Method> getMethods(Class<?> c) {
99 List<Method> methods = new ArrayList<Method>();
100 methods.addAll(Arrays.asList(c.getMethods()));
101 HashMap<Object, Method> map = new HashMap<Object, Method>();
102 Map<Object, Method> pms = getProtectedMethods(c, map);
103 methods.addAll(pms.values());
104 return methods;
105 }
106
107 private Map<Object, Method> getProtectedMethods(Class<?> c,
108 Map<Object, Method> methods) {
109 if (c == null)
110 return methods;
111 for (Method m : c.getDeclaredMethods()) {
112 if (isProtected(m.getModifiers())) {
113 Object types = Arrays.asList(m.getParameterTypes());
114 Object key = Arrays.asList(m.getName(), types);
115 if (!methods.containsKey(key)) {
116 methods.put(key, m);
117 }
118 }
119 }
120 return getProtectedMethods(c.getSuperclass(), methods);
121 }
122
123 private void addConstructor(Class<?> c, ClassTemplate cc) throws Exception {
124 String type = getConstructorParameterType(c);
125 StringBuilder body = new StringBuilder();
126 if (type != null) {
127 body.append("$proceed((");
128 body.append(type).append(")");
129 body.append("$1)");
130 }
131 body.append(BEAN_FIELD_NAME).append(" = $1;");
132 cc.addConstructor(new Class<?>[] { Entity.class }, body.toString());
133 }
134
135 private String getClassName(Class<?> klass) {
136 return CLASS_PREFIX + klass.getName() + "Behaviour";
137 }
138
139 private String getConstructorParameterType(Class<?> javaClass) throws Exception {
140 for (Constructor<?> c : javaClass.getConstructors()) {
141 Class<?>[] param = c.getParameterTypes();
142 if (param.length == 1 && param[0].isInterface()) {
143 return param[0].getName();
144 }
145 }
146 return null;
147 }
148
149 }