Coverage Report - org.apache.tapestry5.internal.transform.RenderPhaseMethodWorker
 
Classes in this File Line Coverage Branch Coverage Complexity
RenderPhaseMethodWorker
98%
39/40
100%
18/18
0
RenderPhaseMethodWorker$1
100%
5/5
90%
9/10
0
 
 1  
 // Copyright 2006, 2007, 2008 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.MarkupWriter;
 18  
 import org.apache.tapestry5.internal.util.MethodInvocationBuilder;
 19  
 import org.apache.tapestry5.ioc.internal.util.InternalUtils;
 20  
 import org.apache.tapestry5.ioc.util.BodyBuilder;
 21  
 import org.apache.tapestry5.model.MutableComponentModel;
 22  
 import org.apache.tapestry5.services.ClassTransformation;
 23  
 import org.apache.tapestry5.services.ComponentClassTransformWorker;
 24  
 import org.apache.tapestry5.services.MethodFilter;
 25  
 import org.apache.tapestry5.services.TransformMethodSignature;
 26  
 
 27  
 import java.lang.annotation.Annotation;
 28  
 import java.util.Iterator;
 29  
 import java.util.List;
 30  
 
 31  
 /**
 32  
  * Converts one of the methods of {@link org.apache.tapestry5.runtime.Component} into a chain of command that, itself,
 33  
  * invokes certain methods (render phase methods) marked with an annotation, or named in a specific way.
 34  
  */
 35  402658
 public class RenderPhaseMethodWorker implements ComponentClassTransformWorker
 36  
 {
 37  
     private static final String CHECK_ABORT_FLAG = "if ($2.isAborted()) return;";
 38  
 
 39  
     private final Class<? extends Annotation> methodAnnotation;
 40  
 
 41  
     private final TransformMethodSignature lifecycleMethodSignature;
 42  
 
 43  
     private final String lifecycleMethodName;
 44  
 
 45  
     private final boolean reverse;
 46  
 
 47  468
     private final MethodInvocationBuilder invocationBuilder = new MethodInvocationBuilder();
 48  
 
 49  
     /**
 50  
      * Normal method invocation: parent class, then methods in ascending alphabetical order. Reverse order: method in
 51  
      * descending alphabetical order, then parent class.
 52  
      *
 53  
      * @param lifecycleMethodSignature the signature of the method to be implemented in the component class
 54  
      * @param methodAnnotation         the class of the corresponding annotation
 55  
      * @param reverse                  if true, the normal method invocation order is reversed
 56  
      */
 57  
     public RenderPhaseMethodWorker(TransformMethodSignature lifecycleMethodSignature,
 58  
                                    Class<? extends Annotation> methodAnnotation, boolean reverse)
 59  468
     {
 60  468
         this.lifecycleMethodSignature = lifecycleMethodSignature;
 61  468
         this.methodAnnotation = methodAnnotation;
 62  468
         this.reverse = reverse;
 63  468
         lifecycleMethodName = lifecycleMethodSignature.getMethodName();
 64  
 
 65  
         // If we ever add more parameters to the methods, then we can add more to the invocation
 66  
         // builder.
 67  
         // *Never* expose the Event parameter ($2), it is for internal use only.
 68  
 
 69  468
         invocationBuilder.addParameter(MarkupWriter.class.getName(), "$1");
 70  468
     }
 71  
 
 72  
     @Override
 73  
     public String toString()
 74  
     {
 75  0
         return String.format("RenderPhaseMethodWorker[%s]", methodAnnotation.getName());
 76  
     }
 77  
 
 78  
     public void transform(final ClassTransformation transformation, MutableComponentModel model)
 79  
     {
 80  6500
         MethodFilter filter = new MethodFilter()
 81  
         {
 82  
             public boolean accept(TransformMethodSignature signature)
 83  
             {
 84  
                 // These methods get added to base classes and otherwise fall into this filter. If
 85  
                 // we don't include this filter, then we get endless loops.
 86  
 
 87  137950
                 if (signature.equals(lifecycleMethodSignature)) return false;
 88  
 
 89  
                 // A degenerate case would be a method, say beginRender(), with an conflicting
 90  
                 // annotation, say @AfterRender. In that case, this code is broken, as the method
 91  
                 // will be invoked for both phases!
 92  
 
 93  132588
                 return (correctName(signature) || correctAnnotation(signature)) &&
 94  
                         !transformation.isMethodOverride(signature);
 95  
             }
 96  
 
 97  
             private boolean correctAnnotation(TransformMethodSignature signature)
 98  
             {
 99  132120
                 return transformation.getMethodAnnotation(signature, methodAnnotation) != null;
 100  
             }
 101  
 
 102  6500
             private boolean correctName(TransformMethodSignature signature)
 103  
             {
 104  132588
                 return signature.getMethodName().equals(lifecycleMethodName);
 105  
             }
 106  
         };
 107  
 
 108  6500
         List<TransformMethodSignature> methods = transformation.findMethods(filter);
 109  
 
 110  
         // Except in the root class, don't bother to add a new method unless there's something to
 111  
         // call (beside super).
 112  
 
 113  6500
         if (methods.isEmpty()) return;
 114  
 
 115  610
         model.addRenderPhase(methodAnnotation);
 116  
 
 117  610
         BodyBuilder builder = new BodyBuilder();
 118  610
         builder.begin();
 119  
 
 120  
         // If in a subclass, and in normal order mode, invoke the super class version first.
 121  
 
 122  610
         if (!(reverse || model.isRootClass()))
 123  
         {
 124  68
             builder.addln("super.%s($$);", lifecycleMethodName);
 125  68
             builder.addln(CHECK_ABORT_FLAG);
 126  
         }
 127  
 
 128  610
         Iterator<TransformMethodSignature> i = reverse ? InternalUtils.reverseIterator(methods) : methods
 129  
                 .iterator();
 130  
 
 131  610
         builder.addln("try");
 132  610
         builder.begin();
 133  
 
 134  1220
         while (i.hasNext())
 135  610
             addMethodCallToBody(builder, i.next(), transformation);
 136  
 
 137  
         // In reverse order in a a subclass, invoke the super method last.
 138  
 
 139  610
         if (reverse && !model.isRootClass()) builder.addln("super.%s($$);", lifecycleMethodName);
 140  
 
 141  
 
 142  610
         builder.end(); // try
 143  
 
 144  
         // Let runtime exceptions work up (they'll be caught at a higher level.
 145  
         // Wrap checked exceptions for later reporting.
 146  
 
 147  610
         builder.addln("catch (RuntimeException ex) { throw ex; }");
 148  610
         builder.addln("catch (Exception ex) { throw new RuntimeException(ex); }");
 149  
 
 150  610
         builder.end();
 151  
 
 152  
         // Let's see if this works; for base classes, we are adding an empty method the adding a
 153  
         // non-empty
 154  
         // method "on top of it".
 155  
 
 156  610
         transformation.addMethod(lifecycleMethodSignature, builder.toString());
 157  610
     }
 158  
 
 159  
 
 160  
     private void addMethodCallToBody(BodyBuilder builder, TransformMethodSignature sig,
 161  
                                      ClassTransformation transformation)
 162  
     {
 163  610
         boolean isVoid = sig.getReturnType().equals("void");
 164  
 
 165  610
         builder.addln("$2.setMethodDescription(\"%s\");", transformation.getMethodIdentifier(sig));
 166  
 
 167  610
         if (!isVoid)
 168  
         {
 169  
             // If we're not going to invoke storeResult(), then there's no reason to invoke
 170  
             // setMethodDescription().
 171  
 
 172  116
             builder.add("if ($2.storeResult(($w) ");
 173  
         }
 174  
 
 175  
         // This is the best part; the method can even be private and this still works. It's a lot
 176  
         // like how javac enables access to private members for inner classes (by introducing
 177  
         // synthetic, static methods).
 178  
 
 179  610
         builder.add(invocationBuilder.buildMethodInvocation(sig, transformation));
 180  
 
 181  
         // Now, if non void ...
 182  
 
 183  610
         if (!isVoid)
 184  
         {
 185  
             // Complete the call to storeResult(). If storeResult() returns true, then
 186  
             // the event is aborted and no further processing is required.
 187  
 
 188  116
             builder.addln(")) return;");
 189  
         }
 190  
         else
 191  494
             builder.addln(";");
 192  610
     }
 193  
 }