Coverage Report - org.apache.tapestry5.internal.transform.ParameterWorker
 
Classes in this File Line Coverage Branch Coverage Complexity
ParameterWorker
100%
109/109
100%
26/26
0
ParameterWorker$1
100%
2/2
100%
4/4
0
 
 1  
 // Copyright 2006, 2007, 2008, 2009 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.tapestry5.internal.transform;
 16  
 
 17  
 import org.apache.tapestry5.Binding;
 18  
 import org.apache.tapestry5.annotations.Parameter;
 19  
 import org.apache.tapestry5.internal.InternalComponentResources;
 20  
 import org.apache.tapestry5.internal.ParameterAccess;
 21  
 import org.apache.tapestry5.internal.bindings.LiteralBinding;
 22  
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 23  
 import org.apache.tapestry5.ioc.util.BodyBuilder;
 24  
 import org.apache.tapestry5.model.MutableComponentModel;
 25  
 import org.apache.tapestry5.services.*;
 26  
 
 27  
 import java.lang.reflect.Modifier;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 
 31  
 /**
 32  
  * Responsible for identifying parameters via the {@link org.apache.tapestry5.annotations.Parameter} annotation on
 33  
  * component fields. This is one of the most complex of the transformations.
 34  
  */
 35  
 public class ParameterWorker implements ComponentClassTransformWorker
 36  
 {
 37  2
     private static final String BIND_METHOD_NAME = ParameterWorker.class.getName() + ".bind";
 38  
 
 39  
     private final BindingSource bindingSource;
 40  
 
 41  
     private ComponentDefaultProvider defaultProvider;
 42  
 
 43  
     public ParameterWorker(BindingSource bindingSource, ComponentDefaultProvider defaultProvider)
 44  58
     {
 45  58
         this.bindingSource = bindingSource;
 46  58
         this.defaultProvider = defaultProvider;
 47  58
     }
 48  
 
 49  
     public void transform(final ClassTransformation transformation, MutableComponentModel model)
 50  
     {
 51  814
         List<String> fieldNames = transformation.findFieldsWithAnnotation(Parameter.class);
 52  
 
 53  2440
         for (int pass = 0; pass < 2; pass++)
 54  
         {
 55  1628
             Iterator<String> i = fieldNames.iterator();
 56  
 
 57  3334
             while (i.hasNext())
 58  
             {
 59  1708
                 String fieldName = i.next();
 60  
 
 61  1708
                 Parameter annotation = transformation.getFieldAnnotation(fieldName, Parameter.class);
 62  
 
 63  
                 // Process the principal annotations on the first pass, handle the others
 64  
                 // on the second pass.
 65  
 
 66  1708
                 boolean process = pass == 0
 67  
                                   ? annotation.principal()
 68  
                                   : true;
 69  
 
 70  1708
                 if (process)
 71  
                 {
 72  882
                     convertFieldIntoParameter(fieldName, annotation, transformation, model);
 73  
 
 74  880
                     i.remove();
 75  
                 }
 76  1706
             }
 77  
         }
 78  812
     }
 79  
 
 80  
     private void convertFieldIntoParameter(String name, Parameter annotation, ClassTransformation transformation,
 81  
                                            MutableComponentModel model)
 82  
     {
 83  882
         transformation.claimField(name, annotation);
 84  
 
 85  880
         String parameterName = getParameterName(name, annotation.name());
 86  
 
 87  880
         model.addParameter(parameterName, annotation.required(), annotation.allowNull(), annotation.defaultPrefix());
 88  
 
 89  880
         String type = transformation.getFieldType(name);
 90  
 
 91  880
         boolean cache = annotation.cache();
 92  
 
 93  880
         String cachedFieldName = transformation.addField(Modifier.PRIVATE, "boolean", name + "_cached");
 94  
 
 95  880
         String resourcesFieldName = transformation.getResourcesFieldName();
 96  
 
 97  880
         String accessFieldName = addParameterSetup(name, annotation.defaultPrefix(), annotation.value(),
 98  
                                                    parameterName, cachedFieldName, cache, type, resourcesFieldName,
 99  
                                                    transformation, annotation.autoconnect());
 100  
 
 101  880
         addReaderMethod(name, cachedFieldName, accessFieldName, cache, parameterName, type, resourcesFieldName,
 102  
                         transformation);
 103  
 
 104  880
         addWriterMethod(name, cachedFieldName, accessFieldName, cache, parameterName, type, resourcesFieldName,
 105  
                         transformation);
 106  880
     }
 107  
 
 108  
     /**
 109  
      * Returns the name of a field that stores whether the parameter binding is invariant.
 110  
      */
 111  
     private String addParameterSetup(String fieldName, String defaultPrefix, String defaultBinding,
 112  
                                      String parameterName, String cachedFieldName, boolean cache, String fieldType,
 113  
                                      String resourcesFieldName, ClassTransformation transformation, boolean autoconnect)
 114  
     {
 115  
 
 116  880
         String accessFieldName = transformation.addField(Modifier.PRIVATE, ParameterAccess.class.getName(),
 117  
                                                          fieldName + "_access");
 118  
 
 119  880
         String defaultFieldName = transformation.addField(Modifier.PRIVATE, fieldType, fieldName + "_default");
 120  
 
 121  880
         BodyBuilder builder = new BodyBuilder().begin();
 122  
 
 123  880
         addDefaultBindingSetup(parameterName, defaultPrefix, defaultBinding, resourcesFieldName,
 124  
                                transformation,
 125  
                                builder, autoconnect);
 126  
 
 127  
         // Order is (alas) important here: must invoke getParameterAccess() after the binding setup, as
 128  
         // that code may invoke InternalComponentResources.bindParameter().
 129  
 
 130  880
         builder.addln("%s = %s.getParameterAccess(\"%s\");", accessFieldName, resourcesFieldName, parameterName);
 131  
 
 132  
         // Store the current value of the field into the default field. This value will
 133  
         // be used to reset the field after rendering.
 134  
 
 135  880
         builder.addln("%s = %s;", defaultFieldName, fieldName);
 136  880
         builder.end();
 137  
 
 138  880
         transformation.extendMethod(TransformConstants.CONTAINING_PAGE_DID_LOAD_SIGNATURE, builder
 139  
                 .toString());
 140  
 
 141  
         // Now, when the component completes rendering, ensure that any variant parameters are
 142  
         // are returned to default value. This isn't necessary when the parameter is not cached,
 143  
         // because (unless the binding is invariant), there's no value to get rid of (and if it is
 144  
         // invariant, there's no need to get rid of it).
 145  
 
 146  880
         if (cache)
 147  
         {
 148  878
             builder.clear();
 149  
 
 150  878
             builder.addln("if (! %s.isInvariant())", accessFieldName);
 151  878
             builder.begin();
 152  878
             builder.addln("%s = %s;", fieldName, defaultFieldName);
 153  878
             builder.addln("%s = false;", cachedFieldName);
 154  878
             builder.end();
 155  
 
 156  
             // Clean up after the component renders.
 157  
 
 158  878
             String body = builder.toString();
 159  
 
 160  878
             transformation.extendMethod(TransformConstants.POST_RENDER_CLEANUP_SIGNATURE, body);
 161  
 
 162  
             // And again, when the page is detached (TAPESTRY-2460)
 163  
 
 164  878
             transformation.extendMethod(TransformConstants.CONTAINING_PAGE_DID_DETACH_SIGNATURE, builder.toString());
 165  
         }
 166  
 
 167  880
         return accessFieldName;
 168  
     }
 169  
 
 170  
     private void addDefaultBindingSetup(String parameterName, String defaultPrefix, String defaultBinding,
 171  
                                         String resourcesFieldName,
 172  
                                         ClassTransformation transformation,
 173  
                                         BodyBuilder builder, boolean autoconnect)
 174  
     {
 175  880
         if (InternalUtils.isNonBlank(defaultBinding))
 176  
         {
 177  176
             builder.addln("if (! %s.isBound(\"%s\"))", resourcesFieldName, parameterName);
 178  
 
 179  176
             String bindingFactoryFieldName = transformation.addInjectedField(BindingSource.class, "bindingSource",
 180  
                                                                              bindingSource);
 181  
 
 182  176
             builder
 183  
                     .addln("  %s.bindParameter(\"%s\", %s.newBinding(\"default %2$s\", %1$s, \"%s\", \"%s\"));",
 184  
                            resourcesFieldName, parameterName, bindingFactoryFieldName, defaultPrefix, defaultBinding);
 185  
 
 186  176
             return;
 187  
         }
 188  
 
 189  704
         if (autoconnect)
 190  
         {
 191  48
             String defaultProviderFieldName = transformation.addInjectedField(ComponentDefaultProvider.class,
 192  
                                                                               "defaultProvider", defaultProvider);
 193  
 
 194  48
             builder.addln("if (! %s.isBound(\"%s\"))", resourcesFieldName, parameterName);
 195  
 
 196  48
             builder.addln("  %s.bindParameter(\"%s\", %s.defaultBinding(\"%s\", %s));", resourcesFieldName,
 197  
                           parameterName, defaultProviderFieldName, parameterName, resourcesFieldName);
 198  48
             return;
 199  
         }
 200  
 
 201  
         // If no default binding expression provided in the annotation, then look for a default
 202  
         // binding method to provide the binding.
 203  
 
 204  656
         final String methodName = "default" + parameterName;
 205  
 
 206  656
         MethodFilter filter = new MethodFilter()
 207  
         {
 208  656
             public boolean accept(TransformMethodSignature signature)
 209  
             {
 210  21512
                 return signature.getParameterTypes().length == 0
 211  
                         && signature.getMethodName().equalsIgnoreCase(methodName);
 212  
             }
 213  
         };
 214  
 
 215  
         // This will match exactly 0 or 1 methods, and if it matches, we know the name
 216  
         // of the method.
 217  
 
 218  656
         List<TransformMethodSignature> signatures = transformation.findMethods(filter);
 219  
 
 220  656
         if (signatures.isEmpty()) return;
 221  
 
 222  
         // Because the check was case-insensitive, we need to determine the actual
 223  
         // name.
 224  
 
 225  186
         String actualMethodName = signatures.get(0).getMethodName();
 226  
 
 227  186
         builder.addln("if (! %s.isBound(\"%s\"))", resourcesFieldName, parameterName);
 228  186
         builder.addln("  %s(\"%s\", %s, ($w) %s());",
 229  
                       BIND_METHOD_NAME,
 230  
                       parameterName,
 231  
                       resourcesFieldName,
 232  
                       actualMethodName);
 233  186
     }
 234  
 
 235  
     private void addWriterMethod(String fieldName, String cachedFieldName, String accessFieldName, boolean cache,
 236  
                                  String parameterName,
 237  
                                  String fieldType, String resourcesFieldName,
 238  
                                  ClassTransformation transformation)
 239  
     {
 240  880
         BodyBuilder builder = new BodyBuilder();
 241  880
         builder.begin();
 242  
 
 243  
         // Before the component is loaded, updating the property sets the default value
 244  
         // for the parameter. The value is stored in the field, but will be
 245  
         // rolled into default field inside containingPageDidLoad().
 246  
 
 247  880
         builder.addln("if (! %s.isLoaded())", resourcesFieldName);
 248  880
         builder.begin();
 249  880
         builder.addln("%s = $1;", fieldName);
 250  880
         builder.addln("return;");
 251  880
         builder.end();
 252  
 
 253  
         // Always start by updating the parameter; this will implicitly check for
 254  
         // read-only or unbound parameters. $1 is the single parameter
 255  
         // to the method.
 256  
 
 257  880
         builder.addln("%s.write(($w)$1);", accessFieldName);
 258  
 
 259  880
         builder.addln("%s = $1;", fieldName);
 260  
 
 261  880
         if (cache) builder.addln("%s = %s.isRendering();", cachedFieldName, resourcesFieldName);
 262  
 
 263  880
         builder.end();
 264  
 
 265  880
         String methodName = transformation.newMemberName("update_parameter", parameterName);
 266  
 
 267  880
         TransformMethodSignature signature = new TransformMethodSignature(Modifier.PRIVATE, "void", methodName,
 268  
                                                                           new String[] {fieldType}, null);
 269  
 
 270  880
         transformation.addMethod(signature, builder.toString());
 271  
 
 272  880
         transformation.replaceWriteAccess(fieldName, methodName);
 273  880
     }
 274  
 
 275  
     /**
 276  
      * Adds a private method that will be the replacement for read-access to the field.
 277  
      */
 278  
     private void addReaderMethod(String fieldName, String cachedFieldName, String accessFieldName, boolean cache,
 279  
                                  String parameterName, String fieldType, String resourcesFieldName,
 280  
                                  ClassTransformation transformation)
 281  
     {
 282  880
         BodyBuilder builder = new BodyBuilder();
 283  880
         builder.begin();
 284  
 
 285  
         // While the component is still loading, or when the value for the component is cached,
 286  
         // or if the value is not bound, then return the current value of the field.
 287  
 
 288  880
         builder.addln("if (%s || ! %s.isLoaded() || ! %s.isBound()) return %s;", cachedFieldName,
 289  
                       resourcesFieldName, accessFieldName, fieldName);
 290  
 
 291  880
         String cast = TransformUtils.getWrapperTypeName(fieldType);
 292  
 
 293  
         // The ($r) cast will convert the result to the method return type; generally
 294  
         // this does nothing. but for primitive types, it will unwrap
 295  
         // the wrapper type back to a primitive.  We pass the desired type name
 296  
         // to readParameter(), since its easier to convert it properly to
 297  
         // a type on that end than in the generated code.
 298  
 
 299  880
         builder.addln("%s result = ($r) ((%s) %s.read(\"%2$s\"));", fieldType, cast, accessFieldName);
 300  
 
 301  
         // If the binding is invariant, then it's ok to cache. Othewise, its only
 302  
         // ok to cache if a) the @Parameter says to cache and b) the component
 303  
         // is rendering at the point when field is accessed.
 304  
 
 305  880
         builder.add("if (%s.isInvariant()", accessFieldName);
 306  
 
 307  880
         if (cache) builder.add(" || %s.isRendering()", resourcesFieldName);
 308  
 
 309  880
         builder.addln(")");
 310  880
         builder.begin();
 311  880
         builder.addln("%s = result;", fieldName);
 312  880
         builder.addln("%s = true;", cachedFieldName);
 313  880
         builder.end();
 314  
 
 315  880
         builder.addln("return result;");
 316  880
         builder.end();
 317  
 
 318  880
         String methodName = transformation.newMemberName("read_parameter", parameterName);
 319  
 
 320  880
         TransformMethodSignature signature = new TransformMethodSignature(Modifier.PRIVATE, fieldType, methodName, null,
 321  
                                                                           null);
 322  
 
 323  880
         transformation.addMethod(signature, builder.toString());
 324  
 
 325  880
         transformation.replaceReadAccess(fieldName, methodName);
 326  880
     }
 327  
 
 328  
     private String getParameterName(String fieldName, String annotatedName)
 329  
     {
 330  880
         if (InternalUtils.isNonBlank(annotatedName)) return annotatedName;
 331  
 
 332  848
         return InternalUtils.stripMemberName(fieldName);
 333  
     }
 334  
 
 335  
     /**
 336  
      * Invoked from generated code as part of the handling of parameter default methods.
 337  
      */
 338  
     public static void bind(String parameterName, InternalComponentResources resources, Object value)
 339  
     {
 340  1480
         if (value == null) return;
 341  
 
 342  1368
         if (value instanceof Binding)
 343  
         {
 344  254
             Binding binding = (Binding) value;
 345  
 
 346  254
             resources.bindParameter(parameterName, binding);
 347  254
             return;
 348  
         }
 349  
 
 350  1114
         resources.bindParameter(parameterName, new LiteralBinding(null, "default " + parameterName, value));
 351  1114
     }
 352  
 }