001    //  Copyright 2008 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.services.ClassFab;
020    import org.apache.tapestry5.ioc.services.ClassFabUtils;
021    import org.apache.tapestry5.ioc.services.ClassFactory;
022    import org.apache.tapestry5.ioc.services.MethodSignature;
023    import org.apache.tapestry5.ioc.util.BodyBuilder;
024    
025    import java.lang.reflect.Method;
026    import java.lang.reflect.Modifier;
027    import java.util.List;
028    import java.util.concurrent.atomic.AtomicLong;
029    
030    /**
031     * Manages a single method of an advised service, responsible for constructing a subclass of {@link
032     * org.apache.tapestry5.ioc.internal.services.AbstractInvocation}.
033     */
034    public class AdvisedMethodInvocationBuilder
035    {
036        /**
037         * Parameters of the invocation are stored as fields name "p0", "p1", etc.
038         */
039        private static final String PARAMETER_FIELD = "p";
040    
041        private static final String DELEGATE_FIELD_NAME = "delegate";
042    
043        private static final int PRIVATE_FINAL = Modifier.PRIVATE | Modifier.FINAL;
044    
045        private static final MethodSignature GET_PARAMETER_SIGNATURE = new MethodSignature(Object.class, "getParameter",
046                                                                                           new Class[] {int.class}, null);
047    
048        private static final MethodSignature OVERRIDE_SIGNATURE = new MethodSignature(void.class, "override",
049                                                                                      new Class[] {int.class, Object.class},
050                                                                                      null);
051    
052        private static final MethodSignature INVOKE_DELEGATE_METHOD_SIGNATURE = new MethodSignature(void.class,
053                                                                                                    "invokeDelegateMethod",
054                                                                                                    null, null);
055    
056        private static final AtomicLong UID_GENERATOR = new AtomicLong(System.currentTimeMillis());
057    
058        private final Class serviceInterface;
059    
060        private final Method method;
061    
062        private final MethodInfo info;
063    
064        private final ClassFab classFab;
065    
066        public AdvisedMethodInvocationBuilder(ClassFactory classFactory, Class serviceInterface, Method method)
067        {
068            this.serviceInterface = serviceInterface;
069            this.method = method;
070    
071            info = new MethodInfo(method);
072    
073            String name = "Invocation$" + serviceInterface.getSimpleName() +
074                    "$" + method.getName() +
075                    "$" + Long.toHexString(UID_GENERATOR.getAndIncrement());
076    
077            classFab = classFactory.newClass(name, AbstractInvocation.class);
078    
079            addInfrastructure();
080            addGetParameter();
081            addOverride();
082            addInvokeDelegateMethod();
083    
084            classFab.addToString(String.format("<Method invocation %s>", method));
085        }
086    
087        private void addInfrastructure()
088        {
089            List<Class> constructorTypes = CollectionFactory.newList();
090    
091            // First two parameters are fixed:
092    
093            // Passed to the AbstractInvocation base class
094            constructorTypes.add(MethodInfo.class);
095            BodyBuilder constructorBuilder = new BodyBuilder().begin().addln("super($1);");
096    
097            // Stored for chaining purposes.
098            classFab.addField(DELEGATE_FIELD_NAME, PRIVATE_FINAL, serviceInterface);
099            constructorTypes.add(serviceInterface);
100            constructorBuilder.addln("%s = $2;", DELEGATE_FIELD_NAME);
101    
102            // Now, a field for each method parameter. 
103            for (int i = 0; i < method.getParameterTypes().length; i++)
104            {
105                Class type = method.getParameterTypes()[i];
106    
107                String name = PARAMETER_FIELD + i;
108    
109                classFab.addField(name, type);
110    
111                constructorTypes.add(type);
112    
113                // $0 is this
114                // $1 is MethodInfo
115                // $2 is delegate
116                // $3 is first method parameter ...
117    
118                constructorBuilder.addln("%s = $%d;", name, i + 3);
119            }
120    
121            constructorBuilder.end();
122    
123            Class[] typesArray = constructorTypes.toArray(new Class[constructorTypes.size()]);
124    
125            classFab.addConstructor(typesArray, null, constructorBuilder.toString());
126        }
127    
128        private void addGetParameter()
129        {
130            Class[] parameterTypes = method.getParameterTypes();
131    
132            BodyBuilder builder = new BodyBuilder().begin();
133    
134            builder.addln("switch ($1)").begin();
135    
136            for (int i = 0; i < parameterTypes.length; i++)
137            {
138                // ($w) will wrap a primitive as a wrapper type
139                builder.addln("case %d: return ($w) %s%d;", i, PARAMETER_FIELD, i);
140            }
141    
142            builder.addln("default: throw new IllegalArgumentException(\"Parameter index out of range.\");");
143    
144            builder.end().end(); // switch and method
145    
146            classFab.addMethod(Modifier.PUBLIC, GET_PARAMETER_SIGNATURE, builder.toString());
147        }
148    
149        private void addOverride()
150        {
151            Class[] parameterTypes = method.getParameterTypes();
152    
153            BodyBuilder builder = new BodyBuilder().begin();
154    
155            builder.addln("switch ($1)").begin();
156    
157            for (int i = 0; i < parameterTypes.length; i++)
158            {
159                Class type = parameterTypes[i];
160                String typeName = ClassFabUtils.toJavaClassName(type);
161    
162                builder.addln("case %d: %s%d = %s; return;",
163                              i, PARAMETER_FIELD, i,
164                              ClassFabUtils.castReference("$2", typeName));
165            }
166    
167            builder.addln("default: throw new IllegalArgumentException(\"Parameter index out of range.\");");
168    
169            builder.end().end(); // switch and method
170    
171            classFab.addMethod(Modifier.PUBLIC, OVERRIDE_SIGNATURE, builder.toString());
172        }
173    
174        private void addInvokeDelegateMethod()
175        {
176            Class returnType = method.getReturnType();
177            Class[] exceptionTypes = method.getExceptionTypes();
178    
179            boolean isNonVoid = !returnType.equals(void.class);
180            boolean hasChecked = exceptionTypes.length > 0;
181    
182            BodyBuilder builder = new BodyBuilder().begin();
183    
184            if (hasChecked) builder.addln("try").begin();
185    
186            if (isNonVoid)
187                builder.add("%s result = ", ClassFabUtils.toJavaClassName(returnType));
188    
189            builder.add("%s.%s(", DELEGATE_FIELD_NAME, method.getName());
190    
191            for (int i = 0; i < method.getParameterTypes().length; i++)
192            {
193                if (i > 0) builder.add(", ");
194    
195                builder.add(PARAMETER_FIELD + i);
196            }
197    
198            builder.addln(");"); // Call on delegate
199    
200            if (isNonVoid)
201            {
202                builder.add("overrideResult(($w) result);");
203            }
204    
205            if (hasChecked)
206            {
207                builder.end();   // try
208    
209                for (Class exception : exceptionTypes)
210                {
211                    builder.addln("catch (%s ex) { overrideThrown(ex); }", exception.getName());
212                }
213            }
214    
215            builder.end(); // method
216    
217            classFab.addMethod(Modifier.PUBLIC, INVOKE_DELEGATE_METHOD_SIGNATURE, builder.toString());
218        }
219    
220        public void addAdvice(MethodAdvice advice)
221        {
222            info.addAdvice(advice);
223        }
224    
225        /**
226         * Invoked at the end of construction of the interceptor to intercept the method invocation and hook it into the
227         * advice.
228         *
229         * @param interceptorClassFab classfab for the service interceptor under construction
230         * @param injector            allows constant values to be injected into the interceptor class as final fields
231         */
232        public void commit(ClassFab interceptorClassFab, String delegateFieldName, ConstantInjector injector)
233        {
234            Class invocationClass = classFab.createClass();
235    
236            BodyBuilder builder = new BodyBuilder().begin();
237    
238            builder.addln("%s invocation = new %<s(%s, %s, $$);",
239                          invocationClass.getName(),
240                          injector.inject(MethodInfo.class, info),
241                          delegateFieldName);
242    
243            builder.addln("invocation.proceed();");
244    
245            Class[] exceptionTypes = method.getExceptionTypes();
246    
247            builder.addln("if (invocation.isFail())").begin();
248    
249            for (Class exceptionType : exceptionTypes)
250            {
251                String name = exceptionType.getSimpleName().toLowerCase();
252    
253                String exceptionTypeFieldName = injector.inject(Class.class, exceptionType);
254    
255                builder.addln("%s %s = (%s) invocation.getThrown(%s);", exceptionType.getName(), name,
256                              exceptionType.getName(), exceptionTypeFieldName);
257                builder.addln("if (%s != null) throw %s;", name, name);
258            }
259    
260            builder.addln(
261                    "throw new IllegalStateException(\"Impossible exception thrown from intercepted invocation.\");");
262    
263            builder.end(); // if fail
264    
265            builder.addln("return ($r) invocation.getResult();");
266    
267            builder.end();
268    
269            interceptorClassFab.addMethod(Modifier.PUBLIC, new MethodSignature(method), builder.toString());
270        }
271    }