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 }