This is an automated email from the ASF dual-hosted git repository.

thiagohp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git


The following commit(s) were added to refs/heads/master by this push:
     new 199795a8d TAP5-2779: multiple classloader fixes
199795a8d is described below

commit 199795a8d326fc13562f7d4d74e8ebdbc163bf00
Author: Thiago H. de Paula Figueiredo <[email protected]>
AuthorDate: Fri May 17 09:42:21 2024 -0300

    TAP5-2779: multiple classloader fixes
---
 .../org/apache/tapestry5/plastic/PlasticUtils.java |  81 ++++++++-
 .../tapestry5/plastic/PropertyValueProvider.java   |  38 +++-
 .../apache/tapestry5/plastic/PlasticUtilsTest.java |  37 +++-
 .../plastic/test/PlasticUtilsTestObject.java       |  17 +-
 .../test/PlasticUtilsTestObjectSuperclass.java     |   2 +
 .../Enumeration.java}                              |  27 +--
 .../services/ComponentInstantiatorSourceImpl.java  |   2 +-
 .../internal/services/PageSourceImpl.java          |  21 ++-
 .../tapestry5/internal/transform/CachedWorker.java | 198 +++++++++++++++++++--
 .../app1/components/SubclassWithImport.java        |  20 ++-
 .../app1/components/SuperclassWithImport.java      |  33 +++-
 .../app1/components/SubclassWithImport.tml         |   3 +
 12 files changed, 410 insertions(+), 69 deletions(-)

diff --git 
a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java 
b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
index 8dd3c76f1..d36993b1d 100644
--- a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
+++ b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
@@ -25,6 +25,10 @@ import org.apache.tapestry5.internal.plastic.PrimitiveType;
  */
 public class PlasticUtils
 {
+    private static final String SETTER_METHOD_NAME = 
"__propertyValueProvider__set";
+
+    private static final String GETTER_METHOD_NAME = 
"__propertyValueProvider__get";
+
     /**
      * The {@code toString()} method inherited from Object.
      */
@@ -37,14 +41,17 @@ public class PlasticUtils
 
     private static final AtomicLong UID_GENERATOR = new 
AtomicLong(System.nanoTime());
     
-    private static final MethodDescription 
PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION;
+    private static final MethodDescription 
PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION;
+    
+    private static final MethodDescription 
PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION;
     
     private static final MethodDescription 
FIELD_VALUE_PROVIDER_METHOD_DESCRIPTION;
     
     static
     {
         try {
-            PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION = new 
MethodDescription(PropertyValueProvider.class.getMethod("__propertyValueProvider__get",
 String.class));
+            PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION = new 
MethodDescription(PropertyValueProvider.class.getMethod(GETTER_METHOD_NAME, 
String.class));
+            PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION = new 
MethodDescription(PropertyValueProvider.class.getMethod(SETTER_METHOD_NAME, 
String.class, Object.class));            
             FIELD_VALUE_PROVIDER_METHOD_DESCRIPTION = new 
MethodDescription(FieldValueProvider.class.getMethod("__fieldValueProvider__get",
 String.class));
         } catch (Exception e) {
             throw new ExceptionInInitializerError(e);
@@ -250,7 +257,7 @@ public class PlasticUtils
         
         final Set<PlasticMethod> methods = 
plasticClass.introduceInterface(PropertyValueProvider.class);
         
-        final InstructionBuilderCallback callback = (builder) -> {
+        final InstructionBuilderCallback getterCallback = (builder) -> {
             
             for (FieldInfo field : fieldInfos) 
             {
@@ -273,6 +280,9 @@ public class PlasticUtils
                 
             }
             
+            // Field/property not found, so let's try the superclass in case
+            // it also implement
+            
             builder.loadThis();
             builder.instanceOf(PropertyValueProvider.class);
             
@@ -281,31 +291,86 @@ public class PlasticUtils
                 builder.loadArgument(0);
                 ifBuilder.invokeSpecial(
                         plasticClass.getSuperClassName(), 
-                        PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION);
+                        PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION);
                 ifBuilder.returnResult();
             });
             
+            // Giving up
+            
+            builder.throwException(RuntimeException.class, "Property not found 
or not supported");
+            
+        };
+        
+        final InstructionBuilderCallback setterCallback = (builder) -> {
+            
+            for (FieldInfo field : fieldInfos) 
+            {
+                builder.loadArgument(0);
+                builder.loadConstant(field.name);
+                builder.invokeVirtual(String.class.getName(), "boolean", 
"equals", Object.class.getName());
+                builder.when(Condition.NON_ZERO, ifBuilder -> 
+                {
+                    final String methodName = "set" + 
PlasticInternalUtils.capitalize(field.name);
+                    
+                    ifBuilder.loadThis();
+                    ifBuilder.loadArgument(1);
+                    ifBuilder.castOrUnbox(field.type);
+                    ifBuilder.invokeVirtual(
+                            plasticClass.getClassName(), 
+                            void.class.getName(), 
+                            methodName,
+                            field.type);
+                    ifBuilder.returnResult();
+                });
+                
+            }
+            
             // Field/property not found, so let's try the superclass in case
             // it also implement
             
+            builder.loadThis();
+            builder.instanceOf(PropertyValueProvider.class);
+            
+            builder.when(Condition.NON_ZERO, ifBuilder -> {
+                builder.loadThis();
+                builder.loadArgument(0);
+                builder.loadArgument(1);
+                ifBuilder.invokeSpecial(
+                        plasticClass.getSuperClassName(), 
+                        PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION);
+                ifBuilder.returnResult();
+            });
+            
+            // Giving up
+            
             builder.throwException(RuntimeException.class, "Property not found 
or not supported");
             
         };
         
-        final PlasticMethod method;
+        final PlasticMethod getterMethod;
+        final PlasticMethod setterMethod;
         
         // Superclass has already defined this method, so we need to override 
it so
         // it can also find the subclasses' declared fields/properties.
         if (methods.isEmpty())
         {
-            method = 
plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION , 
callback);
+            getterMethod = 
plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_GETTER_METHOD_DESCRIPTION, 
getterCallback);
+            setterMethod = 
plasticClass.introduceMethod(PROPERTY_VALUE_PROVIDER_SETTER_METHOD_DESCRIPTION, 
setterCallback);
         }
         else
         {
-            method = methods.iterator().next();
+            getterMethod = methods.stream()
+                    .filter(m -> 
m.getDescription().methodName.equals(GETTER_METHOD_NAME))
+                    .findFirst()
+                    .get();
+            setterMethod = methods.stream()
+                    .filter(m -> 
m.getDescription().methodName.equals(SETTER_METHOD_NAME))
+                    .findFirst()
+                    .get();
         }
         
-        method.changeImplementation(callback);
+        getterMethod.changeImplementation(getterCallback);
+        setterMethod.changeImplementation(setterCallback);
         
     }
 
