blob: 122eb12f51552973d1571736befddbb8f506470b [file] [log] [blame]
// Copyright 2008 The Apache Software Foundation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package org.apache.tapestry5.ioc.internal.services;
import org.apache.tapestry5.ioc.MethodAdvice;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.services.ClassFab;
import org.apache.tapestry5.ioc.services.ClassFabUtils;
import org.apache.tapestry5.ioc.services.ClassFactory;
import org.apache.tapestry5.ioc.services.MethodSignature;
import org.apache.tapestry5.ioc.util.BodyBuilder;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
/**
* Manages a single method of an advised service, responsible for constructing a subclass of {@link
* org.apache.tapestry5.ioc.internal.services.AbstractInvocation}.
*/
public class AdvisedMethodInvocationBuilder
{
/**
* Parameters of the invocation are stored as fields name "p0", "p1", etc.
*/
private static final String PARAMETER_FIELD = "p";
private static final String DELEGATE_FIELD_NAME = "delegate";
private static final int PRIVATE_FINAL = Modifier.PRIVATE | Modifier.FINAL;
private static final MethodSignature GET_PARAMETER_SIGNATURE = new MethodSignature(Object.class, "getParameter",
new Class[] {int.class}, null);
private static final MethodSignature OVERRIDE_SIGNATURE = new MethodSignature(void.class, "override",
new Class[] {int.class, Object.class},
null);
private static final MethodSignature INVOKE_DELEGATE_METHOD_SIGNATURE = new MethodSignature(void.class,
"invokeDelegateMethod",
null, null);
private static final AtomicLong UID_GENERATOR = new AtomicLong(System.currentTimeMillis());
private final Class serviceInterface;
private final Method method;
private final MethodInfo info;
private final ClassFab classFab;
public AdvisedMethodInvocationBuilder(ClassFactory classFactory, Class serviceInterface, Method method)
{
this.serviceInterface = serviceInterface;
this.method = method;
info = new MethodInfo(method);
String name = "Invocation$" + serviceInterface.getSimpleName() +
"$" + method.getName() +
"$" + Long.toHexString(UID_GENERATOR.getAndIncrement());
classFab = classFactory.newClass(name, AbstractInvocation.class);
addInfrastructure();
addGetParameter();
addOverride();
addInvokeDelegateMethod();
classFab.addToString(String.format("<Method invocation %s>", method));
}
private void addInfrastructure()
{
List<Class> constructorTypes = CollectionFactory.newList();
// First two parameters are fixed:
// Passed to the AbstractInvocation base class
constructorTypes.add(MethodInfo.class);
BodyBuilder constructorBuilder = new BodyBuilder().begin().addln("super($1);");
// Stored for chaining purposes.
classFab.addField(DELEGATE_FIELD_NAME, PRIVATE_FINAL, serviceInterface);
constructorTypes.add(serviceInterface);
constructorBuilder.addln("%s = $2;", DELEGATE_FIELD_NAME);
// Now, a field for each method parameter.
for (int i = 0; i < method.getParameterTypes().length; i++)
{
Class type = method.getParameterTypes()[i];
String name = PARAMETER_FIELD + i;
classFab.addField(name, type);
constructorTypes.add(type);
// $0 is this
// $1 is MethodInfo
// $2 is delegate
// $3 is first method parameter ...
constructorBuilder.addln("%s = $%d;", name, i + 3);
}
constructorBuilder.end();
Class[] typesArray = constructorTypes.toArray(new Class[constructorTypes.size()]);
classFab.addConstructor(typesArray, null, constructorBuilder.toString());
}
private void addGetParameter()
{
Class[] parameterTypes = method.getParameterTypes();
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("switch ($1)").begin();
for (int i = 0; i < parameterTypes.length; i++)
{
// ($w) will wrap a primitive as a wrapper type
builder.addln("case %d: return ($w) %s%d;", i, PARAMETER_FIELD, i);
}
builder.addln("default: throw new IllegalArgumentException(\"Parameter index out of range.\");");
builder.end().end(); // switch and method
classFab.addMethod(Modifier.PUBLIC, GET_PARAMETER_SIGNATURE, builder.toString());
}
private void addOverride()
{
Class[] parameterTypes = method.getParameterTypes();
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("switch ($1)").begin();
for (int i = 0; i < parameterTypes.length; i++)
{
Class type = parameterTypes[i];
String typeName = ClassFabUtils.toJavaClassName(type);
builder.addln("case %d: %s%d = %s; return;",
i, PARAMETER_FIELD, i,
ClassFabUtils.castReference("$2", typeName));
}
builder.addln("default: throw new IllegalArgumentException(\"Parameter index out of range.\");");
builder.end().end(); // switch and method
classFab.addMethod(Modifier.PUBLIC, OVERRIDE_SIGNATURE, builder.toString());
}
private void addInvokeDelegateMethod()
{
Class returnType = method.getReturnType();
Class[] exceptionTypes = method.getExceptionTypes();
boolean isNonVoid = !returnType.equals(void.class);
boolean hasChecked = exceptionTypes.length > 0;
BodyBuilder builder = new BodyBuilder().begin();
if (hasChecked) builder.addln("try").begin();
if (isNonVoid)
builder.add("%s result = ", ClassFabUtils.toJavaClassName(returnType));
builder.add("%s.%s(", DELEGATE_FIELD_NAME, method.getName());
for (int i = 0; i < method.getParameterTypes().length; i++)
{
if (i > 0) builder.add(", ");
builder.add(PARAMETER_FIELD + i);
}
builder.addln(");"); // Call on delegate
if (isNonVoid)
{
builder.add("overrideResult(($w) result);");
}
if (hasChecked)
{
builder.end(); // try
for (Class exception : exceptionTypes)
{
builder.addln("catch (%s ex) { overrideThrown(ex); }", exception.getName());
}
}
builder.end(); // method
classFab.addMethod(Modifier.PUBLIC, INVOKE_DELEGATE_METHOD_SIGNATURE, builder.toString());
}
public void addAdvice(MethodAdvice advice)
{
info.addAdvice(advice);
}
/**
* Invoked at the end of construction of the interceptor to intercept the method invocation and hook it into the
* advice.
*
* @param interceptorClassFab classfab for the service interceptor under construction
* @param injector allows constant values to be injected into the interceptor class as final fields
*/
public void commit(ClassFab interceptorClassFab, String delegateFieldName, ConstantInjector injector)
{
Class invocationClass = classFab.createClass();
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("%s invocation = new %<s(%s, %s, $$);",
invocationClass.getName(),
injector.inject(MethodInfo.class, info),
delegateFieldName);
builder.addln("invocation.proceed();");
Class[] exceptionTypes = method.getExceptionTypes();
builder.addln("if (invocation.isFail())").begin();
for (Class exceptionType : exceptionTypes)
{
String name = exceptionType.getSimpleName().toLowerCase();
String exceptionTypeFieldName = injector.inject(Class.class, exceptionType);
builder.addln("%s %s = (%s) invocation.getThrown(%s);", exceptionType.getName(), name,
exceptionType.getName(), exceptionTypeFieldName);
builder.addln("if (%s != null) throw %s;", name, name);
}
builder.addln(
"throw new IllegalStateException(\"Impossible exception thrown from intercepted invocation.\");");
builder.end(); // if fail
builder.addln("return ($r) invocation.getResult();");
builder.end();
interceptorClassFab.addMethod(Modifier.PUBLIC, new MethodSignature(method), builder.toString());
}
}