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 }