You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2011/04/30 04:05:24 UTC

svn commit: r1098037 - in /tapestry/tapestry5/trunk/plastic/src: main/java/org/apache/tapestry5/internal/plastic/ main/java/org/apache/tapestry5/plastic/ test/groovy/org/apache/tapestry5/plastic/

Author: hlship
Date: Sat Apr 30 02:05:23 2011
New Revision: 1098037

URL: http://svn.apache.org/viewvc?rev=1098037&view=rev
Log:
TAP5-853: Add support for listener notifications as classes are loaded

Added:
    tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ClassType.java
    tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassEvent.java
    tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListener.java
    tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListenerHub.java
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/PlasticInternalUtils.java
    tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java
    tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ProxyCreation.groovy

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=1098037&r1=1098036&r2=1098037&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 Sat Apr 30 02:05:23 2011
@@ -41,6 +41,7 @@ import org.apache.tapestry5.internal.pla
 import org.apache.tapestry5.internal.plastic.asm.tree.VarInsnNode;
 import org.apache.tapestry5.plastic.AnnotationAccess;
 import org.apache.tapestry5.plastic.ClassInstantiator;
+import org.apache.tapestry5.plastic.ClassType;
 import org.apache.tapestry5.plastic.ComputedValue;
 import org.apache.tapestry5.plastic.Condition;
 import org.apache.tapestry5.plastic.FieldConduit;