diff --git 
a/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java 
b/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
index 3f841b82d..a10ecfeb7 100644
--- 
a/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
+++ 
b/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
@@ -1,4 +1,4 @@
-// Copyright 2023 The Apache Software Foundation
+// Copyright 2023, 2024 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.
@@ -34,7 +34,15 @@ public interface PropertyValueProvider
      * @return the field value.
      */
     Object __propertyValueProvider__get(String fieldName);
-    
+
+    /**
+     * Sets the value of a given field.
+     * @param fieldName the field name.
+     * @param value the field value.
+     * @since 5.8.7
+     */
+    void __propertyValueProvider__set(String fieldName, Object value);
+
     /**
      * <p>
      * Returns the value of a given field in a given object if it belongs to a 
class
@@ -60,4 +68,30 @@ public interface PropertyValueProvider
         }
     }
     
+    /**
+     * <p>
+     * Sets the value of a given field in a given object if it belongs to a 
class
+     * that implements {@linkplain PropertyValueProvider}. Otherwise, it 
throws an exception.
+     * </p>
+     * <p>
+     * This is an utility method to avoid having to make casts very time you 
need to call
+     * {@linkplain #__propertyValueProvider__set(String)}.
+     * </p>
+     * @param object an object.
+     * @param fieldName the field name.
+     * @param value the field value.
+     * @since 5.8.7
+     */
+    static void set(Object object, String fieldName, Object value)
+    {
+        if (object instanceof PropertyValueProvider)
+        {
+            ((PropertyValueProvider) 
object).__propertyValueProvider__set(fieldName, value);
+        }
+        else
+        {
+            throw new RuntimeException("Class " + object.getClass().getName() 
+ " doesn't implement " + PropertyValueProvider.class.getSimpleName());
+        }
+    }
+    
 }
