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 }