@@ -1309,7 +1310,7 @@ public class PlasticClassImpl extends Lo
 
         private void rewriteOriginalMethod()
         {
-            pool.realize(invocationClassNode);
+            pool.realize(className, ClassType.METHOD_INVOCATION, invocationClassNode);
 
             String fieldName = String.format("methodinvocationbundle_%s_%s", description.methodName,
                     PlasticUtils.nextUID());
@@ -1566,7 +1567,7 @@ public class PlasticClassImpl extends Lo
             // TODO: Perhaps we should defer this private check until it is needed,
             // i.e., when we do some work on the field that requires it to be private?
             // However, given class loading issues, public fields are likely to cause
-            // their own problems when passing the ClassLoader boundrary.
+            // their own problems when passing the ClassLoader boundary.
 
             if (!Modifier.isPrivate(node.access))
                 throw new IllegalArgumentException(
@@ -2069,10 +2070,10 @@ public class PlasticClassImpl extends Lo
 
     private PlasticClassHandleShim instantiateShim(ClassNode shimClassNode)
     {
-        Class shimClass = pool.realize(shimClassNode);
-
         try
         {
+            Class shimClass = pool.realize(className, ClassType.SUPPORT, shimClassNode);
+
             return (PlasticClassHandleShim) shimClass.newInstance();
         }
         catch (Exception ex)

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=1098037&r1=1098036&r2=1098037&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 Sat Apr 30 02:05:23 2011
@@ -25,6 +25,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import org.apache.tapestry5.internal.plastic.asm.ClassReader;
 import org.apache.tapestry5.internal.plastic.asm.ClassWriter;
@@ -33,6 +34,10 @@ import org.apache.tapestry5.internal.pla
 import org.apache.tapestry5.internal.plastic.asm.tree.ClassNode;
 import org.apache.tapestry5.plastic.AnnotationAccess;
 import org.apache.tapestry5.plastic.ClassInstantiator;
+import org.apache.tapestry5.plastic.ClassType;
+import org.apache.tapestry5.plastic.PlasticClassEvent;
+import org.apache.tapestry5.plastic.PlasticClassListener;
+import org.apache.tapestry5.plastic.PlasticClassListenerHub;
 import org.apache.tapestry5.plastic.PlasticClassTransformation;
 import org.apache.tapestry5.plastic.PlasticManagerDelegate;
 
@@ -41,7 +46,7 @@ import org.apache.tapestry5.plastic.Plas
  * to be instantiated as runtime classes.
  */
 @SuppressWarnings("rawtypes")
-public class PlasticClassPool implements ClassLoaderDelegate, Opcodes
+public class PlasticClassPool implements ClassLoaderDelegate, Opcodes, PlasticClassListenerHub
 {
     final PlasticClassLoader loader;
 
@@ -55,6 +60,8 @@ public class PlasticClassPool implements
 
     private final StaticContext emptyStaticContext = new StaticContext();
 
+    private final List<PlasticClassListener> listeners = new CopyOnWriteArrayList<PlasticClassListener>();
+
     private final Cache<String, TypeCategory> typeName2Category = new Cache<String, TypeCategory>()
     {
 
@@ -108,16 +115,19 @@ public class PlasticClassPool implements
     public synchronized Class realizeTransformedClass(ClassNode classNode, MethodBundle methodBundle,
             StaticContext staticContext)
     {
-        Class result = realize(classNode);
+        Class result = realize(PlasticInternalUtils.toClassName(classNode.name), ClassType.PRIMARY, classNode);
 
         baseClassDefs.put(result.getName(), new BaseClassDef(methodBundle, staticContext));
 
         return result;
     }
 
-    public synchronized Class realize(ClassNode classNode)
+    public synchronized Class realize(String primaryClassName, ClassType classType, final ClassNode classNode)
     {
-        PlasticInternalUtils.debugClass(classNode);
+        if (!listeners.isEmpty())
+        {
+            fire(toEvent(primaryClassName, classType, classNode));
+        }
 
         byte[] bytecode = toBytecode(classNode);
 
@@ -126,6 +136,41 @@ public class PlasticClassPool implements
         return loader.defineClassWithBytecode(className, bytecode);
     }
 
+    private PlasticClassEvent toEvent(final String primaryClassName, final ClassType classType,
+            final ClassNode classNode)
+    {
+        return new PlasticClassEvent()
+        {
+            public ClassType getType()
+            {
+                return classType;
+            }
+
+            public String getPrimaryClassName()
+            {
+                return primaryClassName;
+            }
+
+            public String getDissasembledBytecode()
+            {
+                return PlasticInternalUtils.dissasembleBytecode(classNode);
+            }
+
+            public String getClassName()
+            {
+                return PlasticInternalUtils.toClassName(classNode.name);
+            }
+        };
+    }
+
+    private void fire(PlasticClassEvent event)
+    {
+        for (PlasticClassListener listener : listeners)
+        {
+            listener.classWillLoad(event);
+        }
+    }
+
     private byte[] toBytecode(ClassNode classNode)
     {
         ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
@@ -468,4 +513,19 @@ public class PlasticClassPool implements
 
         return typeName2Category.get(typeName);
     }
+
+    public void addPlasticClassListener(PlasticClassListener listener)
+    {
+        assert listener != null;
+
+        listeners.add(listener);
+    }
+
+    public void removePlasticClassListener(PlasticClassListener listener)
+    {
+        assert listener != null;
+
+        listeners.remove(listener);
+    }
+
 }

Modified: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticInternalUtils.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticInternalUtils.java?rev=1098037&r1=1098036&r2=1098037&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticInternalUtils.java (original)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/internal/plastic/PlasticInternalUtils.java Sat Apr 30 02:05:23 2011
@@ -17,6 +17,7 @@ package org.apache.tapestry5.internal.pl
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -37,11 +38,6 @@ import org.apache.tapestry5.plastic.Meth
 @SuppressWarnings("rawtypes")
 public class PlasticInternalUtils
 {
-
-    private static final String SEP = "================================================";
-
-    public static final boolean DEBUG_ENABLED = Boolean.getBoolean("plastic-classnode-debug");
-
     public static final String[] EMPTY = new String[0];
 
     public static boolean isEmpty(Object[] input)
@@ -218,22 +214,18 @@ public class PlasticInternalUtils
         return new ArrayList<T>();
     }
 
-    public static void debugClass(ClassNode classNode)
+    public static String dissasembleBytecode(ClassNode classNode)
     {
-        if (!DEBUG_ENABLED)
-            return;
-
-        PrintWriter pw = new PrintWriter(System.out);
-
-        TraceClassVisitor visitor = new TraceClassVisitor(pw);
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter writer = new PrintWriter(stringWriter);
 
-        System.out.println(SEP);
+        TraceClassVisitor visitor = new TraceClassVisitor(writer);
 
         classNode.accept(visitor);
 
-        pw.flush();
+        writer.close();
 
-        System.out.println(SEP);
+        return stringWriter.toString();
     }
 
     private static final Pattern PROPERTY_PATTERN = Pattern.compile("^(m?_+)?(.+?)_*$", Pattern.CASE_INSENSITIVE);

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ClassType.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ClassType.java?rev=1098037&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ClassType.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/ClassType.java Sat Apr 30 02:05:23 2011
@@ -0,0 +1,37 @@
+// Copyright 2011 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.plastic;
+
+/**
+ * Identifies the type of class referenced in a {@link PlasticClassEvent}.
+ * 
+ * @see PlasticClassListener
+ */
+public enum ClassType
+{
+    /**
+     * A primary class, either created new or by transforming an existing class.
+     */
+    PRIMARY,
+
+    /** A supporting class, needed to implement some of the logic of the primary class. */
+    SUPPORT,
+
+    /**
+     * An implementation of {@link MethodInvocation}, needed to handle advice added to
+     * a method of a primary class.
+     */
+    METHOD_INVOCATION
+}

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassEvent.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassEvent.java?rev=1098037&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassEvent.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassEvent.java Sat Apr 30 02:05:23 2011
@@ -0,0 +1,44 @@
+// Copyright 2011 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.plastic;
+
+/**
+ * Describes a transformed class about to be loaded.
+ */
+public interface PlasticClassEvent
+{
+    /**
+     * The primary class name, i.e., the class being created or transformed.
+     */
+    String getPrimaryClassName();
+
+    /**
+     * The name of the actual class, which will differ from the primary class name
+     * for some types.
+     */
+    String getClassName();
+
+    /**
+     * Identifies what type of class is being loaded. Typically, the supporting and method invocation
+     * types will <em>precede</em> the primary class.
+     */
+    ClassType getType();
+
+    /**
+     * The bytecode for the class, dissassembled and formatted as a string. This is useful
+     * for debugging purposes.
+     */
+    String getDissasembledBytecode();
+}

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListener.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListener.java?rev=1098037&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListener.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListener.java Sat Apr 30 02:05:23 2011
@@ -0,0 +1,37 @@
+// Copyright 2011 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.plastic;
+
+/**
+ * Allows a listener to be notified about classes about to be loaded by the manager's class loader.
+ * Some classes are loaded by the same class loader but not transformed (for example, inner classes). These
+ * do not generate events. In many cases, transforming a class will cause supporting classes (representing
+ * {@link MethodInvocation}s, and other things) to be created; these do generate events, and can be distinguished
+ * by the {@linkplain PlasticClassEvent#getType() event's type property}.
+ * 
+ * @see PlasticClassListenerHub
+ */
+public interface PlasticClassListener
+{
+    /**
+     * Invoked just before a class is to be loaded. Separate events are fired for supporting classes before
+     * the event for the primary class (the class being transformed or created from scratch).
+     * 
+     * @param event
+     *            describes the class to be loaded, and gives access to its dissasembled
+     *            bytecode (for debugging purposes)
+     */
+    void classWillLoad(PlasticClassEvent event);
+}

Added: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListenerHub.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListenerHub.java?rev=1098037&view=auto
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListenerHub.java (added)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticClassListenerHub.java Sat Apr 30 02:05:23 2011
@@ -0,0 +1,22 @@
+// Copyright 2011 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.plastic;
+
+public interface PlasticClassListenerHub
+{
+    void addPlasticClassListener(PlasticClassListener listener);
+
+    void removePlasticClassListener(PlasticClassListener listener);
+}

Modified: tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java?rev=1098037&r1=1098036&r2=1098037&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java (original)
+++ tapestry/tapestry5/trunk/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticManager.java Sat Apr 30 02:05:23 2011
@@ -26,7 +26,7 @@ import org.apache.tapestry5.internal.pla
  * or to create new classes dynamically at runtime.
  */
 @SuppressWarnings("unchecked")
-public class PlasticManager
+public class PlasticManager implements PlasticClassListenerHub
 {
     private final PlasticClassPool pool;
 
@@ -182,7 +182,7 @@ public class PlasticManager
                     "Class %s is not an interface; proxies may only be created for interfaces.",
                     interfaceType.getName()));
 
-        String name = String.format("$PlasticProxy$%s_%s", interfaceType.getSimpleName(), PlasticUtils.nextUID());
+        String name = String.format("$%s_%s", interfaceType.getSimpleName(), PlasticUtils.nextUID());
 
         PlasticClassTransformation<T> result = pool.createTransformation("java.lang.Object", name);
 
@@ -190,4 +190,14 @@ public class PlasticManager
 
         return result;
     }
+
+    public void addPlasticClassListener(PlasticClassListener listener)
+    {
+        pool.addPlasticClassListener(listener);
+    }
+
+    public void removePlasticClassListener(PlasticClassListener listener)
+    {
+        pool.removePlasticClassListener(listener);
+    }
 }

Modified: tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ProxyCreation.groovy
URL: http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ProxyCreation.groovy?rev=1098037&r1=1098036&r2=1098037&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ProxyCreation.groovy (original)
+++ tapestry/tapestry5/trunk/plastic/src/test/groovy/org/apache/tapestry5/plastic/ProxyCreation.groovy Sat Apr 30 02:05:23 2011
@@ -36,7 +36,7 @@ class ProxyCreation extends Specificatio
     def "create a method delegating proxy"() {
         setup:
         "Each method of the interface delegates through a method that returns the new target for the method."
-        
+
         def mgr = new PlasticManager()
 
         Runnable r = Mock(Runnable)
@@ -90,4 +90,34 @@ class ProxyCreation extends Specificatio
         e.message == "Class java.lang.String is not an interface; proxies may only be created for interfaces."
     }
 
+
+    def "listen to plastic class events"() {
+        Runnable r = Mock(Runnable)
+        PlasticClassListener listener = { PlasticClassEvent event ->
+
+            assert event.className == event.primaryClassName
+
+            assert event.dissasembledBytecode.contains(event.primaryClassName)
+
+            assert event.type == ClassType.PRIMARY
+        } as PlasticClassListener
+
+        def mgr = new PlasticManager()
+
+        mgr.addPlasticClassListener listener
+
+        def ins = mgr.createProxy(Runnable.class, {
+            def f = it.introduceField(Runnable.class, "delegate").inject(r)
+
+            it.introduceMethod(new MethodDescription("void", "run")).delegateTo(f)
+        } as PlasticClassTransformer)
+
+        when:
+
+        def proxy = ins.newInstance()
+
+        then:
+
+        proxy != null
+    }
 }



