View Javadoc

1   /*
2    * Copyright 2002-2004 The Apache Software Foundation
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.commons.clazz.reflect;
17  
18  import java.beans.BeanInfo;
19  import java.beans.IndexedPropertyDescriptor;
20  import java.beans.Introspector;
21  import java.beans.PropertyDescriptor;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.List;
27  import java.util.Map;
28  
29  import org.apache.commons.clazz.Clazz;
30  import org.apache.commons.clazz.ClazzInstanceFactory;
31  import org.apache.commons.clazz.ClazzLoader;
32  import org.apache.commons.clazz.ClazzOperation;
33  import org.apache.commons.clazz.ClazzProperty;
34  import org.apache.commons.clazz.reflect.common.ReflectedListProperty;
35  import org.apache.commons.clazz.reflect.common.ReflectedScalarProperty;
36  
37  /**
38   * This implementation of Clazz is based on Java reflection. 
39   * 
40   * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
41   * @version $Id: ReflectedClazz.java 155436 2005-02-26 13:17:48Z dirkv $
42   */
43  public abstract class ReflectedClazz extends Clazz {
44  
45      private Class instanceClass;
46      private boolean superClazzKnown;
47      private Clazz superClazz;
48  
49      /**
50       * This map includes all properties, declared and inherited
51       */
52      private Map propertyMap;
53      private List propertyList;
54      private List declaredPropertyList;
55  
56      /**
57       * This map includes all operations, declared and inherited
58       */
59      private Map operationMap;
60      private List operationList;
61      private List declaredOperationList;
62  
63      /**
64       * This map includes all factories
65       */
66      private Map factoryMap;
67      private List factoryList;
68      
69      private boolean hasLookedForBeanInfo = false;
70      private BeanInfo beanInfo;
71  
72      /**
73       * 
74       * @param loader
75       * @param instanceClass
76       */
77      public ReflectedClazz(ClazzLoader loader, Class instanceClass) {
78          super(loader, getCanonicalClassName(instanceClass));
79          this.instanceClass = instanceClass;
80      }
81  
82      public Class getInstanceClass() {
83          return instanceClass;
84      }
85  
86      /**
87       * The order of introspectors is significant, they are invoked sequencially.
88       * Once a property has been recoginzed by an introspector, it will not be
89       * overridden by subsequently invoked ones.
90       */
91      protected abstract ReflectedPropertyIntrospector[] 
92              getPropertyIntrospectors();
93  
94      /**
95       * @see #getPropertyIntrospectors()
96       */
97      protected abstract ReflectedOperationIntrospector[] 
98              getOperationIntrospectors();
99  
100     /**
101      * @see #getPropertyIntrospectors()
102      */
103     protected abstract ReflectedInstanceFactoryIntrospector[] 
104             getInstanceFactoryIntrospectors();
105 
106     /**
107      * Returns true if diagnostic is enabled for this clazz
108      */
109     public boolean isLoggingEnabled() {
110         return getClazzLoader().isLoggingEnabled(getName());
111     }
112 
113     /**
114      * Returns the class this ReflectedClazz is based upon.
115      * @return Class
116      */
117     public Class getReflectedClass() {
118         return instanceClass;
119     }
120 
121     /**
122      * @see Clazz#getSuperclazz()
123      */
124     public Clazz getSuperclazz() {
125         if (!superClazzKnown) {
126             superClazzKnown = true;
127             Class superclass = instanceClass.getSuperclass();
128             if (superclass == null) {
129                 superClazz = null;
130             }
131             else {
132                 superClazz =
133                     getClazzLoader().getClazzForName(superclass.getName());
134             }
135         }
136         return superClazz;
137     }
138 
139     /**
140      * Returns properties introduced by this clazz as compared 
141      * to the superclazz.  Note, that some of the methods comprizing
142      * a property may be declared on the superclass. This makes it
143      * impossible to build a list of declared properties by looking
144      * exclusively at the current class - we have to reflected all
145      * properties of this clazz, then all properties of the superclazz
146      * and then subtract the latter from the former.
147      */
148     public List getDeclaredProperties() {
149         if (declaredPropertyList == null) {
150             if (instanceClass.getSuperclass() == null) {
151                 declaredPropertyList = getProperties();
152             }
153             else {
154                 List superProperties = getSuperclazz().getProperties();
155                 if (superProperties.size() == 0) {
156                     declaredPropertyList = getProperties();
157                 }
158                 else {
159                     HashSet superNames = new HashSet();
160                     for (int i = 0; i < superProperties.size(); i++) {
161                         ClazzProperty property =
162                             (ClazzProperty) superProperties.get(i);
163                         superNames.add(property.getName());
164                     }
165 
166                     List properties = getProperties();
167                     declaredPropertyList = new ArrayList();
168                     for (int i = 0; i < properties.size(); i++) {
169                         ClazzProperty property =
170                             (ClazzProperty) properties.get(i);
171                         String name = property.getName();
172                         if (!superNames.contains(name)) {
173                             declaredPropertyList.add(property);
174                         }
175                     }
176                 }
177             }
178         }
179         return declaredPropertyList;
180     }
181 
182     /**
183      */
184     public List getProperties() {
185         if (propertyList == null) {
186             introspectProperties();
187             if (isLoggingEnabled()) {
188                 logPropertyParseResults();
189             }
190         }
191         return propertyList;
192     }
193 
194     /**
195      */
196     public ClazzProperty getProperty(String name) {
197         if (propertyMap == null) {
198             introspectProperties();
199             if (isLoggingEnabled()) {
200                 logPropertyParseResults();
201             }
202         }
203         return (ClazzProperty) propertyMap.get(name);
204     }
205 
206     protected void addProperty(ClazzProperty property) {
207         propertyMap.put(property.getName(), property);
208         if (property instanceof ReflectedProperty) {
209             String[] aliases = ((ReflectedProperty) property).getAliases();
210             for (int i = 0; i < aliases.length; i++) {
211                 propertyMap.put(aliases[i], property);
212             }
213         }
214         propertyList.add(property);
215     }
216 
217     /**
218      */
219     public List getOperations() {
220         if (operationList == null) {
221             introspectOperations();
222 //          if (isLoggingEnabled()) {
223 //              logOperationParseResults();
224 //          }
225         }
226         return operationList;
227     }
228 
229     /**
230      * @see Clazz#getDeclaredOperations()
231      */
232     public List getDeclaredOperations() {
233         if (declaredOperationList == null) {
234             introspectOperations();
235 //          if (isLoggingEnabled()) {
236 //              logOperationParseResults();
237 //          }
238         }
239         return declaredOperationList;
240     }
241 
242     /**
243      * @see Clazz#getOperation(java.lang.String)
244      */
245     public ClazzOperation getOperation(String signature) {
246         if (operationMap == null) {
247             introspectOperations();
248 //          if (isLoggingEnabled()) {
249 //              logOperationParseResults();
250 //          }
251         }
252         return (ClazzOperation) operationMap.get(signature);
253     }
254 
255     protected void addOperation(ClazzOperation operation) {
256         operationMap.put(operation.getSignature(), operation);
257         declaredOperationList.add(operation);
258     }
259 
260     /**
261      * Returns all InstanceFactories for this clazz.
262      */
263     public List getInstanceFactories() {
264         if (factoryList == null) {
265             introspectInstanceFactories();
266 //          if (isLoggingEnabled()) {
267 //              logInstanceFactoryParseResults();
268 //          }
269         }
270         return factoryList;
271     }
272 
273     /**
274      * Returns the InstanceFactories that match the Predicate.
275      */
276     //    public List getInstanceFactories(Predicate predicate);
277 
278     /**
279      * @see org.apache.commons.clazz.Clazz#getInstanceFactory(java.lang.String)
280      */
281     public ClazzInstanceFactory getInstanceFactory(String signature) {
282         if (factoryMap == null) {
283             introspectInstanceFactories();
284 //          if (isLoggingEnabled()) {
285 //              logInstanceFactoryParseResults();
286 //          }
287         }
288         if (signature == null) {
289             signature = "()";
290         }
291         return (ClazzInstanceFactory) factoryMap.get(signature);
292     }
293 
294     protected void addInstanceFactory(ClazzInstanceFactory factory) {
295         factoryMap.put(factory.getSignature(), factory);
296         factoryList.add(factory);
297     }
298 
299     /**
300      * Overrides the default implementation, checks if the supplied clazz is
301      * also a ReflectedClazz and if so invokes isAssignableFrom on the
302      * corresponding java classes.
303      */
304     public boolean isAssignableFrom(Clazz clazz) {
305         if (clazz == this) {
306             return true;
307         }
308         if (clazz instanceof ReflectedClazz) {
309             return getReflectedClass().isAssignableFrom(
310                 ((ReflectedClazz) clazz).getReflectedClass());
311         }
312         return super.isAssignableFrom(clazz);
313     }
314 
315     /**
316      * Override this method to provide an alternate way of mapping
317      * fields and methods to properties.
318      */
319     protected void introspectProperties() {
320         propertyList = new ArrayList();
321         propertyMap = new HashMap();
322 
323         BeanInfo beanInfo = getBeanInfo();
324         
325         if (beanInfo != null) {
326             PropertyDescriptor pds[] = beanInfo.getPropertyDescriptors();
327             if (pds != null) {
328                 for (int i = 0; i < pds.length; i++) {
329                     PropertyDescriptor pd = pds[i];
330                     if (pd instanceof IndexedPropertyDescriptor) {
331                         IndexedPropertyDescriptor ipd =
332                             (IndexedPropertyDescriptor) pd;
333                         ReflectedListProperty prop =
334                             new ReflectedListProperty(this, ipd.getName());
335                         prop.setType(ipd.getPropertyType());
336                         prop.setContentType(ipd.getIndexedPropertyType());
337                         prop.setReadMethod(ipd.getReadMethod());
338                         prop.setWriteMethod(ipd.getWriteMethod());
339                         prop.setGetMethod(ipd.getIndexedReadMethod());
340                         prop.setSetMethod(ipd.getIndexedWriteMethod());
341                         addProperty(prop);
342                     }
343                     else {
344                         ReflectedScalarProperty prop =
345                             new ReflectedScalarProperty(this, pd.getName());
346                         prop.setType(pd.getPropertyType());
347                         prop.setReadMethod(pd.getReadMethod());
348                         prop.setWriteMethod(pd.getWriteMethod());
349                         addProperty(prop);
350                     }
351                 }
352             }
353         }
354         else {
355             ReflectedPropertyIntrospector introspectors[] =
356                 getPropertyIntrospectors();
357     
358             if (introspectors != null) {
359                 for (int i = 0; i < introspectors.length; i++) {
360                     List properties =
361                         introspectors[i].introspectProperties(
362                             this,
363                             instanceClass);
364                     for (int j = 0; j < properties.size(); j++) {
365                         ClazzProperty property =
366                             (ClazzProperty) properties.get(j);
367                         addProperty(property);
368                     }
369                 }
370             }
371         }
372     }
373     
374     /**
375      * Performs BeanInfo lookup in the same manner as the standard
376      * java.beans.Introspector.
377      */
378     protected BeanInfo getBeanInfo() {
379         if (!hasLookedForBeanInfo) {
380             hasLookedForBeanInfo = true;            
381             beanInfo = lookupBeanInfo();
382         }
383         return beanInfo;
384     }
385 
386     /**
387      * Search for a custom implementation of BeanInfo according to
388      * the JavaBeans standard definition.
389      */
390     private BeanInfo lookupBeanInfo() {
391         ClassLoader classLoader = instanceClass.getClassLoader();
392         
393         String name = instanceClass.getName() + "BeanInfo";
394         try {
395             return (BeanInfo) instantiate(classLoader, name);
396         }
397         catch (Exception ex) {
398             // Just drop through
399         }
400         
401         // Now try checking if the bean is its own BeanInfo.
402         try {
403             if (BeanInfo.class.isAssignableFrom(instanceClass)) {
404                 return (BeanInfo) instanceClass.newInstance();
405             }
406         }
407         catch (Exception ex) {
408             // Just drop through
409         }
410         
411         // Now try looking for <searchPath>.fooBeanInfo
412         while (name.indexOf('.') > 0) {
413             name = name.substring(name.indexOf('.') + 1);
414         }
415         
416         String[] searchPath = Introspector.getBeanInfoSearchPath();
417         for (int i = 0; i < searchPath.length; i++) {
418             try {
419                 String fullName = searchPath[i] + "." + name;
420                 return (BeanInfo) instantiate(classLoader, fullName);
421             }
422             catch (Exception ex) {
423                 // Silently ignore any errors.
424             }
425         }
426         return null;
427     }
428     
429     static Object instantiate(ClassLoader classLoader, String className)
430         throws
431             InstantiationException,
432             IllegalAccessException,
433             ClassNotFoundException 
434     {
435         if (classLoader != null) {
436             try {
437                 Class cls = classLoader.loadClass(className);
438                 return cls.newInstance();
439             }
440             catch (Exception ex) {
441                 // Just drop through and try the system classloader.
442             }
443         }
444         
445         // Now try the default classloader.
446         Class cls = Class.forName(className);
447         return cls.newInstance();
448     }
449 
450     /**
451      * Override this method to provide an alternate way of mapping
452      * methods (and possibly fields) to Operations.
453      */
454     protected void introspectOperations() {
455         declaredOperationList = new ArrayList();
456         operationMap = new HashMap();
457         operationList = new ArrayList();
458 
459         Clazz superClazz = getSuperclazz();
460         if (superClazz != null) {
461             List superOps = superClazz.getOperations();
462             for (int i = 0; i < superOps.size(); i++) {
463                 ClazzOperation operation = (ClazzOperation) superOps.get(i);
464                 operationMap.put(operation.getSignature(), operation);
465             }
466         }
467 
468         ReflectedOperationIntrospector introspectors[] =
469             getOperationIntrospectors();
470 
471         if (introspectors != null) {
472             for (int i = 0; i < introspectors.length; i++) {
473                 List operations =
474                     introspectors[i].introspectOperations(this, instanceClass);
475                 for (int j = 0; j < operations.size(); j++) {
476                     ClazzOperation operation =
477                         (ClazzOperation) operations.get(j);
478                     addOperation(operation);
479                 }
480             }
481         }
482         operationList.addAll(operationMap.values());
483     }
484 
485     /**
486      * Override this method to provide an alternate way of mapping
487      * constructors, methods (and possibly fields) to InstanceFactories.
488      */
489     protected void introspectInstanceFactories() {
490         factoryMap = new HashMap();
491         factoryList = new ArrayList();
492 
493         ReflectedInstanceFactoryIntrospector introspectors[] =
494             getInstanceFactoryIntrospectors();
495 
496         if (introspectors != null) {
497             for (int i = 0; i < introspectors.length; i++) {
498                 List factories =
499                     introspectors[i].introspectInstanceFactories(
500                         this,
501                         instanceClass);
502                 for (int j = 0; j < factories.size(); j++) {
503                     ClazzInstanceFactory factory =
504                         (ClazzInstanceFactory) factories.get(j);
505                     addInstanceFactory(factory);
506                 }
507             }
508         }
509     }
510 
511     private List propertyParseResults;
512 
513     /**
514      * Called by ReflectedPropertyIntrospector's to log results of
515      * introspection, successful or not.
516      */
517     public void logPropertyParseResults(Object parseResults) {
518         if (propertyParseResults == null) {
519             propertyParseResults = new ArrayList();
520         }
521         propertyParseResults.add(parseResults);
522     }
523 
524     /**
525      * Prints diagnostics of property introspection.
526      */
527     protected void logPropertyParseResults() {
528         if (propertyParseResults == null) {
529             return;
530         }
531 
532         // PropertyParseResults are supposed to implement Comparable
533         Collections.sort(propertyParseResults);
534 
535         // @todo: use a logger
536         System.err.println("[Clazz: " + getName());
537         for (int i = 0; i < propertyParseResults.size(); i++) {
538             System.err.println(propertyParseResults.get(i));
539         }
540         System.err.println("]");
541     }
542 }