Clover coverage report - Code Coverage for tapestry release 4.0-beta-1
Coverage timestamp: Fri Jun 24 2005 14:32:46 EDT
file stats: LOC: 611   Methods: 35
NCLOC: 370   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
EnhancementOperationImpl.java 98.1% 95.8% 97.1% 96.5%
coverage coverage
 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;
 18    import java.beans.IntrospectionException;
 19    import java.beans.Introspector;
 20    import java.beans.PropertyDescriptor;
 21    import java.lang.reflect.Constructor;
 22    import java.lang.reflect.Method;
 23    import java.lang.reflect.Modifier;
 24    import java.util.ArrayList;
 25    import java.util.HashMap;
 26    import java.util.HashSet;
 27    import java.util.IdentityHashMap;
 28    import java.util.Iterator;
 29    import java.util.List;
 30    import java.util.Map;
 31    import java.util.Set;
 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 _baseClass;
 61   
 62    private ClassFab _classFab;
 63   
 64    private final Set _claimedProperties = new HashSet();
 65   
 66    private final JavaClassMapping _javaClassMapping = new JavaClassMapping();
 67   
 68    private final List _constructorTypes = new ArrayList();
 69   
 70    private final List _constructorArguments = new ArrayList();
 71   
 72    private final Map _finalFields = new IdentityHashMap();
 73   
 74    /**
 75    * Set of interfaces added to the enhanced class.
 76    */
 77   
 78    private Set _addedInterfaces = new HashSet();
 79   
 80    /**
 81    * Map of {@link BodyBuilder}, keyed on {@link MethodSignature}.
 82    */
 83   
 84    private Map _incompleteMethods = new HashMap();
 85   
 86    /**
 87    * Map of property names to {@link PropertyDescriptor}.
 88    */
 89   
 90    private Map _properties = new HashMap();
 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  577 public EnhancementOperationImpl(ClassResolver classResolver,
 105    IComponentSpecification specification, Class baseClass, ClassFactory classFactory)
 106    {
 107  577 Defense.notNull(classResolver, "classResolver");
 108  577 Defense.notNull(specification, "specification");
 109  577 Defense.notNull(baseClass, "baseClass");
 110  577 Defense.notNull(classFactory, "classFactory");
 111   
 112  577 _resolver = classResolver;
 113  577 _specification = specification;
 114  577 _baseClass = baseClass;
 115   
 116  577 introspectBaseClass();
 117   
 118  577 String name = newClassName();
 119   
 120  577 _classFab = classFactory.newClass(name, _baseClass);
 121    }
 122   
 123  0 public String toString()
 124    {
 125  0 ToStringBuilder builder = new ToStringBuilder(this);
 126   
 127  0 builder.append("baseClass", _baseClass.getName());
 128  0 builder.append("claimedProperties", _claimedProperties);
 129  0 builder.append("classFab", _classFab);
 130   
 131  0 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  577 private void introspectBaseClass()
 144    {
 145  577 try
 146    {
 147  577 synchronized (HiveMind.INTROSPECTOR_MUTEX)
 148    {
 149  577 addPropertiesDeclaredInBaseClass();
 150    }
 151    }
 152    catch (IntrospectionException ex)
 153    {
 154  0 throw new ApplicationRuntimeException(EnhanceMessages.unabelToIntrospectClass(
 155    _baseClass,
 156    ex), ex);
 157    }
 158   
 159    }
 160   
 161  577 private void addPropertiesDeclaredInBaseClass() throws IntrospectionException
 162    {
 163  577 Class introspectClass = _baseClass;
 164   
 165  577 addPropertiesDeclaredInClass(introspectClass);
 166   
 167  577 List interfaceQueue = new ArrayList();
 168   
 169  577 while (introspectClass != null)
 170    {
 171  2843 addInterfacesToQueue(introspectClass, interfaceQueue);
 172   
 173  2843 introspectClass = introspectClass.getSuperclass();
 174    }
 175   
 176  577 while (!interfaceQueue.isEmpty())
 177    {
 178  6138 Class interfaceClass = (Class) interfaceQueue.remove(0);
 179   
 180  6138 addPropertiesDeclaredInClass(interfaceClass);
 181   
 182  6138 addInterfacesToQueue(interfaceClass, interfaceQueue);
 183    }
 184    }
 185   
 186  8981 private void addInterfacesToQueue(Class introspectClass, List interfaceQueue)
 187    {
 188  8981 Class[] interfaces = introspectClass.getInterfaces();
 189   
 190  8981 for (int i = 0; i < interfaces.length; i++)
 191  6138 interfaceQueue.add(interfaces[i]);
 192    }
 193   
 194  6715 private void addPropertiesDeclaredInClass(Class introspectClass) throws IntrospectionException
 195    {
 196  6715 BeanInfo bi = Introspector.getBeanInfo(introspectClass);
 197   
 198  6715 PropertyDescriptor[] pds = bi.getPropertyDescriptors();
 199   
 200  6715 for (int i = 0; i < pds.length; i++)
 201    {
 202  35891 PropertyDescriptor pd = pds[i];
 203   
 204  35891 String name = pd.getName();
 205   
 206  35891 if (!_properties.containsKey(name))
 207  14391 _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  1 EnhancementOperationImpl()
 217    {
 218    }
 219   
 220  3564 public void claimProperty(String propertyName)
 221    {
 222  3564 Defense.notNull(propertyName, "propertyName");
 223   
 224  3564 if (_claimedProperties.contains(propertyName))
 225  1 throw new ApplicationRuntimeException(EnhanceMessages.claimedProperty(propertyName));
 226   
 227  3563 _claimedProperties.add(propertyName);
 228    }
 229   
 230  5929 public void addField(String name, Class type)
 231    {
 232  5929 _classFab.addField(name, type);
 233    }
 234   
 235  1786 public String addInjectedField(String fieldName, Class fieldType, Object value)
 236    {
 237  1786 Defense.notNull(fieldName, "fieldName");
 238  1786 Defense.notNull(fieldType, "fieldType");
 239  1786 Defense.notNull(value, "value");
 240   
 241  1786 String existing = (String) _finalFields.get(value);
 242   
 243    // See if this object has been previously added.
 244   
 245  1786 if (existing != null)
 246  0 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  1786 String 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  1786 _classFab.addField(uniqueName, fieldType);
 259   
 260  1786 int parameterIndex = addConstructorParameter(fieldType, value);
 261   
 262  1786 constructorBuilder().addln("{0} = ${1};", uniqueName, Integer.toString(parameterIndex));
 263   
 264    // Remember the mapping from the value to the field name.
 265   
 266  1786 _finalFields.put(value, uniqueName);
 267   
 268  1786 return uniqueName;
 269    }
 270   
 271  92 public Class convertTypeName(String type)
 272    {
 273  92 Defense.notNull(type, "type");
 274   
 275  92 Class result = _javaClassMapping.getType(type);
 276   
 277  92 if (result == null)
 278    {
 279  22 result = _resolver.findClass(type);
 280   
 281  21 _javaClassMapping.recordType(type, result);
 282    }
 283   
 284  91 return result;
 285    }
 286   
 287  2623 public Class getPropertyType(String name)
 288    {
 289  2623 Defense.notNull(name, "name");
 290   
 291  2623 PropertyDescriptor pd = getPropertyDescriptor(name);
 292   
 293  2623 return pd == null ? null : pd.getPropertyType();
 294    }
 295   
 296  89 public void validateProperty(String name, Class expectedType)
 297    {
 298  89 Defense.notNull(name, "name");
 299  89 Defense.notNull(expectedType, "expectedType");
 300   
 301  89 PropertyDescriptor pd = getPropertyDescriptor(name);
 302   
 303  89 if (pd == null)
 304  25 return;
 305   
 306  64 Class propertyType = pd.getPropertyType();
 307   
 308  64 if (propertyType.equals(expectedType))
 309  63 return;
 310   
 311  1 throw new ApplicationRuntimeException(EnhanceMessages.propertyTypeMismatch(
 312    _baseClass,
 313    name,
 314    propertyType,
 315    expectedType));
 316    }
 317   
 318  5513 private PropertyDescriptor getPropertyDescriptor(String name)
 319    {
 320  5513 return (PropertyDescriptor) _properties.get(name);
 321    }
 322   
 323  2801 public String getAccessorMethodName(String propertyName)
 324    {
 325  2801 Defense.notNull(propertyName, "propertyName");
 326   
 327  2801 PropertyDescriptor pd = getPropertyDescriptor(propertyName);
 328   
 329  2801 if (pd != null && pd.getReadMethod() != null)
 330  2617 return pd.getReadMethod().getName();
 331   
 332  184 return EnhanceUtils.createAccessorMethodName(propertyName);
 333    }
 334   
 335  5927 public void addMethod(int modifier, MethodSignature sig, String methodBody)
 336    {
 337  5927 _classFab.addMethod(modifier, sig, methodBody);
 338    }
 339   
 340  1 public Class getBaseClass()
 341    {
 342  1 return _baseClass;
 343    }
 344   
 345  767 public String getClassReference(Class clazz)
 346    {
 347  767 Defense.notNull(clazz, "clazz");
 348   
 349  767 String result = (String) _finalFields.get(clazz);
 350   
 351  767 if (result == null)
 352  592 result = addClassReference(clazz);
 353   
 354  767 return result;
 355    }
 356   
 357  592 private String addClassReference(Class clazz)
 358    {
 359  592 StringBuffer buffer = new StringBuffer("_class$");
 360   
 361  592 Class c = clazz;
 362   
 363  592 while (c.isArray())
 364    {
 365  15 buffer.append("array$");
 366  15 c = c.getComponentType();
 367    }
 368   
 369  592 buffer.append(c.getName().replace('.', '$'));
 370   
 371  592 String fieldName = buffer.toString();
 372   
 373  592 return addInjectedField(fieldName, Class.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  1786 private int addConstructorParameter(Class type, Object value)
 382    {
 383  1786 _constructorTypes.add(type);
 384  1786 _constructorArguments.add(value);
 385   
 386  1786 return _constructorArguments.size();
 387    }
 388   
 389  1786 private BodyBuilder constructorBuilder()
 390    {
 391  1786 if (_constructorBuilder == null)
 392    {
 393  430 _constructorBuilder = new BodyBuilder();
 394  430 _constructorBuilder.begin();
 395    }
 396   
 397  1786 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  560 public ComponentConstructor getConstructor()
 406    {
 407  560 finalizeEnhancedClass();
 408   
 409  560 Constructor c = findConstructor();
 410   
 411  560 Object[] params = _constructorArguments.toArray();
 412   
 413  560 return new ComponentConstructorImpl(c, params, _classFab.toString(), _specification
 414    .getLocation());
 415    }
 416   
 417  561 void finalizeEnhancedClass()
 418    {
 419  561 finalizeIncompleteMethods();
 420   
 421  561 if (_constructorBuilder != null)
 422    {
 423  429 _constructorBuilder.end();
 424   
 425  429 Class[] types = (Class[]) _constructorTypes
 426    .toArray(new Class[_constructorTypes.size()]);
 427   
 428  429 _classFab.addConstructor(types, null, _constructorBuilder.toString());
 429    }
 430    }
 431   
 432  561 private void finalizeIncompleteMethods()
 433    {
 434  561 Iterator i = _incompleteMethods.entrySet().iterator();
 435  561 while (i.hasNext())
 436    {
 437  658 Map.Entry e = (Map.Entry) i.next();
 438  658 MethodSignature sig = (MethodSignature) e.getKey();
 439  658 BodyBuilder builder = (BodyBuilder) e.getValue();
 440   
 441    // Each BodyBuilder is created and given a begin(), this is
 442    // the matching end()
 443   
 444  658 builder.end();
 445   
 446  658 _classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
 447    }
 448    }
 449   
 450  560 private Constructor findConstructor()
 451    {
 452  560 Class componentClass = _classFab.createClass();
 453   
 454    // The fabricated base class always has exactly one constructor
 455   
 456  560 return componentClass.getConstructors()[0];
 457    }
 458   
 459    static int _uid = 0;
 460   
 461  577 private String newClassName()
 462    {
 463  577 String baseName = _baseClass.getName();
 464  577 int dotx = baseName.lastIndexOf('.');
 465   
 466  577 return "$" + baseName.substring(dotx + 1) + "_" + _uid++;
 467    }
 468   
 469  3258 public void extendMethodImplementation(Class interfaceClass, MethodSignature methodSignature,
 470    String code)
 471    {
 472  3258 addInterfaceIfNeeded(interfaceClass);
 473   
 474  3258 BodyBuilder builder = (BodyBuilder) _incompleteMethods.get(methodSignature);
 475   
 476  3258 if (builder == null)
 477    {
 478  660 builder = createIncompleteMethod(methodSignature);
 479   
 480  660 _incompleteMethods.put(methodSignature, builder);
 481    }
 482   
 483  3258 builder.addln(code);
 484    }
 485   
 486  3258 private void addInterfaceIfNeeded(Class interfaceClass)
 487    {
 488  3258 if (implementsInterface(interfaceClass))
 489  3074 return;
 490   
 491  184 _classFab.addInterface(interfaceClass);
 492  184 _addedInterfaces.add(interfaceClass);
 493    }
 494   
 495  5317 public boolean implementsInterface(Class interfaceClass)
 496    {
 497  5317 if (interfaceClass.isAssignableFrom(_baseClass))
 498  2199 return true;
 499   
 500  3118 Iterator i = _addedInterfaces.iterator();
 501  3118 while (i.hasNext())
 502    {
 503  1118 Class addedInterface = (Class) i.next();
 504   
 505  1118 if (interfaceClass.isAssignableFrom(addedInterface))
 506  937 return true;
 507    }
 508   
 509  2181 return false;
 510    }
 511   
 512  660 private BodyBuilder createIncompleteMethod(MethodSignature sig)
 513    {
 514  660 BodyBuilder result = new BodyBuilder();
 515   
 516    // Matched inside finalizeIncompleteMethods()
 517   
 518  660 result.begin();
 519   
 520  660 if (existingImplementation(sig))
 521  462 result.addln("super.{0}($$);", sig.getName());
 522   
 523  660 return result;
 524    }
 525   
 526    /**
 527    * Returns true if the base class implements the provided method as either a public or a
 528    * protected method.
 529    */
 530   
 531  660 private boolean existingImplementation(MethodSignature sig)
 532    {
 533  660 Method m = findMethod(sig);
 534   
 535  660 return m != null && !Modifier.isAbstract(m.getModifiers());
 536    }
 537   
 538    /**
 539    * Finds a public or protected method in the base class.
 540    */
 541  660 private Method findMethod(MethodSignature sig)
 542    {
 543    // Finding a public method is easy:
 544   
 545  660 try
 546    {
 547  660 return _baseClass.getMethod(sig.getName(), sig.getParameterTypes());
 548   
 549    }
 550    catch (NoSuchMethodException ex)
 551    {
 552    // Good; no super-implementation to invoke.
 553    }
 554   
 555  460 Class c = _baseClass;
 556   
 557  460 while (c != Object.class)
 558    {
 559  1316 try
 560    {
 561  1316 return c.getDeclaredMethod(sig.getName(), sig.getParameterTypes());
 562    }
 563    catch (NoSuchMethodException ex)
 564    {
 565    // Ok, continue loop up to next base class.
 566    }
 567   
 568  1040 c = c.getSuperclass();
 569    }
 570   
 571  184 return null;
 572    }
 573   
 574  543 public List findUnclaimedAbstractProperties()
 575    {
 576  543 List result = new ArrayList();
 577   
 578  543 Iterator i = _properties.values().iterator();
 579   
 580  543 while (i.hasNext())
 581    {
 582  13626 PropertyDescriptor pd = (PropertyDescriptor) i.next();
 583   
 584  13626 String name = pd.getName();
 585   
 586  13626 if (_claimedProperties.contains(name))
 587  2395 continue;
 588   
 589  11231 if (isAbstractProperty(pd))
 590  999 result.add(name);
 591    }
 592   
 593  543 return result;
 594    }
 595   
 596    /**
 597    * A property is abstract if either its read method or it write method is abstract. We could do
 598    * some additional checking to ensure that both are abstract if either is. Note that in many
 599    * cases, there will only be one accessor (a reader or a writer).
 600    */
 601  11231 private boolean isAbstractProperty(PropertyDescriptor pd)
 602    {
 603  11231 return isExistingAbstractMethod(pd.getReadMethod())
 604    || isExistingAbstractMethod(pd.getWriteMethod());
 605    }
 606   
 607  21513 private boolean isExistingAbstractMethod(Method m)
 608    {
 609  21513 return m != null && Modifier.isAbstract(m.getModifiers());
 610    }
 611    }