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