Coverage Report - org.apache.tapestry.enhance.ParameterPropertyWorker
 
Classes in this File Line Coverage Branch Coverage Complexity
ParameterPropertyWorker
100% 
100% 
1.5
 
 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 org.apache.hivemind.ApplicationRuntimeException;
 18  
 import org.apache.hivemind.ErrorLog;
 19  
 import org.apache.hivemind.Location;
 20  
 import org.apache.hivemind.service.BodyBuilder;
 21  
 import org.apache.hivemind.service.ClassFabUtils;
 22  
 import org.apache.hivemind.service.MethodSignature;
 23  
 import org.apache.hivemind.util.Defense;
 24  
 import org.apache.tapestry.IBinding;
 25  
 import org.apache.tapestry.IComponent;
 26  
 import org.apache.tapestry.spec.IComponentSpecification;
 27  
 import org.apache.tapestry.spec.IParameterSpecification;
 28  
 
 29  
 import java.lang.reflect.Modifier;
 30  
 import java.util.Iterator;
 31  
 
 32  
 /**
 33  
  * Responsible for creating properties for connected parameters.
 34  
  *
 35  
  * @author Howard M. Lewis Ship
 36  
  * @since 4.0
 37  
  */
 38  6
 public class ParameterPropertyWorker implements EnhancementWorker
 39  
 {
 40  
 
 41  
     private ErrorLog _errorLog;
 42  
 
 43  
     public void performEnhancement(EnhancementOperation op, IComponentSpecification spec)
 44  
     {
 45  4
         Iterator i = spec.getParameterNames().iterator();
 46  8
         while(i.hasNext())
 47  
         {
 48  4
             String name = (String) i.next();
 49  
 
 50  4
             IParameterSpecification ps = spec.getParameter(name);
 51  
 
 52  
             try
 53  
             {
 54  4
                 performEnhancement(op, name, ps);
 55  
             }
 56  1
             catch (RuntimeException ex)
 57  
             {
 58  1
                 _errorLog.error(EnhanceMessages.errorAddingProperty(ps.getPropertyName(), op.getBaseClass(), ex),
 59  
                                 ps.getLocation(), ex);
 60  3
             }
 61  4
         }
 62  4
     }
 63  
 
 64  
     /**
 65  
      * Performs the enhancement for a single parameter; this is about to change
 66  
      * radically in release 4.0 but for the moment we're emulating 3.0 behavior.
 67  
      *
 68  
      * @param op
 69  
      *          Enhancement operation service.
 70  
      * @param parameterName
 71  
      *          Name of the parameter being enhanced.
 72  
      * @param ps
 73  
      *          Specification of parameter.
 74  
      */
 75  
 
 76  
     private void performEnhancement(EnhancementOperation op, String parameterName, IParameterSpecification ps)
 77  
     {
 78  
         // If the parameter name doesn't match, its because this is an alias
 79  
         // for a true parameter; we ignore aliases.
 80  
 
 81  4
         if (!parameterName.equals(ps.getParameterName()))
 82  1
             return;
 83  
 
 84  3
         String propertyName = ps.getPropertyName();
 85  3
         String specifiedType = ps.getType();
 86  3
         boolean cache = ps.getCache();
 87  
 
 88  3
         addParameter(op, parameterName, propertyName, specifiedType, cache, ps.getLocation());
 89  2
     }
 90  
 
 91  
     /**
 92  
      * Adds a parameter as a (very smart) property.
 93  
      *
 94  
      * @param op
 95  
      *            the enhancement operation
 96  
      * @param parameterName
 97  
      *            the name of the parameter (used to access the binding)
 98  
      * @param propertyName
 99  
      *            the name of the property to create (usually, but not always,
 100  
      *            matches the parameterName)
 101  
      * @param specifiedType
 102  
      *            the type declared in the DTD (only 3.0 DTD supports this), may
 103  
      *            be null (always null for 4.0 DTD)
 104  
      * @param cache
 105  
      *            if true, then the value should be cached while the component
 106  
      *            renders; false (a much less common case) means that every
 107  
      *            access will work through binding object.
 108  
      * @param location
 109  
      *            Used for reporting line-precise errors in binding resolution / setting / etc..
 110  
      */
 111  
 
 112  
     public void addParameter(EnhancementOperation op, String parameterName,
 113  
                              String propertyName, String specifiedType, boolean cache,
 114  
                              Location location)
 115  
     {
 116  3
         Defense.notNull(op, "op");
 117  3
         Defense.notNull(parameterName, "parameterName");
 118  3
         Defense.notNull(propertyName, "propertyName");
 119  
 
 120  3
         Class propertyType = EnhanceUtils.extractPropertyType(op, propertyName, specifiedType);
 121  
 
 122  
         // 3.0 would allow connected parameter properties to be fully
 123  
         // implemented
 124  
         // in the component class. This is not supported in 4.0 and an existing
 125  
         // property will be overwritten in the subclass.
 126  
 
 127  2
         op.claimProperty(propertyName);
 128  
 
 129  
         // 3.0 used to support a property for the binding itself. That's
 130  
         // no longer the case.
 131  
 
 132  2
         String fieldName = "_$" + propertyName;
 133  2
         String defaultFieldName = fieldName + "$Default";
 134  2
         String cachedFieldName = fieldName + "$Cached";
 135  
 
 136  2
         op.addField(fieldName, propertyType);
 137  2
         op.addField(defaultFieldName, propertyType);
 138  2
         op.addField(cachedFieldName, boolean.class);
 139  
 
 140  2
         String bindingFieldName = buildBindingAccessor(op, fieldName, parameterName, location);
 141  
 
 142  2
         buildAccessor(op, propertyName, propertyType, fieldName,
 143  
                       defaultFieldName, cachedFieldName, bindingFieldName,
 144  
                       cache, location);
 145  
 
 146  2
         buildMutator(op, parameterName, propertyName, propertyType, fieldName,
 147  
                      defaultFieldName, cachedFieldName, bindingFieldName, location);
 148  
 
 149  2
         extendCleanupAfterRender(op, bindingFieldName, fieldName, defaultFieldName, cachedFieldName);
 150  2
     }
 151  
 
 152  
     String buildBindingAccessor(EnhancementOperation op, String fieldName, String parameterName, Location location)
 153  
     {
 154  2
         BodyBuilder body = new BodyBuilder();
 155  2
         body.begin();
 156  
         
 157  2
         String bindingFieldName = fieldName + "$Binding";
 158  
 
 159  5
         op.addField(bindingFieldName, IBinding.class);
 160  
 
 161  2
         body.addln("if ({0} == null)", bindingFieldName);
 162  2
         body.begin();
 163  2
         body.addln("{0} = getBinding(\"{1}\");", bindingFieldName, parameterName);
 164  2
         body.end();
 165  
 
 166  2
         body.addln("return {0};", bindingFieldName);
 167  
 
 168  2
         body.end();
 169  
 
 170  2
         String methodName = EnhanceUtils.createAccessorMethodName(bindingFieldName);
 171  
 
 172  2
         op.addMethod(Modifier.PUBLIC,
 173  
                      new MethodSignature(IBinding.class, methodName, new Class[0], null),
 174  
                      body.toString(), location);
 175  
 
 176  2
         return methodName + "()";
 177  
     }
 178  
 
 179  
     void extendCleanupAfterRender(EnhancementOperation op, String bindingFieldName,
 180  
                                   String fieldName, String defaultFieldName, String cachedFieldName)
 181  
     {
 182  2
         BodyBuilder cleanupBody = new BodyBuilder();
 183  
 
 184  
         // Cached is only set when the field is updated in the accessor or
 185  
         // mutator.
 186  
         // After rendering, we want to clear the cached value and cached flag
 187  
         // unless the binding is invariant, in which case it can stick around
 188  
         // for some future render.
 189  
 
 190  2
         cleanupBody.addln("if ({0} && ! {1}.isInvariant())", cachedFieldName, bindingFieldName);
 191  2
         cleanupBody.begin();
 192  2
         cleanupBody.addln("{0} = false;", cachedFieldName);
 193  2
         cleanupBody.addln("{0} = {1};", fieldName, defaultFieldName);
 194  2
         cleanupBody.end();
 195  
 
 196  2
         op.extendMethodImplementation(IComponent.class,
 197  
                                       EnhanceUtils.CLEANUP_AFTER_RENDER_SIGNATURE, cleanupBody.toString());
 198  2
     }
 199  
 
 200  
     private void buildMutator(EnhancementOperation op, String parameterName,
 201  
                               String propertyName, Class propertyType, String fieldName,
 202  
                               String defaultFieldName, String cachedFieldName,
 203  
                               String bindingFieldName, Location location)
 204  
     {
 205  2
         BodyBuilder builder = new BodyBuilder();
 206  2
         builder.begin();
 207  
 
 208  
         // The mutator method may be invoked from finishLoad(), in which
 209  
         // case it changes the default value for the parameter property, if the
 210  
         // parameter
 211  
         // is not bound.
 212  
 
 213  2
         builder.addln("if (! isInActiveState())");
 214  2
         builder.begin();
 215  2
         builder.addln("{0} = $1;", defaultFieldName);
 216  2
         builder.addln("return;");
 217  2
         builder.end();
 218  
 
 219  
         // In the normal state, we update the binding first - and it's an error
 220  
         // if the parameter is not bound.
 221  
 
 222  2
         builder.addln("if ({0} == null)", bindingFieldName);
 223  2
         builder.addln("  throw new {0}(\"Parameter ''{1}'' is not bound and can not be updated.\");",
 224  
                       ApplicationRuntimeException.class.getName(), parameterName);
 225  
 
 226  
         // Always updated the binding first (which may fail with an exception).
 227  
 
 228  2
         builder.addln("{0}.setObject(($w) $1);", bindingFieldName);
 229  
 
 230  
         // While rendering, we store the updated value for fast
 231  
         // access again (while the component is still rendering).
 232  
         // The property value will be reset to default by cleanupAfterRender().
 233  
 
 234  2
         builder.addln("if (isRendering())");
 235  2
         builder.begin();
 236  2
         builder.addln("{0} = $1;", fieldName);
 237  2
         builder.addln("{0} = true;", cachedFieldName);
 238  2
         builder.end();
 239  
 
 240  2
         builder.end();
 241  
 
 242  2
         String mutatorMethodName = EnhanceUtils.createMutatorMethodName(propertyName);
 243  
 
 244  2
         op.addMethod(Modifier.PUBLIC,
 245  
                      new MethodSignature(void.class, mutatorMethodName, new Class[] { propertyType }, null),
 246  
                      builder.toString(), location);
 247  2
     }
 248  
 
 249  
     // Package private for testing
 250  
 
 251  
     void buildAccessor(EnhancementOperation op, String propertyName, Class propertyType,
 252  
                        String fieldName, String defaultFieldName, String cachedFieldName,
 253  
                        String bindingFieldName, boolean cache, Location location)
 254  
     {
 255  4
         BodyBuilder builder = new BodyBuilder();
 256  4
         builder.begin();
 257  
 
 258  4
         builder.addln("if ({0}) return {1};", cachedFieldName, fieldName);
 259  4
         builder.addln("if ({0} == null) return {1};", bindingFieldName, defaultFieldName);
 260  
 
 261  4
         String javaTypeName = ClassFabUtils.getJavaClassName(propertyType);
 262  
 
 263  4
         builder.addln("{0} result = {1};", javaTypeName, EnhanceUtils.createUnwrapExpression(op, bindingFieldName, propertyType));
 264  
 
 265  
         // Values read via the binding are cached during the render of
 266  
         // the component (if the parameter defines cache to be true, which
 267  
         // is the default), or any time the binding is invariant
 268  
         // (such as most bindings besides ExpressionBinding.
 269  
 
 270  4
         String expression = cache ? "isRendering() || {0}.isInvariant()" : "{0}.isInvariant()";
 271  
 
 272  4
         builder.addln("if (" + expression + ")", bindingFieldName);
 273  4
         builder.begin();
 274  4
         builder.addln("{0} = result;", fieldName);
 275  4
         builder.addln("{0} = true;", cachedFieldName);
 276  4
         builder.end();
 277  
 
 278  4
         builder.addln("return result;");
 279  
 
 280  4
         builder.end();
 281  
 
 282  4
         String accessorMethodName = op.getAccessorMethodName(propertyName);
 283  
 
 284  4
         op.addMethod(Modifier.PUBLIC, new MethodSignature(propertyType,
 285  
                                                           accessorMethodName, null, null), builder.toString(), location);
 286  4
     }
 287  
 
 288  
     public void setErrorLog(ErrorLog errorLog)
 289  
     {
 290  1
         _errorLog = errorLog;
 291  1
     }
 292  
 }