You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2011/12/23 19:54:25 UTC
svn commit: r1222787 - in
/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic:
PlasticClassImpl.java PlasticClassPool.java PlasticFieldImpl.java
Author: hlship
Date: Fri Dec 23 18:54:25 2011
New Revision: 1222787
URL: http://svn.apache.org/viewvc?rev=1222787&view=rev
Log:
TAP5-1801: Allow non-public fields in instrumented classes
Added most of the logic for tracking instrument methods for fields of classes
Modified:
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java
Modified: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java?rev=1222787&r1=1222786&r2=1222787&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java (original)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassImpl.java Fri Dec 23 18:54:25 2011
@@ -85,6 +85,8 @@ public class PlasticClassImpl extends Lo
final PlasticClassPool pool;
+ private final boolean proxy;
+
final String className;
private final String superClassName;
@@ -135,13 +137,13 @@ public class PlasticClassImpl extends Lo
* Maps a field name to a replacement method that should be invoked instead of reading the
* field.
*/
- final Map<String, MethodNode> fieldToReadMethod = PlasticInternalUtils.newMap();
+ private final Map<String, MethodNode> fieldToReadMethod = PlasticInternalUtils.newMap();
/**
* Maps a field name to a replacement method that should be invoked instead of writing the
* field.
*/
- final Map<String, MethodNode> fieldToWriteMethod = PlasticInternalUtils.newMap();
+ private final Map<String, MethodNode> fieldToWriteMethod = PlasticInternalUtils.newMap();
/**
* This normal no-arguments constructor, or null. By the end of the transformation
@@ -175,12 +177,14 @@ public class PlasticClassImpl extends Lo
* @param pool
* @param parentInheritanceData
* @param parentStaticContext
+ * @param proxy
*/
public PlasticClassImpl(ClassNode classNode, PlasticClassPool pool, InheritanceData parentInheritanceData,
- StaticContext parentStaticContext)
+ StaticContext parentStaticContext, boolean proxy)
{
this.classNode = classNode;
this.pool = pool;
+ this.proxy = proxy;
staticContext = parentStaticContext.dupe();
@@ -1132,4 +1136,28 @@ public class PlasticClassImpl extends Lo
return this;
}
+ void redirectFieldWrite(String fieldName, MethodNode method)
+ {
+ fieldToWriteMethod.put(fieldName, method);
+
+ if (!(proxy || isPrivate(method)))
+ {
+ pool.setFieldWriteInstrumentation(classNode.name, fieldName, method.name);
+ }
+ }
+
+ void redirectFieldRead(String fieldName, MethodNode method)
+ {
+ fieldToReadMethod.put(fieldName, method);
+
+ if (! (proxy || isPrivate(method)))
+ {
+ pool.setFieldReadInstrumentation(classNode.name, fieldName, method.name);
+ }
+ }
+
+ private boolean isPrivate(MethodNode method)
+ {
+ return Modifier.isPrivate(method.access);
+ }
}
Modified: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java?rev=1222787&r1=1222786&r2=1222787&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java (original)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticClassPool.java Fri Dec 23 18:54:25 2011
@@ -39,6 +39,11 @@ public class PlasticClassPool implements
private final Set<String> controlledPackages;
+
+ // Would use Deque, but that's added in 1.6 and we're still striving for 1.5 code compatibility.
+
+ private final Stack<String> activeInstrumentClassNames = new Stack<String>();
+
/**
* Maps class names to instantiators for that class name.
* Synchronized on the loader.
@@ -53,10 +58,9 @@ public class PlasticClassPool implements
private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>()
{
-
protected TypeCategory convert(String typeName)
{
- ClassNode cn = constructClassNode(typeName);
+ ClassNode cn = constructClassNodeFromBytecode(typeName);
return Modifier.isInterface(cn.access) ? TypeCategory.INTERFACE : TypeCategory.CLASS;
}
@@ -78,7 +82,29 @@ public class PlasticClassPool implements
/**
* Map from FQCN to BaseClassDef. Synchronized on the loader.
*/
- private final Map<String, BaseClassDef> baseClassDefs = new HashMap<String, PlasticClassPool.BaseClassDef>();
+ private final Map<String, BaseClassDef> baseClassDefs = PlasticInternalUtils.newMap();
+
+ /**
+ * Tracks field read and write instrumentations for a particular class.
+ */
+ static class FieldInstrumentations
+ {
+ /**
+ * Map field name to a read method.
+ */
+ final Map<String, String> read = PlasticInternalUtils.newMap();
+
+ /**
+ * Maps field name to a write method.
+ */
+ final Map<String, String> write = PlasticInternalUtils.newMap();
+ }
+
+
+ private final Map<String, FieldInstrumentations> instrumentations = PlasticInternalUtils.newMap();
+
+ private final FieldInstrumentations placeholder = new FieldInstrumentations();
+
private final Set<TransformationOption> options;
@@ -298,7 +324,9 @@ public class PlasticClassPool implements
return false;
}
- public Class<?> loadAndTransformClass(String className) throws ClassNotFoundException
+ // Hopefully the synchronized will not cause a deadlock
+
+ public synchronized Class<?> loadAndTransformClass(String className) throws ClassNotFoundException
{
// Inner classes are not transformed, but they are loaded by the same class loader.
@@ -308,16 +336,42 @@ public class PlasticClassPool implements
// TODO: What about interfaces, enums, annotations, etc. ... they shouldn't be in the package, but
// we should generate a reasonable error message.
- InternalPlasticClassTransformation transformation = getPlasticClassTransformation(className);
+ if (activeInstrumentClassNames.contains(className))
+ {
+ StringBuilder builder = new StringBuilder("");
+ String sep = "";
- delegate.transform(transformation.getPlasticClass());
+ for (String name : activeInstrumentClassNames)
+ {
+ builder.append(sep);
+ builder.append(name);
- ClassInstantiator createInstantiator = transformation.createInstantiator();
- ClassInstantiator configuredInstantiator = delegate.configureInstantiator(className, createInstantiator);
+ sep = ", ";
+ }
- instantiators.put(className, configuredInstantiator);
+ throw new IllegalStateException(String.format("Unable to transform class %s as it is already in the process of being transformed; there is a cycle among the following classes: %s.",
+ className, builder));
+ }
+
+ activeInstrumentClassNames.push(className);
+
+ try
+ {
+
+ InternalPlasticClassTransformation transformation = getPlasticClassTransformation(className);
+
+ delegate.transform(transformation.getPlasticClass());
+
+ ClassInstantiator createInstantiator = transformation.createInstantiator();
+ ClassInstantiator configuredInstantiator = delegate.configureInstantiator(className, createInstantiator);
+
+ instantiators.put(className, configuredInstantiator);
- return transformation.getTransformedClass();
+ return transformation.getTransformedClass();
+ } finally
+ {
+ activeInstrumentClassNames.pop();
+ }
}
private Class loadInnerClass(String className)
@@ -338,14 +392,24 @@ public class PlasticClassPool implements
{
assert PlasticInternalUtils.isNonBlank(className);
- ClassNode classNode = constructClassNode(className);
+ ClassNode classNode = constructClassNodeFromBytecode(className);
String baseClassName = PlasticInternalUtils.toClassName(classNode.superName);
- return createTransformation(baseClassName, classNode);
+ instrumentations.put(classNode.name, new FieldInstrumentations());
+
+ return createTransformation(baseClassName, classNode, false);
}
- private InternalPlasticClassTransformation createTransformation(String baseClassName, ClassNode classNode)
+ /**
+ *
+ * @param baseClassName class from which the transformed class extends
+ * @param classNode node for the class
+ * @param proxy if true, the class is a new empty class; if false an existing class that's being transformed
+ * @return
+ * @throws ClassNotFoundException
+ */
+ private InternalPlasticClassTransformation createTransformation(String baseClassName, ClassNode classNode, boolean proxy)
throws ClassNotFoundException
{
if (shouldInterceptClassLoading(baseClassName))
@@ -356,12 +420,12 @@ public class PlasticClassPool implements
assert def != null;
- return new PlasticClassImpl(classNode, this, def.inheritanceData, def.staticContext);
+ return new PlasticClassImpl(classNode, this, def.inheritanceData, def.staticContext, proxy);
}
// When the base class is Object, or otherwise not in a transformed package,
// then start with the empty
- return new PlasticClassImpl(classNode, this, emptyInheritanceData, emptyStaticContext);
+ return new PlasticClassImpl(classNode, this, emptyInheritanceData, emptyStaticContext, proxy);
}
/**
@@ -371,7 +435,7 @@ public class PlasticClassPool implements
* @param className fully qualified class name
* @return corresponding ClassNode
*/
- public ClassNode constructClassNode(String className)
+ public ClassNode constructClassNodeFromBytecode(String className)
{
byte[] bytecode = readBytecode(className);
@@ -397,7 +461,7 @@ public class PlasticClassPool implements
newClassNode.visit(V1_5, ACC_PUBLIC, PlasticInternalUtils.toInternalName(newClassName), null,
PlasticInternalUtils.toInternalName(baseClassName), null);
- return createTransformation(baseClassName, newClassNode);
+ return createTransformation(baseClassName, newClassNode, true);
} catch (ClassNotFoundException ex)
{
throw new RuntimeException(String.format("Unable to create class %s as sub-class of %s: %s", newClassName,
@@ -481,4 +545,74 @@ public class PlasticClassPool implements
{
return options.contains(option);
}
+
+
+ void setFieldReadInstrumentation(String classInternalName, String fieldName, String methodName)
+ {
+ instrumentations.get(classInternalName).read.put(fieldName, methodName);
+ }
+
+ private FieldInstrumentations getFieldInstrumentation(String classInternalName)
+ {
+ FieldInstrumentations result = instrumentations.get(classInternalName);
+
+ if (result != null)
+ {
+ return result;
+ }
+
+ String className = PlasticInternalUtils.toClassName(classInternalName);
+
+ // If it is a top-level (not inner) class in a controlled package, then we
+ // will recursively load the class, to identify any field instrumentations
+ // in it.
+ if (!className.contains("$") && shouldInterceptClassLoading(className))
+ {
+ try
+ {
+ loadAndTransformClass(className);
+
+ // The key is written into the instrumentations map as a side-effect
+ // of loading the class.
+ return instrumentations.get(classInternalName);
+ } catch (Exception ex)
+ {
+ throw new RuntimeException(PlasticInternalUtils.toMessage(ex), ex);
+ }
+ }
+
+ // Either a class outside of controlled packages, or an inner class. Use a placeholder
+ // that contains empty maps.
+
+ result = placeholder;
+ instrumentations.put(classInternalName, result);
+
+ return result;
+ }
+
+
+ /**
+ * Checks to see if there is an instrumentation for the given class and field.
+ * This may cause the class to be transformed. Returns the name of the method that replaces
+ * the field read, or null if the field is not instrumented in such a way that the method is required.
+ */
+ String getInstrumentedReadMethod(String classInternalName, String fieldName)
+ {
+ return getFieldInstrumentation(classInternalName).read.get(fieldName);
+ }
+
+ void setFieldWriteInstrumentation(String classInternalName, String fieldName, String methodName)
+ {
+ instrumentations.get(classInternalName).write.put(fieldName, methodName);
+ }
+
+ /**
+ * Checks to see if there is an instrumentation for the given class and field.
+ * This may cause the class to be transformed. Returns the name of the method that replaces
+ * the field write, or null if the field is not instrumented in such a way that the method is required.
+ */
+ String getInstrumentedWriteMethod(String classInternalName, String fieldName)
+ {
+ return getFieldInstrumentation(classInternalName).write.get(fieldName);
+ }
}
Modified: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java?rev=1222787&r1=1222786&r2=1222787&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java (original)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticFieldImpl.java Fri Dec 23 18:54:25 2011
@@ -355,7 +355,7 @@ class PlasticFieldImpl extends PlasticMe
String setAccessName = plasticClass.makeUnique(plasticClass.methodNames, "conduit_set_" + node.name);
- setAccess = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, setAccessName, "(" + node.desc + ")V", null, null);
+ setAccess = new MethodNode(accessForMethod(), setAccessName, "(" + node.desc + ")V", null, null);
InstructionBuilder builder = plasticClass.newBuilder(setAccess);
@@ -381,7 +381,15 @@ class PlasticFieldImpl extends PlasticMe
plasticClass.addMethod(setAccess);
- plasticClass.fieldToWriteMethod.put(node.name, setAccess);
+ plasticClass.redirectFieldWrite(node.name, setAccess);
+ }
+
+ /** Determines the access for a method that takes the place of reading from or writing to the field. Escalates
+ * private fields to package private visibility, but leaves protected and package private alone (public fields
+ * can not be instrumented).
+ */
+ private int accessForMethod() {
+ return (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL | node.access) & ~Opcodes.ACC_PRIVATE;
}
private void replaceFieldReadAccess(String conduitFieldName)
@@ -392,7 +400,7 @@ class PlasticFieldImpl extends PlasticMe
String getAccessName = plasticClass.makeUnique(plasticClass.methodNames, "conduit_get_" + node.name);
- getAccess = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, getAccessName, "()" + node.desc, null, null);
+ getAccess = new MethodNode(accessForMethod(), getAccessName, "()" + node.desc, null, null);
InstructionBuilder builder = plasticClass.newBuilder(getAccess);
@@ -434,7 +442,7 @@ class PlasticFieldImpl extends PlasticMe
plasticClass.addMethod(getAccess);
- plasticClass.fieldToReadMethod.put(node.name, getAccess);
+ plasticClass.redirectFieldRead(node.name, getAccess);
}
private boolean isWriteBehindEnabled()
@@ -461,7 +469,7 @@ class PlasticFieldImpl extends PlasticMe
String setAccessName = plasticClass.makeUnique(plasticClass.methodNames, "reject_field_change_" + node.name);
- setAccess = new MethodNode(Opcodes.ACC_SYNTHETIC | Opcodes.ACC_FINAL, setAccessName, "(" + node.desc + ")V", null, null);
+ setAccess = new MethodNode(accessForMethod(), setAccessName, "(" + node.desc + ")V", null, null);
String message = String.format("Field %s of class %s is read-only.", node.name, plasticClass.className);
@@ -469,7 +477,7 @@ class PlasticFieldImpl extends PlasticMe
plasticClass.addMethod(setAccess);
- plasticClass.fieldToWriteMethod.put(node.name, setAccess);
+ plasticClass.redirectFieldWrite(node.name, setAccess);
node.access |= Opcodes.ACC_FINAL;
}