org.apache.tapestry5.internal.services.InternalClassTransformationImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tapestry-core Show documentation
Show all versions of tapestry-core Show documentation
Central module for Tapestry, containing interfaces to the Java
Servlet API and all core services and components.
// Copyright 2006, 2007, 2008, 2009, 2010 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 java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Formatter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javassist.*;
import javassist.expr.ExprEditor;
import javassist.expr.FieldAccess;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.func.Predicate;
import org.apache.tapestry5.internal.InternalComponentResources;
import org.apache.tapestry5.ioc.internal.services.CtClassSource;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
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.FieldValueConduit;
import org.apache.tapestry5.ioc.services.MethodSignature;
import org.apache.tapestry5.ioc.util.BodyBuilder;
import org.apache.tapestry5.ioc.util.IdAllocator;
import org.apache.tapestry5.model.ComponentModel;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.runtime.ComponentEvent;
import org.apache.tapestry5.services.*;
import org.slf4j.Logger;
/**
* Implementation of the {@link org.apache.tapestry5.internal.services.InternalClassTransformation} interface.
*/
@SuppressWarnings("all")
public final class InternalClassTransformationImpl implements InternalClassTransformation
{
public static final MethodSignature INVOKE_SIGNATURE = new MethodSignature(MethodInvocationResult.class, "invoke",
new Class[]
{ Object.class, Object[].class }, null);
public static final MethodSignature FIELD_ACCESS_READ_SIGNATURE = new MethodSignature(Object.class, "read",
new Class[]
{ Object.class }, null);
public static final MethodSignature FIELD_ACCESS_WRITE_SIGNATURE = new MethodSignature(void.class, "write",
new Class[]
{ Object.class, Object.class }, null);
private static final int INIT_BUFFER_SIZE = 100;
private boolean frozen;
private final CtClass ctClass;
private final Logger logger;
private final InternalClassTransformation parentTransformation;
private final ClassPool classPool;
private final IdAllocator idAllocator;
private final CtClass providerType;
class TransformMethodImpl implements TransformMethod
{
final CtMethod method;
private final TransformMethodSignature sig;
private List annotations;
private final boolean added;
private ComponentMethodInvocationBuilder builder;
private MethodAccess access;
private String identifier;
private Boolean override;
private List> parameterAnnotations;
TransformMethodImpl(CtMethod method, boolean added)
{
this.method = method;
this.sig = toMethodSignature(method);
this.added = added;
}
@Override
public String toString()
{
return String.format("TransformMethod[%s]", getMethodIdentifier());
}
public int compareTo(TransformMethod o)
{
return sig.compareTo(o.getSignature());
}
public T getAnnotation(Class annotationClass)
{
if (annotations == null)
annotations = extractAnnotations(method);
return findAnnotationInList(annotationClass, annotations);
}
public A getParameterAnnotation(int index, Class annotationType)
{
if (parameterAnnotations == null)
extractParameterAnnotations();
return findAnnotationInList(annotationType, parameterAnnotations.get(index));
}
private void extractParameterAnnotations()
{
int count = sig.getParameterTypes().length;
parameterAnnotations = CollectionFactory.newList();
for (int parameterIndex = 0; parameterIndex < count; parameterIndex++)
{
List annotations = extractAnnotationsForParameter(parameterIndex);
parameterAnnotations.add(annotations);
}
}
private List extractAnnotationsForParameter(int parameterIndex)
{
List result = CollectionFactory.newList();
Object[] parameterAnnotations = method.getAvailableParameterAnnotations()[parameterIndex];
addAnnotationsToList(result, parameterAnnotations, false);
return result;
}
public TransformMethodSignature getSignature()
{
return sig;
}
public String getName()
{
return sig.getMethodName();
}
public void addAdvice(ComponentMethodAdvice advice)
{
failIfFrozen();
assert advice != null;
if (builder == null)
builder = createBuilder(sig);
builder.addAdvice(advice);
formatter.format("add advice %s : %s\n\n", sig.getMediumDescription(), advice);
}
public void addOperationAfter(ComponentInstanceOperation operation)
{
addAdvice(toAfterAdvice(operation));
}
public void addOperationBefore(ComponentInstanceOperation operation)
{
addAdvice(toBeforeAdvice(operation));
}
public MethodAccess getAccess()
{
failIfFrozen();
if (access == null)
access = createMethodAccess();
return access;
}
private MethodAccess createMethodAccess()
{
if (isPrivate())
return createPrivateMethodAccess();
return createNonPrivateMethodAccess();
}
private boolean isPrivate()
{
return Modifier.isPrivate(sig.getModifiers());
}
private MethodAccess createNonPrivateMethodAccess()
{
// For a public method, given the instance, we can just invoke the method directly
// from the MethodAccess object.
String accessTarget = "instance." + sig.getMethodName();
return createMethodAccessForTarget(accessTarget, false);
}
private MethodAccess createMethodAccessForTarget(String accessTarget, boolean passInstance)
{
boolean isVoid = sig.getReturnType().equals("void");
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("%s instance = (% parameterTypes = CollectionFactory.newList(getClassName());
parameterTypes.addAll(Arrays.asList(sig.getParameterTypes()));
String methodName = newMemberName("access", sig.getMethodName());
TransformMethodSignature accessMethodSignature = new TransformMethodSignature(Modifier.PUBLIC
+ Modifier.STATIC, sig.getReturnType(), methodName, parameterTypes.toArray(new String[0]),
sig.getExceptionTypes());
boolean isVoid = sig.getReturnType().equals("void");
BodyBuilder builder = new BodyBuilder();
builder.begin();
if (!isVoid)
builder.add("return ");
builder.add("$1.%s(", sig.getMethodName());
for (int i = 0; i < sig.getParameterTypes().length; i++)
{
if (i > 0)
builder.add(", ");
builder.add("$%d", i + 2);
}
builder.addln(");");
builder.end();
addNewMethod(accessMethodSignature, builder.toString());
return methodName;
}
public String getMethodIdentifier()
{
if (identifier == null)
{
int lineNumber = method.getMethodInfo2().getLineNumber(0);
CtClass enclosingClass = method.getDeclaringClass();
String sourceFile = enclosingClass.getClassFile2().getSourceFile();
identifier = String.format("%s.%s (at %s:%d)", enclosingClass.getName(), sig.getMediumDescription(),
sourceFile, lineNumber);
}
return identifier;
}
public boolean isOverride()
{
if (override == null)
override = searchForOverride();
return override;
}
private boolean searchForOverride()
{
InternalClassTransformation search = parentTransformation;
while (search != null)
{
if (search.isMethod(sig))
return true;
search = search.getParentTransformation();
}
// Not found in any super-class.
return false;
}
void doFinish()
{
if (builder != null)
{
builder.commit();
builder = null;
}
}
}
class TransformFieldImpl implements TransformField
{
private final CtField field;
private final CtClass fieldType;
private final String name, type;
private boolean added;
private List annotations;
private Object claimTag;
String readValueBody, writeValueBody;
private DelegateFieldAccess delegateFieldAccess;
private org.apache.tapestry5.services.FieldAccess access;
TransformFieldImpl(CtField field, boolean added)
{
this.field = field;
this.name = field.getName();
this.added = added;
try
{
fieldType = field.getType();
type = fieldType.getName();
}
catch (NotFoundException ex)
{
throw new RuntimeException(ex);
}
}
void doFinish()
{
if (delegateFieldAccess != null)
{
access = createAccess();
delegateFieldAccess.delegate = access;
delegateFieldAccess = null;
}
}
@Override
public String toString()
{
return String.format("TransformField[%s %s.%s(%s)]", Modifier.toString(field.getModifiers()),
getClassName(), name, type);
}
public int compareTo(TransformField o)
{
return name.compareTo(o.getName());
}
public String getName()
{
return name;
}
public String getType()
{
return type;
}
public T getAnnotation(Class annotationClass)
{
failIfFrozen();
if (annotations == null)
annotations = extractAnnotations(field);
return findAnnotationInList(annotationClass, annotations);
}
public void claim(Object tag)
{
assert tag != null;
failIfFrozen();
if (claimTag != null)
throw new IllegalStateException(String.format(
"Field %s of class %s is already claimed by %s and can not be claimed by %s.", getName(),
getClassName(), claimTag, tag));
claimTag = tag;
formatter.format("Field %s claimed by %s\n\n", name, tag);
}
public boolean isClaimed()
{
return claimTag != null;
}
public int getModifiers()
{
return field.getModifiers();
}
void replaceReadAccess(String methodName)
{
failIfFrozen();
if (readValueBody != null)
throw new IllegalStateException(String.format("Field %s.%s has already had read access replaced.",
getClassName(), name));
// Explicitly reference $0 (aka "this") because of TAPESTRY-1511.
// $0 is valid even inside a static method.
readValueBody = String.format("$_ = $0.%s();", methodName);
formatter.format("replace read %s: %s();\n\n", name, methodName);
fieldAccessReplaced = true;
}
void replaceWriteAccess(String methodName)
{
failIfFrozen();
if (writeValueBody != null)
throw new IllegalStateException(String.format("Field %s.%s has already had write access replaced.",
getClassName(), name));
// Explicitly reference $0 (aka "this") because of TAPESTRY-1511.
// $0 is valid even inside a static method.
writeValueBody = String.format("$0.%s($1);", methodName);
formatter.format("replace write %s: %s();\n\n", name, methodName);
fieldAccessReplaced = true;
}
public org.apache.tapestry5.services.FieldAccess getAccess()
{
failIfFrozen();
if (access != null)
return access;
if (delegateFieldAccess == null)
{
delegateFieldAccess = new DelegateFieldAccess();
}
return delegateFieldAccess;
}
private org.apache.tapestry5.services.FieldAccess createAccess()
{
TransformMethod reader = createReader();
TransformMethod writer = createWriter();
return createFieldAccess(reader, writer);
}
private org.apache.tapestry5.services.FieldAccess createFieldAccess(TransformMethod reader,
TransformMethod writer)
{
ClassFab cf = classFactory.newClass(org.apache.tapestry5.services.FieldAccess.class);
addFieldAccessReadMethod(cf, reader);
addFieldAccessWriteMethod(cf, writer);
cf.addToString(String.format("FieldAccess<%s.%s>", getClassName(), name));
Class accessClass = cf.createClass();
try
{
return (org.apache.tapestry5.services.FieldAccess) accessClass.newInstance();
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}
private void addFieldAccessReadMethod(ClassFab cf, TransformMethod readAccess)
{
BodyBuilder builder = new BodyBuilder().begin();
builder.addln("%s instance = (% conduitProvider)
{
replaceAccess(addIndirectInjectedField(FieldValueConduit.class, name + "$conduit", conduitProvider));
}
public void replaceAccess(FieldValueConduit conduit)
{
String fieldName = addInjectedFieldUncached(FieldValueConduit.class, name + "$conduit", conduit);
// TODO: If access != null?
access = toFieldAccess(conduit);
if (delegateFieldAccess != null)
{
delegateFieldAccess.delegate = access;
delegateFieldAccess = null;
}
replaceAccess(getTransformFieldImpl(fieldName));
}
public void replaceAccess(TransformField conduitField)
{
failIfFrozen();
String conduitFieldName = conduitField.getName();
String readMethodName = newMemberName("get", name);
TransformMethodSignature readSig = new TransformMethodSignature(Modifier.PRIVATE, type, readMethodName,
null, null);
String cast = TransformUtils.getWrapperTypeName(type);
// The ($r) cast will convert the result to the method return type; generally
// this does nothing. but for primitive types, it will unwrap
// the wrapper type back to a primitive.
addNewMethod(readSig, String.format("return ($r) ((%s) %s.get());", cast, conduitFieldName));
replaceReadAccess(readMethodName);
String writeMethodName = newMemberName("set", name);
TransformMethodSignature writeSig = new TransformMethodSignature(Modifier.PRIVATE, "void", writeMethodName,
new String[]
{ type }, null);
addNewMethod(writeSig, String.format("%s.set(($w) $1);", conduitFieldName));
replaceWriteAccess(writeMethodName);
}
public void inject(Object value)
{
failIfFrozen();
addInjectToConstructor(name, fieldType, value);
makeReadOnly(name);
}
public void injectIndirect(ComponentValueProvider provider)
{
assert provider != null;
failIfFrozen();
String argReference = addConstructorArg(providerType, provider);
addToConstructor(String.format(" %s = (%s) (%s).get(%s);", name, type, argReference, resourcesFieldName));
makeReadOnly(name);
}
}
private final Map methods = CollectionFactory.newMap();
private Map fields = CollectionFactory.newMap();
/**
* Map, keyed on InjectKey, of field name. Injections are always added as protected (not
* private) fields to support
* sharing of injections between a base class and a sub class.
*/
private final Map injectionCache = CollectionFactory.newMap();
// Cache of class annotation
private List classAnnotations;
/**
* Contains the assembled Javassist code for the class' default constructor.
*/
private StringBuilder constructor = new StringBuilder(INIT_BUFFER_SIZE);
private final List