diff --git 
a/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java 
b/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
index 40faac5d2..399ac3960 100644
--- a/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
+++ b/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
@@ -22,8 +22,10 @@ import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.apache.tapestry5.internal.plastic.asm.util.ASMifier;
 import org.apache.tapestry5.plastic.test.PlasticUtilsTestObject;
 import org.apache.tapestry5.plastic.test.PlasticUtilsTestObjectSuperclass;
+import org.apache.tapestry5.plastic.test_.Enumeration;
 import org.junit.jupiter.api.Test;
 
 // [Thiago] This is only here because I couldn't get Groovy tests to run on 
Eclipse
@@ -31,11 +33,12 @@ import org.junit.jupiter.api.Test;
 public class PlasticUtilsTest 
 {
     
-//    public static void main(String[] args) throws ClassNotFoundException {
-//        final PlasticUtilsTest plasticUtilsTest = new PlasticUtilsTest();
-//        plasticUtilsTest.implement_field_value_provider();
-//        plasticUtilsTest.implement_property_value_provider();
-//    }
+    public static void main(String[] args) throws Exception {
+//        ASMifier.main(new String[] {PlasticUtilsTestObject.class.getName(), 
"-nodebug"});
+        final PlasticUtilsTest plasticUtilsTest = new PlasticUtilsTest();
+        plasticUtilsTest.implement_field_value_provider();
+        plasticUtilsTest.implement_property_value_provider();
+    }
     
     @Test
     public void implement_field_value_provider() throws ClassNotFoundException
@@ -110,6 +113,30 @@ public class PlasticUtilsTest
         assertEquals(PlasticUtilsTestObject.TRUE_OF_FALSE, (Boolean) 
PropertyValueProvider.get(object, "trueOrFalse"));
         assertEquals(PlasticUtilsTestObjectSuperclass.SUPER, 
PropertyValueProvider.get(object, "superString"));
         
+        final String newStringValue = "something else";
+        final String newOtherStringValue = "what?";
+        final String newNullStringValue = "not null anymore";
+        final Enumeration newEnumerationValue = Enumeration.TRUE;
+        final int[] newIntArrayValue = new int[] { 3, 1, 4 };
+        final boolean newTrueOfFalseValue = 
!PlasticUtilsTestObject.TRUE_OF_FALSE;
+        final String newSuperStringValue = "Batman";
+        
+        PropertyValueProvider.set(object, "string", newStringValue);
+        PropertyValueProvider.set(object, "otherString", newOtherStringValue);
+        PropertyValueProvider.set(object, "nullString", newNullStringValue);
+        PropertyValueProvider.set(object, "enumeration", newEnumerationValue);
+        PropertyValueProvider.set(object, "intArray", newIntArrayValue);
+        PropertyValueProvider.set(object, "trueOrFalse", newTrueOfFalseValue);
+        PropertyValueProvider.set(object, "superString", newSuperStringValue);
+        
+        assertEquals(newStringValue, PropertyValueProvider.get(object, 
"string"));
+        assertEquals(newOtherStringValue, PropertyValueProvider.get(object, 
"otherString"));
+        assertEquals(newNullStringValue, PropertyValueProvider.get(object, 
"nullString"));
+        assertEquals(newEnumerationValue.toString(), 
PropertyValueProvider.get(object, "enumeration").toString());
+        assertTrue(Arrays.equals(newIntArrayValue, (int[]) 
PropertyValueProvider.get(object, "intArray")));
+        assertEquals(newTrueOfFalseValue, (Boolean) 
PropertyValueProvider.get(object, "trueOrFalse"));
+        assertEquals(newSuperStringValue, PropertyValueProvider.get(object, 
"superString"));
+        
     }
     
 }
diff --git 
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
 
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
index bd34620ff..484d60ed2 100644
--- 
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
+++ 
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
@@ -14,22 +14,29 @@
 
 package org.apache.tapestry5.plastic.test;
 