Re: svn commit: r1098037 - in /tapestry/tapestry5/trunk/plastic/src: main/java/org/apache/tapestry5/internal/plastic/ main/java/org/apache/tapestry5/plastic/ test/groovy/org/apache/tapestry5/plastic/

Posted by Howard Lewis Ship <hl...@gmail.com>.
It's there to support tapestry.transform logging; fun to see the bytecode
stream by. Out could also be useful for some kind of JMX monitoring.
On May 3, 2011 1:59 AM, "Massimo Lusetti" <ml...@gmail.com> wrote:
> On Sat, Apr 30, 2011 at 4:05 AM, <hl...@apache.org> wrote:
>
>> Author: hlship
>> Date: Sat Apr 30 02:05:23 2011
>> New Revision: 1098037
>>
>> URL: http://svn.apache.org/viewvc?rev=1098037&view=rev
>> Log:
>> TAP5-853: Add support for listener notifications as classes are loaded
>
> Oh man... this is cool and nice to play with!
>
> Cheers
> --
> Massimo
> http://meridio.blogspot.com
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@tapestry.apache.org
> For additional commands, e-mail: dev-help@tapestry.apache.org
>

Re: svn commit: r1098037 - in /tapestry/tapestry5/trunk/plastic/src: main/java/org/apache/tapestry5/internal/plastic/ main/java/org/apache/tapestry5/plastic/ test/groovy/org/apache/tapestry5/plastic/

Posted by Massimo Lusetti <ml...@gmail.com>.
On Sat, Apr 30, 2011 at 4:05 AM,  <hl...@apache.org> wrote:

> Author: hlship
> Date: Sat Apr 30 02:05:23 2011
> New Revision: 1098037
>
> URL: http://svn.apache.org/viewvc?rev=1098037&view=rev
> Log:
> TAP5-853: Add support for listener notifications as classes are loaded

Oh man... this is cool and nice to play with!

Cheers
-- 
Massimo
http://meridio.blogspot.com

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tapestry.apache.org
For additional commands, e-mail: dev-help@tapestry.apache.org