001    // Copyright 2008, 2009 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.ioc.internal.services;
016    
017    import org.apache.tapestry5.ioc.MethodAdvice;
018    import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
019    import org.apache.tapestry5.ioc.internal.util.Defense;
020    import org.apache.tapestry5.ioc.internal.util.OneShotLock;
021    import org.apache.tapestry5.ioc.services.*;
022    
023    import java.lang.reflect.Constructor;
024    import java.lang.reflect.Method;
025    import java.lang.reflect.Modifier;
026    import java.util.Arrays;
027    import java.util.Map;
028    import java.util.Set;
029    
030    public class AspectInterceptorBuilderImpl<T> implements AspectInterceptorBuilder<T>
031    {
032        private final ClassFactory classFactory;
033    
034        private final Class<T> serviceInterface;
035    
036        private final ClassFab interceptorFab;
037    
038        private final ConstantInjectorImpl injector;
039    
040        private final String delegateFieldName;
041    
042        private final String description;
043    
044        private final OneShotLock lock = new OneShotLock();
045    
046        private final Set<Method> remainingMethods = CollectionFactory.newSet();
047    
048        private final Map<Method, AdvisedMethodInvocationBuilder> methodToBuilder = CollectionFactory.newMap();
049    
050        /**
051         * Set to true if we ever see toString() as a method of the interface; either advised or pass thru. If false at the
052         * end, we add our own implementation.
053         */
054        private boolean sawToString;
055    
056        public AspectInterceptorBuilderImpl(ClassFactory classFactory, Class<T> serviceInterface, T delegate,
057                                            String description)
058        {
059            this.classFactory = classFactory;
060            this.serviceInterface = serviceInterface;
061            this.description = description;
062    
063            interceptorFab = this.classFactory.newClass(serviceInterface);
064    
065            injector = new ConstantInjectorImpl(interceptorFab);
066    
067            delegateFieldName = injector.inject(serviceInterface, delegate);
068    
069            remainingMethods.addAll(Arrays.asList(serviceInterface.getMethods()));
070        }
071    
072        public void adviseMethod(Method method, MethodAdvice advice)
073        {
074            Defense.notNull(method, "method");
075            Defense.notNull(advice, "advice");
076    
077            lock.check();
078    
079            AdvisedMethodInvocationBuilder builder = methodToBuilder.get(method);
080    
081            if (builder == null)
082            {
083                if (!remainingMethods.contains(method))
084                    throw new IllegalArgumentException(
085                            String.format("Method %s is not defined for interface %s.", method, serviceInterface));
086    
087                // One less method to pass thru to the delegate
088    
089                remainingMethods.remove(method);
090    
091                sawToString |= ClassFabUtils.isToString(method);
092    
093                builder = new AdvisedMethodInvocationBuilder(classFactory, serviceInterface, method);
094    
095                methodToBuilder.put(method, builder);
096            }
097    
098            builder.addAdvice(advice);
099        }
100    
101        public void adviseAllMethods(MethodAdvice advice)
102        {
103            for (Method m : serviceInterface.getMethods())
104                adviseMethod(m, advice);
105        }
106    
107        public Class getInterface()
108        {
109            return serviceInterface;
110        }
111    
112        public T build()
113        {
114            lock.lock();
115    
116            // Finish up each method that has been advised
117    
118            for (AdvisedMethodInvocationBuilder builder : methodToBuilder.values())
119            {
120                builder.commit(interceptorFab, delegateFieldName, injector);
121            }
122    
123            // Hit all the methods that haven't been referenced so far.
124    
125            addPassthruMethods();
126    
127            // And if we haven't seen a toString(), we can add it now.
128    
129            if (!sawToString)
130                interceptorFab.addToString(description);
131    
132            injector.implementConstructor();
133    
134            Class interceptorClass = interceptorFab.createClass();
135    
136            Object[] parameters = injector.getParameters();
137    
138            try
139            {
140                Constructor constructor = interceptorClass.getConstructors()[0];
141    
142                Object raw = constructor.newInstance(parameters);
143    
144                return serviceInterface.cast(raw);
145            }
146            catch (Exception ex)
147            {
148                throw new RuntimeException(ex);
149            }
150        }
151    
152        private void addPassthruMethods()
153        {
154            for (Method m : remainingMethods)
155            {
156                sawToString |= ClassFabUtils.isToString(m);
157    
158                MethodSignature sig = new MethodSignature(m);
159    
160                String body = String.format("return ($r) %s.%s($$);", delegateFieldName, m.getName());
161    
162                interceptorFab.addMethod(Modifier.PUBLIC, sig, body);
163            }
164        }
165    }