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
015package org.apache.tapestry5.internal.transform;
016
017import org.apache.tapestry5.annotations.PageReset;
018import org.apache.tapestry5.func.*;
019import org.apache.tapestry5.internal.InternalComponentResources;
020import org.apache.tapestry5.internal.structure.PageResetListener;
021import org.apache.tapestry5.model.MutableComponentModel;
022import org.apache.tapestry5.plastic.*;
023import org.apache.tapestry5.services.TransformConstants;
024import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
025import 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 */
032public 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}