Clover coverage report - Code Coverage for tapestry release 3.1-alpha-1
Coverage timestamp: Mon Feb 21 2005 09:16:14 EST
file stats: LOC: 532   Methods: 32
NCLOC: 330   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% 95.9% 96.9% 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.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 3.1
 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  597
     public EnhancementOperationImpl(ClassResolver classResolver,
 100   
             IComponentSpecification specification, Class baseClass, ClassFactory classFactory)
 101   
     {
 102  597
         Defense.notNull(classResolver, "classResolver");
 103  597
         Defense.notNull(specification, "specification");
 104  597
         Defense.notNull(baseClass, "baseClass");
 105  597
         Defense.notNull(classFactory, "classFactory");
 106   
 
 107  597
         _resolver = classResolver;
 108  597
         _specification = specification;
 109  597
         _baseClass = baseClass;
 110   
 
 111  597
         introspectBaseClass();
 112   
 
 113  597
         String name = newClassName();
 114   
 
 115  597
         _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  597
     private void introspectBaseClass()
 130   
     {
 131  597
         try
 132   
         {
 133  597
             synchronized (HiveMind.INTROSPECTOR_MUTEX)
 134   
             {
 135  597
                 BeanInfo bi = Introspector.getBeanInfo(_baseClass);
 136   
 
 137  597
                 PropertyDescriptor[] pds = bi.getPropertyDescriptors();
 138   
 
 139  597
                 for (int i = 0; i < pds.length; i++)
 140   
                 {
 141  14283
                     PropertyDescriptor pd = pds[i];
 142   
 
 143  14283
                     _properties.put(pd.getName(), pd);
 144   
                 }
 145   
             }
 146   
         }
 147   
         catch (IntrospectionException ex)
 148   
         {
 149  0
             throw new ApplicationRuntimeException(EnhanceMessages.unabelToIntrospectClass(
 150   
                     _baseClass,
 151   
                     ex), ex);
 152   
         }
 153   
 
 154   
     }
 155   
 
 156   
     /**
 157   
      * Alternate package private constructor used by the test suite, to bypass the defense checks
 158   
      * above.
 159   
      */
 160   
 
 161  1
     EnhancementOperationImpl()
 162   
     {
 163   
     }
 164   
 
 165  2959
     public void claimProperty(String propertyName)
 166   
     {
 167  2959
         Defense.notNull(propertyName, "propertyName");
 168   
 
 169  2959
         if (_claimedProperties.contains(propertyName))
 170  1
             throw new ApplicationRuntimeException(EnhanceMessages.claimedProperty(propertyName));
 171   
 
 172  2958
         _claimedProperties.add(propertyName);
 173   
     }
 174   
 
 175  5115
     public void addField(String name, Class type)
 176   
     {
 177  5115
         _classFab.addField(name, type);
 178   
     }
 179   
 
 180  2137
     public void addField(String name, Class type, Object value)
 181   
     {
 182  2137
         _classFab.addField(name, type);
 183   
 
 184  2137
         int x = addConstructorParameter(type, value);
 185   
 
 186  2137
         constructorBuilder().addln("{0} = ${1};", name, Integer.toString(x));
 187   
     }
 188   
 
 189  130
     public Class convertTypeName(String type)
 190   
     {
 191  130
         Defense.notNull(type, "type");
 192   
 
 193  130
         Class result = _javaClassMapping.getType(type);
 194   
 
 195  130
         if (result == null)
 196   
         {
 197  27
             result = _resolver.findClass(type);
 198   
 
 199  26
             _javaClassMapping.recordType(type, result);
 200   
         }
 201   
 
 202  129
         return result;
 203   
     }
 204   
 
 205  1739
     public Class getPropertyType(String name)
 206   
     {
 207  1739
         Defense.notNull(name, "name");
 208   
 
 209  1739
         PropertyDescriptor pd = getPropertyDescriptor(name);
 210   
 
 211  1739
         return pd == null ? null : pd.getPropertyType();
 212   
     }
 213   
 
 214  127
     public void validateProperty(String name, Class expectedType)
 215   
     {
 216  127
         Defense.notNull(name, "name");
 217  127
         Defense.notNull(expectedType, "expectedType");
 218   
 
 219  127
         PropertyDescriptor pd = getPropertyDescriptor(name);
 220   
 
 221  127
         if (pd == null)
 222  47
             return;
 223   
 
 224  80
         Class propertyType = pd.getPropertyType();
 225   
 
 226  80
         if (propertyType.equals(expectedType))
 227  79
             return;
 228   
 
 229  1
         throw new ApplicationRuntimeException(EnhanceMessages.propertyTypeMismatch(
 230   
                 _baseClass,
 231   
                 name,
 232   
                 propertyType,
 233   
                 expectedType));
 234   
     }
 235   
 
 236  4034
     private PropertyDescriptor getPropertyDescriptor(String name)
 237   
     {
 238  4034
         return (PropertyDescriptor) _properties.get(name);
 239   
     }
 240   
 
 241  2168
     public String getAccessorMethodName(String propertyName)
 242   
     {
 243  2168
         Defense.notNull(propertyName, "propertyName");
 244   
 
 245  2168
         PropertyDescriptor pd = getPropertyDescriptor(propertyName);
 246   
 
 247  2168
         if (pd != null && pd.getReadMethod() != null)
 248  2042
             return pd.getReadMethod().getName();
 249   
 
 250  126
         return EnhanceUtils.createAccessorMethodName(propertyName);
 251   
     }
 252   
 
 253  4573
     public void addMethod(int modifier, MethodSignature sig, String methodBody)
 254   
     {
 255  4573
         _classFab.addMethod(modifier, sig, methodBody);
 256   
     }
 257   
 
 258  3
     public Class getBaseClass()
 259   
     {
 260  3
         return _baseClass;
 261   
     }
 262   
 
 263  977
     public String getClassReference(Class clazz)
 264   
     {
 265  977
         Defense.notNull(clazz, "clazz");
 266   
 
 267  977
         String result = (String) _classReferences.get(clazz);
 268   
 
 269  977
         if (result == null)
 270  793
             result = addClassReference(clazz);
 271   
 
 272  977
         return result;
 273   
     }
 274   
 
 275  793
     private String addClassReference(Class clazz)
 276   
     {
 277  793
         StringBuffer buffer = new StringBuffer("_class$");
 278   
 
 279  793
         Class c = clazz;
 280   
 
 281  793
         while (c.isArray())
 282   
         {
 283  20
             buffer.append("array$");
 284  20
             c = c.getComponentType();
 285   
         }
 286   
 
 287  793
         buffer.append(c.getName().replace('.', '$'));
 288   
 
 289  793
         String fieldName = buffer.toString();
 290   
 
 291  793
         addField(fieldName, Class.class, clazz);
 292   
 
 293  793
         _classReferences.put(clazz, fieldName);
 294   
 
 295  793
         return fieldName;
 296   
     }
 297   
 
 298   
     /**
 299   
      * Adds a new constructor parameter, returning the new count. This is convienient, because the
 300   
      * first element added is accessed as $1, etc.
 301   
      */
 302   
 
 303  2137
     private int addConstructorParameter(Class type, Object value)
 304   
     {
 305  2137
         _constructorTypes.add(type);
 306  2137
         _constructorArguments.add(value);
 307   
 
 308  2137
         return _constructorArguments.size();
 309   
     }
 310   
 
 311  2137
     private BodyBuilder constructorBuilder()
 312   
     {
 313  2137
         if (_constructorBuilder == null)
 314   
         {
 315  549
             _constructorBuilder = new BodyBuilder();
 316  549
             _constructorBuilder.begin();
 317   
         }
 318   
 
 319  2137
         return _constructorBuilder;
 320   
     }
 321   
 
 322   
     /**
 323   
      * Returns an object that can be used to construct instances of the enhanced component subclass.
 324   
      * This should only be called once.
 325   
      */
 326   
 
 327  582
     public ComponentConstructor getConstructor()
 328   
     {
 329  582
         finalizeEnhancedClass();
 330   
 
 331  582
         Constructor c = findConstructor();
 332   
 
 333  582
         Object[] params = _constructorArguments.toArray();
 334   
 
 335  582
         return new ComponentConstructorImpl(c, params, _specification.getLocation());
 336   
     }
 337   
 
 338  582
     private void finalizeEnhancedClass()
 339   
     {
 340  582
         finalizeIncompleteMethods();
 341   
 
 342  582
         if (_constructorBuilder != null)
 343   
         {
 344  548
             _constructorBuilder.end();
 345   
 
 346  548
             Class[] types = (Class[]) _constructorTypes
 347   
                     .toArray(new Class[_constructorTypes.size()]);
 348   
 
 349  548
             _classFab.addConstructor(types, null, _constructorBuilder.toString());
 350   
         }
 351   
     }
 352   
 
 353  582
     private void finalizeIncompleteMethods()
 354   
     {
 355  582
         Iterator i = _incompleteMethods.entrySet().iterator();
 356  582
         while (i.hasNext())
 357   
         {
 358  593
             Map.Entry e = (Map.Entry) i.next();
 359  593
             MethodSignature sig = (MethodSignature) e.getKey();
 360  593
             BodyBuilder builder = (BodyBuilder) e.getValue();
 361   
 
 362   
             // Each BodyBuilder is created and given a begin(), this is
 363   
             // the matching end()
 364   
 
 365  593
             builder.end();
 366   
 
 367  593
             _classFab.addMethod(Modifier.PUBLIC, sig, builder.toString());
 368   
         }
 369   
     }
 370   
 
 371  582
     private Constructor findConstructor()
 372   
     {
 373  582
         Class componentClass = _classFab.createClass();
 374   
 
 375   
         // The fabricated base class always has exactly one constructor
 376   
 
 377  582
         return componentClass.getConstructors()[0];
 378   
     }
 379   
 
 380   
     static int _uid = 0;
 381   
 
 382  597
     private String newClassName()
 383   
     {
 384  597
         String baseName = _baseClass.getName();
 385  597
         int dotx = baseName.lastIndexOf('.');
 386   
 
 387  597
         return "$" + baseName.substring(dotx + 1) + "_" + _uid++;
 388   
     }
 389   
 
 390  1955
     public void extendMethodImplementation(Class interfaceClass, MethodSignature methodSignature,
 391   
             String code)
 392   
     {
 393  1955
         addInterfaceIfNeeded(interfaceClass);
 394   
 
 395  1955
         BodyBuilder builder = (BodyBuilder) _incompleteMethods.get(methodSignature);
 396   
 
 397  1955
         if (builder == null)
 398   
         {
 399  595
             builder = createIncompleteMethod(methodSignature);
 400   
 
 401  595
             _incompleteMethods.put(methodSignature, builder);
 402   
         }
 403   
 
 404  1955
         builder.addln(code);
 405   
     }
 406   
 
 407  1955
     private void addInterfaceIfNeeded(Class interfaceClass)
 408   
     {
 409  1955
         if (implementsInterface(interfaceClass))
 410  1832
             return;
 411   
 
 412  123
         _classFab.addInterface(interfaceClass);
 413  123
         _addedInterfaces.add(interfaceClass);
 414   
     }
 415   
 
 416  4614
     public boolean implementsInterface(Class interfaceClass)
 417   
     {
 418  4614
         if (interfaceClass.isAssignableFrom(_baseClass))
 419  1665
             return true;
 420   
 
 421  2949
         Iterator i = _addedInterfaces.iterator();
 422  2949
         while (i.hasNext())
 423   
         {
 424  371
             Class addedInterface = (Class) i.next();
 425   
 
 426  371
             if (interfaceClass.isAssignableFrom(addedInterface))
 427  210
                 return true;
 428   
         }
 429   
 
 430  2739
         return false;
 431   
     }
 432   
 
 433  595
     private BodyBuilder createIncompleteMethod(MethodSignature sig)
 434   
     {
 435  595
         BodyBuilder result = new BodyBuilder();
 436   
 
 437   
         // Matched inside finalizeIncompleteMethods()
 438   
 
 439  595
         result.begin();
 440   
 
 441  595
         if (existingImplementation(sig))
 442  472
             result.addln("super.{0}($$);", sig.getName());
 443   
 
 444  595
         return result;
 445   
     }
 446   
 
 447   
     /**
 448   
      * Returns true if the base class implements the provided method as either a public or a
 449   
      * protected method.
 450   
      */
 451   
 
 452  595
     private boolean existingImplementation(MethodSignature sig)
 453   
     {
 454  595
         Method m = findMethod(sig);
 455   
 
 456  595
         return m != null && !Modifier.isAbstract(m.getModifiers());
 457   
     }
 458   
 
 459   
     /**
 460   
      * Finds a public or protected method in the base class.
 461   
      */
 462  595
     private Method findMethod(MethodSignature sig)
 463   
     {
 464   
         // Finding a public method is easy:
 465   
 
 466  595
         try
 467   
         {
 468  595
             return _baseClass.getMethod(sig.getName(), sig.getParameterTypes());
 469   
 
 470   
         }
 471   
         catch (NoSuchMethodException ex)
 472   
         {
 473   
             // Good; no super-implementation to invoke.
 474   
         }
 475   
 
 476  484
         Class c = _baseClass;
 477   
 
 478  484
         while (c != Object.class)
 479   
         {
 480  1224
             try
 481   
             {
 482  1224
                 return c.getDeclaredMethod(sig.getName(), sig.getParameterTypes());
 483   
             }
 484   
             catch (NoSuchMethodException ex)
 485   
             {
 486   
                 // Ok, continue loop up to next base class.
 487   
             }
 488   
 
 489  863
             c = c.getSuperclass();
 490   
         }
 491   
 
 492  123
         return null;
 493   
     }
 494   
 
 495  565
     public List findUnclaimedAbstractProperties()
 496   
     {
 497  565
         List result = new ArrayList();
 498   
 
 499  565
         Iterator i = _properties.values().iterator();
 500   
 
 501  565
         while (i.hasNext())
 502   
         {
 503  13578
             PropertyDescriptor pd = (PropertyDescriptor) i.next();
 504   
 
 505  13578
             String name = pd.getName();
 506   
 
 507  13578
             if (_claimedProperties.contains(name))
 508  2660
                 continue;
 509   
 
 510  10918
             if (isAbstractProperty(pd))
 511  211
                 result.add(name);
 512   
         }
 513   
 
 514  565
         return result;
 515   
     }
 516   
 
 517   
     /**
 518   
      * A property is abstract if either its read method or it write method is abstract. We could do
 519   
      * some additional checking to ensure that both are abstract if either is. Note that in many
 520   
      * cases, there will only be one accessor (a reader or a writer).
 521   
      */
 522  10918
     private boolean isAbstractProperty(PropertyDescriptor pd)
 523   
     {
 524  10918
         return isExistingAbstractMethod(pd.getReadMethod())
 525   
                 || isExistingAbstractMethod(pd.getWriteMethod());
 526   
     }
 527   
 
 528  21627
     private boolean isExistingAbstractMethod(Method m)
 529   
     {
 530  21627
         return m != null && Modifier.isAbstract(m.getModifiers());
 531   
     }
 532   
 }