Clover coverage report - Code Coverage for tapestry release 4.0-beta-6
Coverage timestamp: Wed Sep 7 2005 18:41:34 EDT
file stats: LOC: 661   Methods: 36
NCLOC: 404   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
EnhancementOperationImpl.java 98.3% 96.2% 97.2% 96.8%
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.Iterator;
 28    import java.util.List;
 29    import java.util.Map;
 30    import java.util.Set;
 31   
 32    import org.apache.commons.logging.Log;
 33    import org.apache.hivemind.ApplicationRuntimeException;
 34    import org.apache.hivemind.ClassResolver;
 35    import org.apache.hivemind.HiveMind;
 36    import org.apache.hivemind.Location;
 37    import org.apache.hivemind.service.BodyBuilder;
 38    import org.apache.hivemind.service.ClassFab;
 39    import org.apache.hivemind.service.ClassFactory;
 40    import org.apache.hivemind.service.MethodSignature;
 41    import org.apache.hivemind.util.Defense;
 42    import org.apache.hivemind.util.ToStringBuilder;
 43    import org.apache.tapestry.services.ComponentConstructor;
 44    import org.apache.tapestry.spec.IComponentSpecification;
 45    import org.apache.tapestry.util.IdAllocator;
 46    import org.apache.tapestry.util.ObjectIdentityMap;
 47   
 48    /**
 49    * Implementation of {@link org.apache.tapestry.enhance.EnhancementOperation}that knows how to
 50    * collect class changes from enhancements. The method {@link #getConstructor()} finalizes the
 51    * enhancement into a {@link org.apache.tapestry.services.ComponentConstructor}.
 52    *
 53    * @author Howard M. Lewis Ship
 54    * @since 4.0
 55    */
 56    public class EnhancementOperationImpl implements EnhancementOperation
 57    {
 58    private ClassResolver _resolver;
 59   
 60    private IComponentSpecification _specification;
 61   
 62    private Class _baseClass;
 63   
 64    private ClassFab _classFab;
 65   
 66    private final Set _claimedProperties = new HashSet();
 67   
 68    private final JavaClassMapping _javaClassMapping = new JavaClassMapping();
 69   
 70    private final List _constructorTypes = new ArrayList();
 71   
 72    private final List _constructorArguments = new ArrayList();
 73   
 74    private final ObjectIdentityMap _finalFields = new ObjectIdentityMap();
 75   
 76    /**
 77    * Set of interfaces added to the enhanced class.
 78    */
 79   
 80    private Set _addedInterfaces = new HashSet();
 81   
 82    /**
 83    * Map of {@link BodyBuilder}, keyed on {@link MethodSignature}.
 84    */
 85   
 86    private Map _incompleteMethods = new HashMap();
 87   
 88    /**
 89    * Map of property names to {@link PropertyDescriptor}.
 90    */
 91   
 92    private Map _properties = new HashMap();
 93   
 94    /**
 95    * Used to incrementally assemble the constructor for the enhanced class.
 96    */
 97   
 98    private BodyBuilder _constructorBuilder;
 99   
 100    /**
 101    * Makes sure that names created by {@link #addInjectedField(String, Object)} have unique names.
 102    */
 103   
 104    private final IdAllocator _idAllocator = new IdAllocator();
 105   
 106    /**
 107    * Map keyed on MethodSignature, value is Location. Used to track which methods have been
 108    * created, based on which location data (identified conflicts).
 109    */
 110   
 111    private final Map _methods = new HashMap();
 112   
 113    // May be null
 114   
 115    private final Log _log;
 116   
 117  615 public EnhancementOperationImpl(ClassResolver classResolver,
 118    IComponentSpecification specification, Class baseClass, ClassFactory classFactory,
 119    Log log)
 120    {
 121  615 Defense.notNull(classResolver, "classResolver");
 122  615 Defense.notNull(specification, "specification");
 123  615 Defense.notNull(baseClass, "baseClass");
 124  615 Defense.notNull(classFactory, "classFactory");
 125   
 126  615 _resolver = classResolver;
 127  615 _specification = specification;
 128  615 _baseClass = baseClass;
 129   
 130  615 introspectBaseClass();
 131   
 132  615 String name = newClassName();
 133   
 134  615 _classFab = classFactory.newClass(name, _baseClass);
 135  615 _log = log;
 136    }
 137   
 138  0 public String toString()
 139    {
 140  0 ToStringBuilder builder = new ToStringBuilder(this);
 141   
 142  0 builder.append("baseClass", _baseClass.getName());
 143  0 builder.append("claimedProperties", _claimedProperties);
 144  0 builder.append("classFab", _classFab);
 145   
 146  0 return builder.toString();
 147    }
 148   
 149    /**
 150    * We want to find the properties of the class, but in many cases, the class is abstract. Some
 151    * JDK's (Sun) will include public methods from interfaces implemented by the class in the
 152    * public declared methods for the class (which is used by the Introspector). Eclipse's built-in
 153    * compiler does not appear to (this may have to do with compiler options I've been unable to
 154    * track down). The solution is to augment the information provided directly by the Introspector
 155    * with additional information compiled by Introspecting the interfaces directly or indirectly
 156    * implemented by the class.
 157    */
 158  615 private void introspectBaseClass()
 159    {
 160  615 try
 161    {
 162  615 synchronized (HiveMind.INTROSPECTOR_MUTEX)
 163    {
 164  615 addPropertiesDeclaredInBaseClass();
 165    }
 166    }
 167    catch (IntrospectionException ex)
 168    {
 169  0 throw new ApplicationRuntimeException(EnhanceMessages.unabelToIntrospectClass(
 170    _baseClass,
 171    ex), ex);
 172    }
 173   
 174    }
 175   
 176  615 private void addPropertiesDeclaredInBaseClass() throws IntrospectionException
 177    {
 178  615 Class introspectClass = _baseClass;
 179   
 180  615 addPropertiesDeclaredInClass(introspectClass);
 181   
 182  615 List interfaceQueue = new ArrayList();
 183   
 184  615 while (introspectClass != null)
 185    {
 186  3011 addInterfacesToQueue(introspectClass, interfaceQueue);
 187   
 188  3011 introspectClass = introspectClass.getSuperclass();
 189    }
 190   
 191  615 while (!interfaceQueue.isEmpty())
 192    {
 193  6662 Class interfaceClass = (Class) interfaceQueue.remove(0);
 194   
 195  6662 addPropertiesDeclaredInClass(interfaceClass);
 196   
 197  6662 addInterfacesToQueue(interfaceClass, interfaceQueue);
 198    }
 199    }
 200   
 201  9673 private void addInterfacesToQueue(Class introspectClass, List interfaceQueue)
 202    {
 203  9673 Class[] interfaces = introspectClass.getInterfaces();
 204   
 205  9673 for (int i = 0; i < interfaces.length; i++)
 206  6662 interfaceQueue.add(interfaces[i]);
 207    }
 208   
 209  7277 private void addPropertiesDeclaredInClass(Class introspectClass) throws IntrospectionException
 210    {
 211  7277 BeanInfo bi = Introspector.getBeanInfo(introspectClass);
 212   
 213  7277 PropertyDescriptor[] pds = bi.getPropertyDescriptors();
 214   
 215  7277 for (int i = 0; i < pds.length; i++)
 216    {
 217  42740 PropertyDescriptor pd = pds[i];
 218   
 219  42740 String name = pd.getName();
 220   
 221  42740 if (!_properties.containsKey(name))
 222  16884 _properties.put(name, pd);
 223    }
 224    }
 225   
 226    /**
 227    * Alternate package private constructor used by the test suite, to bypass the defense checks
 228    * above.
 229    */
 230   
 231  1 EnhancementOperationImpl()
 232    {
 233  1 _log = null;
 234    }
 235   
 236  4058 public void claimProperty(String propertyName)
 237    {
 238  4058 Defense.notNull(propertyName, "propertyName");
 239   
 240  4058 if (_claimedProperties.contains(propertyName))
 241  2 throw new ApplicationRuntimeException(EnhanceMessages.claimedProperty(propertyName));
 242   
 243  4056 _claimedProperties.add(propertyName);
 244    }
 245   
 246  1252 public void claimReadonlyProperty(String propertyName)
 247    {
 248  1252 claimProperty(propertyName);
 249   
 250  1252 PropertyDescriptor pd = getPropertyDescriptor(propertyName);
 251   
 252  1252 if (pd != null && pd.getWriteMethod() != null)
 253  1 throw new ApplicationRuntimeException(EnhanceMessages.readonlyProperty(propertyName, pd
 254    .getWriteMethod()));
 255    }
 256   
 257  6824 public void addField(String name, Class type)
 258    {
 259  6824 _classFab.addField(name, type);
 260    }
 261   
 262  1876 public String addInjectedField(String fieldName, Class fieldType, Object value)
 263    {
 264  1876 Defense.notNull(fieldName, "fieldName");
 265  1876 Defense.notNull(fieldType, "fieldType");
 266  1876 Defense.notNull(value, "value");
 267   
 268  1876 String existing = (String) _finalFields.get(value);
 269   
 270    // See if this object has been previously added.
 271   
 272  1876 if (existing != null)
 273  0 return existing;
 274   
 275    // TODO: Should be ensure that the name is unique?
 276   
 277    // Make sure that the field has a unique name (at least, among anything added
 278    // via addFinalField().
 279   
 280  1876 String uniqueName = _idAllocator.allocateId(fieldName);
 281   
 282    // ClassFab doesn't have an option for saying the field should be final, just private.
 283    // Doesn't make a huge difference.
 284   
 285  1876 _classFab.addField(uniqueName, fieldType);
 286   
 287  1876 int parameterIndex = addConstructorParameter(fieldType, value);
 288   
 289  1876 constructorBuilder().addln("{0} = ${1};", uniqueName, Integer.toString(parameterIndex));
 290   
 291    // Remember the mapping from the value to the field name.
 292   
 293  1876 _finalFields.put(value, uniqueName);
 294   
 295  1876 return uniqueName;
 296    }
 297   
 298  92 public Class convertTypeName(String type)
 299    {
 300  92 Defense.notNull(type, "type");
 301   
 302  92 Class result = _javaClassMapping.getType(type);
 303   
 304  92 if (result == null)
 305    {
 306  22 result = _resolver.findClass(type);
 307   
 308  21 _javaClassMapping.recordType(type, result);
 309    }
 310   
 311  91 return result;
 312    }
 313   
 314  3094 public Class getPropertyType(String name)
 315    {
 316  3094 Defense.notNull(name, "name");
 317   
 318  3094 PropertyDescriptor pd = getPropertyDescriptor(name);
 319   
 320  3094 return pd == null ? null : pd.getPropertyType();
 321    }
 322   
 323  89 public void validateProperty(String name, Class expectedType)
 324    {
 325  89 Defense.notNull(name, "name");
 326  89 Defense.notNull(expectedType, "expectedType");
 327   
 328  89 PropertyDescriptor pd = getPropertyDescriptor(name);
 329   
 330  89 if (pd == null)
 331  25 return;
 332   
 333  64 Class propertyType = pd.getPropertyType();
 334   
 335  64 if (propertyType.equals(expectedType))
 336  63 return;
 337   
 338  1 throw new ApplicationRuntimeException(EnhanceMessages.propertyTypeMismatch(
 339    _baseClass,
 340    name,
 341    propertyType,
 342    expectedType));
 343    }
 344   
 345  7679 private PropertyDescriptor getPropertyDescriptor(String name)
 346    {
 347  7679 return (PropertyDescriptor) _properties.get(name);
 348    }
 349   
 350  3244 public String getAccessorMethodName(String propertyName)
 351    {
 352  3244 Defense.notNull(propertyName, "propertyName");
 353   
 354  3244 PropertyDescriptor pd = getPropertyDescriptor(propertyName);
 355   
 356  3244 if (pd != null && pd.getReadMethod() != null)
 357  3050 return pd.getReadMethod().getName();
 358   
 359  194 return EnhanceUtils.createAccessorMethodName(propertyName);
 360    }
 361   
 362  6852 public void addMethod(int modifier, MethodSignature sig, String methodBody, Location location)
 363    {
 364  6852 Defense.notNull(sig, "sig");
 365  6852 Defense.notNull(methodBody, "methodBody");
 366  6852 Defense.notNull(location, "location");
 367   
 368  6852 Location existing = (Location) _methods.get(sig);
 369  6852 if (existing != null)
 370  1 throw new ApplicationRuntimeException(EnhanceMessages.methodConflict(sig, existing),
 371    location, null);
 372   
 373  6851 _methods.put(sig, location);
 374   
 375  6851 _classFab.addMethod(modifier, sig, methodBody);
 376    }
 377   
 378  1 public Class getBaseClass()
 379    {
 380  1 return _baseClass;
 381    }
 382   
 383  815 public String getClassReference(Class clazz)
 384    {
 385  815 Defense.notNull(clazz, "clazz");
 386   
 387  815 String result = (String) _finalFields.get(clazz);
 388   
 389  815 if (result == null)
 390  624 result = addClassReference(clazz);
 391   
 392  815 return result;
 393    }
 394   
 395  624 private String addClassReference(Class clazz)
 396    {
 397  624 StringBuffer buffer = new StringBuffer("_class$");
 398   
 399  624 Class c = clazz;
 400   
 401  624 while (c.isArray())
 402    {
 403  15 buffer.append("array$");
 404  15 c = c.getComponentType();
 405    }
 406   
 407  624 buffer.append(c.getName().replace('.', '$'));
 408   
 409  624 String fieldName = buffer.toString();
 410   
 411  624 return addInjectedField(fieldName, Class.class, clazz);
 412    }
 413   
 414    /**
 415    * Adds a new constructor parameter, returning the new count. This is convienient, because the
 416    * first element added is accessed as $1, etc.
 417    */
 418   
 419  1876 private int addConstructorParameter(Class type, Object value)
 420    {
 421  1876 _constructorTypes.add(type);
 422  1876 _constructorArguments.add(value);
 423   
 424  1876 return _constructorArguments.size();
 425    }
 426   
 427  1876 private BodyBuilder constructorBuilder()
 428    {
 429  1876 if (_constructorBuilder == null)
 430    {
 431  439 _constructorBuilder = new BodyBuilder();
 432  439 _constructorBuilder.begin();
 433    }
 434   
 435  1876 return _constructorBuilder;
 436    }
 437   
 438    /**
 439    * Returns an object that can be used to construct instances of the enhanced component subclass.
 440    * This should only be called once.
 441    */
 442   
 443  594 public ComponentConstructor getConstructor()
 444    {
 445  594 try
 446    {
 447  594 finalizeEnhancedClass();
 448   
 449  594 Constructor c = findConstructor();
 450   
 451  593 Object[] params = _constructorArguments.toArray();
 452   
 453  593 return new ComponentConstructorImpl(c, params, _classFab.toString(), _specification
 454    .getLocation());
 455    }
 456    catch (Throwable t)
 457    {
 458  1 throw new ApplicationRuntimeException(EnhanceMessages.classEnhancementFailure(
 459    _baseClass,
 460    t), _classFab, null, t);
 461    }
 462    }
 463   
 464  595 void finalizeEnhancedClass()
 465    {
 466  595 finalizeIncompleteMethods();
 467   
 468  595 if (_constructorBuilder != null)
 469    {
 470  438 _constructorBuilder.end();
 471   
 472  438 Class[] types = (Class[]) _constructorTypes
 473    .toArray(new Class[_constructorTypes.size()]);
 474   
 475  438 _classFab.addConstructor(types, null, _constructorBuilder.toString());
 476    }
 477   
 478  595 if (_log != null)
 479  418 _log.debug("Creating class:\n\n" + _classFab);
 480    }
 481   
 482  595 private void finalizeIncompleteMethods()
 483    {
 484  595 Iterator i = _incompleteMethods.entrySet().iterator();
 485  595 while (i.hasNext())
 486    {
 487  738 Map.Entry e = (Map.Entry) i.next();
 488  738 MethodSignature sig = (MethodSignature) e.getKey();
 489  738 BodyBuilder builder = (BodyBuilder) e.getValue();
 490   
 491    // Each BodyBuilder is created and given a begin(), this is
 492    // the matching end()
 493   
 494  738 builder.end();
 495   
 496  738 _classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
 497    }
 498    }
 499   
 500  594 private Constructor findConstructor()
 501    {
 502  594 Class componentClass = _classFab.createClass();
 503   
 504    // The fabricated base class always has exactly one constructor
 505   
 506  593 return componentClass.getConstructors()[0];
 507    }
 508   
 509    static int _uid = 0;
 510   
 511  615 private String newClassName()
 512    {
 513  615 String baseName = _baseClass.getName();
 514  615 int dotx = baseName.lastIndexOf('.');
 515   
 516  615 return "$" + baseName.substring(dotx + 1) + "_" + _uid++;
 517    }
 518   
 519  3999 public void extendMethodImplementation(Class interfaceClass, MethodSignature methodSignature,
 520    String code)
 521    {
 522  3999 addInterfaceIfNeeded(interfaceClass);
 523   
 524  3999 BodyBuilder builder = (BodyBuilder) _incompleteMethods.get(methodSignature);
 525   
 526  3999 if (builder == null)
 527    {
 528  740 builder = createIncompleteMethod(methodSignature);
 529   
 530  740 _incompleteMethods.put(methodSignature, builder);
 531    }
 532   
 533  3999 builder.addln(code);
 534    }
 535   
 536  3999 private void addInterfaceIfNeeded(Class interfaceClass)
 537    {
 538  3999 if (implementsInterface(interfaceClass))
 539  3778 return;
 540   
 541  221 _classFab.addInterface(interfaceClass);
 542  221 _addedInterfaces.add(interfaceClass);
 543    }
 544   
 545  6093 public boolean implementsInterface(Class interfaceClass)
 546    {
 547  6093 if (interfaceClass.isAssignableFrom(_baseClass))
 548  2620 return true;
 549   
 550  3473 Iterator i = _addedInterfaces.iterator();
 551  3473 while (i.hasNext())
 552    {
 553  1561 Class addedInterface = (Class) i.next();
 554   
 555  1561 if (interfaceClass.isAssignableFrom(addedInterface))
 556  1252 return true;
 557    }
 558   
 559  2221 return false;
 560    }
 561   
 562  740 private BodyBuilder createIncompleteMethod(MethodSignature sig)
 563    {
 564  740 BodyBuilder result = new BodyBuilder();
 565   
 566    // Matched inside finalizeIncompleteMethods()
 567   
 568  740 result.begin();
 569   
 570  740 if (existingImplementation(sig))
 571  505 result.addln("super.{0}($$);", sig.getName());
 572   
 573  740 return result;
 574    }
 575   
 576    /**
 577    * Returns true if the base class implements the provided method as either a public or a
 578    * protected method.
 579    */
 580   
 581  740 private boolean existingImplementation(MethodSignature sig)
 582    {
 583  740 Method m = findMethod(sig);
 584   
 585  740 return m != null && !Modifier.isAbstract(m.getModifiers());
 586    }
 587   
 588    /**
 589    * Finds a public or protected method in the base class.
 590    */
 591  740 private Method findMethod(MethodSignature sig)
 592    {
 593    // Finding a public method is easy:
 594   
 595  740 try
 596    {
 597  740 return _baseClass.getMethod(sig.getName(), sig.getParameterTypes());
 598   
 599    }
 600    catch (NoSuchMethodException ex)
 601    {
 602    // Good; no super-implementation to invoke.
 603    }
 604   
 605  503 Class c = _baseClass;
 606   
 607  503 while (c != Object.class)
 608    {
 609  1465 try
 610    {
 611  1465 return c.getDeclaredMethod(sig.getName(), sig.getParameterTypes());
 612    }
 613    catch (NoSuchMethodException ex)
 614    {
 615    // Ok, continue loop up to next base class.
 616    }
 617   
 618  1183 c = c.getSuperclass();
 619    }
 620   
 621  221 return null;
 622    }
 623   
 624  574 public List findUnclaimedAbstractProperties()
 625    {
 626  574 List result = new ArrayList();
 627   
 628  574 Iterator i = _properties.values().iterator();
 629   
 630  574 while (i.hasNext())
 631    {
 632  15880 PropertyDescriptor pd = (PropertyDescriptor) i.next();
 633   
 634  15880 String name = pd.getName();
 635   
 636  15880 if (_claimedProperties.contains(name))
 637  2575 continue;
 638   
 639  13305 if (isAbstractProperty(pd))
 640  1286 result.add(name);
 641    }
 642   
 643  574 return result;
 644    }
 645   
 646    /**
 647    * A property is abstract if either its read method or it write method is abstract. We could do
 648    * some additional checking to ensure that both are abstract if either is. Note that in many
 649    * cases, there will only be one accessor (a reader or a writer).
 650    */
 651  13305 private boolean isAbstractProperty(PropertyDescriptor pd)
 652    {
 653  13305 return isExistingAbstractMethod(pd.getReadMethod())
 654    || isExistingAbstractMethod(pd.getWriteMethod());
 655    }
 656   
 657  25388 private boolean isExistingAbstractMethod(Method m)
 658    {
 659  25388 return m != null && Modifier.isAbstract(m.getModifiers());
 660    }
 661    }