1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
package org.apache.tapestry.enhance; |
16 | |
|
17 | |
import org.apache.commons.logging.Log; |
18 | |
import org.apache.hivemind.ApplicationRuntimeException; |
19 | |
import org.apache.hivemind.ClassResolver; |
20 | |
import org.apache.hivemind.HiveMind; |
21 | |
import org.apache.hivemind.Location; |
22 | |
import org.apache.hivemind.service.BodyBuilder; |
23 | |
import org.apache.hivemind.service.ClassFab; |
24 | |
import org.apache.hivemind.service.ClassFactory; |
25 | |
import org.apache.hivemind.service.MethodSignature; |
26 | |
import org.apache.hivemind.util.Defense; |
27 | |
import org.apache.hivemind.util.ToStringBuilder; |
28 | |
import org.apache.tapestry.services.ComponentConstructor; |
29 | |
import org.apache.tapestry.spec.IComponentSpecification; |
30 | |
import org.apache.tapestry.util.IdAllocator; |
31 | |
import org.apache.tapestry.util.ObjectIdentityMap; |
32 | |
|
33 | |
import java.beans.BeanInfo; |
34 | |
import java.beans.IntrospectionException; |
35 | |
import java.beans.Introspector; |
36 | |
import java.beans.PropertyDescriptor; |
37 | |
import java.lang.reflect.Constructor; |
38 | |
import java.lang.reflect.Method; |
39 | |
import java.lang.reflect.Modifier; |
40 | |
import java.util.*; |
41 | |
|
42 | |
|
43 | |
|
44 | |
|
45 | |
|
46 | |
|
47 | |
|
48 | |
|
49 | |
|
50 | |
|
51 | |
public class EnhancementOperationImpl implements EnhancementOperation |
52 | |
{ |
53 | 0 | static int _uid = 0; |
54 | |
|
55 | |
private ClassResolver _resolver; |
56 | |
|
57 | |
private IComponentSpecification _specification; |
58 | |
|
59 | |
private Class _baseClass; |
60 | |
|
61 | |
private ClassFab _classFab; |
62 | |
|
63 | 0 | private final Set _claimedProperties = new HashSet(); |
64 | |
|
65 | 0 | private final JavaClassMapping _javaClassMapping = new JavaClassMapping(); |
66 | |
|
67 | 0 | private final List _constructorTypes = new ArrayList(); |
68 | |
|
69 | 0 | private final List _constructorArguments = new ArrayList(); |
70 | |
|
71 | 0 | private final ObjectIdentityMap _finalFields = new ObjectIdentityMap(); |
72 | |
|
73 | |
|
74 | |
|
75 | |
|
76 | |
|
77 | 0 | private Set _addedInterfaces = new HashSet(); |
78 | |
|
79 | |
|
80 | |
|
81 | |
|
82 | |
|
83 | 0 | private Map _incompleteMethods = new HashMap(); |
84 | |
|
85 | |
|
86 | |
|
87 | |
|
88 | |
|
89 | 0 | private Map _properties = new HashMap(); |
90 | |
|
91 | |
|
92 | |
|
93 | |
|
94 | |
|
95 | |
private BodyBuilder _constructorBuilder; |
96 | |
|
97 | |
|
98 | |
|
99 | |
|
100 | |
|
101 | |
|
102 | 0 | private final IdAllocator _idAllocator = new IdAllocator(); |
103 | |
|
104 | |
|
105 | |
|
106 | |
|
107 | |
|
108 | |
|
109 | |
|
110 | 0 | private final Map _methods = new HashMap(); |
111 | |
|
112 | |
|
113 | |
|
114 | |
private final Log _log; |
115 | |
|
116 | |
|
117 | |
|
118 | |
|
119 | |
|
120 | |
|
121 | |
EnhancementOperationImpl() |
122 | 0 | { |
123 | 0 | _log = null; |
124 | 0 | } |
125 | |
|
126 | |
public EnhancementOperationImpl(ClassResolver classResolver, |
127 | |
IComponentSpecification specification, Class baseClass, |
128 | |
ClassFactory classFactory, Log log) |
129 | 0 | { |
130 | 0 | Defense.notNull(classResolver, "classResolver"); |
131 | 0 | Defense.notNull(specification, "specification"); |
132 | 0 | Defense.notNull(baseClass, "baseClass"); |
133 | 0 | Defense.notNull(classFactory, "classFactory"); |
134 | |
|
135 | 0 | _resolver = classResolver; |
136 | 0 | _specification = specification; |
137 | 0 | _baseClass = baseClass; |
138 | |
|
139 | 0 | introspectBaseClass(); |
140 | |
|
141 | 0 | String name = newClassName(); |
142 | |
|
143 | 0 | _classFab = classFactory.newClass(name, _baseClass); |
144 | 0 | _log = log; |
145 | 0 | } |
146 | |
|
147 | |
public String toString() |
148 | |
{ |
149 | 0 | ToStringBuilder builder = new ToStringBuilder(this); |
150 | |
|
151 | 0 | builder.append("baseClass", _baseClass.getName()); |
152 | 0 | builder.append("claimedProperties", _claimedProperties); |
153 | 0 | builder.append("classFab", _classFab); |
154 | |
|
155 | 0 | return builder.toString(); |
156 | |
} |
157 | |
|
158 | |
|
159 | |
|
160 | |
|
161 | |
|
162 | |
|
163 | |
|
164 | |
|
165 | |
|
166 | |
|
167 | |
|
168 | |
private void introspectBaseClass() |
169 | |
{ |
170 | |
try |
171 | |
{ |
172 | 0 | synchronized(HiveMind.INTROSPECTOR_MUTEX) |
173 | |
{ |
174 | 0 | addPropertiesDeclaredInBaseClass(); |
175 | 0 | } |
176 | |
} |
177 | 0 | catch (IntrospectionException ex) |
178 | |
{ |
179 | 0 | throw new ApplicationRuntimeException(EnhanceMessages.unabelToIntrospectClass(_baseClass, ex), ex); |
180 | 0 | } |
181 | |
|
182 | 0 | } |
183 | |
|
184 | |
private void addPropertiesDeclaredInBaseClass() |
185 | |
throws IntrospectionException |
186 | |
{ |
187 | 0 | Class introspectClass = _baseClass; |
188 | |
|
189 | 0 | addPropertiesDeclaredInClass(introspectClass); |
190 | |
|
191 | 0 | List interfaceQueue = new ArrayList(); |
192 | |
|
193 | 0 | while(introspectClass != null) |
194 | |
{ |
195 | 0 | addInterfacesToQueue(introspectClass, interfaceQueue); |
196 | |
|
197 | 0 | introspectClass = introspectClass.getSuperclass(); |
198 | |
} |
199 | |
|
200 | 0 | while(!interfaceQueue.isEmpty()) |
201 | |
{ |
202 | 0 | Class interfaceClass = (Class) interfaceQueue.remove(0); |
203 | |
|
204 | 0 | addPropertiesDeclaredInClass(interfaceClass); |
205 | |
|
206 | 0 | addInterfacesToQueue(interfaceClass, interfaceQueue); |
207 | 0 | } |
208 | 0 | } |
209 | |
|
210 | |
private void addInterfacesToQueue(Class introspectClass, List interfaceQueue) |
211 | |
{ |
212 | 0 | Class[] interfaces = introspectClass.getInterfaces(); |
213 | |
|
214 | 0 | for(int i = 0; i < interfaces.length; i++) |
215 | 0 | interfaceQueue.add(interfaces[i]); |
216 | 0 | } |
217 | |
|
218 | |
private void addPropertiesDeclaredInClass(Class introspectClass) |
219 | |
throws IntrospectionException |
220 | |
{ |
221 | |
|
222 | 0 | BeanInfo bi = Introspector.getBeanInfo(introspectClass); |
223 | |
|
224 | 0 | PropertyDescriptor[] pds = bi.getPropertyDescriptors(); |
225 | |
|
226 | 0 | for(int i = 0; i < pds.length; i++) |
227 | |
{ |
228 | 0 | PropertyDescriptor pd = pds[i]; |
229 | |
|
230 | 0 | String name = pd.getName(); |
231 | |
|
232 | 0 | if (!_properties.containsKey(name)) |
233 | 0 | _properties.put(name, pd); |
234 | |
} |
235 | 0 | } |
236 | |
|
237 | |
public void claimProperty(String propertyName) |
238 | |
{ |
239 | 0 | Defense.notNull(propertyName, "propertyName"); |
240 | |
|
241 | 0 | if (_claimedProperties.contains(propertyName)) |
242 | 0 | throw new ApplicationRuntimeException(EnhanceMessages.claimedProperty(propertyName)); |
243 | |
|
244 | 0 | _claimedProperties.add(propertyName); |
245 | 0 | } |
246 | |
|
247 | |
|
248 | |
|
249 | |
|
250 | |
public boolean canClaimAsReadOnlyProperty(String propertyName) |
251 | |
{ |
252 | 0 | if(_claimedProperties.contains(propertyName)) |
253 | 0 | return false; |
254 | |
|
255 | 0 | PropertyDescriptor pd = getPropertyDescriptor(propertyName); |
256 | |
|
257 | 0 | if (pd == null) |
258 | 0 | return false; |
259 | |
|
260 | 0 | return pd.getWriteMethod() == null ? true : false; |
261 | |
} |
262 | |
|
263 | |
public void claimReadonlyProperty(String propertyName) |
264 | |
{ |
265 | 0 | claimProperty(propertyName); |
266 | |
|
267 | 0 | PropertyDescriptor pd = getPropertyDescriptor(propertyName); |
268 | |
|
269 | 0 | if (pd != null && pd.getWriteMethod() != null) |
270 | 0 | throw new ApplicationRuntimeException(EnhanceMessages.readonlyProperty(propertyName, pd.getWriteMethod())); |
271 | 0 | } |
272 | |
|
273 | |
public void addField(String name, Class type) |
274 | |
{ |
275 | 0 | _classFab.addField(name, type); |
276 | 0 | } |
277 | |
|
278 | |
public String addInjectedField(String fieldName, Class fieldType, Object value) |
279 | |
{ |
280 | 0 | Defense.notNull(fieldName, "fieldName"); |
281 | 0 | Defense.notNull(fieldType, "fieldType"); |
282 | 0 | Defense.notNull(value, "value"); |
283 | |
|
284 | 0 | String existing = (String) _finalFields.get(value); |
285 | |
|
286 | |
|
287 | |
|
288 | 0 | if (existing != null) |
289 | 0 | return existing; |
290 | |
|
291 | |
|
292 | |
|
293 | |
|
294 | |
|
295 | |
|
296 | |
|
297 | 0 | String uniqueName = _idAllocator.allocateId(fieldName); |
298 | |
|
299 | |
|
300 | |
|
301 | |
|
302 | |
|
303 | 0 | _classFab.addField(uniqueName, fieldType); |
304 | |
|
305 | 0 | int parameterIndex = addConstructorParameter(fieldType, value); |
306 | |
|
307 | 0 | constructorBuilder().addln("{0} = ${1};", uniqueName, Integer.toString(parameterIndex)); |
308 | |
|
309 | |
|
310 | |
|
311 | 0 | _finalFields.put(value, uniqueName); |
312 | |
|
313 | 0 | return uniqueName; |
314 | |
} |
315 | |
|
316 | |
public Class convertTypeName(String type) |
317 | |
{ |
318 | 0 | Defense.notNull(type, "type"); |
319 | |
|
320 | 0 | Class result = _javaClassMapping.getType(type); |
321 | |
|
322 | 0 | if (result == null) |
323 | |
{ |
324 | 0 | result = _resolver.findClass(type); |
325 | |
|
326 | 0 | _javaClassMapping.recordType(type, result); |
327 | |
} |
328 | |
|
329 | 0 | return result; |
330 | |
} |
331 | |
|
332 | |
public Class getPropertyType(String name) |
333 | |
{ |
334 | 0 | Defense.notNull(name, "name"); |
335 | |
|
336 | 0 | PropertyDescriptor pd = getPropertyDescriptor(name); |
337 | |
|
338 | 0 | return pd == null ? null : pd.getPropertyType(); |
339 | |
} |
340 | |
|
341 | |
public void validateProperty(String name, Class expectedType) |
342 | |
{ |
343 | 0 | Defense.notNull(name, "name"); |
344 | 0 | Defense.notNull(expectedType, "expectedType"); |
345 | |
|
346 | 0 | PropertyDescriptor pd = getPropertyDescriptor(name); |
347 | |
|
348 | 0 | if (pd == null) |
349 | 0 | return; |
350 | |
|
351 | 0 | Class propertyType = pd.getPropertyType(); |
352 | |
|
353 | 0 | if (propertyType.equals(expectedType)) |
354 | 0 | return; |
355 | |
|
356 | 0 | throw new ApplicationRuntimeException(EnhanceMessages.propertyTypeMismatch(_baseClass, name, propertyType, expectedType)); |
357 | |
} |
358 | |
|
359 | |
PropertyDescriptor getPropertyDescriptor(String name) |
360 | |
{ |
361 | 0 | return (PropertyDescriptor) _properties.get(name); |
362 | |
} |
363 | |
|
364 | |
public String getAccessorMethodName(String propertyName) |
365 | |
{ |
366 | 0 | Defense.notNull(propertyName, "propertyName"); |
367 | |
|
368 | 0 | PropertyDescriptor pd = getPropertyDescriptor(propertyName); |
369 | |
|
370 | 0 | if (pd != null && pd.getReadMethod() != null) |
371 | 0 | return pd.getReadMethod().getName(); |
372 | |
|
373 | 0 | return EnhanceUtils.createAccessorMethodName(propertyName); |
374 | |
} |
375 | |
|
376 | |
public void addMethod(int modifier, MethodSignature sig, String methodBody, Location location) |
377 | |
{ |
378 | 0 | Defense.notNull(sig, "sig"); |
379 | 0 | Defense.notNull(methodBody, "methodBody"); |
380 | 0 | Defense.notNull(location, "location"); |
381 | |
|
382 | 0 | Location existing = (Location) _methods.get(sig); |
383 | 0 | if (existing != null) |
384 | 0 | throw new ApplicationRuntimeException(EnhanceMessages.methodConflict(sig, existing), location, null); |
385 | |
|
386 | 0 | _methods.put(sig, location); |
387 | |
|
388 | 0 | _classFab.addMethod(modifier, sig, methodBody); |
389 | 0 | } |
390 | |
|
391 | |
public Class getBaseClass() |
392 | |
{ |
393 | 0 | return _baseClass; |
394 | |
} |
395 | |
|
396 | |
public String getClassReference(Class clazz) |
397 | |
{ |
398 | 0 | Defense.notNull(clazz, "clazz"); |
399 | |
|
400 | 0 | String result = (String) _finalFields.get(clazz); |
401 | |
|
402 | 0 | if (result == null) |
403 | 0 | result = addClassReference(clazz); |
404 | |
|
405 | 0 | return result; |
406 | |
} |
407 | |
|
408 | |
private String addClassReference(Class clazz) |
409 | |
{ |
410 | 0 | StringBuffer buffer = new StringBuffer("_class$"); |
411 | |
|
412 | 0 | Class c = clazz; |
413 | |
|
414 | 0 | while(c.isArray()) |
415 | |
{ |
416 | 0 | buffer.append("array$"); |
417 | 0 | c = c.getComponentType(); |
418 | |
} |
419 | |
|
420 | 0 | buffer.append(c.getName().replace('.', '$')); |
421 | |
|
422 | 0 | String fieldName = buffer.toString(); |
423 | |
|
424 | 0 | return addInjectedField(fieldName, Class.class, clazz); |
425 | |
} |
426 | |
|
427 | |
|
428 | |
|
429 | |
|
430 | |
|
431 | |
|
432 | |
private int addConstructorParameter(Class type, Object value) |
433 | |
{ |
434 | 0 | _constructorTypes.add(type); |
435 | 0 | _constructorArguments.add(value); |
436 | |
|
437 | 0 | return _constructorArguments.size(); |
438 | |
} |
439 | |
|
440 | |
private BodyBuilder constructorBuilder() |
441 | |
{ |
442 | 0 | if (_constructorBuilder == null) |
443 | |
{ |
444 | 0 | _constructorBuilder = new BodyBuilder(); |
445 | 0 | _constructorBuilder.begin(); |
446 | |
} |
447 | |
|
448 | 0 | return _constructorBuilder; |
449 | |
} |
450 | |
|
451 | |
|
452 | |
|
453 | |
|
454 | |
|
455 | |
|
456 | |
public ComponentConstructor getConstructor() |
457 | |
{ |
458 | |
try |
459 | |
{ |
460 | 0 | finalizeEnhancedClass(); |
461 | |
|
462 | 0 | Constructor c = findConstructor(); |
463 | |
|
464 | 0 | Object[] params = _constructorArguments.toArray(); |
465 | |
|
466 | 0 | return new ComponentConstructorImpl(c, params, _classFab.toString(), _specification.getLocation()); |
467 | |
} |
468 | 0 | catch (Throwable t) |
469 | |
{ |
470 | 0 | throw new ApplicationRuntimeException(EnhanceMessages.classEnhancementFailure(_baseClass, t), _classFab, null, t); |
471 | |
} |
472 | |
} |
473 | |
|
474 | |
void finalizeEnhancedClass() |
475 | |
{ |
476 | 0 | finalizeIncompleteMethods(); |
477 | |
|
478 | 0 | if (_constructorBuilder != null) |
479 | |
{ |
480 | 0 | _constructorBuilder.end(); |
481 | |
|
482 | 0 | Class[] types = (Class[]) _constructorTypes.toArray(new Class[_constructorTypes.size()]); |
483 | |
|
484 | 0 | _classFab.addConstructor(types, null, _constructorBuilder.toString()); |
485 | |
} |
486 | |
|
487 | 0 | if (_log != null && _log.isDebugEnabled()) |
488 | 0 | _log.debug("Creating class:\n\n" + _classFab); |
489 | 0 | } |
490 | |
|
491 | |
private void finalizeIncompleteMethods() |
492 | |
{ |
493 | 0 | Iterator i = _incompleteMethods.entrySet().iterator(); |
494 | 0 | while(i.hasNext()) |
495 | |
{ |
496 | 0 | Map.Entry e = (Map.Entry) i.next(); |
497 | 0 | MethodSignature sig = (MethodSignature) e.getKey(); |
498 | 0 | BodyBuilder builder = (BodyBuilder) e.getValue(); |
499 | |
|
500 | |
|
501 | |
|
502 | |
|
503 | 0 | builder.end(); |
504 | |
|
505 | 0 | _classFab.addMethod(Modifier.PUBLIC, sig, builder.toString()); |
506 | 0 | } |
507 | 0 | } |
508 | |
|
509 | |
private Constructor findConstructor() |
510 | |
{ |
511 | 0 | Class componentClass = _classFab.createClass(); |
512 | |
|
513 | |
|
514 | |
|
515 | 0 | return componentClass.getConstructors()[0]; |
516 | |
} |
517 | |
|
518 | |
private String newClassName() |
519 | |
{ |
520 | 0 | String baseName = _baseClass.getName(); |
521 | 0 | int dotx = baseName.lastIndexOf('.'); |
522 | |
|
523 | 0 | return "$" + baseName.substring(dotx + 1) + "_" + _uid++; |
524 | |
} |
525 | |
|
526 | |
public void extendMethodImplementation(Class interfaceClass, MethodSignature methodSignature, String code) |
527 | |
{ |
528 | 0 | addInterfaceIfNeeded(interfaceClass); |
529 | |
|
530 | 0 | BodyBuilder builder = (BodyBuilder) _incompleteMethods.get(methodSignature); |
531 | |
|
532 | 0 | if (builder == null) |
533 | |
{ |
534 | 0 | builder = createIncompleteMethod(methodSignature); |
535 | |
|
536 | 0 | _incompleteMethods.put(methodSignature, builder); |
537 | |
} |
538 | |
|
539 | 0 | builder.addln(code); |
540 | 0 | } |
541 | |
|
542 | |
private void addInterfaceIfNeeded(Class interfaceClass) |
543 | |
{ |
544 | 0 | if (implementsInterface(interfaceClass)) |
545 | 0 | return; |
546 | |
|
547 | 0 | _classFab.addInterface(interfaceClass); |
548 | 0 | _addedInterfaces.add(interfaceClass); |
549 | 0 | } |
550 | |
|
551 | |
public boolean implementsInterface(Class interfaceClass) |
552 | |
{ |
553 | 0 | if (interfaceClass.isAssignableFrom(_baseClass)) |
554 | 0 | return true; |
555 | |
|
556 | 0 | Iterator i = _addedInterfaces.iterator(); |
557 | 0 | while(i.hasNext()) |
558 | |
{ |
559 | 0 | Class addedInterface = (Class) i.next(); |
560 | |
|
561 | 0 | if (interfaceClass.isAssignableFrom(addedInterface)) |
562 | 0 | return true; |
563 | 0 | } |
564 | |
|
565 | 0 | return false; |
566 | |
} |
567 | |
|
568 | |
private BodyBuilder createIncompleteMethod(MethodSignature sig) |
569 | |
{ |
570 | 0 | BodyBuilder result = new BodyBuilder(); |
571 | |
|
572 | |
|
573 | |
|
574 | 0 | result.begin(); |
575 | |
|
576 | 0 | if (existingImplementation(sig)) |
577 | 0 | result.addln("super.{0}($$);", sig.getName()); |
578 | |
|
579 | 0 | return result; |
580 | |
} |
581 | |
|
582 | |
|
583 | |
|
584 | |
|
585 | |
|
586 | |
|
587 | |
private boolean existingImplementation(MethodSignature sig) |
588 | |
{ |
589 | 0 | Method m = findMethod(sig); |
590 | |
|
591 | 0 | return m != null && !Modifier.isAbstract(m.getModifiers()); |
592 | |
} |
593 | |
|
594 | |
|
595 | |
|
596 | |
|
597 | |
private Method findMethod(MethodSignature sig) |
598 | |
{ |
599 | |
|
600 | |
|
601 | |
try |
602 | |
{ |
603 | 0 | return _baseClass.getMethod(sig.getName(), sig.getParameterTypes()); |
604 | |
|
605 | |
} |
606 | 0 | catch (NoSuchMethodException ex) |
607 | |
{ |
608 | |
|
609 | |
} |
610 | |
|
611 | 0 | Class c = _baseClass; |
612 | |
|
613 | 0 | while(c != Object.class) |
614 | |
{ |
615 | |
try |
616 | |
{ |
617 | 0 | return c.getDeclaredMethod(sig.getName(), sig |
618 | |
.getParameterTypes()); |
619 | |
} |
620 | 0 | catch (NoSuchMethodException ex) |
621 | |
{ |
622 | |
|
623 | |
} |
624 | |
|
625 | 0 | c = c.getSuperclass(); |
626 | |
} |
627 | |
|
628 | 0 | return null; |
629 | |
} |
630 | |
|
631 | |
public List findUnclaimedAbstractProperties() |
632 | |
{ |
633 | 0 | List result = new ArrayList(); |
634 | |
|
635 | 0 | Iterator i = _properties.values().iterator(); |
636 | |
|
637 | 0 | while(i.hasNext()) |
638 | |
{ |
639 | 0 | PropertyDescriptor pd = (PropertyDescriptor) i.next(); |
640 | |
|
641 | 0 | String name = pd.getName(); |
642 | |
|
643 | 0 | if (_claimedProperties.contains(name)) |
644 | 0 | continue; |
645 | |
|
646 | 0 | if (isAbstractProperty(pd)) |
647 | 0 | result.add(name); |
648 | 0 | } |
649 | |
|
650 | 0 | return result; |
651 | |
} |
652 | |
|
653 | |
|
654 | |
|
655 | |
|
656 | |
|
657 | |
|
658 | |
|
659 | |
private boolean isAbstractProperty(PropertyDescriptor pd) |
660 | |
{ |
661 | 0 | return isExistingAbstractMethod(pd.getReadMethod()) |
662 | |
|| isExistingAbstractMethod(pd.getWriteMethod()); |
663 | |
} |
664 | |
|
665 | |
private boolean isExistingAbstractMethod(Method m) |
666 | |
{ |
667 | 0 | return m != null && Modifier.isAbstract(m.getModifiers()); |
668 | |
} |
669 | |
|
670 | |
public IComponentSpecification getSpecification() |
671 | |
{ |
672 | 0 | return _specification; |
673 | |
} |
674 | |
} |