001    // Copyright 2010, 2011 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    // http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry5.internal.transform;
016    
017    import org.apache.tapestry5.annotations.PageReset;
018    import org.apache.tapestry5.func.*;
019    import org.apache.tapestry5.internal.InternalComponentResources;
020    import org.apache.tapestry5.internal.structure.PageResetListener;
021    import org.apache.tapestry5.model.MutableComponentModel;
022    import org.apache.tapestry5.plastic.*;
023    import org.apache.tapestry5.services.TransformConstants;
024    import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
025    import org.apache.tapestry5.services.transform.TransformationSupport;
026    
027    /**
028     * Implementation of the {@link PageReset} annotation. Makes the component implement {@link PageResetListener}.
029     *
030     * @since 5.2.0
031     */
032    public class PageResetAnnotationWorker implements ComponentClassTransformWorker2
033    {
034        private static final String META_KEY = "tapestry.page-reset-listener";
035    
036        private final ConstructorCallback REGISTER_AS_LISTENER = new ConstructorCallback()
037        {
038            public void onConstruct(Object instance, InstanceContext context)
039            {
040                InternalComponentResources resources = context.get(InternalComponentResources.class);
041    
042                resources.addPageResetListener((PageResetListener) instance);
043            }
044        };
045    
046        private final Predicate<PlasticMethod> METHOD_MATCHER = new Predicate<PlasticMethod>()
047        {
048            public boolean accept(PlasticMethod method)
049            {
050                return method.getDescription().methodName.equalsIgnoreCase("pageReset") ||
051                        method.hasAnnotation(PageReset.class);
052            }
053        };
054    
055        private final Worker<PlasticMethod> METHOD_VALIDATOR = new Worker<PlasticMethod>()
056        {
057            public void work(PlasticMethod method)
058            {
059                boolean valid = method.isVoid() && method.getParameters().isEmpty();
060    
061                if (!valid)
062                {
063                    throw new RuntimeException(
064                            String.format(
065                                    "Method %s is invalid: methods with the @PageReset annotation must return void, and have no parameters.",
066                                    method.getMethodIdentifier()));
067                }
068            }
069        };
070    
071        private final Mapper<PlasticMethod, MethodHandle> TO_HANDLE = new Mapper<PlasticMethod, MethodHandle>()
072        {
073            public MethodHandle map(PlasticMethod method)
074            {
075                return method.getHandle();
076            }
077        };
078    
079        public void transform(PlasticClass plasticClass, TransformationSupport support, MutableComponentModel model)
080        {
081            Flow<PlasticMethod> methods = findResetMethods(plasticClass);
082    
083            if (!methods.isEmpty())
084            {
085                if (!plasticClass.isInterfaceImplemented(PageResetListener.class))
086                {
087                    plasticClass.introduceInterface(PageResetListener.class);
088                    plasticClass.onConstruct(REGISTER_AS_LISTENER);
089                }
090    
091                invokeMethodsOnPageReset(plasticClass, methods);
092            }
093        }
094    
095        private void invokeMethodsOnPageReset(PlasticClass plasticClass, Flow<PlasticMethod> methods)
096        {
097            final MethodHandle[] handles = methods.map(TO_HANDLE).toArray(MethodHandle.class);
098    
099            plasticClass.introduceMethod(TransformConstants.CONTAINING_PAGE_DID_RESET_DESCRIPTION).addAdvice(new MethodAdvice()
100            {
101                public void advise(MethodInvocation invocation)
102                {
103                    invocation.proceed();
104    
105                    Object instance = invocation.getInstance();
106    
107                    for (MethodHandle handle : handles)
108                    {
109                        handle.invoke(instance);
110                    }
111                }
112            });
113        }
114    
115        private Flow<PlasticMethod> findResetMethods(PlasticClass plasticClass)
116        {
117            return F.flow(plasticClass.getMethods()).filter(METHOD_MATCHER).each(METHOD_VALIDATOR);
118        }
119    }