Clover coverage report - Code Coverage for tapestry release 4.0-beta-2
Coverage timestamp: Sat Jul 9 2005 22:02:17 EDT
file stats: LOC: 620   Methods: 35
NCLOC: 379   Classes: 1
30 day Evaluation License registered to hlship@comcast.net Your 30 day evaluation period has expired. Please visit http://www.cenqua.com to obtain a licensed version of Clover
 
 Source file Conditionals Statements Methods TOTAL
EnhancementOperationImpl.java 98.1% 95.9% 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  587 public EnhancementOperationImpl(ClassResolver classResolver,
 105    IComponentSpecification specification, Class baseClass, ClassFactory classFactory)
 106    {
 107  587 Defense.notNull(classResolver, "classResolver");
 108  587 Defense.notNull(specification, "specification");
 109  587 Defense.notNull(baseClass, "baseClass");
 110  587 Defense.notNull(classFactory, "classFactory");
 111   
 112  587 _resolver = classResolver;
 113  587 _specification = specification;
 114  587 _baseClass = baseClass;
 115   
 116  587 introspectBaseClass();
 117   
 118  587 String name = newClassName();
 119   
 120  587 _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  587 private void introspectBaseClass()
 144    {
 145  587 try
 146    {
 147  587 synchronized (HiveMind.INTROSPECTOR_MUTEX)
 148    {
 149  587 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  587 private void addPropertiesDeclaredInBaseClass() throws IntrospectionException
 162    {
 163  587 Class introspectClass = _baseClass;
 164   
 165  587 addPropertiesDeclaredInClass(introspectClass);
 166   
 167  587 List interfaceQueue = new ArrayList();
 168   
 169  587 while (introspectClass != null)
 170    {
 171  2900 addInterfacesToQueue(introspectClass, interfaceQueue);
 172   
 173  2900 introspectClass = introspectClass.getSuperclass();
 174    }
 175   
 176  587 while (!interfaceQueue.isEmpty())
 177    {
 178  6305 Class interfaceClass = (Class) interfaceQueue.remove(0);
 179   
 180  6305 addPropertiesDeclaredInClass(interfaceClass);
 181   
 182  6305 addInterfacesToQueue(interfaceClass, interfaceQueue);
 183    }
 184    }
 185   
 186  9205 private void addInterfacesToQueue(Class introspectClass, List interfaceQueue)
 187    {
 188  9205 Class[] interfaces = introspectClass.getInterfaces();
 189   
 190  9205 for (int i = 0; i < interfaces.length; i++)
 191  6305 interfaceQueue.add(interfaces[i]);
 192    }
 193   
 194  6892 private void addPropertiesDeclaredInClass(Class introspectClass) throws IntrospectionException
 195    {
 196  6892 BeanInfo bi = Introspector.getBeanInfo(introspectClass);
 197   
 198  6892 PropertyDescriptor[] pds = bi.getPropertyDescriptors();
 199   
 200  6892 for (int i = 0; i < pds.length; i++)
 201    {
 202  36937 PropertyDescriptor pd = pds[i];
 203   
 204  36937 String name = pd.getName();
 205   
 206  36937 if (!_properties.containsKey(name))
 207  14792 _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  3791 public void claimProperty(String propertyName)
 221    {
 222  3791 Defense.notNull(propertyName, "propertyName");
 223   
 224  3791 if (_claimedProperties.contains(propertyName))
 225  1 throw new ApplicationRuntimeException(EnhanceMessages.claimedProperty(propertyName));
 226   
 227  3790 _claimedProperties.add(propertyName);
 228    }
 229   
 230  6343 public void addField(String name, Class type)
 231    {
 232  6343 _classFab.addField(name, type);
 233    }
 234   
 235  1864 public String addInjectedField(String fieldName, Class fieldType, Object value)
 236    {
 237  1864 Defense.notNull(fieldName, "fieldName");
 238  1864 Defense.notNull(fieldType, "fieldType");
 239  1864 Defense.notNull(value, "value");
 240   
 241  1864 String existing = (String) _finalFields.get(value);
 242   
 243    // See if this object has been previously added.
 244   
 245  1864 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  1864 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  1864 _classFab.addField(uniqueName, fieldType);
 259   
 260  1864 int parameterIndex = addConstructorParameter(fieldType, value);
 261   
 262  1864 constructorBuilder().addln("{0} = ${1};", uniqueName, Integer.toString(parameterIndex));
 263   
 264    // Remember the mapping from the value to the field name.
 265   
 266  1864 _finalFields.put(value, uniqueName);
 267   
 268  1864 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  2836 public Class getPropertyType(String name)
 288    {
 289  2836 Defense.notNull(name, "name");
 290   
 291  2836 PropertyDescriptor pd = getPropertyDescriptor(name);
 292   
 293  2836 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  5909 private PropertyDescriptor getPropertyDescriptor(String name)
 319    {
 320  5909 return (PropertyDescriptor) _properties.get(name);
 321    }
 322   
 323  2984 public String getAccessorMethodName(String propertyName)
 324    {
 325  2984 Defense.notNull(propertyName, "propertyName");
 326   
 327  2984 PropertyDescriptor pd = getPropertyDescriptor(propertyName);
 328   
 329  2984 if (pd != null && pd.getReadMethod() != null)
 330  2797 return pd.getReadMethod().getName();
 331   
 332  187 return EnhanceUtils.createAccessorMethodName(propertyName);
 333    }
 334   
 335  6330 public void addMethod(int modifier, MethodSignature sig, String methodBody)
 336    {
 337  6330 _classFab.addMethod(modifier, sig, methodBody);
 338    }
 339   
 340  1 public Class getBaseClass()
 341    {
 342  1 return _baseClass;
 343    }
 344   
 345  802 public String getClassReference(Class clazz)
 346    {
 347  802 Defense.notNull(clazz, "clazz");
 348   
 349  802 String result = (String) _finalFields.get(clazz);
 350   
 351  802 if (result == null)
 352  619 result = addClassReference(clazz);
 353   
 354  802 return result;
 355    }
 356   
 357  619 private String addClassReference(Class clazz)
 358    {
 359  619 StringBuffer buffer = new StringBuffer("_class$");
 360   
 361  619 Class c = clazz;
 362   
 363  619 while (c.isArray())
 364    {
 365  15 buffer.append("array$");
 366  15 c = c.getComponentType();
 367    }
 368   
 369  619 buffer.append(c.getName().replace('.', '$'));
 370   
 371  619 String fieldName = buffer.toString();
 372   
 373  619 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  1864 private int addConstructorParameter(Class type, Object value)
 382    {
 383  1864 _constructorTypes.add(type);
 384  1864 _constructorArguments.add(value);
 385   
 386  1864 return _constructorArguments.size();
 387    }
 388   
 389  1864 private BodyBuilder constructorBuilder()
 390    {
 391  1864 if (_constructorBuilder == null)
 392    {
 393  437 _constructorBuilder = new BodyBuilder();
 394  437 _constructorBuilder.begin();
 395    }
 396   
 397  1864 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  570 public ComponentConstructor getConstructor()
 406    {
 407  570 try
 408    {
 409  570 finalizeEnhancedClass();
 410   
 411  570 Constructor c = findConstructor();
 412   
 413  569 Object[] params = _constructorArguments.toArray();
 414   
 415  569 return new ComponentConstructorImpl(c, params, _classFab.toString(), _specification
 416    .getLocation());
 417    }
 418    catch (Throwable t)
 419    {
 420  1 throw new ApplicationRuntimeException(EnhanceMessages.classEnhancementFailure(
 421    _baseClass,
 422    t), _classFab, null, t);
 423    }
 424    }
 425   
 426  571 void finalizeEnhancedClass()
 427    {
 428  571 finalizeIncompleteMethods();
 429   
 430  571 if (_constructorBuilder != null)
 431    {
 432  436 _constructorBuilder.end();
 433   
 434  436 Class[] types = (Class[]) _constructorTypes
 435    .toArray(new Class[_constructorTypes.size()]);
 436   
 437  436 _classFab.addConstructor(types, null, _constructorBuilder.toString());
 438    }
 439    }
 440   
 441  571 private void finalizeIncompleteMethods()
 442    {
 443  571 Iterator i = _incompleteMethods.entrySet().iterator();
 444  571 while (i.hasNext())
 445    {
 446  696 Map.Entry e = (Map.Entry) i.next();
 447  696 MethodSignature sig = (MethodSignature) e.getKey();
 448  696 BodyBuilder builder = (BodyBuilder) e.getValue();
 449   
 450    // Each BodyBuilder is created and given a begin(), this is
 451    // the matching end()
 452   
 453  696 builder.end();
 454   
 455  696 _classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
 456    }
 457    }
 458   
 459  570 private Constructor findConstructor()
 460    {
 461  570 Class componentClass = _classFab.createClass();
 462   
 463    // The fabricated base class always has exactly one constructor
 464   
 465  569 return componentClass.getConstructors()[0];
 466    }
 467   
 468    static int _uid = 0;
 469   
 470  587 private String newClassName()
 471    {
 472  587 String baseName = _baseClass.getName();
 473  587 int dotx = baseName.lastIndexOf('.');
 474   
 475  587 return "$" + baseName.substring(dotx + 1) + "_" + _uid++;
 476    }
 477   
 478  3558 public void extendMethodImplementation(Class interfaceClass, MethodSignature methodSignature,
 479    String code)
 480    {
 481  3558 addInterfaceIfNeeded(interfaceClass);
 482   
 483  3558 BodyBuilder builder = (BodyBuilder) _incompleteMethods.get(methodSignature);
 484   
 485  3558 if (builder == null)
 486    {
 487  698 builder = createIncompleteMethod(methodSignature);
 488   
 489  698 _incompleteMethods.put(methodSignature, builder);
 490    }
 491   
 492  3558 builder.addln(code);
 493    }
 494   
 495  3558 private void addInterfaceIfNeeded(Class interfaceClass)
 496    {
 497  3558 if (implementsInterface(interfaceClass))
 498  3358 return;
 499   
 500  200 _classFab.addInterface(interfaceClass);
 501  200 _addedInterfaces.add(interfaceClass);
 502    }
 503   
 504  5652 public boolean implementsInterface(Class interfaceClass)
 505    {
 506  5652 if (interfaceClass.isAssignableFrom(_baseClass))
 507  2386 return true;
 508   
 509  3266 Iterator i = _addedInterfaces.iterator();
 510  3266 while (i.hasNext())
 511    {
 512  1290 Class addedInterface = (Class) i.next();
 513   
 514  1290 if (interfaceClass.isAssignableFrom(addedInterface))
 515  1049 return true;
 516    }
 517   
 518  2217 return false;
 519    }
 520   
 521  698 private BodyBuilder createIncompleteMethod(MethodSignature sig)
 522    {
 523  698 BodyBuilder result = new BodyBuilder();
 524   
 525    // Matched inside finalizeIncompleteMethods()
 526   
 527  698 result.begin();
 528   
 529  698 if (existingImplementation(sig))
 530  484 result.addln("super.{0}($$);", sig.getName());
 531   
 532  698 return result;
 533    }
 534   
 535    /**
 536    * Returns true if the base class implements the provided method as either a public or a
 537    * protected method.
 538    */
 539   
 540  698 private boolean existingImplementation(MethodSignature sig)
 541    {
 542  698 Method m = findMethod(sig);
 543   
 544  698 return m != null && !Modifier.isAbstract(m.getModifiers());
 545    }
 546   
 547    /**
 548    * Finds a public or protected method in the base class.
 549    */
 550  698 private Method findMethod(MethodSignature sig)
 551    {
 552    // Finding a public method is easy:
 553   
 554  698 try
 555    {
 556  698 return _baseClass.getMethod(sig.getName(), sig.getParameterTypes());
 557   
 558    }
 559    catch (NoSuchMethodException ex)
 560    {
 561    // Good; no super-implementation to invoke.
 562    }
 563   
 564  482 Class c = _baseClass;
 565   
 566  482 while (c != Object.class)
 567    {
 568  1407 try
 569    {
 570  1407 return c.getDeclaredMethod(sig.getName(), sig.getParameterTypes());
 571    }
 572    catch (NoSuchMethodException ex)
 573    {
 574    // Ok, continue loop up to next base class.
 575    }
 576   
 577  1125 c = c.getSuperclass();
 578    }
 579   
 580  200 return null;
 581    }
 582   
 583  552 public List findUnclaimedAbstractProperties()
 584    {
 585  552 List result = new ArrayList();
 586   
 587  552 Iterator i = _properties.values().iterator();
 588   
 589  552 while (i.hasNext())
 590    {
 591  14002 PropertyDescriptor pd = (PropertyDescriptor) i.next();
 592   
 593  14002 String name = pd.getName();
 594   
 595  14002 if (_claimedProperties.contains(name))
 596  2521 continue;
 597   
 598  11481 if (isAbstractProperty(pd))
 599  1082 result.add(name);
 600    }
 601   
 602  552 return result;
 603    }
 604   
 605    /**
 606    * A property is abstract if either its read method or it write method is abstract. We could do
 607    * some additional checking to ensure that both are abstract if either is. Note that in many
 608    * cases, there will only be one accessor (a reader or a writer).
 609    */
 610  11481 private boolean isAbstractProperty(PropertyDescriptor pd)
 611    {
 612  11481 return isExistingAbstractMethod(pd.getReadMethod())
 613    || isExistingAbstractMethod(pd.getWriteMethod());
 614    }
 615   
 616  21930 private boolean isExistingAbstractMethod(Method m)
 617    {
 618  21930 return m != null && Modifier.isAbstract(m.getModifiers());
 619    }
 620    }