KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tapestry > enhance > EnhancementOperationImpl


1 // Copyright 2004, 2005 The Apache Software Foundation
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15 package org.apache.tapestry.enhance;
16
17 import java.beans.BeanInfo JavaDoc;
18 import java.beans.IntrospectionException JavaDoc;
19 import java.beans.Introspector JavaDoc;
20 import java.beans.PropertyDescriptor JavaDoc;
21 import java.lang.reflect.Constructor JavaDoc;
22 import java.lang.reflect.Method JavaDoc;
23 import java.lang.reflect.Modifier JavaDoc;
24 import java.util.ArrayList JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.HashSet JavaDoc;
27 import java.util.IdentityHashMap JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.Map JavaDoc;
31 import java.util.Set JavaDoc;
32
33 import org.apache.hivemind.ApplicationRuntimeException;
34 import org.apache.hivemind.ClassResolver;
35 import org.apache.hivemind.HiveMind;
36 import org.apache.hivemind.service.BodyBuilder;
37 import org.apache.hivemind.service.ClassFab;
38 import org.apache.hivemind.service.ClassFactory;
39 import org.apache.hivemind.service.MethodSignature;
40 import org.apache.hivemind.util.Defense;
41 import org.apache.hivemind.util.ToStringBuilder;
42 import org.apache.tapestry.services.ComponentConstructor;
43 import org.apache.tapestry.spec.IComponentSpecification;
44 import org.apache.tapestry.util.IdAllocator;
45
46 /**
47  * Implementation of {@link org.apache.tapestry.enhance.EnhancementOperation}that knows how to
48  * collect class changes from enhancements. The method {@link #getConstructor()} finalizes the
49  * enhancement into a {@link org.apache.tapestry.services.ComponentConstructor}.
50  *
51  * @author Howard M. Lewis Ship
52  * @since 4.0
53  */

