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 <thi...@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); + } + }