+import org.apache.tapestry5.plastic.test_.Enumeration;
+
 public class PlasticUtilsTestObject extends PlasticUtilsTestObjectSuperclass
 {
     
     public static final String STRING = "A nice string";
     public static final int[] INT_ARRAY = new int[] {1, 42};
     public static final String OTHER_STRING = "Another nice string";
-    public static final Enumeration ENUMERATION = 
PlasticUtilsTestObject.Enumeration.FILE_NOT_FOUND;
+    public static final Enumeration ENUMERATION = Enumeration.FILE_NOT_FOUND;
     public static final boolean TRUE_OF_FALSE = true;
     
-    public static enum Enumeration
+    public PlasticUtilsTestObject()
     {
-        TRUE,
-        FALSE,
-        FILE_NOT_FOUND
     }
     
+    public PlasticUtilsTestObject(String ignored, boolean b)
+    {
+        setString("?");
+        setString(ignored);
+        setTrueOrFalse(b);
+    }
+    
+    
     private String string = STRING;
     
     private String otherString = OTHER_STRING;
diff --git 
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
 
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
index f82995f88..8a713b18c 100644
--- 
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
+++ 
b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
@@ -20,6 +20,8 @@ public class PlasticUtilsTestObjectSuperclass
     public static final String SUPER = "Super!!!";
     
     private String superString = SUPER;
+    
+    private String overridden = "from superclass";
 
     public String getSuperString() 
     {
diff --git 
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
 b/plastic/src/test/java/org/apache/tapestry5/plastic/test_/Enumeration.java
similarity index 56%
copy from 
plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
copy to 
plastic/src/test/java/org/apache/tapestry5/plastic/test_/Enumeration.java
index f82995f88..660161c59 100644
--- 
a/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
+++ b/plastic/src/test/java/org/apache/tapestry5/plastic/test_/Enumeration.java
@@ -1,4 +1,4 @@
-// Copyright 2023 The Apache Software Foundation
+// Copyright 2024 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.
@@ -11,24 +11,11 @@
 // 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.test_;
 
-package org.apache.tapestry5.plastic.test;
-
-public class PlasticUtilsTestObjectSuperclass 
+public enum Enumeration
 {
-    
-    public static final String SUPER = "Super!!!";
-    
-    private String superString = SUPER;
-
-    public String getSuperString() 
-    {
-        return superString;
-    }
-
-    public void setSuperString(String superString) 
-    {
-        this.superString = superString;
-    }
-    
-}
+    TRUE,
+    FALSE,
+    FILE_NOT_FOUND
+}
\ No newline at end of file
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
index 3573b474b..8a5344c72 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ComponentInstantiatorSourceImpl.java
@@ -185,7 +185,7 @@ public final class ComponentInstantiatorSourceImpl 
implements ComponentInstantia
         this.tracker = tracker;
         this.invalidationHub = invalidationHub;
         this.productionMode = productionMode;
-        this.multipleClassLoaders = multipleClassLoaders;
+        this.multipleClassLoaders = multipleClassLoaders && !productionMode;
         this.resolver = resolver;
         this.pageClassLoaderContextManager = pageClassLoaderContextManager;
         this.componentDependencyRegistry = componentDependencyRegistry;
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
index 48a004fd8..6c67fe802 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/PageSourceImpl.java
@@ -17,6 +17,7 @@ package org.apache.tapestry5.internal.services;
 import java.lang.ref.SoftReference;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -121,7 +122,7 @@ public class PageSourceImpl implements PageSource
         this.componentClassResolver = componentClassResolver;
         this.productionMode = productionMode;
         this.pageCachingReferenceTypeService = pageCachingReferenceTypeService;
-        this.multipleClassLoaders = multipleClassLoaders;
+        this.multipleClassLoaders = multipleClassLoaders && !productionMode;
         this.pageClassLoaderContextManager = pageClassLoaderContextManager;
         this.logger = logger;
     }
@@ -134,7 +135,9 @@ public class PageSourceImpl implements PageSource
         }
         try
         {
-            return getPage(canonicalPageName, true);
+            @SuppressWarnings("unchecked")
+            Set<String> alreadyProcessed = multipleClassLoaders ? new 
HashSet<>() : Collections.EMPTY_SET;
+            return getPage(canonicalPageName, true, alreadyProcessed);
         }
         finally
         {
@@ -145,7 +148,7 @@ public class PageSourceImpl implements PageSource
         }
     }
 
