blob: ed6fe87beb73b159970ed5b5543cc81b8b889ec0 [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.internal.services;
import javassist.*;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.ioc.internal.services.CtClassSource;
import org.apache.tapestry5.ioc.services.ClassFabUtils;
import org.apache.tapestry5.ioc.util.BodyBuilder;
import org.apache.tapestry5.services.ComponentMethodAdvice;
import org.apache.tapestry5.services.TransformMethodSignature;
import java.lang.reflect.Modifier;
import java.util.concurrent.atomic.AtomicLong;
/**
* Used by {@link org.apache.tapestry5.internal.services.InternalClassTransformationImpl} to manage adding method
* invocation advice to arbitrary component methods.
*
* @see org.apache.tapestry5.ioc.MethodAdvice
*/
class InvocationBuilder
{
private static final String FIELD_NAME = "_p";
private static final int PROTECTED_FINAL = Modifier.PROTECTED | Modifier.FINAL;
private static final int PUBLIC_FINAL = Modifier.PUBLIC | Modifier.FINAL;
private final InternalClassTransformation transformation;
private final CtClassSource classSource;
private final TransformMethodSignature advisedMethod;
private final MethodInvocationInfo info;
private final CtClass invocationCtClass;
private final String invocationClassName;
private static final AtomicLong UID_GENERATOR = new AtomicLong(System.currentTimeMillis());
private static String nextUID()
{
return Long.toHexString(UID_GENERATOR.getAndIncrement());
}
public InvocationBuilder(InternalClassTransformation transformation,
ComponentClassCache componentClassCache, TransformMethodSignature advisedMethod,
CtClassSource classSource)
{
this.transformation = transformation;
this.advisedMethod = advisedMethod;
this.classSource = classSource;
info = new MethodInvocationInfo(advisedMethod, componentClassCache);
invocationClassName = this.transformation.getClassName() + "$" + this.advisedMethod.getMethodName() + "$invocation_" + nextUID();
invocationCtClass = this.classSource.newClass(invocationClassName, AbstractComponentMethodInvocation.class);
}
public void addAdvice(ComponentMethodAdvice advice)
{
info.addAdvice(advice);
}
/**
* Commit the changes, creating the new class for the invocation, and renaming and rewriting the advised method.
*/
public void commit()
{
// The class name is the component class name plus the method name plus a unique uid. This places
// the invocation in the same package as the component class; the original method will ultimately
// be renamed and modified to be package private.
try
{
createConstructor();
implementOverride();
implementGetParameter();
String renamed = copyAdvisedMethod();
implementInvokeAdvisedMethod(renamed);
classSource.createClass(invocationCtClass);
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
rebuildOriginalMethod();
}
private void rebuildOriginalMethod()
{
String methodInfoField = transformation.addInjectedField(MethodInvocationInfo.class,
advisedMethod.getMethodName() + "Info",
info);
String componentResourcesField = transformation.getResourcesFieldName();
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("%s invocation = new %<s(%s, %s, $$);", invocationClassName, methodInfoField,
componentResourcesField);
// Off into the first MethodAdvice
builder.addln("invocation.proceed();");
String[] exceptionTypes = advisedMethod.getExceptionTypes();
int exceptionCount = exceptionTypes.length;
if (exceptionCount > 0)
{
for (int i = 0; i < exceptionCount; i++)
{
String type = exceptionTypes[i];
String name = "ex" + i;
builder.addln("%s %s = (%1$s) invocation.getThrown(%s.getExceptionType(%d));",
type, name, methodInfoField, i);
builder.addln("if (%s != null) throw %<s;", name);
}
}
String returnType = advisedMethod.getReturnType();
if (!returnType.equals("void"))
{
builder.addln("return %s;",
ClassFabUtils.castReference("invocation.getResult()", returnType));
}
builder.end();
/** Replace the original method with the new implementation. */
transformation.addMethod(advisedMethod, builder.toString());
}
private void implementInvokeAdvisedMethod(String advisedMethodName) throws CannotCompileException
{
BodyBuilder builder = new BodyBuilder().begin();
boolean isVoid = advisedMethod.getReturnType().equals("void");
builder.addln("%s component = (%<s) getComponentResources().getComponent();", transformation.getClassName());
String[] exceptionTypes = advisedMethod.getExceptionTypes();
int exceptionCount = exceptionTypes.length;
if (exceptionCount > 0)
builder.add("try").begin();
if (!isVoid) builder.add("overrideResult(($w) ");
builder.add("component.%s(", advisedMethodName);
for (int i = 0; i < advisedMethod.getParameterTypes().length; i++)
{
if (i > 0) builder.add(", ");
builder.add("%s%d", FIELD_NAME, i);
}
builder.add(")");
if (!isVoid) builder.add(")");
builder.addln(";");
if (exceptionCount > 0)
{
builder.end(); // try
for (int i = 0; i < exceptionCount; i++)
{
builder.addln("catch (%s ex) { overrideThrown(ex); }", exceptionTypes[i]);
}
}
builder.end();
CtMethod method = new CtMethod(CtClass.voidType, "invokeAdvisedMethod",
new CtClass[0], invocationCtClass);
method.setModifiers(PROTECTED_FINAL);
method.setBody(builder.toString());
invocationCtClass.addMethod(method);
}
private String copyAdvisedMethod()
{
String newName = transformation.newMemberName("advised$" + advisedMethod.getMethodName());
transformation.copyMethod(advisedMethod, Modifier.FINAL, newName);
return newName;
}
private void createConstructor() throws CannotCompileException
{
int parameterCount = info.getParameterCount();
CtClass[] parameterTypes = new CtClass[parameterCount + 2];
parameterTypes[0] = toCtClass(MethodInvocationInfo.class);
parameterTypes[1] = toCtClass(ComponentResources.class);
BodyBuilder builder = new BodyBuilder().begin().addln("super($1,$2);");
for (int i = 0; i < parameterCount; i++)
{
String name = FIELD_NAME + i;
String parameterTypeName = advisedMethod.getParameterTypes()[i];
CtClass parameterType = classSource.toCtClass(parameterTypeName);
CtField field = new CtField(parameterType, name, invocationCtClass);
field.setModifiers(Modifier.PRIVATE);
invocationCtClass.addField(field);
parameterTypes[2 + i] = parameterType;
builder.addln("%s = $%d;", name, 3 + i);
}
builder.end();
CtConstructor constructor = new CtConstructor(parameterTypes, invocationCtClass);
constructor.setBody(builder.toString());
invocationCtClass.addConstructor(constructor);
}
private CtClass toCtClass(Class input)
{
return classSource.toCtClass(input);
}
private void implementOverride() throws CannotCompileException
{
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("switch ($1)").begin();
int count = advisedMethod.getParameterTypes().length;
for (int i = 0; i < count; i++)
{
String type = advisedMethod.getParameterTypes()[i];
builder.addln("case %d: %s = %s; break;", i, FIELD_NAME + i, ClassFabUtils.castReference("$2", type));
}
builder.addln("default: throw new IllegalArgumentException(\"Index out of range.\");");
builder.end().end();
CtMethod method = new CtMethod(CtClass.voidType, "override",
new CtClass[] { CtClass.intType, toCtClass(Object.class) }, invocationCtClass);
method.setModifiers(PUBLIC_FINAL);
method.setBody(builder.toString());
invocationCtClass.addMethod(method);
}
private void implementGetParameter() throws CannotCompileException
{
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("switch ($1)").begin();
int count = advisedMethod.getParameterTypes().length;
for (int i = 0; i < count; i++)
{
builder.addln("case %d: return ($w) %s;", i, FIELD_NAME + i);
}
builder.addln("default: throw new IllegalArgumentException(\"Index out of range.\");");
builder.end().end();
CtMethod method = new CtMethod(toCtClass(Object.class), "getParameter",
new CtClass[] { CtClass.intType }, invocationCtClass);
method.setModifiers(PUBLIC_FINAL);
method.setBody(builder.toString());
invocationCtClass.addMethod(method);
}
}