Clover coverage report - Code Coverage for tapestry release 4.0-alpha-3
Coverage timestamp: Mon May 16 2005 09:05:49 EDT
file stats: LOC: 611   Methods: 35
NCLOC: 369   Classes: 1
30 day Evaluation Version distributed via the Maven Jar Repository. Clover is not free. You have 30 days to evaluate it. Please visit http://www.thecortex.net/clover to obtain a licensed version of Clover
 
 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   
  * provide a {@link org.apache.tapestry.services.ComponentConstructor}from any enhancements.
 49   
  * 
 50   
  * @author Howard M. Lewis Ship
 51   
  * @since 4.0
 52   
  */
 53   
 public class EnhancementOperationImpl implements EnhancementOperation
 54   
 {
 55   
     private ClassResolver _resolver;
 56   
 
 57   
     private IComponentSpecification _specification;
 58   
 
 59   
     private Class _baseClass;
 60   
 
 61   
     private ClassFab _classFab;
 62   
 
 63   
     private final Set _claimedProperties = new HashSet();
 64   
 
 65   
     private final JavaClassMapping _javaClassMapping = new JavaClassMapping();
 66   
 
 67   
     private final List _constructorTypes = new ArrayList();
 68   
 
 69   
     private final List _constructorArguments = new ArrayList();
 70   
 
 71   
     private final Map _finalFields = new IdentityHashMap();
 72   
 
 73   
     /**
 74   
      * Set of interfaces added to the enhanced class.
 75   
      */
 76   
 
 77   
     private Set _addedInterfaces = new HashSet();
 78   
 
 79   
     /**
 80   
      * Map of {@link BodyBuilder}, keyed on {@link MethodSignature}.
 81   
      */
 82   
 
 83   
     private Map _incompleteMethods = new HashMap();
 84   
 
 85   
     /**
 86   
      * Map of property names to {@link PropertyDescriptor}.
 87   
      */
 88   
 
 89   
     private Map _properties = new HashMap();
 90   
 
 91   
     /**
 92   
      * Used to incrementally assemble the constructor for the enhanced class.
 93   
      */
 94   
 
 95   
     private BodyBuilder _constructorBuilder;
 96   
 
 97   
     /**
 98   
      * Makes sure that names created by {@link #addInjectedField(String, Object)} have unique names.
 99   
      */
 100   
 
 101   
     private final IdAllocator _idAllocator = new IdAllocator();
 102   
 
 103  561
     public EnhancementOperationImpl(ClassResolver classResolver,
 104   
             IComponentSpecification specification, Class baseClass, ClassFactory classFactory)
 105   
     {
 106  561
         Defense.notNull(classResolver, "classResolver");
 107  561
         Defense.notNull(specification, "specification");
 108  561
         Defense.notNull(baseClass, "baseClass");
 109  561
         Defense.notNull(classFactory, "classFactory");
 110   
 
 111  561
         _resolver = classResolver;
 112  561
         _specification = specification;
 113  561
         _baseClass = baseClass;
 114   
 
 115  561
         introspectBaseClass();
 116   
 
 117  561
         String name = newClassName();
 118   
 
 119  561
         _classFab = classFactory.newClass(name, _baseClass);
 120   
     }
 121   
 
 122  0
     public String toString()
 123   
     {
 124  0
         ToStringBuilder builder = new ToStringBuilder(this);
 125   
 
 126  0
         builder.append("baseClass", _baseClass.getName());
 127  0
         builder.append("claimedProperties", _claimedProperties);
 128  0
         builder.append("classFab", _classFab);
 129   
 
 130  0
         return builder.toString();
 131   
     }
 132   
 
 133   
     /**
 134   
      * We want to find the properties of the class, but in many cases, the class is abstract. Some
 135   
      * JDK's (Sun) will include public methods from interfaces implemented by the class in the
 136   
      * public declared methods for the class (which is used by the Introspector). Eclipse's built-in
 137   
      * compiler does not appear to (this may have to do with compiler options I've been unable to
 138   
      * track down). The solution is to augment the information provided directly by the Introspector
 139   
      * with additional information compiled by Introspecting the interfaces directly or indirectly
 140   
      * implemented by the class.
 141   
      */
 142  561
     private void introspectBaseClass()
 143   
     {
 144  561
         try
 145   
         {
 146  561
             synchronized (HiveMind.INTROSPECTOR_MUTEX)
 147   
             {
 148  561
                 addPropertiesDeclaredInBaseClass();
 149   
             }
 150   
         }
 151   
         catch (IntrospectionException ex)
 152   
         {
 153  0
             throw new ApplicationRuntimeException(EnhanceMessages.unabelToIntrospectClass(
 154   
                     _baseClass,
 155   
                     ex), ex);
 156   
         }
 157   
 
 158   
     }
 159   
 
 160  561
     private void addPropertiesDeclaredInBaseClass() throws IntrospectionException
 161   
     {
 162  561
         Class introspectClass = _baseClass;
 163   
 
 164  561
         addPropertiesDeclaredInClass(introspectClass);
 165   
 
 166  561
         List interfaceQueue = new ArrayList();
 167   
 
 168  561
         while (introspectClass != null)
 169   
         {
 170  2745
             addInterfacesToQueue(introspectClass, interfaceQueue);
 171   
 
 172  2745
             introspectClass = introspectClass.getSuperclass();
 173   
         }
 174   
 
 175  561
         while (!interfaceQueue.isEmpty())
 176   
         {
 177  5846
             Class interfaceClass = (Class) interfaceQueue.remove(0);
 178   
 
 179  5846
             addPropertiesDeclaredInClass(interfaceClass);
 180   
 
 181  5846
             addInterfacesToQueue(interfaceClass, interfaceQueue);
 182   
         }
 183   
     }
 184   
 
 185  8591
     private void addInterfacesToQueue(Class introspectClass, List interfaceQueue)
 186   
     {
 187  8591
         Class[] interfaces = introspectClass.getInterfaces();
 188   
 
 189  8591
         for (int i = 0; i < interfaces.length; i++)
 190  5846
             interfaceQueue.add(interfaces[i]);
 191   
     }
 192   
 
 193  6407
     private void addPropertiesDeclaredInClass(Class introspectClass) throws IntrospectionException
 194   
     {
 195  6407
         BeanInfo bi = Introspector.getBeanInfo(introspectClass);
 196   
 
 197  6407
         PropertyDescriptor[] pds = bi.getPropertyDescriptors();
 198   
 
 199  6407
         for (int i = 0; i < pds.length; i++)
 200   
         {
 201  34020
             PropertyDescriptor pd = pds[i];
 202   
 
 203  34020
             String name = pd.getName();
 204   
 
 205  34020
             if (!_properties.containsKey(name))
 206  13752
                 _properties.put(name, pd);
 207   
         }
 208   
     }
 209   
 
 210   
     /**
 211   
      * Alternate package private constructor used by the test suite, to bypass the defense checks
 212   
      * above.
 213   
      */
 214   
 
 215  1
     EnhancementOperationImpl()
 216   
     {
 217   
     }
 218   
 
 219  3110
     public void claimProperty(String propertyName)
 220   
     {
 221  3110
         Defense.notNull(propertyName, "propertyName");
 222   
 
 223  3110
         if (_claimedProperties.contains(propertyName))
 224  1
             throw new ApplicationRuntimeException(EnhanceMessages.claimedProperty(propertyName));
 225   
 
 226  3109
         _claimedProperties.add(propertyName);
 227   
     }
 228   
 
 229  5014
     public void addField(String name, Class type)
 230   
     {
 231  5014
         _classFab.addField(name, type);
 232   
     }
 233   
 
 234  2018
     public String addInjectedField(String fieldName, Object value)
 235   
     {
 236  2018
         Defense.notNull(fieldName, "fieldName");
 237  2018
         Defense.notNull(value, "value");
 238   
 
 239  2018
         String existing = (String) _finalFields.get(value);
 240   
 
 241   
         // See if this object has been previously added.
 242   
 
 243  2018
         if (existing != null)
 244  0
             return existing;
 245   
 
 246   
         // TODO: Should be ensure that the name is unique?
 247   
         // Add a new field using the object's actual type.
 248   
 
 249  2018
         Class type = value.getClass();
 250   
 
 251   
         // Make sure that the field has a unique name (at least, among anything added
 252   
         // via addFinalField().
 253   
 
 254  2018
         String uniqueName = _idAllocator.allocateId(fieldName);
 255   
 
 256   
         // ClassFab doesn't have an option for saying the field should be final, just private.
 257   
         // Doesn't make a huge difference.
 258   
 
 259  2018
         _classFab.addField(uniqueName, type);
 260   
 
 261  2018
         int parameterIndex = addConstructorParameter(type, value);
 262   
 
 263  2018
         constructorBuilder().addln("{0} = ${1};", uniqueName, Integer.toString(parameterIndex));
 264   
 
 265   
         // Remember the mapping from the value to the field name.
 266   
 
 267  2018
         _finalFields.put(value, uniqueName);
 268   
 
 269  2018
         return uniqueName;
 270   
     }
 271   
 
 272  106
     public Class convertTypeName(String type)
 273   
     {
 274  106
         Defense.notNull(type, "type");
 275   
 
 276  106
         Class result = _javaClassMapping.getType(type);
 277   
 
 278  106
         if (result == null)
 279   
         {
 280  26
             result = _resolver.findClass(type);
 281   
 
 282  25
             _javaClassMapping.recordType(type, result);
 283   
         }
 284   
 
 285  105
         return result;
 286   
     }
 287   
 
 288  2053
     public Class getPropertyType(String name)
 289   
     {
 290  2053
         Defense.notNull(name, "name");
 291   
 
 292  2053
         PropertyDescriptor pd = getPropertyDescriptor(name);
 293   
 
 294  2053
         return pd == null ? null : pd.getPropertyType();
 295   
     }
 296   
 
 297  103
     public void validateProperty(String name, Class expectedType)
 298   
     {
 299  103
         Defense.notNull(name, "name");
 300  103
         Defense.notNull(expectedType, "expectedType");
 301   
 
 302  103
         PropertyDescriptor pd = getPropertyDescriptor(name);
 303   
 
 304  103
         if (pd == null)
 305  26
             return;
 306   
 
 307  77
         Class propertyType = pd.getPropertyType();
 308   
 
 309  77
         if (propertyType.equals(expectedType))
 310  76
             return;
 311   
 
 312  1
         throw new ApplicationRuntimeException(EnhanceMessages.propertyTypeMismatch(
 313   
                 _baseClass,
 314   
                 name,
 315   
                 propertyType,
 316   
                 expectedType));
 317   
     }
 318   
 
 319  4408
     private PropertyDescriptor getPropertyDescriptor(String name)
 320   
     {
 321  4408
         return (PropertyDescriptor) _properties.get(name);
 322   
     }
 323   
 
 324  2252
     public String getAccessorMethodName(String propertyName)
 325   
     {
 326  2252
         Defense.notNull(propertyName, "propertyName");
 327   
 
 328  2252
         PropertyDescriptor pd = getPropertyDescriptor(propertyName);
 329   
 
 330  2252
         if (pd != null && pd.getReadMethod() != null)
 331  2056
             return pd.getReadMethod().getName();
 332   
 
 333  196
         return EnhanceUtils.createAccessorMethodName(propertyName);
 334   
     }
 335   
 
 336  4873
     public void addMethod(int modifier, MethodSignature sig, String methodBody)
 337   
     {
 338  4873
         _classFab.addMethod(modifier, sig, methodBody);
 339   
     }
 340   
 
 341  1
     public Class getBaseClass()
 342   
     {
 343  1
         return _baseClass;
 344   
     }
 345   
 
 346  831
     public String getClassReference(Class clazz)
 347   
     {
 348  831
         Defense.notNull(clazz, "clazz");
 349   
 
 350  831
         String result = (String) _finalFields.get(clazz);
 351   
 
 352  831
         if (result == null)
 353  676
             result = addClassReference(clazz);
 354   
 
 355  831
         return result;
 356   
     }
 357   
 
 358  676
     private String addClassReference(Class clazz)
 359   
     {
 360  676
         StringBuffer buffer = new StringBuffer("_class$");
 361   
 
 362  676
         Class c = clazz;
 363   
 
 364  676
         while (c.isArray())
 365   
         {
 366  16
             buffer.append("array$");
 367  16
             c = c.getComponentType();
 368   
         }
 369   
 
 370  676
         buffer.append(c.getName().replace('.', '$'));
 371   
 
 372  676
         String fieldName = buffer.toString();
 373   
 
 374  676
         return addInjectedField(fieldName, clazz);
 375   
     }
 376   
 
 377   
     /**
 378   
      * Adds a new constructor parameter, returning the new count. This is convienient, because the
 379   
      * first element added is accessed as $1, etc.
 380   
      */
 381   
 
 382  2018
     private int addConstructorParameter(Class type, Object value)
 383   
     {
 384  2018
         _constructorTypes.add(type);
 385  2018
         _constructorArguments.add(value);
 386   
 
 387  2018
         return _constructorArguments.size();
 388   
     }
 389   
 
 390  2018
     private BodyBuilder constructorBuilder()
 391   
     {
 392  2018
         if (_constructorBuilder == null)
 393   
         {
 394  481
             _constructorBuilder = new BodyBuilder();
 395  481
             _constructorBuilder.begin();
 396   
         }
 397   
 
 398  2018
         return _constructorBuilder;
 399   
     }
 400   
 
 401   
     /**
 402   
      * Returns an object that can be used to construct instances of the enhanced component subclass.
 403   
      * This should only be called once.
 404   
      */
 405   
 
 406  544
     public ComponentConstructor getConstructor()
 407   
     {
 408  544
         finalizeEnhancedClass();
 409   
 
 410  544
         Constructor c = findConstructor();
 411   
 
 412  544
         Object[] params = _constructorArguments.toArray();
 413   
 
 414  544
         return new ComponentConstructorImpl(c, params, _specification.getLocation());
 415   
     }
 416   
 
 417  545
     void finalizeEnhancedClass()
 418   
     {
 419  545
         finalizeIncompleteMethods();
 420   
 
 421  545
         if (_constructorBuilder != null)
 422   
         {
 423  480
             _constructorBuilder.end();
 424   
 
 425  480
             Class[] types = (Class[]) _constructorTypes
 426   
                     .toArray(new Class[_constructorTypes.size()]);
 427   
 
 428  480
             _classFab.addConstructor(types, null, _constructorBuilder.toString());
 429   
         }
 430   
     }
 431   
 
 432  545
     private void finalizeIncompleteMethods()
 433   
     {
 434  545
         Iterator i = _incompleteMethods.entrySet().iterator();
 435  545
         while (i.hasNext())
 436   
         {
 437  593
             Map.Entry e = (Map.Entry) i.next();
 438  593
             MethodSignature sig = (MethodSignature) e.getKey();
 439  593
             BodyBuilder builder = (BodyBuilder) e.getValue();
 440   
 
 441   
             // Each BodyBuilder is created and given a begin(), this is
 442   
             // the matching end()
 443   
 
 444  593
             builder.end();
 445   
 
 446  593
             _classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
 447   
         }
 448   
     }
 449   
 
 450  544
     private Constructor findConstructor()
 451   
     {
 452  544
         Class componentClass = _classFab.createClass();
 453   
 
 454   
         // The fabricated base class always has exactly one constructor
 455   
 
 456  544
         return componentClass.getConstructors()[0];
 457   
     }
 458   
 
 459   
     static int _uid = 0;
 460   
 
 461  561
     private String newClassName()
 462   
     {
 463  561
         String baseName = _baseClass.getName();
 464  561
         int dotx = baseName.lastIndexOf('.');
 465   
 
 466  561
         return "$" + baseName.substring(dotx + 1) + "_" + _uid++;
 467   
     }
 468   
 
 469  2235
     public void extendMethodImplementation(Class interfaceClass, MethodSignature methodSignature,
 470   
             String code)
 471   
     {
 472  2235
         addInterfaceIfNeeded(interfaceClass);
 473   
 
 474  2235
         BodyBuilder builder = (BodyBuilder) _incompleteMethods.get(methodSignature);
 475   
 
 476  2235
         if (builder == null)
 477   
         {
 478  595
             builder = createIncompleteMethod(methodSignature);
 479   
 
 480  595
             _incompleteMethods.put(methodSignature, builder);
 481   
         }
 482   
 
 483  2235
         builder.addln(code);
 484   
     }
 485   
 
 486  2235
     private void addInterfaceIfNeeded(Class interfaceClass)
 487   
     {
 488  2235
         if (implementsInterface(interfaceClass))
 489  2104
             return;
 490   
 
 491  131
         _classFab.addInterface(interfaceClass);
 492  131
         _addedInterfaces.add(interfaceClass);
 493   
     }
 494   
 
 495  4549
     public boolean implementsInterface(Class interfaceClass)
 496   
     {
 497  4549
         if (interfaceClass.isAssignableFrom(_baseClass))
 498  1744
             return true;
 499   
 
 500  2805
         Iterator i = _addedInterfaces.iterator();
 501  2805
         while (i.hasNext())
 502   
         {
 503  634
             Class addedInterface = (Class) i.next();
 504   
 
 505  634
             if (interfaceClass.isAssignableFrom(addedInterface))
 506  429
                 return true;
 507   
         }
 508   
 
 509  2376
         return false;
 510   
     }
 511   
 
 512  595
     private BodyBuilder createIncompleteMethod(MethodSignature sig)
 513   
     {
 514  595
         BodyBuilder result = new BodyBuilder();
 515   
 
 516   
         // Matched inside finalizeIncompleteMethods()
 517   
 
 518  595
         result.begin();
 519   
 
 520  595
         if (existingImplementation(sig))
 521  449
             result.addln("super.{0}($$);", sig.getName());
 522   
 
 523  595
         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  595
     private boolean existingImplementation(MethodSignature sig)
 532   
     {
 533  595
         Method m = findMethod(sig);
 534   
 
 535  595
         return m != null && !Modifier.isAbstract(m.getModifiers());
 536   
     }
 537   
 
 538   
     /**
 539   
      * Finds a public or protected method in the base class.
 540   
      */
 541  595
     private Method findMethod(MethodSignature sig)
 542   
     {
 543   
         // Finding a public method is easy:
 544   
 
 545  595
         try
 546   
         {
 547  595
             return _baseClass.getMethod(sig.getName(), sig.getParameterTypes());
 548   
 
 549   
         }
 550   
         catch (NoSuchMethodException ex)
 551   
         {
 552   
             // Good; no super-implementation to invoke.
 553   
         }
 554   
 
 555  446
         Class c = _baseClass;
 556   
 
 557  446
         while (c != Object.class)
 558   
         {
 559  1188
             try
 560   
             {
 561  1188
                 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  873
             c = c.getSuperclass();
 569   
         }
 570   
 
 571  131
         return null;
 572   
     }
 573   
 
 574  527
     public List findUnclaimedAbstractProperties()
 575   
     {
 576  527
         List result = new ArrayList();
 577   
 
 578  527
         Iterator i = _properties.values().iterator();
 579   
 
 580  527
         while (i.hasNext())
 581   
         {
 582  12987
             PropertyDescriptor pd = (PropertyDescriptor) i.next();
 583   
 
 584  12987
             String name = pd.getName();
 585   
 
 586  12987
             if (_claimedProperties.contains(name))
 587  2514
                 continue;
 588   
 
 589  10473
             if (isAbstractProperty(pd))
 590  418
                 result.add(name);
 591   
         }
 592   
 
 593  527
         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  10473
     private boolean isAbstractProperty(PropertyDescriptor pd)
 602   
     {
 603  10473
         return isExistingAbstractMethod(pd.getReadMethod())
 604   
                 || isExistingAbstractMethod(pd.getWriteMethod());
 605   
     }
 606   
 
 607  20578
     private boolean isExistingAbstractMethod(Method m)
 608   
     {
 609  20578
         return m != null && Modifier.isAbstract(m.getModifiers());
 610   
     }
 611   
 }