-    public Page getPage(String canonicalPageName, boolean 
invalidateUnknownContext)
+    public Page getPage(String canonicalPageName, boolean 
invalidateUnknownContext, Set<String> alreadyProcessed)
     {
         ComponentResourceSelector selector = 
selectorAnalyzer.buildSelectorForRequest();
 
@@ -178,7 +181,13 @@ public class PageSourceImpl implements PageSource
                 
                 for (String pageClassName : pageDependencies)
                 {
-                    page = 
getPage(componentClassResolver.resolvePageClassNameToPageName(pageClassName), 
false);
+                    // Avoiding infinite recursion caused by circular 
dependencies
+                    if (!alreadyProcessed.contains(pageClassName))
+                    {
+                        alreadyProcessed.add(pageClassName);
+                        page = 
getPage(componentClassResolver.resolvePageClassNameToPageName(pageClassName), 
+                                invalidateUnknownContext, alreadyProcessed);
+                    }
                 }
                 
             }
@@ -203,7 +212,7 @@ public class PageSourceImpl implements PageSource
             {
                 final ComponentPageElement rootElement = page.getRootElement();
                 componentDependencyRegistry.clear(rootElement);
-                componentDependencyRegistry.register(rootElement);
+                
componentDependencyRegistry.register(rootElement.getComponent().getClass());
                 PageClassLoaderContext context = 
pageClassLoaderContextManager.get(className);
                 
                 if (context.isUnknown() && multipleClassLoaders)
@@ -216,7 +225,7 @@ public class PageSourceImpl implements PageSource
                     }
                     context.getClassNames().clear();
                     // Avoiding bad invalidations
-                    return getPage(canonicalPageName, false);
+                    return getPage(canonicalPageName, false, alreadyProcessed);
                 }
             }
             
diff --git 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java
 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java
index 7a7fbe38f..e5848dcd3 100644
--- 
a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java
+++ 
b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/CachedWorker.java
@@ -24,6 +24,9 @@ import org.apache.tapestry5.ioc.annotations.Inject;
 import org.apache.tapestry5.ioc.annotations.Symbol;
 import org.apache.tapestry5.ioc.services.PerThreadValue;
 import org.apache.tapestry5.ioc.services.PerthreadManager;
+import org.apache.tapestry5.json.JSONArray;
+import org.apache.tapestry5.json.JSONObject;
+import org.apache.tapestry5.model.ComponentModel;
 import org.apache.tapestry5.model.MutableComponentModel;
 import org.apache.tapestry5.plastic.*;
 import org.apache.tapestry5.plastic.PlasticUtils.FieldInfo;
@@ -33,9 +36,15 @@ import org.apache.tapestry5.services.TransformConstants;
 import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
 import org.apache.tapestry5.services.transform.TransformationSupport;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Caches method return values for methods annotated with {@link Cached}.
@@ -43,7 +52,25 @@ import java.util.Set;
 @SuppressWarnings("all")
 public class CachedWorker implements ComponentClassTransformWorker2
 {
+    private static final String WATCH_BINDING_PREFIX = "cache$watchBinding$";
+
     private static final String FIELD_PREFIX = "cache$";
+    
+    private static final String META_PROPERTY = "cachedWorker";
+
+    private static final String MODIFIERS = "modifiers";
+    
+    private static final String RETURN_TYPE = "returnType";
+    
+    private static final String NAME = "name";
+    
+    private static final String GENERIC_SIGNATURE = "genericSignature";
+    
+    private static final String ARGUMENT_TYPES = "argumentTypes";
+    
+    private static final String CHECKED_EXCEPTION_TYPES = 
"checkedExceptionTypes";
+    
+    private static final String WATCH = "watch";
 
     private final BindingSource bindingSource;
 
@@ -143,14 +170,39 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
 
     public void transform(PlasticClass plasticClass, TransformationSupport 
support, MutableComponentModel model)
     {
-        List<PlasticMethod> methods = 
plasticClass.getMethodsWithAnnotation(Cached.class);
-        Set<PlasticUtils.FieldInfo> fieldInfos = multipleClassLoaders ? new 
HashSet<>() : null;
+        final List<PlasticMethod> methods = 
plasticClass.getMethodsWithAnnotation(Cached.class);
+        final Set<PlasticUtils.FieldInfo> fieldInfos = multipleClassLoaders ? 
new HashSet<>() : null;
+        final Map<String, String> extraMethodCachedWatchMap = 
multipleClassLoaders ? new HashMap<>() : null;
+        
+        if (multipleClassLoaders)
+        {
+            
+            // Store @Cache-annotated methods information so subclasses can 
+            // know about them.
+            
+            model.setMeta(META_PROPERTY, 
toJSONArray(methods).toCompactString());
+            
+            // Use the information from superclasses
+            
+            ComponentModel parentModel = model.getParentModel();
+            Set<PlasticMethod> extraMethods = new HashSet<>();
+            while (parentModel != null)
+            {
+                extraMethods.addAll(
+                        toPlasticMethodList(
+                                parentModel.getMeta(META_PROPERTY), 
plasticClass, extraMethodCachedWatchMap));
+                parentModel = parentModel.getParentModel();
+            }
+            
+            methods.addAll(extraMethods);
+            
+        }
 
         for (PlasticMethod method : methods)
         {
             validateMethod(method);
 
-            adviseMethod(plasticClass, method, fieldInfos);
+            adviseMethod(plasticClass, method, fieldInfos, model, 
extraMethodCachedWatchMap);
         }
         
         if (multipleClassLoaders && !fieldInfos.isEmpty())
@@ -158,8 +210,70 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
             this.propertyValueProviderWorker.add(plasticClass, fieldInfos);
         }        
     }