54 public class EnhancementOperationImpl implements EnhancementOperation
55 {
56     private ClassResolver _resolver;
57
58     private IComponentSpecification _specification;
59
60     private Class JavaDoc _baseClass;
61
62     private ClassFab _classFab;
63
64     private final Set JavaDoc _claimedProperties = new HashSet JavaDoc();
65
66     private final JavaClassMapping _javaClassMapping = new JavaClassMapping();
67
68     private final List JavaDoc _constructorTypes = new ArrayList JavaDoc();
69
70     private final List JavaDoc _constructorArguments = new ArrayList JavaDoc();
71
72     private final Map JavaDoc _finalFields = new IdentityHashMap JavaDoc();
73
74     /**
75      * Set of interfaces added to the enhanced class.
76      */

77
78     private Set JavaDoc _addedInterfaces = new HashSet JavaDoc();
79
80     /**
81      * Map of {@link BodyBuilder}, keyed on {@link MethodSignature}.
82      */

83
84     private Map JavaDoc _incompleteMethods = new HashMap JavaDoc();
85
86     /**
87      * Map of property names to {@link PropertyDescriptor}.
88      */

89
90     private Map JavaDoc _properties = new HashMap JavaDoc();
91
92     /**
93      * Used to incrementally assemble the constructor for the enhanced class.
94      */

95
96     private BodyBuilder _constructorBuilder;
97
98     /**
99      * Makes sure that names created by {@link #addInjectedField(String, Object)} have unique names.
100      */

101
102     private final IdAllocator _idAllocator = new IdAllocator();
103
104     public EnhancementOperationImpl(ClassResolver classResolver,
105             IComponentSpecification specification, Class JavaDoc baseClass, ClassFactory classFactory)
106     {
107         Defense.notNull(classResolver, "classResolver");
108         Defense.notNull(specification, "specification");
109         Defense.notNull(baseClass, "baseClass");
110         Defense.notNull(classFactory, "classFactory");
111
112         _resolver = classResolver;
113         _specification = specification;
114         _baseClass = baseClass;
115
116         introspectBaseClass();
117
118         String JavaDoc name = newClassName();
119
120         _classFab = classFactory.newClass(name, _baseClass);
121     }
122
123     public String JavaDoc toString()
124     {
125         ToStringBuilder builder = new ToStringBuilder(this);
126
127         builder.append("baseClass", _baseClass.getName());
128         builder.append("claimedProperties", _claimedProperties);
129         builder.append("classFab", _classFab);
130
131         return builder.toString();
132     }
133
134     /**
135      * We want to find the properties of the class, but in many cases, the class is abstract. Some
136      * JDK's (Sun) will include public methods from interfaces implemented by the class in the
137      * public declared methods for the class (which is used by the Introspector). Eclipse's built-in
138      * compiler does not appear to (this may have to do with compiler options I've been unable to
139      * track down). The solution is to augment the information provided directly by the Introspector
140      * with additional information compiled by Introspecting the interfaces directly or indirectly
141      * implemented by the class.
142      */

143     private void introspectBaseClass()
144     {
145         try
146         {
147             synchronized (HiveMind.INTROSPECTOR_MUTEX)
148             {
149                 addPropertiesDeclaredInBaseClass();
150             }
151         }
152         catch (IntrospectionException JavaDoc ex)
153         {
154             throw new ApplicationRuntimeException(EnhanceMessages.unabelToIntrospectClass(
155                     _baseClass,
156                     ex), ex);
157         }
158
159     }
160
161     private void addPropertiesDeclaredInBaseClass() throws IntrospectionException JavaDoc
162     {
163         Class JavaDoc introspectClass = _baseClass;
164
165         addPropertiesDeclaredInClass(introspectClass);
166
167         List JavaDoc interfaceQueue = new ArrayList JavaDoc();
168
169         while (introspectClass != null)
170         {
171             addInterfacesToQueue(introspectClass, interfaceQueue);
172
173             introspectClass = introspectClass.getSuperclass();
174         }
175
176         while (!interfaceQueue.isEmpty())
177         {
178             Class JavaDoc interfaceClass = (Class JavaDoc) interfaceQueue.remove(0);
179
180             addPropertiesDeclaredInClass(interfaceClass);
181
182             addInterfacesToQueue(interfaceClass, interfaceQueue);
183         }
184     }
185
186     private void addInterfacesToQueue(Class JavaDoc introspectClass, List JavaDoc interfaceQueue)
187     {
188         Class JavaDoc[] interfaces = introspectClass.getInterfaces();
189
190         for (int i = 0; i < interfaces.length; i++)
191             interfaceQueue.add(interfaces[i]);
192     }
193
194     private void addPropertiesDeclaredInClass(Class JavaDoc introspectClass) throws IntrospectionException JavaDoc
195     {
196         BeanInfo JavaDoc bi = Introspector.getBeanInfo(introspectClass);
197
198         PropertyDescriptor JavaDoc[] pds = bi.getPropertyDescriptors();
199
200         for (int i = 0; i < pds.length; i++)
201         {
202             PropertyDescriptor JavaDoc pd = pds[i];
203
204             String JavaDoc name = pd.getName();
205
206             if (!_properties.containsKey(name))
207                 _properties.put(name, pd);
208         }
209     }
210
211     /**
212      * Alternate package private constructor used by the test suite, to bypass the defense checks
213      * above.
214      */

215
216     EnhancementOperationImpl()
217     {
218     }
219
220     public void claimProperty(String JavaDoc propertyName)
221     {
222         Defense.notNull(propertyName, "propertyName");
223
224         if (_claimedProperties.contains(propertyName))
225             throw new ApplicationRuntimeException(EnhanceMessages.claimedProperty(propertyName));
226
227         _claimedProperties.add(propertyName);
228     }
229
230     public void addField(String JavaDoc name, Class JavaDoc type)
231     {
232         _classFab.addField(name, type);
233     }
234
235     public String JavaDoc addInjectedField(String JavaDoc fieldName, Class JavaDoc fieldType, Object JavaDoc value)
236     {
237         Defense.notNull(fieldName, "fieldName");
238         Defense.notNull(fieldType, "fieldType");
239         Defense.notNull(value, "value");
240
241         String JavaDoc existing = (String JavaDoc) _finalFields.get(value);
242
243         // See if this object has been previously added.
244

245         if (existing != null)
246             return existing;
247
248         // TODO: Should be ensure that the name is unique?
249

250         // Make sure that the field has a unique name (at least, among anything added
251
// via addFinalField().
252

253         String JavaDoc uniqueName = _idAllocator.allocateId(fieldName);
254
255         // ClassFab doesn't have an option for saying the field should be final, just private.
256
// Doesn't make a huge difference.
257

258         _classFab.addField(uniqueName, fieldType);
259
260         int parameterIndex = addConstructorParameter(fieldType, value);
261
262         constructorBuilder().addln("{0} = ${1};", uniqueName, Integer.toString(parameterIndex));
263
264         // Remember the mapping from the value to the field name.
265

266         _finalFields.put(value, uniqueName);
267
268         return uniqueName;
269     }
270
271     public Class JavaDoc convertTypeName(String JavaDoc type)
272     {
273         Defense.notNull(type, "type");
274
275         Class JavaDoc result = _javaClassMapping.getType(type);
276
277         if (result == null)
278         {
279             result = _resolver.findClass(type);
280
281             _javaClassMapping.recordType(type, result);
282         }
283
284         return result;
285     }
286
287     public Class JavaDoc getPropertyType(String JavaDoc name)
288     {
289         Defense.notNull(name, "name");
290
291         PropertyDescriptor JavaDoc pd = getPropertyDescriptor(name);
292
293         return pd == null ? null : pd.getPropertyType();
294     }
295
296     public void validateProperty(String JavaDoc name, Class JavaDoc expectedType)
297     {
298         Defense.notNull(name, "name");
299         Defense.notNull(expectedType, "expectedType");
300
301         PropertyDescriptor JavaDoc pd = getPropertyDescriptor(name);
302
303         if (pd == null)
304             return;
305
306         Class JavaDoc propertyType = pd.getPropertyType();
307
308         if (propertyType.equals(expectedType))
309             return;
310
311         throw new ApplicationRuntimeException(EnhanceMessages.propertyTypeMismatch(
312                 _baseClass,
313                 name,
314                 propertyType,
315                 expectedType));
316     }
317
318     private PropertyDescriptor JavaDoc getPropertyDescriptor(String JavaDoc name)
319     {
320         return (PropertyDescriptor JavaDoc) _properties.get(name);
321     }
322
323     public String JavaDoc getAccessorMethodName(String JavaDoc propertyName)
324     {
325         Defense.notNull(propertyName, "propertyName");
326
327         PropertyDescriptor JavaDoc pd = getPropertyDescriptor(propertyName);
328
329         if (pd != null && pd.getReadMethod() != null)
330             return pd.getReadMethod().getName();
331
332         return EnhanceUtils.createAccessorMethodName(propertyName);
333     }
334
335     public void addMethod(int modifier, MethodSignature sig, String JavaDoc methodBody)
336     {
337         _classFab.addMethod(modifier, sig, methodBody);
338     }
339
340     public Class JavaDoc getBaseClass()
341     {
342         return _baseClass;
343     }
344
345     public String JavaDoc getClassReference(Class JavaDoc clazz)
346     {
347         Defense.notNull(clazz, "clazz");
348
349         String JavaDoc result = (String JavaDoc) _finalFields.get(clazz);
350
351         if (result == null)
352             result = addClassReference(clazz);
353
354         return result;
355     }
356
357     private String JavaDoc addClassReference(Class JavaDoc clazz)
358     {
359         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc("_class$");
360
361         Class JavaDoc c = clazz;
362
363         while (c.isArray())
364         {
365             buffer.append("array$");
366             c = c.getComponentType();
367         }
368
369         buffer.append(c.getName().replace('.', '$'));
370
371         String JavaDoc fieldName = buffer.toString();
372
373         return addInjectedField(fieldName, Class JavaDoc.class, clazz);
374     }
375
376     /**
377      * Adds a new constructor parameter, returning the new count. This is convienient, because the
378      * first element added is accessed as $1, etc.
379      */

380
381     private int addConstructorParameter(Class JavaDoc type, Object JavaDoc value)
382     {
383         _constructorTypes.add(type);
384         _constructorArguments.add(value);
385
386         return _constructorArguments.size();
387     }
388
389     private BodyBuilder constructorBuilder()
390     {
391         if (_constructorBuilder == null)
392         {
393             _constructorBuilder = new BodyBuilder();
394             _constructorBuilder.begin();
395         }
396
397         return _constructorBuilder;
398     }
399
400     /**
401      * Returns an object that can be used to construct instances of the enhanced component subclass.
402      * This should only be called once.
403      */

404
405     public ComponentConstructor getConstructor()
406     {
407         try
408         {
409             finalizeEnhancedClass();
410
411             Constructor JavaDoc c = findConstructor();
412
413             Object JavaDoc[] params = _constructorArguments.toArray();
414
415             return new ComponentConstructorImpl(c, params, _classFab.toString(), _specification
416                     .getLocation());
417         }
418         catch (Throwable JavaDoc t)
419         {
420             throw new ApplicationRuntimeException(EnhanceMessages.classEnhancementFailure(
421                     _baseClass,
422                     t), _classFab, null, t);
423         }
424     }
425
426     void finalizeEnhancedClass()
427     {
428         finalizeIncompleteMethods();
429
430         if (_constructorBuilder != null)
431         {
432             _constructorBuilder.end();
433
434             Class JavaDoc[] types = (Class JavaDoc[]) _constructorTypes
435                     .toArray(new Class JavaDoc[_constructorTypes.size()]);
436
437             _classFab.addConstructor(types, null, _constructorBuilder.toString());
438         }
439     }
440
441     private void finalizeIncompleteMethods()
442     {
443         Iterator JavaDoc i = _incompleteMethods.entrySet().iterator();
444         while (i.hasNext())
445         {
446             Map.Entry JavaDoc e = (Map.Entry JavaDoc) i.next();
447             MethodSignature sig = (MethodSignature) e.getKey();
448             BodyBuilder builder = (BodyBuilder) e.getValue();
449
450             // Each BodyBuilder is created and given a begin(), this is
451
// the matching end()
452

453             builder.end();
454
455             _classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
456         }
457     }
458
459     private Constructor JavaDoc findConstructor()
460     {
461         Class JavaDoc componentClass = _classFab.createClass();
462
463         // The fabricated base class always has exactly one constructor
464

465         return componentClass.getConstructors()[0];
466     }
467
468     static int _uid = 0;
469
470     private String JavaDoc newClassName()
471     {
472         String JavaDoc baseName = _baseClass.getName();
473         int dotx = baseName.lastIndexOf('.');
474
475         return "$" + baseName.substring(dotx + 1) + "_" + _uid++;
476     }
477
478     public void extendMethodImplementation(Class JavaDoc interfaceClass, MethodSignature methodSignature,
479             String JavaDoc code)
480     {
481         addInterfaceIfNeeded(interfaceClass);
482
483         BodyBuilder builder = (BodyBuilder) _incompleteMethods.get(methodSignature);
484
485         if (builder == null)
486         {
487             builder = createIncompleteMethod(methodSignature);
488
489             _incompleteMethods.put(methodSignature, builder);
490         }
491
492         builder.addln(code);
493     }
494
495     private void addInterfaceIfNeeded(Class JavaDoc interfaceClass)
496     {
497         if (implementsInterface(interfaceClass))
498             return;
499
500         _classFab.addInterface(interfaceClass);
501         _addedInterfaces.add(interfaceClass);
502     }
503
504     public boolean implementsInterface(Class JavaDoc interfaceClass)
505     {
506         if (interfaceClass.isAssignableFrom(_baseClass))
507             return true;
508
509         Iterator JavaDoc i = _addedInterfaces.iterator();
510         while (i.hasNext())
511         {
512             Class JavaDoc addedInterface = (Class JavaDoc) i.next();
513
514             if (interfaceClass.isAssignableFrom(addedInterface))
515                 return true;
516         }
517
518         return false;
519     }
520
521     private BodyBuilder createIncompleteMethod(MethodSignature sig)
522     {
523         BodyBuilder result = new BodyBuilder();
524
525         // Matched inside finalizeIncompleteMethods()
526

527         result.begin();
528
529         if (existingImplementation(sig))
530             result.addln("super.{0}($$);", sig.getName());
531
532         return result;
533     }
534
535     /**
536      * Returns true if the base class implements the provided method as either a public or a
537      * protected method.
538      */

539
540     private boolean existingImplementation(MethodSignature sig)
541     {
542         Method JavaDoc m = findMethod(sig);
543
544         return m != null && !Modifier.isAbstract(m.getModifiers());
545     }
546
547     /**
548      * Finds a public or protected method in the base class.
549      */

550     private Method JavaDoc findMethod(MethodSignature sig)
551     {
552         // Finding a public method is easy:
553

554         try
555         {
556             return _baseClass.getMethod(sig.getName(), sig.getParameterTypes());
557
558         }
559         catch (NoSuchMethodException JavaDoc ex)
560         {
561             // Good; no super-implementation to invoke.
562
}
563
564         Class JavaDoc c = _baseClass;
565
566         while (c != Object JavaDoc.class)
567         {
568             try
569             {
570                 return c.getDeclaredMethod(sig.getName(), sig.getParameterTypes());
571             }
572             catch (NoSuchMethodException JavaDoc ex)
573             {
574                 // Ok, continue loop up to next base class.
575
}
576
577             c = c.getSuperclass();
578         }
579
580         return null;
581     }
582
583     public List JavaDoc findUnclaimedAbstractProperties()
584     {
585         List JavaDoc result = new ArrayList JavaDoc();
586
587         Iterator JavaDoc i = _properties.values().iterator();
588
589         while (i.hasNext())
590         {
591             PropertyDescriptor JavaDoc pd = (PropertyDescriptor JavaDoc) i.next();
592
593             String JavaDoc name = pd.getName();
594
595             if (_claimedProperties.contains(name))
596                 continue;
597
598             if (isAbstractProperty(pd))
599                 result.add(name);
600         }
601
602         return result;
603     }
604
605     /**
606      * A property is abstract if either its read method or it write method is abstract. We could do
607      * some additional checking to ensure that both are abstract if either is. Note that in many
608      * cases, there will only be one accessor (a reader or a writer).
609      */

610     private boolean isAbstractProperty(PropertyDescriptor JavaDoc pd)
611     {
612         return isExistingAbstractMethod(pd.getReadMethod())
613                 || isExistingAbstractMethod(pd.getWriteMethod());
614     }
615
616     private boolean isExistingAbstractMethod(Method JavaDoc m)
617     {
618         return m != null && Modifier.isAbstract(m.getModifiers());
619     }
620 }
Popular Tags