You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2024/03/10 14:13:15 UTC
(tapestry-5) 01/04: TAP5-2770: avoiding using FieldHandle when in multiple classloader mode
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
commit c6aac55764f4910a5f8d3c3a1beb247650445c99
Author: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
AuthorDate: Wed Dec 20 19:00:29 2023 -0300
TAP5-2770: avoiding using FieldHandle when in multiple classloader mode
For now, just in ImportWorker.
---
.../internal/plastic/InstructionBuilderImpl.java | 45 ++++-
.../tapestry5/plastic/FieldValueProvider.java | 64 +++++++
.../tapestry5/plastic/InstructionBuilder.java | 16 ++
.../org/apache/tapestry5/plastic/PlasticUtils.java | 191 ++++++++++++++++++++-
.../tapestry5/plastic/PropertyValueProvider.java | 74 ++++++++
.../apache/tapestry5/plastic/PlasticUtilsTest.java | 105 +++++++++++
.../plastic/test/PlasticUtilsTestObject.java | 105 +++++++++++
.../test/PlasticUtilsTestObjectSuperclass.java | 34 ++++
.../tapestry5/internal/transform/ImportWorker.java | 80 +++++++--
9 files changed, 689 insertions(+), 25 deletions(-)
diff --git a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/InstructionBuilderImpl.java b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/InstructionBuilderImpl.java
index dd4488349..a8be0b928 100644
--- a/plastic/src/main/java/org/apache/tapestry5/internal/plastic/InstructionBuilderImpl.java
+++ b/plastic/src/main/java/org/apache/tapestry5/internal/plastic/InstructionBuilderImpl.java
@@ -14,16 +14,28 @@
package org.apache.tapestry5.internal.plastic;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
import org.apache.tapestry5.internal.plastic.InstructionBuilderState.LVInfo;
import org.apache.tapestry5.internal.plastic.asm.Label;
import org.apache.tapestry5.internal.plastic.asm.MethodVisitor;
import org.apache.tapestry5.internal.plastic.asm.Opcodes;
import org.apache.tapestry5.internal.plastic.asm.Type;
-import org.apache.tapestry5.plastic.*;
-
-import java.lang.reflect.Method;
-import java.util.HashMap;
-import java.util.Map;
+import org.apache.tapestry5.plastic.Condition;
+import org.apache.tapestry5.plastic.InstructionBuilder;
+import org.apache.tapestry5.plastic.InstructionBuilderCallback;
+import org.apache.tapestry5.plastic.LocalVariable;
+import org.apache.tapestry5.plastic.LocalVariableCallback;
+import org.apache.tapestry5.plastic.MethodDescription;
+import org.apache.tapestry5.plastic.PlasticField;
+import org.apache.tapestry5.plastic.PlasticMethod;
+import org.apache.tapestry5.plastic.PlasticUtils;
+import org.apache.tapestry5.plastic.SwitchCallback;
+import org.apache.tapestry5.plastic.TryCatchCallback;
+import org.apache.tapestry5.plastic.WhenCallback;
+import org.apache.tapestry5.plastic.WhileCallback;
@SuppressWarnings("rawtypes")
public class InstructionBuilderImpl extends Lockable implements Opcodes, InstructionBuilder
@@ -469,6 +481,29 @@ public class InstructionBuilderImpl extends Lockable implements Opcodes, Instruc
return checkcast(cache.toTypeName(clazz));
}
+
+ @Override
+ public InstructionBuilder instanceOf(String className)
+ {
+ check();
+
+ // Found out the hard way that array names are handled differently; you cast to the descriptor, not the internal
+ // name.
+
+ String internalName = className.contains("[") ? cache.toDesc(className) : cache.toInternalName(className);
+
+ v.visitTypeInsn(INSTANCEOF, internalName);
+
+ return this;
+ }
+
+ @Override
+ public InstructionBuilder instanceOf(Class clazz)
+ {
+ check();
+
+ return instanceOf(cache.toTypeName(clazz));
+ }
@Override
public InstructionBuilder startTryCatch(TryCatchCallback callback)
diff --git a/plastic/src/main/java/org/apache/tapestry5/plastic/FieldValueProvider.java b/plastic/src/main/java/org/apache/tapestry5/plastic/FieldValueProvider.java
new file mode 100644
index 000000000..79087e8b8
--- /dev/null
+++ b/plastic/src/main/java/org/apache/tapestry5/plastic/FieldValueProvider.java
@@ -0,0 +1,64 @@
+// Copyright 2023 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;
+
+/**
+ * <p>
+ * Interface that can be implemented to provide access to field values based on their name.
+ * Usually implemented with {@linkplain PlasticUtils#implementFieldValueProvider(PlasticClass, java.util.Set)},
+ * which doesn't use reflection.
+ * <p/>
+ * <p>
+ * The name of its abstract method is intended to avoid clashes with other existing methods
+ * in the class.
+ * </p>
+ * @see PlasticUtils#implementFieldValueProvider(PlasticClass, java.util.Set)
+ * @since 5.8.4
+ */
+public interface FieldValueProvider
+{
+ /**
+ * Returns the value of a given field.
+ * @param fieldName the field name.
+ * @return the field value.
+ */
+ Object __fieldValueProvider__get(String fieldName);
+
+ /**
+ * <p>
+ * Returns the value of a given field in a given object if it belongs to a class
+ * that implements {@linkplain FieldValueProvider}. 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 #__fieldValueProvider__get(String)}.
+ * </p>
+ * @param object an object.
+ * @param fieldName the field name.
+ * @return the field value.
+ */
+ static Object get(Object object, String fieldName)
+ {
+ if (object instanceof FieldValueProvider)
+ {
+ return ((FieldValueProvider) object).__fieldValueProvider__get(fieldName);
+ }
+ else
+ {
+ throw new RuntimeException("Class " + object.getClass().getName() + " doesn't implement " + FieldValueProvider.class.getSimpleName());
+ }
+ }
+
+}
diff --git a/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java b/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java
index 97a4f1768..7f14c860d 100644
--- a/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java
+++ b/plastic/src/main/java/org/apache/tapestry5/plastic/InstructionBuilder.java
@@ -247,6 +247,22 @@ public interface InstructionBuilder
@Opcodes("CHECKCAST")
InstructionBuilder checkcast(Class clazz);
+ /**
+ * Adds a check that the object on top of the stack is assignable to the indicated class.
+ *
+ * @param className class to cast to
+ * @since 5.8.4
+ */
+ @Opcodes("CHECKCAST")
+ InstructionBuilder instanceOf(String className);
+
+ /**
+ * Adds a check that the object on top of the stack is assignable to the indicated class.
+ * @since 5.8.4
+ */
+ @Opcodes("CHECKCAST")
+ InstructionBuilder instanceOf(Class clazz);
+
/**
* Defines the start of a block that can have exception handlers and finally blocks applied.
* Continue using this InstructionBuilder to define code inside the block, then call
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 2fab09efd..9c35d3428 100644
--- a/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
+++ b/plastic/src/main/java/org/apache/tapestry5/plastic/PlasticUtils.java
@@ -12,11 +12,14 @@
package org.apache.tapestry5.plastic;
-import org.apache.tapestry5.internal.plastic.PrimitiveType;
-
import java.lang.reflect.Method;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
+import org.apache.tapestry5.internal.plastic.PlasticInternalUtils;
+import org.apache.tapestry5.internal.plastic.PrimitiveType;
+
/**
* Utilities for user code making use of Plastic.
*/
@@ -33,6 +36,18 @@ public class PlasticUtils
public static final MethodDescription TO_STRING_DESCRIPTION = new MethodDescription(TO_STRING);
private static final AtomicLong UID_GENERATOR = new AtomicLong(System.nanoTime());
+
+ private static final MethodDescription PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION;
+
+ static
+ {
+ try {
+ PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION = new MethodDescription(PropertyValueProvider.class.getMethod("__propertyValueProvider__get", String.class));
+ } catch (Exception e) {
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
/**
* Returns a string that can be used as part of a Java identifier and is unique
@@ -151,4 +166,176 @@ public class PlasticUtils
int index = className.indexOf('$');
return index <= 0 ? className : className.substring(0, index);
}
+
+ /**
+ * Utility method for creating {@linkplain FieldInfo} instances.
+ * @param field a {@linkplain PlasticField}.
+ * @return a corresponding {@linkplain FieldInfo}.
+ * @since 5.8.4
+ */
+ public static FieldInfo toFieldInfo(PlasticField field)
+ {
+ return new FieldInfo(field.getName(), field.getTypeName());
+ }
+
+ /**
+ * Transforms this {@linkplain PlasticClass} so it implements
+ * {@linkplain FieldValueProvider} for the given set of field names.
+ * Notice attempts to read a superclass' private field will result in
+ * an {@linkplain IllegalAccessError}.
+ *
+ * @param plasticClass a {@linkplain PlasticClass} instance.
+ * @param fieldNames a {@linkplain Set} of {@linkplain String}s containing the field names.
+ * @since 5.8.4
+ */
+ public static void implementFieldValueProvider(PlasticClass plasticClass, Set<FieldInfo> fields)
+ {
+
+ final Set<PlasticMethod> methods = plasticClass.introduceInterface(FieldValueProvider.class);
+
+ if (!methods.isEmpty())
+ {
+ final PlasticMethod method = methods.iterator().next();
+
+ method.changeImplementation((builder) -> {
+
+ for (FieldInfo field : fields)
+ {
+ builder.loadArgument(0);
+ builder.loadConstant(field.name);
+ builder.invokeVirtual(String.class.getName(), "boolean", "equals", Object.class.getName());
+ builder.when(Condition.NON_ZERO, ifBuilder -> {
+ ifBuilder.loadThis();
+ ifBuilder.getField(plasticClass.getClassName(), field.name, field.type);
+ ifBuilder.boxPrimitive(field.type);
+ ifBuilder.returnResult();
+ });
+ }
+
+ builder.throwException(RuntimeException.class, "Field not found or not supported");
+
+ });
+
+ }
+
+ }
+
+ /**
+ * Transforms this {@linkplain PlasticClass} so it implements
+ * {@linkplain PropertyValueProvider} for the given set of field names.
+ * The implementation will use the fields' corresponding getters instead
+ * of direct fields access.
+ *
+ * @param plasticClass a {@linkplain PlasticClass} instance.
+ * @param fieldNames a {@linkplain Set} of {@linkplain String}s containing the filed (i.e. property) names.
+ * @since 5.8.4
+ */
+ public static void implementPropertyValueProvider(PlasticClass plasticClass, Set<FieldInfo> fields)
+ {
+
+ final Set<PlasticMethod> methods = plasticClass.introduceInterface(PropertyValueProvider.class);
+
+ final InstructionBuilderCallback callback = (builder) -> {
+
+ for (FieldInfo field : fields)
+ {
+ 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 prefix = field.type.equals("boolean") ? "is" : "get";
+ final String methodName = prefix + PlasticInternalUtils.capitalize(field.name);
+
+ ifBuilder.loadThis();
+ builder.invokeVirtual(
+ plasticClass.getClassName(),
+ field.type,
+ methodName);
+ ifBuilder.boxPrimitive(field.type);
+ ifBuilder.returnResult();
+ });
+
+ }
+
+ builder.loadThis();
+ builder.instanceOf(PropertyValueProvider.class);
+
+ builder.when(Condition.NON_ZERO, ifBuilder -> {
+ builder.loadThis();
+ builder.loadArgument(0);
+ ifBuilder.invokeSpecial(
+ plasticClass.getSuperClassName(),
+ PROPERTY_VALUE_PROVIDER_METHOD_DESCRIPTION);
+ ifBuilder.returnResult();
+ });
+
+ // Field/property not found, so let's try the superclass in case
+ // it also implement
+
+ builder.throwException(RuntimeException.class, "Property not found or not supported");
+
+ };
+
+ final PlasticMethod method;
+
+ // 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);
+ }
+ else
+ {
+ method = methods.iterator().next();
+ }
+
+ method.changeImplementation(callback);
+
+ }
+
+ /**
+ * Class used to represent a field name and its type for
+ * {@linkplain PlasticUtils#implementFieldValueProvider(PlasticClass, Set)}.
+ * It shouldn't be used directly. Use {@linkplain PlasticUtils#toFieldInfo(PlasticField)}
+ * instead.
+ * @see PlasticUtils#implementFieldValueProvider(PlasticClass, Set)
+ * @since 5.8.4
+ */
+ public static class FieldInfo {
+ final private String name;
+ final private String type;
+ public FieldInfo(String name, String type)
+ {
+ super();
+ this.name = name;
+ this.type = type;
+ }
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(name);
+ }
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (!(obj instanceof FieldInfo))
+ {
+ return false;
+ }
+ FieldInfo other = (FieldInfo) obj;
+ return Objects.equals(name, other.name);
+ }
+ @Override
+ public String toString()
+ {
+ return "FieldInfo [name=" + name + ", type=" + type + "]";
+ }
+
+ }
+
}
diff --git a/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java b/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
new file mode 100644
index 000000000..e33301f48
--- /dev/null
+++ b/plastic/src/main/java/org/apache/tapestry5/plastic/PropertyValueProvider.java
@@ -0,0 +1,74 @@
+// Copyright 2023 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;
+
+import java.lang.reflect.Method;
+
+/**
+ * <p>
+ * Interface that can be implemented to provide access to field values based on their name.
+ * Usually implemented with {@linkplain} FieldValueProviderTransformation}.
+ * <p/>
+ * <p>
+ * The name of its abstract method is intended to avoid clashes with other existing methods
+ * in the class.
+ * </p>
+ * @see FieldValueProviderTransformation
+ * @since 5.8.4
+ */
+public interface PropertyValueProvider
+{
+ /**
+ * Returns the value of a given field.
+ * @param fieldName the field name.
+ * @return the field value.
+ */
+ Object __propertyValueProvider__get(String fieldName);
+
+ /**
+ * <p>
+ * Returns 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__get(String)}.
+ * </p>
+ * @param object an object.
+ * @param fieldName the field name.
+ * @return the field value.
+ */
+ static Object get(Object object, String fieldName)
+ {
+ if (object instanceof PropertyValueProvider)
+ {
+ try {
+ return ((PropertyValueProvider) object).__propertyValueProvider__get(fieldName);
+ }
+ catch (Exception e) {
+ final Method[] methods = object.getClass().getMethods();
+ for (Method method : methods) {
+ System.out.println(method);
+ }
+ throw new RuntimeException(e.getMessage() + ": " + fieldName, e);
+ }
+ }
+ 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
new file mode 100644
index 000000000..3d0e4f14f
--- /dev/null
+++ b/plastic/src/test/java/org/apache/tapestry5/plastic/PlasticUtilsTest.java
@@ -0,0 +1,105 @@
+// Copyright 2023 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;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.tapestry5.plastic.test.PlasticUtilsTestObject;
+import org.apache.tapestry5.plastic.test.PlasticUtilsTestObjectSuperclass;
+import org.junit.jupiter.api.Test;
+
+// [Thiago] This is only here because I couldn't get Groovy tests to run on Eclipse
+// (and I admit to not liking Groovy anyway . . .).
+public class PlasticUtilsTest
+{
+
+ public static void main(String[] args) throws ClassNotFoundException {
+ final PlasticUtilsTest plasticUtilsTest = new PlasticUtilsTest();
+ plasticUtilsTest.implement_field_value_pProvider();
+ plasticUtilsTest.implement_property_value_provider();
+ }
+
+ @Test
+ public void implement_field_value_pProvider() throws ClassNotFoundException
+ {
+
+ Set<String> packages = new HashSet<>();
+ packages.add(PlasticUtilsTestObject.class.getPackage().getName());
+ PlasticManager plasticManager = PlasticManager.withContextClassLoader()
+ .packages(packages).create();
+ final PlasticClassTransformation<Object> transformation = plasticManager.getPlasticClass(PlasticUtilsTestObject.class.getName());
+ PlasticClass pc = transformation.getPlasticClass();
+ Set<PlasticUtils.FieldInfo> fieldInfos = new HashSet<PlasticUtils.FieldInfo>();
+ for (PlasticField field : pc.getAllFields()) {
+ fieldInfos.add(PlasticUtils.toFieldInfo(field));
+ }
+ fieldInfos.add(new PlasticUtils.FieldInfo("superString", "java.lang.String"));
+ PlasticUtils.implementFieldValueProvider(pc, fieldInfos);
+ Object object = transformation.createInstantiator().newInstance();
+
+ Class<?> original = PlasticUtilsTestObject.class;
+ Class<?> transformed = object.getClass();
+
+ assertNotEquals(original, transformed);
+
+ assertEquals(PlasticUtilsTestObject.STRING, FieldValueProvider.get(object, "string"));
+ assertEquals(PlasticUtilsTestObject.OTHER_STRING, FieldValueProvider.get(object, "otherString"));
+ assertEquals(null, FieldValueProvider.get(object, "nullString"));
+ assertEquals(PlasticUtilsTestObject.ENUMERATION.toString(), FieldValueProvider.get(object, "enumeration").toString());
+ assertTrue(Arrays.equals(PlasticUtilsTestObject.INT_ARRAY, (int[]) FieldValueProvider.get(object, "intArray")));
+ assertEquals(PlasticUtilsTestObject.TRUE_OF_FALSE, (Boolean) FieldValueProvider.get(object, "trueOrFalse"));
+
+ }
+
+ @Test
+ public void implement_property_value_provider() throws ClassNotFoundException
+ {
+
+ Set<String> packages = new HashSet<>();
+ packages.add(PlasticUtilsTestObject.class.getPackage().getName());
+ PlasticManager plasticManager = PlasticManager.withContextClassLoader()
+ .packages(packages).create();
+ final PlasticClassTransformation<Object> transformation = plasticManager.getPlasticClass(PlasticUtilsTestObject.class.getName());
+ PlasticClass pc = transformation.getPlasticClass();
+ Set<PlasticUtils.FieldInfo> fieldInfos = new HashSet<PlasticUtils.FieldInfo>();
+ for (PlasticField field : pc.getAllFields()) {
+ fieldInfos.add(PlasticUtils.toFieldInfo(field));
+ }
+ fieldInfos.add(new PlasticUtils.FieldInfo("superString", "java.lang.String"));
+ PlasticUtils.implementPropertyValueProvider(pc, fieldInfos);
+ Object object = transformation.createInstantiator().newInstance();
+
+ Class<?> original = PlasticUtilsTestObject.class;
+ Class<?> transformed = object.getClass();
+
+ assertNotEquals(original, transformed);
+
+ assertEquals(PlasticUtilsTestObject.STRING, PropertyValueProvider.get(object, "string"));
+ assertEquals(PlasticUtilsTestObject.OTHER_STRING, PropertyValueProvider.get(object, "otherString"));
+ assertEquals(null, PropertyValueProvider.get(object, "nullString"));
+ assertEquals(PlasticUtilsTestObject.ENUMERATION.toString(), PropertyValueProvider.get(object, "enumeration").toString());
+ assertTrue(Arrays.equals(PlasticUtilsTestObject.INT_ARRAY, (int[]) PropertyValueProvider.get(object, "intArray")));
+ assertEquals(PlasticUtilsTestObject.TRUE_OF_FALSE, (Boolean) PropertyValueProvider.get(object, "trueOrFalse"));
+ assertEquals(PlasticUtilsTestObjectSuperclass.SUPER, 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
new file mode 100644
index 000000000..bd34620ff
--- /dev/null
+++ b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObject.java
@@ -0,0 +1,105 @@
+// Copyright 2023 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.test;
+
+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 boolean TRUE_OF_FALSE = true;
+
+ public static enum Enumeration
+ {
+ TRUE,
+ FALSE,
+ FILE_NOT_FOUND
+ }
+
+ private String string = STRING;
+
+ private String otherString = OTHER_STRING;
+
+ private String nullString = null;
+
+ private Enumeration enumeration = ENUMERATION;
+
+ private int[] intArray = INT_ARRAY;
+
+ private boolean trueOrFalse = TRUE_OF_FALSE;
+
+ public String getString()
+ {
+ return string;
+ }
+
+ public void setString(String string)
+ {
+ this.string = string;
+ }
+
+ public String getOtherString()
+ {
+ return otherString;
+ }
+
+ public void setOtherString(String otherString)
+ {
+ this.otherString = otherString;
+ }
+
+ public String getNullString()
+ {
+ return nullString;
+ }
+
+ public void setNullString(String nullString)
+ {
+ this.nullString = nullString;
+ }
+
+ public Enumeration getEnumeration()
+ {
+ return enumeration;
+ }
+
+ public void setEnumeration(Enumeration enumeration)
+ {
+ this.enumeration = enumeration;
+ }
+
+ public int[] getIntArray()
+ {
+ return intArray;
+ }
+
+ public void setIntArray(int[] intArray)
+ {
+ this.intArray = intArray;
+ }
+
+ public boolean isTrueOrFalse()
+ {
+ return trueOrFalse;
+ }
+
+ public void setTrueOrFalse(boolean trueOrFalse)
+ {
+ this.trueOrFalse = trueOrFalse;
+ }
+
+}
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
new file mode 100644
index 000000000..f82995f88
--- /dev/null
+++ b/plastic/src/test/java/org/apache/tapestry5/plastic/test/PlasticUtilsTestObjectSuperclass.java
@@ -0,0 +1,34 @@
+// Copyright 2023 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.test;
+
+public class PlasticUtilsTestObjectSuperclass
+{
+
+ public static final String SUPER = "Super!!!";
+
+ private String superString = SUPER;
+
+ public String getSuperString()
+ {
+ return superString;
+ }
+
+ public void setSuperString(String superString)
+ {
+ this.superString = superString;
+ }
+
+}
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java
index e47553fbd..9e5b85967 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/transform/ImportWorker.java
@@ -24,6 +24,7 @@ import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker;
import org.apache.tapestry5.ioc.services.SymbolSource;
import org.apache.tapestry5.model.MutableComponentModel;
import org.apache.tapestry5.plastic.*;
+import org.apache.tapestry5.plastic.PlasticUtils.FieldInfo;
import org.apache.tapestry5.services.AssetSource;
import org.apache.tapestry5.services.TransformConstants;
import org.apache.tapestry5.services.javascript.Initialization;
@@ -32,7 +33,9 @@ import org.apache.tapestry5.services.transform.ComponentClassTransformWorker2;
import org.apache.tapestry5.services.transform.TransformationSupport;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Implements the {@link Import} annotation, both at the class and at the method level.
@@ -41,6 +44,9 @@ import java.util.List;
*/
public class ImportWorker implements ComponentClassTransformWorker2
{
+
+ private static final String FIELD_PREFIX = "importedAssets_";
+
private final JavaScriptSupport javascriptSupport;
private final SymbolSource symbolSource;
@@ -90,17 +96,26 @@ public class ImportWorker implements ComponentClassTransformWorker2
public void transform(PlasticClass componentClass, TransformationSupport support, MutableComponentModel model)
{
resourceChangeTracker.setCurrentClassName(model.getComponentClassName());
- processClassAnnotationAtSetupRenderPhase(componentClass, model);
+
+ Set<PlasticUtils.FieldInfo> fieldInfos = multipleClassLoaders ? new HashSet<>() : null;
+ processClassAnnotationAtSetupRenderPhase(componentClass, model, fieldInfos);
- for (PlasticMethod m : componentClass.getMethodsWithAnnotation(Import.class))
+ final List<PlasticMethod> methods = componentClass.getMethodsWithAnnotation(Import.class);
+ for (PlasticMethod m : methods)
{
- decorateMethod(componentClass, model, m);
+ decorateMethod(componentClass, model, m, fieldInfos);
+ }
+
+ if (multipleClassLoaders && !fieldInfos.isEmpty())
+ {
+ PlasticUtils.implementPropertyValueProvider(componentClass, fieldInfos);
}
resourceChangeTracker.clearCurrentClassName();
}
- private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model)
+ private void processClassAnnotationAtSetupRenderPhase(PlasticClass componentClass, MutableComponentModel model,
+ Set<FieldInfo> fieldInfos)
{
Import annotation = componentClass.getAnnotation(Import.class);
@@ -108,27 +123,27 @@ public class ImportWorker implements ComponentClassTransformWorker2
{
PlasticMethod setupRender = componentClass.introduceMethod(TransformConstants.SETUP_RENDER_DESCRIPTION);
- decorateMethod(componentClass, model, setupRender, annotation);
+ decorateMethod(componentClass, model, setupRender, annotation, fieldInfos);
model.addRenderPhase(SetupRender.class);
}
}
- private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method)
+ private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method, Set<FieldInfo> fieldInfos)
{
Import annotation = method.getAnnotation(Import.class);
- decorateMethod(componentClass, model, method, annotation);
+ decorateMethod(componentClass, model, method, annotation, fieldInfos);
}
private void decorateMethod(PlasticClass componentClass, MutableComponentModel model, PlasticMethod method,
- Import annotation)
+ Import annotation, Set<FieldInfo> fieldInfos)
{
importStacks(method, annotation.stack());
- importLibraries(componentClass, model, method, annotation.library());
+ importLibraries(componentClass, model, method, annotation.library(), fieldInfos);
- importStylesheets(componentClass, model, method, annotation.stylesheet());
+ importStylesheets(componentClass, model, method, annotation.stylesheet(), fieldInfos);
importModules(method, annotation.module());
}
@@ -215,19 +230,20 @@ public class ImportWorker implements ComponentClassTransformWorker2
}
private void importLibraries(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
- String[] paths)
+ String[] paths, Set<FieldInfo> fieldInfos)
{
- decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary);
+ decorateMethodWithOperation(plasticClass, model, method, paths, importLibrary, fieldInfos);
}
private void importStylesheets(PlasticClass plasticClass, MutableComponentModel model, PlasticMethod method,
- String[] paths)
+ String[] paths, Set<FieldInfo> fieldInfos)
{
- decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet);
+ decorateMethodWithOperation(plasticClass, model, method, paths, importStylesheet, fieldInfos);
}
private void decorateMethodWithOperation(PlasticClass componentClass, MutableComponentModel model,
- PlasticMethod method, String[] paths, Worker<Asset> operation)
+ PlasticMethod method, String[] paths, Worker<Asset> operation,
+ Set<FieldInfo> fieldInfos)
{
if (paths.length == 0)
{
@@ -236,14 +252,32 @@ public class ImportWorker implements ComponentClassTransformWorker2
String[] expandedPaths = expandPaths(paths);
- PlasticField assetListField = componentClass.introduceField(Asset[].class,
- "importedAssets_" + method.getDescription().methodName);
+ final String fieldName = getFieldName(method);
+ PlasticField assetListField = componentClass.introduceField(Asset[].class, fieldName);
+
+ if (multipleClassLoaders)
+ {
+ fieldInfos.add(PlasticUtils.toFieldInfo(assetListField));
+ assetListField.createAccessors(PropertyAccessType.READ_ONLY);
+ }
initializeAssetsFromPaths(expandedPaths, assetListField, model.getLibraryName());
addMethodAssetOperationAdvice(method, assetListField.getHandle(), operation);
}
+ private String getFieldName(PlasticMethod method)
+ {
+ final StringBuilder builder = new StringBuilder(FIELD_PREFIX);
+ builder.append(method.getDescription().methodName);
+ if (multipleClassLoaders)
+ {
+ builder.append("_");
+ builder.append(method.getPlasticClass().getClassName().replace('.', '_'));
+ }
+ return builder.toString();
+ }
+
private String[] expandPaths(String[] paths)
{
return F.flow(paths).map(expandSymbols).toArray(String.class);
@@ -277,13 +311,17 @@ public class ImportWorker implements ComponentClassTransformWorker2
final Worker<Asset> operation)
{
final String className = method.getPlasticClass().getClassName();
+ final String fieldName = getFieldName(method);
method.addAdvice(new MethodAdvice()
{
public void advise(MethodInvocation invocation)
{
invocation.proceed();
- Asset[] assets = (Asset[]) access.get(invocation.getInstance());
+ final Object instance = invocation.getInstance();
+ Asset[] assets = (Asset[]) (multipleClassLoaders ?
+ PropertyValueProvider.get(instance, fieldName) :
+ access.get(instance));
if (multipleClassLoaders)
{
@@ -299,4 +337,10 @@ public class ImportWorker implements ComponentClassTransformWorker2
}
});
}
+
+ public static interface ImportWorkerDataProvider
+ {
+ Asset[] get(int fieldNameHashcode);
+ }
+
}