001    /*
002     * Copyright 2002-2004 The Apache Software Foundation
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.apache.commons.clazz.reflect;
017    
018    import java.beans.BeanInfo;
019    import java.beans.IndexedPropertyDescriptor;
020    import java.beans.Introspector;
021    import java.beans.PropertyDescriptor;
022    import java.util.ArrayList;
023    import java.util.Collections;
024    import java.util.HashMap;
025    import java.util.HashSet;
026    import java.util.List;
027    import java.util.Map;
028    
029    import org.apache.commons.clazz.Clazz;
030    import org.apache.commons.clazz.ClazzInstanceFactory;
031    import org.apache.commons.clazz.ClazzLoader;
032    import org.apache.commons.clazz.ClazzOperation;
033    import org.apache.commons.clazz.ClazzProperty;
034    import org.apache.commons.clazz.reflect.common.ReflectedListProperty;
035    import org.apache.commons.clazz.reflect.common.ReflectedScalarProperty;
036    
037    /**
038     * This implementation of Clazz is based on Java reflection. 
039     * 
040     * @author <a href="mailto:dmitri@apache.org">Dmitri Plotnikov</a>
041     * @version $Id: ReflectedClazz.java 155436 2005-02-26 13:17:48Z dirkv $
042     */
043    public abstract class ReflectedClazz extends Clazz {
044    
045        private Class instanceClass;
046        private boolean superClazzKnown;
047        private Clazz superClazz;
048    
049        /**
050         * This map includes all properties, declared and inherited
051         */
052        private Map propertyMap;
053        private List propertyList;
054        private List declaredPropertyList;
055    
056        /**
057         * This map includes all operations, declared and inherited
058         */
059        private Map operationMap;
060        private List operationList;
061        private List declaredOperationList;
062    
063        /**
064         * This map includes all factories
065         */
066        private Map factoryMap;
067        private List factoryList;
068        
069        private boolean hasLookedForBeanInfo = false;
070        private BeanInfo beanInfo;
071    
072        /**
073         * 
074         * @param loader
075         * @param instanceClass
076         */
077        public ReflectedClazz(ClazzLoader loader, Class instanceClass) {
078            super(loader, getCanonicalClassName(instanceClass));
079            this.instanceClass = instanceClass;
080        }
081    
082        public Class getInstanceClass() {
083            return instanceClass;
084        }
085    
086        /**
087         * The order of introspectors is significant, they are invoked sequencially.
088         * Once a property has been recoginzed by an introspector, it will not be
089         * overridden by subsequently invoked ones.
090         */
091        protected abstract ReflectedPropertyIntrospector[] 
092                getPropertyIntrospectors();
093    
094        /**
095         * @see #getPropertyIntrospectors()
096         */
097        protected abstract ReflectedOperationIntrospector[] 
098                getOperationIntrospectors();
099    
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    }