+    
+    private Collection<PlasticMethod> toPlasticMethodList(String meta, 
PlasticClass plasticClass,
+            Map<String, String> extraMethodCachedWatchMap) 
+    {
+        final JSONArray array = new JSONArray(meta);
+        List<PlasticMethod> methods = new ArrayList<>(array.size());
+        for (int i = 0; i < array.size(); i++)
+        {
+            final JSONObject jsonObject = array.getJSONObject(i);
+            methods.add(toPlasticMethod(jsonObject, plasticClass, 
extraMethodCachedWatchMap));
+        }
+        return methods;
+    }
+
+
+    private static PlasticMethod toPlasticMethod(JSONObject jsonObject, 
PlasticClass plasticClass,
+            Map<String, String> extraMethodCachedWatchMap) 
+    {
+        final int modifiers = jsonObject.getInt(MODIFIERS);
+        final String returnType = jsonObject.getString(RETURN_TYPE);
+        final String methodName = jsonObject.getString(NAME);
+        final String genericSignature = 
jsonObject.getStringOrDefault(GENERIC_SIGNATURE, null);
+        final JSONArray argumentTypesArray = 
jsonObject.getJSONArray(ARGUMENT_TYPES);
+        final String[] argumentTypes = argumentTypesArray.stream()
+                .collect(Collectors.toList()).toArray(new 
String[argumentTypesArray.size()]);
+        final JSONArray checkedExceptionTypesArray = 
jsonObject.getJSONArray(CHECKED_EXCEPTION_TYPES);
+        final String[] checkedExceptionTypes = 
checkedExceptionTypesArray.stream()
+                .collect(Collectors.toList()).toArray(new 
String[checkedExceptionTypesArray.size()]);
+        
+        if (!extraMethodCachedWatchMap.containsKey(methodName))
+        {
+            extraMethodCachedWatchMap.put(methodName, 
jsonObject.getString(WATCH));
+        }
+        
+        return plasticClass.introduceMethod(new MethodDescription(
+                modifiers, returnType, methodName, argumentTypes, 
+                genericSignature, checkedExceptionTypes));
+    }
+
+    private static JSONArray toJSONArray(List<PlasticMethod> methods)
+    {
+        final JSONArray array = new JSONArray();
+        for (PlasticMethod method : methods) 
+        {
+            array.add(toJSONObject(method));
+        }
+        return array;
+    }
+
+    private static JSONObject toJSONObject(PlasticMethod method) 
+    {
+        final MethodDescription description = method.getDescription();
+        return new JSONObject(
+                MODIFIERS, description.modifiers,
+                RETURN_TYPE, description.returnType,
+                NAME, description.methodName,
+                GENERIC_SIGNATURE, description.genericSignature,
+                ARGUMENT_TYPES, new JSONArray(description.argumentTypes),
+                CHECKED_EXCEPTION_TYPES, new 
JSONArray(description.checkedExceptionTypes),
+                WATCH, method.getAnnotation(Cached.class).watch());
+    }
 
