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 java.beans.PropertyDescriptor;
32 import java.lang.reflect.Method;
33 import java.lang.reflect.ParameterizedType;
34 import java.lang.reflect.Type;
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.HashSet;
38 import java.util.List;
39 import java.util.Set;
40
41 import javassist.CannotCompileException;
42 import javassist.NotFoundException;
43
44 import org.openrdf.elmo.ElmoMapperResolver;
45 import org.openrdf.elmo.ElmoProperty;
46 import org.openrdf.elmo.ElmoPropertyFactory;
47 import org.openrdf.elmo.Entity;
48 import org.openrdf.elmo.Mergeable;
49 import org.openrdf.elmo.Refreshable;
50 import org.openrdf.elmo.annotations.inverseOf;
51 import org.openrdf.elmo.annotations.rdf;
52 import org.openrdf.elmo.dynacode.ClassFactory;
53 import org.openrdf.elmo.dynacode.ClassTemplate;
54 import org.openrdf.elmo.dynacode.CodeBuilder;
55 import org.openrdf.elmo.exceptions.ElmoCompositionException;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59
60
61
62
63
64
65
66 public class ElmoMapperClassFactory implements ElmoMapperResolver {
67 private static final String PROPERTY_SUFFIX = "Property";
68
69 private static final String FACTORY_SUFFIX = "Factory";
70
71 private static final String SET_PROPERTY_DESCRIPTOR = "setPropertyDescriptor";
72
73 private static final String CREATE_ELMO_PROPERTY = "createElmoProperty";
74
75 private static final String CLASS_PREFIX = "elmobeans.mappers.";
76
77 private static final String BEAN_FIELD_NAME = "_$elmoBean";
78
79 private static final String GET_ALL = "getAll";
80
81 private static final String GET_SINGLE = "getSingle";
82
83 private static final String SET_ALL = "setAll";
84
85 private static final String SET_SINGLE = "setSingle";
86
87 private final Logger logger = LoggerFactory
88 .getLogger(ElmoMapperClassFactory.class);
89
90 private ClassFactory cp;
91
92 private Class<?> propertyFactoryClass;
93
94 public ElmoMapperClassFactory() {
95 }
96
97 public void setClassDefiner(ClassFactory definer) {
98 this.cp = definer;
99 }
100
101 @SuppressWarnings("unchecked")
102 public void setElmoPropertyFactoryClass(
103 Class<? extends ElmoPropertyFactory> propertyFactoryClass) {
104 this.propertyFactoryClass = propertyFactoryClass;
105 }
106
107 public Collection<Class<?>> findMappers(Collection<Class<?>> interfaces) {
108 try {
109 Set<Class<?>> faces = new HashSet<Class<?>>();
110 for (Class<?> i : interfaces) {
111 faces.add(i);
112 faces = getInterfaces(i, faces);
113 }
114 List<Class<?>> mappers = new ArrayList<Class<?>>();
115 for (Class<?> concept : faces) {
116 if (isRdfPropertyPresent(concept)) {
117 mappers.add(findBehaviour(concept));
118 }
119 }
120 return mappers;
121 } catch (ElmoCompositionException e) {
122 throw e;
123 } catch (Exception e) {
124 throw new ElmoCompositionException(e);
125 }
126 }
127
128 private Class<?> findBehaviour(Class<?> concept) throws Exception {
129 String className = getJavaClassName(concept);
130 try {
131 return Class.forName(className, true, cp);
132 } catch (ClassNotFoundException e1) {
133 synchronized (cp) {
134 try {
135 return Class.forName(className, true, cp);
136 } catch (ClassNotFoundException e2) {
137 return implement(className, concept);
138 }
139 }
140 }
141 }
142
143 private boolean isRdfPropertyPresent(Class<?> concept) {
144 for (Method m : concept.getDeclaredMethods()) {
145 if (m.isAnnotationPresent(rdf.class))
146 return true;
147 }
148 return false;
149 }
150
151 private String getJavaClassName(Class<?> concept) {
152 String cn = concept.getName();
153 return CLASS_PREFIX + cn + "Mapper";
154 }
155
156 private Class<?> implement(String className, Class<?> concept)
157 throws Exception {
158 ClassTemplate cc;
159 HashSet<Class<?>> interfaces = new HashSet<Class<?>>();
160 assert concept.isInterface();
161 interfaces.add(concept);
162 cc = cp.createClassTemplate(className);
163 cc.addInterface(Mergeable.class);
164 cc.addInterface(Refreshable.class);
165 addNewConstructor(cc);
166 enhance(cc, concept);
167 return cp.createClass(cc);
168 }
169
170 private Set<Class<?>> getInterfaces(Class<?> concept,
171 Set<Class<?>> interfaces) throws NotFoundException {
172 for (Class<?> face : concept.getInterfaces()) {
173 if (!interfaces.contains(face)) {
174 interfaces.add(face);
175 getInterfaces(face, interfaces);
176 }
177 }
178 return interfaces;
179 }
180
181 private void addNewConstructor(ClassTemplate cc) throws NotFoundException,
182 CannotCompileException {
183 cc.createField(Entity.class, BEAN_FIELD_NAME);
184 cc.addConstructor(new Class<?>[] { Entity.class }, BEAN_FIELD_NAME + " = $1;");
185 }
186
187 private void enhance(ClassTemplate cc, Class<?> concept) throws Exception {
188 for (Method method : concept.getDeclaredMethods()) {
189 if (method.isAnnotationPresent(rdf.class)
190 || method.isAnnotationPresent(inverseOf.class)) {
191 if (method.getParameterTypes().length == 0) {
192 overrideMethod(method, cc);
193 } else {
194 String msg = "@rdf and @inverseOf annotations should only be on getter methods: {}#{}";
195 logger.warn(msg, concept.getSimpleName(), method.getName());
196 }
197 }
198 }
199 overrideMergeMethod(cc, concept);
200 overrideRefreshMethod(cc, concept);
201 }
202
203 private void overrideMergeMethod(ClassTemplate cc, Class<?> concept)
204 throws Exception {
205 Method merge = Mergeable.class.getMethod("merge", Object.class);
206 CodeBuilder sb = cc.overrideMethod(merge);
207 sb.code("if($1 instanceof ").code(concept.getName());
208 sb.code("){\n");
209 for (Method method : concept.getDeclaredMethods()) {
210 if (method.isAnnotationPresent(rdf.class)) {
211 String property = getPropertyName(method);
212 Method setter = getSetterMethod(property, method);
213 if (setter == null)
214 continue;
215 if (method.getReturnType().isPrimitive()) {
216 sb.code(setter.getName());
217 sb.code("(").code("((").code(concept.getName());
218 sb.code(") $1).").code(method.getName());
219 sb.code("()").code(");\n");
220 } else {
221 sb.declareObject(method.getReturnType(), property + "Var");
222 sb.code("((").code(concept.getName());
223 sb.code(") $1).").code(method.getName());
224 sb.code("();\n");
225
226 sb.code("if(");
227 sb.codeInstanceof(property + "Var", method.getReturnType());
228 sb.code("){");
229 sb.code(setter.getName());
230 sb.code("(").code(property).code("Var);}\n");
231 }
232 }
233 }
234 sb.code("}").end();
235 }
236
237 private void overrideRefreshMethod(ClassTemplate cc, Class<?> concept)
238 throws Exception {
239 Method refresh = Refreshable.class.getMethod("refresh");
240 CodeBuilder sb = cc.overrideMethod(refresh);
241 for (String field : cc.getDeclaredFieldNames()) {
242 if (field.endsWith(PROPERTY_SUFFIX)) {
243 sb.code("if (").code(field).code(" != null) {");
244 sb.code(field).code(".refresh();}\n");
245 }
246 }
247 sb.end();
248 }
249
250 private void overrideMethod(Method method, ClassTemplate cc) throws Exception {
251 String property = getPropertyName(method);
252 Method setter = getSetterMethod(property, method);
253 Class<?> type = method.getReturnType();
254 String field = createPropertyField(property, cc);
255 String factory = createFactoryField(property, method, setter, cc);
256 CodeBuilder body = cc.overrideMethod(method);
257 appendNullCheck(body, field, factory, method, cc);
258 appendGetterMethod(body, field, type, method, cc);
259 body.end();
260 if (setter != null) {
261 body = cc.overrideMethod(setter);
262 appendNullCheck(body, field, factory, method, cc);
263 appendSetterMethod(body, field, type, method, cc);
264 body.end();
265 }
266 }
267
268 private Method getSetterMethod(String property, Method getter) {
269 try {
270 StringBuilder smn = new StringBuilder();
271 smn.append("set").append(Character.toUpperCase(property.charAt(0)));
272 smn.append(property, 1, property.length());
273 Class<?> dc = getter.getDeclaringClass();
274 Class<?> rt = getter.getReturnType();
275 return dc.getDeclaredMethod(smn.toString(), rt);
276 } catch (NoSuchMethodException exc) {
277 return null;
278 }
279 }
280
281 private String getPropertyName(Method method) {
282 String name = method.getName();
283 for (int i = 0, n = name.length(); i < n; i++) {
284 char chr = name.charAt(i);
285 if (Character.isUpperCase(chr)) {
286 StringBuilder property = new StringBuilder(name.length() - i);
287 property.append(Character.toLowerCase(name.charAt(i)));
288 property.append(name, i + 1, name.length());
289 return property.toString();
290 }
291 }
292 throw new IllegalArgumentException();
293 }
294
295 private String createPropertyField(String property, ClassTemplate cc)
296 throws Exception {
297 StringBuilder name = new StringBuilder();
298 name.append("_$").append(property).append(PROPERTY_SUFFIX);
299 String fieldName = name.toString();
300 cc.createField(ElmoProperty.class, fieldName);
301 return fieldName;
302 }
303
304 private String createFactoryField(String property, Method method,
305 Method setter, ClassTemplate cc) throws Exception {
306 String setterName = setter == null? null:setter.getName();
307 Class<?> declaringClass = method.getDeclaringClass();
308 String getterName = method.getName();
309 Class<?> class1 = PropertyDescriptor.class;
310 Class<?> type = ElmoPropertyFactory.class;
311 String fieldName = "_$" + property + FACTORY_SUFFIX;
312 CodeBuilder code = cc.assignStaticField(type, fieldName);
313 code.construct(propertyFactoryClass);
314 code.code(".").code(SET_PROPERTY_DESCRIPTOR).code("(");
315 code.construct(class1, property, declaringClass, getterName, setterName);
316 code.code(")").end();
317 return fieldName;
318 }
319
320 private boolean isCollection(Class<?> type, Method method, ClassTemplate cc)
321 throws Exception {
322 return Set.class.equals(type);
323 }
324
325 public static Class<?> getClassType(Method method) {
326 return method.getReturnType();
327 }
328
329 public static Class<?> getContentClassType(Method method) {
330 Type[] types = getTypeArguments(method);
331 if (types == null || types.length != 1)
332 return null;
333 if (types[0] instanceof Class)
334 return (Class) types[0];
335 if (types[0] instanceof ParameterizedType)
336 return (Class) ((ParameterizedType) types[0]).getRawType();
337 return null;
338 }
339
340 public static Type[] getTypeArguments(Method method) {
341 Type type = method.getGenericReturnType();
342 if (type instanceof ParameterizedType)
343 return ((ParameterizedType) type).getActualTypeArguments();
344 return null;
345 }
346
347 public static Type[] getContentTypeArguments(Method method) {
348 Type[] types = getTypeArguments(method);
349 if (types == null || types.length != 1)
350 return null;
351 if (types[0] instanceof ParameterizedType)
352 return ((ParameterizedType) types[0]).getActualTypeArguments();
353 return null;
354 }
355
356 private CodeBuilder appendNullCheck(CodeBuilder body, String field,
357 String propertyFactory, Method method, ClassTemplate cc)
358 throws Exception {
359 body.code("if (").code(field).code(" == null) {");
360 body.assign(field).code(propertyFactory);
361 body.code(".").code(CREATE_ELMO_PROPERTY);
362 body.code("(").code(BEAN_FIELD_NAME).code(");}");
363 return body;
364 }
365
366 private CodeBuilder appendGetterMethod(CodeBuilder body, String field,
367 Class<?> type, Method method, ClassTemplate cc) throws Exception {
368 if (isCollection(type, method, cc)) {
369 body.code("return ").code(field);
370 body.code(".").code(GET_ALL);
371 body.code("();");
372 } else if (type.isPrimitive()) {
373 body.declareObject(type, "result");
374 body.castObject(field, type);
375 body.code(".").code(GET_SINGLE);
376 body.code("();");
377 body.code("if (result != null) ");
378 body.code("return result.").code(type.getName()).code("Value();");
379 if (Boolean.TYPE.equals(type)) {
380 body.code("return false;");
381 } else {
382 body.code("return ($r) 0;");
383 }
384 } else {
385 body.code("return ");
386 body.castObject(field, type);
387 body.code(".").code(GET_SINGLE);
388 body.code("();");
389 }
390 return body;
391 }
392
393 private CodeBuilder appendSetterMethod(CodeBuilder body, String field,
394 Class<?> type, Method method, ClassTemplate cc) throws Exception {
395 body.code(field).code(".");
396 if (isCollection(type, method, cc)) {
397 body.code(SET_ALL).code("($1);");
398 } else {
399 body.code(SET_SINGLE).code("(").codeObject("$1", type).code(");");
400 }
401 return body;
402 }
403 }