Coverage Report - org.apache.tapestry.enhance.ParameterPropertyWorker
 
Classes in this File Line Coverage Branch Coverage Complexity
ParameterPropertyWorker
0%
0/96
0%
0/14
1.625
 
 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  0
 public class ParameterPropertyWorker implements EnhancementWorker
 39  
 {
 40  
 
 41  
     private ErrorLog _errorLog;
 42  
 
 43  
     public void performEnhancement(EnhancementOperation op, IComponentSpecification spec)
 44  
     {
 45  0
         Iterator i = spec.getParameterNames().iterator();
 46  0
         while(i.hasNext())
 47  
         {
 48  0
             String name = (String) i.next();
 49  
 
 50  0
             IParameterSpecification ps = spec.getParameter(name);
 51  
 
 52  
             try
 53  
             {
 54  0
                 performEnhancement(op, name, ps);
 55  
             }
 56  0
             catch (RuntimeException ex)
 57  
             {
 58  0
                 _errorLog.error(EnhanceMessages.errorAddingProperty(ps.getPropertyName(), op.getBaseClass(), ex),
 59  
                                 ps.getLocation(), ex);
 60  0
             }
 61  0
         }
 62  0
     }
 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  0
         if (!parameterName.equals(ps.getParameterName()))
 82  0
             return;
 83  
 
 84  0
         String propertyName = ps.getPropertyName();
 85  0
         String specifiedType = ps.getType();
 86  0
         boolean cache = ps.getCache();
 87  
 
 88  0
         addParameter(op, parameterName, propertyName, specifiedType, cache, ps.getLocation());
 89  0
     }
 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  0
         Defense.notNull(op, "op");
 117  0
         Defense.notNull(parameterName, "parameterName");
 118  0
         Defense.notNull(propertyName, "propertyName");
 119  
 
 120  0
         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  0
         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  0
         String fieldName = "_$" + propertyName;
 133  0
         String defaultFieldName = fieldName + "$Default";
 134  0
         String cachedFieldName = fieldName + "$Cached";
 135  
 
 136  0
         op.addField(fieldName, propertyType);
 137  0
         op.addField(defaultFieldName, propertyType);
 138  0
         op.addField(cachedFieldName, boolean.class);
 139  
 
 140  0
         String bindingFieldName = buildBindingAccessor(op, fieldName, parameterName, location);
 141  
 
 142  0
         buildAccessor(op, propertyName, propertyType, fieldName,
 143  
                       defaultFieldName, cachedFieldName, bindingFieldName,
 144  
                       cache, location);
 145  
 
 146  0
         buildMutator(op, parameterName, propertyName, propertyType, fieldName,
 147  
                      defaultFieldName, cachedFieldName, bindingFieldName, location);
 148  
 
 149  0
         extendCleanupAfterRender(op, bindingFieldName, fieldName, defaultFieldName, cachedFieldName);
 150  0
     }
 151  
 
 152  
     String buildBindingAccessor(EnhancementOperation op, String fieldName, String parameterName, Location location)
 153  
     {
 154  0
         BodyBuilder body = new BodyBuilder();
 155  0
         body.begin();
 156  
         
 157  0
         String bindingFieldName = fieldName + "$Binding";
 158  0
         String bindingCheckedName = bindingFieldName + "Checked";
 159  
 
 160  0
         op.addField(bindingFieldName, IBinding.class);
 161  0
         op.addField(bindingCheckedName, Boolean.TYPE);
 162  
 
 163  0
         body.addln("if (!{0})", bindingCheckedName);
 164  0
         body.begin();
 165  0
         body.addln("{0} = getBinding(\"{1}\");", bindingFieldName, parameterName);
 166  0
         body.addln("{0} = true;", bindingCheckedName);
 167  0
         body.end();
 168  
 
 169  0
         body.addln("return {0};", bindingFieldName);
 170  
 
 171  0
         body.end();
 172  
 
 173  0
         String methodName = EnhanceUtils.createAccessorMethodName(bindingFieldName);
 174  
 
 175  0
         op.addMethod(Modifier.PUBLIC,
 176  
                      new MethodSignature(IBinding.class, methodName, new Class[0], null),
 177  
                      body.toString(), location);
 178  
 
 179  0
         return methodName + "()";
 180  
     }
 181  
 
 182  
     void extendCleanupAfterRender(EnhancementOperation op, String bindingFieldName,
 183  
                                   String fieldName, String defaultFieldName, String cachedFieldName)
 184  
     {
 185  0
         BodyBuilder cleanupBody = new BodyBuilder();
 186  
 
 187  
         // Cached is only set when the field is updated in the accessor or
 188  
         // mutator.
 189  
         // After rendering, we want to clear the cached value and cached flag
 190  
         // unless the binding is invariant, in which case it can stick around
 191  
         // for some future render.
 192  
 
 193  0
         cleanupBody.addln("if ({0} && ! {1}.isInvariant())", cachedFieldName, bindingFieldName);
 194  0
         cleanupBody.begin();
 195  0
         cleanupBody.addln("{0} = false;", cachedFieldName);
 196  0
         cleanupBody.addln("{0} = {1};", fieldName, defaultFieldName);
 197  0
         cleanupBody.end();
 198  
 
 199  0
         op.extendMethodImplementation(IComponent.class,
 200  
                                       EnhanceUtils.CLEANUP_AFTER_RENDER_SIGNATURE, cleanupBody.toString());
 201  0
     }
 202  
 
 203  
     private void buildMutator(EnhancementOperation op, String parameterName,
 204  
                               String propertyName, Class propertyType, String fieldName,
 205  
                               String defaultFieldName, String cachedFieldName,
 206  
                               String bindingFieldName, Location location)
 207  
     {
 208  0
         BodyBuilder builder = new BodyBuilder();
 209  0
         builder.begin();
 210  
 
 211  
         // The mutator method may be invoked from finishLoad(), in which
 212  
         // case it changes the default value for the parameter property, if the
 213  
         // parameter
 214  
         // is not bound.
 215  
 
 216  0
         builder.addln("if (! isInActiveState())");
 217  0
         builder.begin();
 218  0
         builder.addln("{0} = $1;", defaultFieldName);
 219  0
         builder.addln("return;");
 220  0
         builder.end();
 221  
 
 222  
         // In the normal state, we update the binding first - and it's an error
 223  
         // if the parameter is not bound.
 224  
 
 225  0
         builder.addln("if ({0} == null)", bindingFieldName);
 226  0
         builder.addln("  throw new {0}(\"Parameter ''{1}'' is not bound and can not be updated.\");",
 227  
                       ApplicationRuntimeException.class.getName(), parameterName);
 228  
 
 229  
         // Always updated the binding first (which may fail with an exception).
 230  
 
 231  0
         builder.addln("{0}.setObject(($w) $1);", bindingFieldName);
 232  
 
 233  
         // While rendering, we store the updated value for fast
 234  
         // access again (while the component is still rendering).
 235  
         // The property value will be reset to default by cleanupAfterRender().
 236  
 
 237  0
         builder.addln("if (isRendering())");
 238  0
         builder.begin();
 239  0
         builder.addln("{0} = $1;", fieldName);
 240  0
         builder.addln("{0} = true;", cachedFieldName);
 241  0
         builder.end();
 242  
 
 243  0
         builder.end();
 244  
 
 245  0
         String mutatorMethodName = EnhanceUtils.createMutatorMethodName(propertyName);
 246  
 
 247  0
         op.addMethod(Modifier.PUBLIC,
 248  
                      new MethodSignature(void.class, mutatorMethodName, new Class[] { propertyType }, null),
 249  
                      builder.toString(), location);
 250  0
     }
 251  
 
 252  
     // Package private for testing
 253  
 
 254  
     void buildAccessor(EnhancementOperation op, String propertyName, Class propertyType,
 255  
                        String fieldName, String defaultFieldName, String cachedFieldName,
 256  
                        String bindingFieldName, boolean cache, Location location)
 257  
     {
 258  0
         BodyBuilder builder = new BodyBuilder();
 259  0
         builder.begin();
 260  
 
 261  0
         builder.addln("if ({0}) return {1};", cachedFieldName, fieldName);
 262  0
         builder.addln("if ({0} == null) return {1};", bindingFieldName, defaultFieldName);
 263  
 
 264  0
         String javaTypeName = ClassFabUtils.getJavaClassName(propertyType);
 265  
 
 266  0
         builder.addln("{0} result = {1};", javaTypeName, EnhanceUtils.createUnwrapExpression(op, bindingFieldName, propertyType));
 267  
 
 268  
         // Values read via the binding are cached during the render of
 269  
         // the component (if the parameter defines cache to be true, which
 270  
         // is the default), or any time the binding is invariant
 271  
         // (such as most bindings besides ExpressionBinding.
 272  
 
 273  0
         String expression = cache ? "isRendering() || {0}.isInvariant()" : "{0}.isInvariant()";
 274  
 
 275  0
         builder.addln("if (" + expression + ")", bindingFieldName);
 276  0
         builder.begin();
 277  0
         builder.addln("{0} = result;", fieldName);
 278  0
         builder.addln("{0} = true;", cachedFieldName);
 279  0
         builder.end();
 280  
 
 281  0
         builder.addln("return result;");
 282  
 
 283  0
         builder.end();
 284  
 
 285  0
         String accessorMethodName = op.getAccessorMethodName(propertyName);
 286  
 
 287  0
         op.addMethod(Modifier.PUBLIC, new MethodSignature(propertyType,
 288  
                                                           accessorMethodName, null, null), builder.toString(), location);
 289  0
     }
 290  
 
 291  
     public void setErrorLog(ErrorLog errorLog)
 292  
     {
 293  0
         _errorLog = errorLog;
 294  0
     }
 295  
 }