-    private void adviseMethod(PlasticClass plasticClass, PlasticMethod method, 
Set<FieldInfo> fieldInfos)
+    private void adviseMethod(PlasticClass plasticClass, PlasticMethod method, 
Set<FieldInfo> fieldInfos,
+            MutableComponentModel model, Map<String, String> 
extraMethodCachedWatchMap)
     {
         // Every instance of the class requires its own per-thread value. This 
handles the case of multiple
         // pages containing the component, or the same page containing the 
component multiple times.
@@ -184,7 +298,10 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
 
         Cached annotation = method.getAnnotation(Cached.class);
 
-        MethodResultCacheFactory factory = createFactory(plasticClass, 
annotation.watch(), method);
+        final String expression = annotation != null ? 
+                annotation.watch() : 
+                    
extraMethodCachedWatchMap.get(method.getDescription().methodName);
+        MethodResultCacheFactory factory = createFactory(plasticClass, 
expression, method, fieldInfos, model);
 
         MethodAdvice advice = createAdvice(cacheField, factory);
 
@@ -192,17 +309,28 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
     }
 
     private String getFieldName(PlasticMethod method) {
-        final StringBuilder builder = new StringBuilder(FIELD_PREFIX);
-        builder.append(method.getDescription().methodName);
+        return getFieldName(method, FIELD_PREFIX);
+    }
+    
+    private String getFieldName(PlasticMethod method, String prefix) 
+    {
+        final String methodName = method.getDescription().methodName;
+        final String className = method.getPlasticClass().getClassName();
+        return getFieldName(prefix, methodName, className);
+    }
+
+    private String getFieldName(String prefix, final String methodName, final 
String className) 
+    {
+        final StringBuilder builder = new StringBuilder(prefix);
+        builder.append(methodName);
         if (multipleClassLoaders)
         {
             builder.append("_");
-            
builder.append(method.getPlasticClass().getClassName().replace('.', '_'));
+            builder.append(className.replace('.', '_'));
         }
         return builder.toString();
     }
 
-
     private MethodAdvice createAdvice(PlasticField cacheField,
                                       final MethodResultCacheFactory factory)
     {
@@ -255,7 +383,8 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
 
 
     private MethodResultCacheFactory createFactory(PlasticClass plasticClass, 
final String watch,
-                                                   PlasticMethod method)
+                                                   PlasticMethod method, 
Set<FieldInfo> fieldInfos,
+                                                   MutableComponentModel model)
     {
         // When there's no watch, a shared factory that just returns a new 
SimpleMethodResultCache
         // will suffice.
@@ -266,8 +395,23 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
 
         // Because of the watch, its necessary to create a factory for 
instances of this component and method.
 
-        final FieldHandle bindingFieldHandle = 
plasticClass.introduceField(Binding.class, "cache$watchBinding$" + 
method.getDescription().methodName).getHandle();
-
+        final String bindingFieldName = WATCH_BINDING_PREFIX + 
method.getDescription().methodName;
+        final PlasticField bindingField = 
plasticClass.introduceField(Binding.class, bindingFieldName);
+        final FieldHandle bindingFieldHandle = bindingField.getHandle();
+        
+        if (multipleClassLoaders)
+        {
+            fieldInfos.add(PlasticUtils.toFieldInfo(bindingField));
+            try
+            {
+                bindingField.createAccessors(PropertyAccessType.READ_WRITE);
+            }
+            catch (IllegalArgumentException e)
+            {
+                // Method already implemented in superclass, so, given we only
+                // care the method exists, we ignore this exception
+            }
+        }
 
         // Each component instance will get its own Binding instance. That 
handles both different locales,
         // and reuse of a component (with a cached method) within a page or 
across pages. However, the binding can't be initialized
@@ -283,7 +427,15 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
                 Binding binding = bindingSource.newBinding("@Cached watch", 
resources,
                         BindingConstants.PROP, watch);
 
-                bindingFieldHandle.set(invocation.getInstance(), binding);
+                final Object instance = invocation.getInstance();
+                if (multipleClassLoaders)
+                {
+                    PropertyValueProvider.set(instance, bindingFieldName, 
binding);
+                }
+                else 
+                {
+                    bindingFieldHandle.set(instance, binding);
+                }
 
                 invocation.proceed();
             }
