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;
     }