@@ -293,10 +445,26 @@ public class CachedWorker implements 
ComponentClassTransformWorker2
         {
             public MethodResultCache create(Object instance)
             {
-                Binding binding = (Binding) bindingFieldHandle.get(instance);
-
+                Binding binding = (Binding) (
+                        multipleClassLoaders ? 
+                        PropertyValueProvider.get(instance, bindingFieldName) :
+                        bindingFieldHandle.get(instance));
+                
                 return new WatchedBindingMethodResultCache(binding);
             }
+
+            private Object getCacheBinding(final String methodName, String 
bindingFieldName, Object instance, ComponentModel model) 
+            {
+                Object value = PropertyValueProvider.get(instance, 
bindingFieldName);
+                while (value == null && model.getParentModel() != null)
+                {
+                    model = model.getParentModel();
+                    bindingFieldName = getFieldName(WATCH_BINDING_PREFIX, 
+                            methodName, model.getComponentClassName());
+                    value = PropertyValueProvider.get(instance, 
bindingFieldName);
+                }
+                return value;
+            }
         };
     }
 
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
index 9b865bef8..286afb0d5 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SubclassWithImport.java
@@ -4,23 +4,37 @@ import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.annotations.Cached;
 import org.apache.tapestry5.annotations.Import;
 import org.apache.tapestry5.annotations.Parameter;
+import org.apache.tapestry5.annotations.Persist;
+import org.apache.tapestry5.annotations.Property;
 import org.apache.tapestry5.corelib.components.Zone;
 
 @Import(stylesheet = "context:css/ie-only.css")
 public class SubclassWithImport extends SuperclassWithImport {
     
+    @Property int getIntCallCount;
+    
     // Just to test a bug happening when a parameter is an array type
     @Parameter
     private Zone[][][][][] zones;
     
-    @Cached
-    public int getInt() 
+    // Just to test CachedWorker with a watch expression
+    @Cached(watch = "counter")
+    public String getInt() 
     { 
-        return 2; 
+        getIntCallCount++;
+        return getCounter() + " from subclass";
+    }
+    
+    @Cached(watch = "counter")
+    public String getOther() 
+    { 
+        return getCounter() + " " + " from superclass"; 
     }
     
     public void setupRender(MarkupWriter writer) {
+        getIntCallCount = 0;
         writer.element("p").text("Int: " + getInt() + " : " + getInt());
         writer.end();
     }
+
 }
diff --git 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
index 2de9d1793..a03c1cde3 100644
--- 
a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
+++ 
b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/SuperclassWithImport.java
@@ -3,19 +3,44 @@ package org.apache.tapestry5.integration.app1.components;
 import org.apache.tapestry5.MarkupWriter;
 import org.apache.tapestry5.annotations.Cached;
 import org.apache.tapestry5.annotations.Import;
+import org.apache.tapestry5.annotations.Persist;
 
 @Import(stylesheet = "context:css/via-import.css")
 public class SuperclassWithImport {
     
-    @Cached
-    public int getOther() 
+    // Just to test CachedWorker with a watch expression 
+    @Persist
+    private int counter;
+    
+    @Persist
+    private int secondCounter;
+    
+    @Cached(watch = "counter")
+    public String getOther() 
+    { 
+        return getCounter() + " " + " from superclass"; 
+    }
+    
+    @Cached(watch = "secondCounter")
+    public String getNonOverriden() 
     { 
-        return 4; 
+        return getCounter() + " " + " non-overriden"; 
     }
     
     public void cleanupRender(MarkupWriter writer) {
-        writer.element("p").text("Other: " + getOther() + " : " + getOther());
+        writer.element("p").text("Other: " + getOther() + " : " + getOther() + 
+                " nonOverriden " + getNonOverriden() + " nonOverriden " + 
getNonOverriden());
+        writer.write(" yeah!!!");
         writer.end();
+        counter++;
+    }
+    
+    public int getCounter() {
+        return counter;
+    }
+    
+    public int getSecondCounter() {
+        return secondCounter;
     }
 
 }
diff --git 
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
 
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
index bc9d802cf..23f7b97da 100644
--- 
a/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
+++ 
b/tapestry-core/src/test/resources/org/apache/tapestry5/integration/app1/components/SubclassWithImport.tml
@@ -3,6 +3,9 @@
 <t:extend xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd"; 
xmlns:p="tapestry:parameter">
        <t:replace id="details">
                Subclass with imports
+               <t:loop source="1..10">
+                       getInt() call count: ${getIntCallCount} <br/>
+               </t:loop>
         <!-- Just for testing ComponentDependencyRegistry.register() -->
         <t:outputRaw value="literal:"/>
        </t:replace>


Reply via email to