This is an automated email from the ASF dual-hosted git repository. struberg pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/openjpa.git
commit 12f780eb763d0e7a39faf74d4fdb58af0f7094d7 Author: Mark Struberg <strub...@apache.org> AuthorDate: Wed May 3 12:58:26 2023 +0200 OPENJPA-2909 implement ASM proxy for Collections --- openjpa-kernel/pom.xml | 2 + .../apache/openjpa/util/ClassWriterTracker.java | 58 ++ .../org/apache/openjpa/util/ProxyManagerImpl.java | 588 ++++++++++++++------- .../org/apache/openjpa/util/asm/AsmHelper.java | 182 +++++++ 4 files changed, 651 insertions(+), 179 deletions(-) diff --git a/openjpa-kernel/pom.xml b/openjpa-kernel/pom.xml index 71b7fe094..c9e5538b0 100644 --- a/openjpa-kernel/pom.xml +++ b/openjpa-kernel/pom.xml @@ -109,6 +109,7 @@ <goal>run</goal> </goals> </execution> +<!-- <execution> <id>generate-standard-sco-proxies</id> <phase>process-classes</phase> @@ -123,6 +124,7 @@ <goal>run</goal> </goals> </execution> +--> </executions> </plugin> <plugin> diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ClassWriterTracker.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ClassWriterTracker.java new file mode 100644 index 000000000..0bcee1d47 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ClassWriterTracker.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.openjpa.util; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.xbean.asm9.ClassWriter; +import org.apache.xbean.asm9.MethodVisitor; + +/** + * Helper to keep track of generated methods when using ASM ClassWriter. + * + * @author <a href="mailto:strub...@apache.org">Mark Struberg</a> + */ +public class ClassWriterTracker { + + private final ClassWriter cw; + private List<String> createdMethods = new ArrayList<>(); + + public ClassWriterTracker(ClassWriter cw) { + this.cw = cw; + } + + public ClassWriter getCw() { + return cw; + } + + public MethodVisitor visitMethod(final int access, + final String name, + final String descriptor, + final String signature, + final String[] exceptionTypes) { + MethodVisitor mv = cw.visitMethod(access, name, descriptor, signature, exceptionTypes); + + createdMethods.add(name + descriptor); + + return mv; + } + + public boolean hasMethod(final String name, final String descriptor) { + return createdMethods.contains(name + descriptor); + } +} diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java index e1a6ce40b..8b4455180 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/ProxyManagerImpl.java @@ -58,6 +58,7 @@ import org.apache.openjpa.lib.util.J2DoPrivHelper; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.lib.util.Options; import org.apache.openjpa.lib.util.StringUtil; +import org.apache.openjpa.util.asm.AsmHelper; import org.apache.openjpa.util.proxy.DelayedArrayListProxy; import org.apache.openjpa.util.proxy.DelayedHashSetProxy; import org.apache.openjpa.util.proxy.DelayedLinkedHashSetProxy; @@ -74,6 +75,7 @@ import org.apache.openjpa.util.proxy.ProxyDate; import org.apache.openjpa.util.proxy.ProxyMap; import org.apache.openjpa.util.proxy.ProxyMaps; import org.apache.xbean.asm9.ClassWriter; +import org.apache.xbean.asm9.Label; import org.apache.xbean.asm9.MethodVisitor; import org.apache.xbean.asm9.Opcodes; import org.apache.xbean.asm9.Type; @@ -403,25 +405,6 @@ public class ProxyManagerImpl return null; } - /** - * Return the cached factory proxy for the given collection type. - */ - private ProxyCollection getFactoryProxyCollection(Class type) { - // we don't lock here; ok if two proxies get generated for same type - ProxyCollection proxy = (ProxyCollection) _proxies.get(type); - if (proxy == null) { - ClassLoader l = GeneratedClasses.getMostDerivedLoader(type, - ProxyCollection.class); - Class pcls = loadBuildTimeProxy(type, l); - if (pcls == null) - pcls = GeneratedClasses.loadBCClass( - generateProxyCollectionBytecode(type, true), l); - proxy = (ProxyCollection) instantiateProxy(pcls, null, null); - _proxies.put(type, proxy); - } - return proxy; - } - /** * Return the cached factory proxy for the given map type. */ @@ -477,6 +460,25 @@ public class ProxyManagerImpl return proxy; } + /** + * Return the cached factory proxy for the given collection type. + */ + private ProxyCollection getFactoryProxyCollection(Class type) { + // we don't lock here; ok if two proxies get generated for same type + ProxyCollection proxy = (ProxyCollection) _proxies.get(type); + if (proxy == null) { + ClassLoader l = GeneratedClasses.getMostDerivedLoader(type, + ProxyCollection.class); + Class pcls = loadBuildTimeProxy(type, l); + if (pcls == null) + pcls = generateAndLoadProxyCollection(type, true, l); + proxy = (ProxyCollection) instantiateProxy(pcls, null, null); + _proxies.put(type, proxy); + } + return proxy; + } + + /** * Return the cached factory proxy for the given bean type. */ @@ -683,6 +685,13 @@ public class ProxyManagerImpl return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyDate.class, l); } + private Class generateAndLoadProxyCollection(Class type, boolean runtime, ClassLoader l) { + final String proxyClassName = getProxyClassName(type, runtime); + final byte[] classBytes = generateProxyCollectionBytecode(type, runtime, proxyClassName); + + return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyDate.class, l); + } + /** * Generate the bytecode for a date proxy for the given type. */ @@ -696,15 +705,16 @@ public class ProxyManagerImpl cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, proxyClassDef, null, superClassFileNname, interfaceNames); + ClassWriterTracker ct = new ClassWriterTracker(cw); String classFileName = runtime ? type.getName() : proxyClassDef; cw.visitSource(classFileName + ".java", null); - delegateConstructors(cw, type, superClassFileNname); - addInstanceVariables(cw); - addProxyMethods(cw, true, proxyClassDef, type); - addProxyDateMethods(cw, proxyClassDef, type); - proxySetters(cw, proxyClassDef, type); - addWriteReplaceMethod(cw, proxyClassDef, runtime); + delegateConstructors(ct, type, superClassFileNname); + addInstanceVariables(ct); + addProxyMethods(ct, true, proxyClassDef, type); + addProxyDateMethods(ct, proxyClassDef, type); + proxySetters(ct, proxyClassDef, type); + addWriteReplaceMethod(ct, proxyClassDef, runtime); return cw.toByteArray(); } @@ -722,31 +732,351 @@ public class ProxyManagerImpl cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, proxyClassDef, null, superClassFileNname, interfaceNames); + ClassWriterTracker ct = new ClassWriterTracker(cw); + String classFileName = runtime ? type.getName() : proxyClassDef; + cw.visitSource(classFileName + ".java", null); + + delegateConstructors(ct, type, superClassFileNname); + addInstanceVariables(ct); + addProxyMethods(ct, true, proxyClassDef, type); + addProxyCalendarMethods(ct, proxyClassDef, type); + proxySetters(ct, proxyClassDef, type); + addWriteReplaceMethod(ct, proxyClassDef, runtime); + + return cw.toByteArray(); + } + + /** + * Generate the bytecode for a collection proxy for the given type. + */ + protected byte[] generateProxyCollectionBytecode(Class type, boolean runtime, String proxyClassName) { + assertNotFinal(type); + String proxyClassDef = proxyClassName.replace('.', '/'); + String superClassFileNname = Type.getInternalName(type); + String[] interfaceNames = new String[]{Type.getInternalName(ProxyCollection.class)}; + + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, proxyClassDef, + null, superClassFileNname, interfaceNames); + + ClassWriterTracker ct = new ClassWriterTracker(cw); String classFileName = runtime ? type.getName() : proxyClassDef; cw.visitSource(classFileName + ".java", null); - delegateConstructors(cw, type, superClassFileNname); - addInstanceVariables(cw); - addProxyMethods(cw, true, proxyClassDef, type); - addProxyCalendarMethods(cw, proxyClassDef, type); - proxySetters(cw, proxyClassDef, type); - addWriteReplaceMethod(cw, proxyClassDef, runtime); + delegateConstructors(ct, type, superClassFileNname); + addInstanceVariables(ct); + addProxyMethods(ct, false, proxyClassDef, type); + addProxyCollectionMethods(ct, proxyClassDef, type); + proxyRecognizedMethods(ct, proxyClassDef, type,ProxyCollections.class, ProxyCollection.class); + proxySetters(ct, proxyClassDef, type); + addWriteReplaceMethod(ct, proxyClassDef, runtime); return cw.toByteArray(); + } + + + private void addProxyCollectionMethods(ClassWriterTracker ct, String proxyClassDef, Class type) { + // change tracker + { + ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, + "changeTracker", Type.getDescriptor(CollectionChangeTracker.class), null, null).visitEnd(); + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "getChangeTracker", + Type.getMethodDescriptor(Type.getType(ChangeTracker.class)) + , null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassDef, "changeTracker", Type.getDescriptor(CollectionChangeTracker.class)); + + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + // collection copy + { + Constructor cons = findCopyConstructor(type); + if (cons == null && SortedSet.class.isAssignableFrom(type)) { + cons = findComparatorConstructor(type); + } + Class[] params = (cons == null) ? new Class[0] + : cons.getParameterTypes(); + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "copy", + Type.getMethodDescriptor(TYPE_OBJECT, TYPE_OBJECT) + , null, null); + mv.visitCode(); + mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(type)); + mv.visitInsn(Opcodes.DUP); + + if (params.length == 1) { + mv.visitVarInsn(Opcodes.ALOAD, 1); + if (params[0] == Comparator.class) { + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(SortedSet.class)); + mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(SortedSet.class), "comparator", + Type.getMethodDescriptor(Type.getType(Comparator.class)), true); + } + else { + // otherwise just pass the parameter + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(params[0])); + } + } + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), "<init>", + Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.getParamTypes(params)), false); + + if (params.length == 0 || params[0] == Comparator.class) { + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Collection.class)); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "addAll", + Type.getMethodDescriptor(Type.BOOLEAN_TYPE, TYPE_OBJECT), true); + mv.visitInsn(Opcodes.POP); + } + + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + // element type + { + ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, + "elementType", Type.getDescriptor(Class.class), null, null).visitEnd(); + + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "getElementType", + Type.getMethodDescriptor(Type.getType(Class.class)) + , null, null); + mv.visitCode(); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassDef, "elementType", Type.getDescriptor(Class.class)); + + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + + } + + // new instance factory + { + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "newInstance", + Type.getMethodDescriptor(Type.getType(ProxyCollection.class), + Type.getType(Class.class), Type.getType(Comparator.class), Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE) + , null, null); + mv.visitCode(); + mv.visitTypeInsn(Opcodes.NEW, proxyClassDef); + mv.visitInsn(Opcodes.DUP); + + Constructor cons = findComparatorConstructor(type); + Class[] params = (cons == null) ? new Class[0] : cons.getParameterTypes(); + if (params.length == 1) { + mv.visitVarInsn(Opcodes.ALOAD, 2); + } + + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, proxyClassDef, "<init>", + Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.getParamTypes(params)), false); + + mv.visitVarInsn(Opcodes.ASTORE, 5); + mv.visitVarInsn(Opcodes.ALOAD, 5); + mv.visitVarInsn(Opcodes.ALOAD, 1); + mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassDef, "elementType", Type.getDescriptor(Class.class)); + + mv.visitVarInsn(Opcodes.ILOAD, 3); + Label lNotTrack = new Label(); + mv.visitJumpInsn(Opcodes.IFEQ, lNotTrack); + mv.visitVarInsn(Opcodes.ALOAD, 5); + mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(CollectionChangeTrackerImpl.class)); + + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 5); + + mv.visitInsn(allowsDuplicates(type) ? Opcodes.ICONST_1 : Opcodes.ICONST_0); + mv.visitInsn(isOrdered(type) ? Opcodes.ICONST_1 : Opcodes.ICONST_0); + mv.visitVarInsn(Opcodes.ILOAD, 4); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(CollectionChangeTrackerImpl.class), "<init>", + Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Collection.class), + Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE, Type.BOOLEAN_TYPE), + false); + mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassDef, "changeTracker", Type.getDescriptor(CollectionChangeTracker.class)); + + mv.visitLabel(lNotTrack); + mv.visitVarInsn(Opcodes.ALOAD, 5); + + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + } + + private void proxyRecognizedMethods(ClassWriterTracker ct, String proxyClassDef, Class<?> type, + Class<?> helper, Class<?> proxyType) { + Method[] meths = type.getMethods(); + + for (Method meth : meths) { + // Java 8 methods with a return type of KeySetView do not need to be proxied + if (meth.getReturnType().getName().contains("KeySetView")) { + continue; + } + + Class[] helperParams = toHelperParameters(meth.getParameterTypes(), proxyType); + + // first check for overriding method + try { + Method match; + match = helper.getMethod(meth.getName(), helperParams); + proxyOverrideMethod(ct, meth, match, helperParams); + continue; + } + catch (NoSuchMethodException nsme) { + // all fine + } + catch (Exception e) { + throw new GeneralException(e); + } + + // check for before and after methods, either of which may not + // exist + Method before = null; + try { + before = helper.getMethod("before" + StringUtil.capitalize(meth.getName()), helperParams); + } + catch (NoSuchMethodException nsme) { + // all fine + } + catch (Exception e) { + throw new GeneralException(e); + } + Method after = null; + Class[] afterParams = null; + + try { + afterParams = toHelperAfterParameters(helperParams, + meth.getReturnType(), (before == null) + ? void.class : before.getReturnType()); + after = helper.getMethod("after" + + StringUtil.capitalize(meth.getName()), afterParams); + } + catch (NoSuchMethodException nsme) { + } + catch (Exception e) { + throw new GeneralException(e); + } + if (before != null || after != null) + proxyBeforeAfterMethod(ct, type, meth, helperParams, before, after, afterParams); + } + } + + /** + * Proxy the given method with one that overrides it by calling into the + * given helper. + */ + private void proxyOverrideMethod(ClassWriterTracker ct, Method meth, Method helper, Class[] helperParams) { + MethodVisitor mv = ct.visitMethod(meth.getModifiers() & ~Modifier.SYNCHRONIZED, meth.getName(), + Type.getMethodDescriptor(meth), null, null); + mv.visitCode(); + + // push all the method params to the stack + // we only start at param[1] as param[0] of the helper method is the instance itself + // and will get loaded with ALOAD_0 (this) + mv.visitVarInsn(Opcodes.ALOAD, 0); + for (int i = 1; i < helperParams.length; i++) + { + mv.visitVarInsn(AsmHelper.getLoadInsn(helperParams[i]), i); + } + + mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(helper.getDeclaringClass()), helper.getName(), + Type.getMethodDescriptor(helper), false); + + mv.visitInsn(AsmHelper.getReturnInsn(meth.getReturnType())); + mv.visitMaxs(-1, -1); + mv.visitEnd(); + } + + /** + * Proxy the given method with one that overrides it by calling into the + * given helper. + */ + private void proxyBeforeAfterMethod(ClassWriterTracker ct, Class type, Method meth, Class[] helperParams, + Method before, Method after, Class[] afterParams) { + + MethodVisitor mv = ct.visitMethod(meth.getModifiers() & ~Modifier.SYNCHRONIZED, meth.getName(), + Type.getMethodDescriptor(meth), null, null); + mv.visitCode(); + + int beforeRetPos = -1; + int variableNr = helperParams.length;; + + // invoke before + if (before != null) { + // push all the method params to the stack + // we only start at param[1] as param[0] of the helper method is the instance itself + // and will get loaded with ALOAD_0 (this) + mv.visitVarInsn(Opcodes.ALOAD, 0); + for (int i = 1; i < helperParams.length; i++) + { + mv.visitVarInsn(AsmHelper.getLoadInsn(helperParams[i]), i); + } + + mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(before.getDeclaringClass()), before.getName(), + Type.getMethodDescriptor(before), false); + + if (after != null && before.getReturnType() != void.class) { + // this is always a boolean and 1 after the + beforeRetPos = variableNr++; + mv.visitVarInsn(AsmHelper.getStoreInsn(before.getReturnType()), beforeRetPos); + } + } + + // invoke super + mv.visitVarInsn(Opcodes.ALOAD, 0); + for (int i = 1; i < helperParams.length; i++) + { + mv.visitVarInsn(AsmHelper.getLoadInsn(helperParams[i]), i); + } + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), meth.getName(), + Type.getMethodDescriptor(meth), false); + + // invoke after + if (after != null) { + int retPos = -1; + if (meth.getReturnType() != void.class) { + retPos = variableNr++; + mv.visitVarInsn(AsmHelper.getStoreInsn(meth.getReturnType()), retPos); + } + + // push all the method params to the stack + // we only start at param[1] as param[0] of the helper method is the instance itself + // and will get loaded with ALOAD_0 (this) + mv.visitVarInsn(Opcodes.ALOAD, 0); + for (int i = 1; i < helperParams.length; i++) + { + mv.visitVarInsn(AsmHelper.getLoadInsn(helperParams[i]), i); + } + + if (retPos != -1) { + mv.visitVarInsn(AsmHelper.getLoadInsn(meth.getReturnType()),retPos); + } + if (beforeRetPos != -1) { + mv.visitVarInsn(AsmHelper.getLoadInsn(before.getReturnType()),beforeRetPos); + } + mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(after.getDeclaringClass()), after.getName(), + Type.getMethodDescriptor(after), false); + } + + mv.visitInsn(AsmHelper.getReturnInsn(meth.getReturnType())); + mv.visitMaxs(-1, -1); + mv.visitEnd(); } /** * add the instance variables to the class to be generated */ - private void addInstanceVariables(ClassWriter cw) { + private void addInstanceVariables(ClassWriterTracker ct) { // variable #1, the state manager - cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, + ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, "sm", Type.getDescriptor(OpenJPAStateManager.class), null, null).visitEnd(); // variable #2, the state manager - cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, + ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, "field", Type.getDescriptor(int.class), null, null).visitEnd(); } @@ -754,19 +1084,19 @@ public class ProxyManagerImpl /** * Create pass-through constructors to base type. */ - private void delegateConstructors(ClassWriter cw, Class type, String superClassFileNname) { + private void delegateConstructors(ClassWriterTracker ct, Class type, String superClassFileNname) { Constructor[] constructors = type.getConstructors(); for (Constructor constructor : constructors) { Class[] params = constructor.getParameterTypes(); - String[] exceptionTypes = getInternalNames(constructor.getExceptionTypes()); + String[] exceptionTypes = AsmHelper.getInternalNames(constructor.getExceptionTypes()); String descriptor = Type.getConstructorDescriptor(constructor); - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", descriptor, null, exceptionTypes); + MethodVisitor mv = ct.visitMethod(Opcodes.ACC_PUBLIC, "<init>", descriptor, null, exceptionTypes); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); for (int i = 1; i <= params.length; i++) { - mv.visitVarInsn(getVarInsn(params[i-1]), i); + mv.visitVarInsn(AsmHelper.getLoadInsn(params[i-1]), i); } mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassFileNname, "<init>", descriptor, false); @@ -780,14 +1110,14 @@ public class ProxyManagerImpl * Implement the methods in the {@link Proxy} interface, with the exception * of {@link Proxy#copy}. * - * @param changeTracker whether to implement a null change tracker; if false + * @param defaultChangeTracker whether to implement a null change tracker; if false * the change tracker method is left unimplemented * @param proxyClassDef */ - private void addProxyMethods(ClassWriter cw, boolean changeTracker, String proxyClassDef, Class<?> parentClass) { + private void addProxyMethods(ClassWriterTracker ct, boolean defaultChangeTracker, String proxyClassDef, Class<?> parentClass) { { - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "setOwner", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "setOwner", Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OpenJPAStateManager.class), Type.INT_TYPE) , null, null); mv.visitCode(); @@ -805,7 +1135,7 @@ public class ProxyManagerImpl } { - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "getOwner", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "getOwner", Type.getMethodDescriptor(Type.getType(OpenJPAStateManager.class)) , null, null); mv.visitCode(); @@ -819,7 +1149,7 @@ public class ProxyManagerImpl } { - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "getOwnerField", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "getOwnerField", Type.getMethodDescriptor(Type.INT_TYPE) , null, null); mv.visitCode(); @@ -840,7 +1170,7 @@ public class ProxyManagerImpl * was invoked. So, we are now overriding the clone() method so as to * provide a detached proxy object (null out the StateManager). */ - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "clone", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "clone", Type.getMethodDescriptor(TYPE_OBJECT) , null, null); mv.visitCode(); @@ -863,8 +1193,8 @@ public class ProxyManagerImpl mv.visitEnd(); } - if (changeTracker) { - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "getChangeTracker", + if (defaultChangeTracker) { + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "getChangeTracker", Type.getMethodDescriptor(Type.getType(ChangeTracker.class)) , null, null); mv.visitCode(); @@ -878,7 +1208,7 @@ public class ProxyManagerImpl /** * Implement the methods in the {@link ProxyDate} interface. */ - private void addProxyDateMethods(ClassWriter cw, String proxyClassDef, Class type) { + private void addProxyDateMethods(ClassWriterTracker ct, String proxyClassDef, Class type) { final boolean hasDefaultCons = hasConstructor(type); final boolean hasMillisCons = hasConstructor(type, long.class); @@ -889,7 +1219,7 @@ public class ProxyManagerImpl // add a default constructor that delegates to the millis constructor if (!hasDefaultCons) { - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", + MethodVisitor mv = ct.visitMethod(Opcodes.ACC_PUBLIC, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE), null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); @@ -918,7 +1248,7 @@ public class ProxyManagerImpl params = new Class[0]; } - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "copy", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "copy", Type.getMethodDescriptor(TYPE_OBJECT, TYPE_OBJECT) , null, null); mv.visitCode(); @@ -926,21 +1256,20 @@ public class ProxyManagerImpl mv.visitInsn(Opcodes.DUP); if (params.length == 1) { + mv.visitVarInsn(Opcodes.ALOAD, 1); if (params[0] == long.class) { // call getTime on the given Date if the current type has a long constructor - mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(java.util.Date.class)); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(java.util.Date.class), "getTime", Type.getMethodDescriptor(Type.LONG_TYPE), false); } else { // otherwise just pass the parameter - mv.visitVarInsn(Opcodes.ALOAD, 1); mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(params[0])); } } mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), "<init>", - Type.getMethodDescriptor(Type.VOID_TYPE, getParamTypes(params)), false); + Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.getParamTypes(params)), false); if (params.length == 0) { mv.visitInsn(Opcodes.DUP); @@ -970,7 +1299,7 @@ public class ProxyManagerImpl { // new instance factory - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "newInstance", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "newInstance", Type.getMethodDescriptor(Type.getType(ProxyDate.class)) , null, null); mv.visitCode(); @@ -985,13 +1314,13 @@ public class ProxyManagerImpl } } - private void addProxyCalendarMethods(ClassWriter cw, String proxyClassDef, Class type) { + private void addProxyCalendarMethods(ClassWriterTracker ct, String proxyClassDef, Class type) { // calendar copy { Constructor cons = findCopyConstructor(type); Class[] params = (cons == null) ? new Class[0] : cons.getParameterTypes(); - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "copy", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "copy", Type.getMethodDescriptor(TYPE_OBJECT, TYPE_OBJECT) , null, null); mv.visitCode(); @@ -999,7 +1328,7 @@ public class ProxyManagerImpl mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(type)); mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), "<init>", - Type.getMethodDescriptor(Type.VOID_TYPE, getParamTypes(params)), false); + Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.getParamTypes(params)), false); // timeInMillis mv.visitInsn(Opcodes.DUP); @@ -1053,7 +1382,7 @@ public class ProxyManagerImpl // newInstance factory { - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "newInstance", + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "newInstance", Type.getMethodDescriptor(Type.getType(ProxyCalendar.class)) , null, null); mv.visitCode(); @@ -1070,7 +1399,7 @@ public class ProxyManagerImpl // proxy the protected computeFields method b/c it is called on // mutate, and some setters are final and therefore not proxyable { - MethodVisitor mv = cw.visitMethod(Modifier.PROTECTED, "computeFields", + MethodVisitor mv = ct.visitMethod(Modifier.PROTECTED, "computeFields", Type.getMethodDescriptor(Type.VOID_TYPE) , null, null); mv.visitCode(); @@ -1088,25 +1417,6 @@ public class ProxyManagerImpl mv.visitMaxs(-1, -1); mv.visitEnd(); } - - - /* - - // proxy the protected computeFields method b/c it is called on - // mutate, and some setters are final and therefore not proxyable - m = bc.declareMethod("computeFields", void.class, null); - m.makeProtected(); - code = m.getCode(true); - code.aload().setThis(); - code.constant().setValue(true); - code.invokestatic().setMethod(Proxies.class, "dirty", void.class, - new Class[] { Proxy.class, boolean.class }); - code.aload().setThis(); - code.invokespecial().setMethod(type, "computeFields", void.class, null); - code.vreturn(); - code.calculateMaxStack(); - code.calculateMaxLocals(); - */ } /** @@ -1114,28 +1424,30 @@ public class ProxyManagerImpl * * @return true if we generated any setters, false otherwise */ - private boolean proxySetters(ClassWriter cw, String proxyClassDef, Class type) { + private boolean proxySetters(ClassWriterTracker ct, String proxyClassDef, Class type) { Method[] meths = type.getMethods(); int setters = 0; for (Method meth : meths) { if (isSetter(meth) && !Modifier.isFinal(meth.getModifiers())) { - setters++; - proxySetter(cw, proxyClassDef, type, meth); + proxySetter(ct, proxyClassDef, type, meth); } } return setters > 0; } - private void proxySetter(ClassWriter cw, String proxyClassDef, Class type, Method meth) { + private void proxySetter(ClassWriterTracker ct, String proxyClassDef, Class type, Method meth) { Class[] params = meth.getParameterTypes(); Class ret = meth.getReturnType(); - final String methodDescriptor = Type.getMethodDescriptor(Type.getType(ret), getParamTypes(params)); - MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, meth.getName(), - methodDescriptor - , null, null); + final String methodDescriptor = Type.getMethodDescriptor(Type.getType(ret), AsmHelper.getParamTypes(params)); + if (ct.hasMethod(meth.getName(), methodDescriptor)) { + // this method already got created + return; + } + + MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, meth.getName(), methodDescriptor, null, null); mv.visitCode(); mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitInsn(Opcodes.ICONST_1); @@ -1147,13 +1459,13 @@ public class ProxyManagerImpl // push all the method params to the stack for (int i = 1; i <= params.length; i++) { - mv.visitVarInsn(getVarInsn(params[i-1]), i); + mv.visitVarInsn(AsmHelper.getLoadInsn(params[i-1]), i); } mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), meth.getName(), methodDescriptor, false); - mv.visitInsn(getReturnInsn(ret)); + mv.visitInsn(AsmHelper.getReturnInsn(ret)); mv.visitMaxs(-1, -1); mv.visitEnd(); } @@ -1163,8 +1475,8 @@ public class ProxyManagerImpl * Add a writeReplace implementation that serializes to a non-proxy type * unless detached and this is a build-time generated class. */ - private void addWriteReplaceMethod(ClassWriter cw, String proxyClassDef, boolean runtime) { - MethodVisitor mv = cw.visitMethod(Modifier.PROTECTED, "writeReplace", + private void addWriteReplaceMethod(ClassWriterTracker ct, String proxyClassDef, boolean runtime) { + MethodVisitor mv = ct.visitMethod(Modifier.PROTECTED, "writeReplace", Type.getMethodDescriptor(TYPE_OBJECT) , null, new String[]{Type.getInternalName(ObjectStreamException.class)}); mv.visitCode(); @@ -1180,23 +1492,7 @@ public class ProxyManagerImpl /* a few utility methods to make life with ASM easier */ - private String[] getInternalNames(Class[] classes) { - String[] internalNames = new String[classes.length]; - for (int i=0; i<classes.length; i++) { - internalNames[i] = Type.getInternalName(classes[i]); - } - return internalNames; - } - - private Type[] getParamTypes(Class[] params) { - Type[] types = new Type[params.length]; - for (int i=0; i<types.length; i++) { - types[i] = Type.getType(params[i]); - } - - return types; - } private boolean hasConstructor(Class type, Class<?>... paramTypes) { try { @@ -1207,76 +1503,6 @@ public class ProxyManagerImpl } } - /** - * Returns the appropriate bytecode instruction to load a value from a variable to the stack - * - * @param type Type to load - * @return Bytecode instruction to use - */ - private int getVarInsn(Class<?> type) - { - if (type.isPrimitive()) - { - if (Integer.TYPE.equals(type)) - { - return Opcodes.ILOAD; - } - else if (Boolean.TYPE.equals(type)) - { - return Opcodes.ILOAD; - } - else if (Character.TYPE.equals(type)) - { - return Opcodes.ILOAD; - } - else if (Byte.TYPE.equals(type)) - { - return Opcodes.ILOAD; - } - else if (Short.TYPE.equals(type)) - { - return Opcodes.ILOAD; - } - else if (Float.TYPE.equals(type)) - { - return Opcodes.FLOAD; - } - else if (Long.TYPE.equals(type)) - { - return Opcodes.LLOAD; - } - else if (Double.TYPE.equals(type)) - { - return Opcodes.DLOAD; - } - } - - return Opcodes.ALOAD; - } - - /** - * calclates the proper Return instruction opcode for the given class - */ - private int getReturnInsn(Class ret) { - if (ret.equals(Void.TYPE)) { - return Opcodes.RETURN; - } - if (ret.equals(Integer.TYPE)) { - return Opcodes.IRETURN; - } - if (ret.equals(Long.TYPE)) { - return Opcodes.LRETURN; - } - if (ret.equals(Float.TYPE)) { - return Opcodes.FRETURN; - } - if (ret.equals(Double.TYPE)) { - return Opcodes.DRETURN; - } - return Opcodes.ARETURN; - } - - /* ASM end */ @@ -1663,7 +1889,7 @@ public class ProxyManagerImpl code.calculateMaxStack(); code.calculateMaxLocals(); } - + /** * Implement the methods in the {@link ProxyBean} interface. */ @@ -2142,16 +2368,22 @@ public class ProxyManagerImpl // ASM generated proxies if (Date.class.isAssignableFrom(cls) || - Calendar.class.isAssignableFrom(cls)) { + Calendar.class.isAssignableFrom(cls) || + Collection.class.isAssignableFrom(cls)) { final String proxyClassName = getProxyClassName(cls, false); byte[] bytes = null; + if (Date.class.isAssignableFrom(cls)) { bytes = mgr.generateProxyDateBytecode(cls, false, proxyClassName); } else if (Calendar.class.isAssignableFrom(cls)) { bytes = mgr.generateProxyCalendarBytecode(cls, false, proxyClassName); } + else if (Collection.class.isAssignableFrom(cls)) { + bytes = mgr.generateProxyCollectionBytecode(cls, false, proxyClassName); + } + if (bytes != null) { final String fileName = cls.getName().replace('.', '$') + PROXY_SUFFIX + ".class"; java.nio.file.Files.write(new File(dir, fileName).toPath(), bytes); @@ -2159,8 +2391,6 @@ public class ProxyManagerImpl continue; } - if (Collection.class.isAssignableFrom(cls)) - bc = mgr.generateProxyCollectionBytecode(cls, false); else if (Map.class.isAssignableFrom(cls)) bc = mgr.generateProxyMapBytecode(cls, false); else { diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java new file mode 100644 index 000000000..ec84023a3 --- /dev/null +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/util/asm/AsmHelper.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.openjpa.util.asm; + +import java.lang.reflect.Method; + +import org.apache.xbean.asm9.Opcodes; +import org.apache.xbean.asm9.Type; + +/** + * Utility methods to deal with ASM bytecode + * + * @author <a href="mailto:strub...@apache.org">Mark Struberg</a> + */ +public final class AsmHelper { + private AsmHelper() { + // utility class ct + } + + /** + * Calclates the proper Return instruction opcode for the given class + * + * @param type the type to get returned + * @return the proper Opcode RETURN, ARETURN, IRETURN, etc + */ + public static int getReturnInsn(Class type) { + if (type.equals(Void.TYPE)) { + return Opcodes.RETURN; + } + if (type.isPrimitive()) { + if (Integer.TYPE.equals(type)) { + return Opcodes.IRETURN; + } + else if (Boolean.TYPE.equals(type)) { + return Opcodes.IRETURN; + } + else if (Character.TYPE.equals(type)) { + return Opcodes.IRETURN; + } + else if (Byte.TYPE.equals(type)) { + return Opcodes.IRETURN; + } + else if (Short.TYPE.equals(type)) { + return Opcodes.IRETURN; + } + else if (Float.TYPE.equals(type)) { + return Opcodes.FRETURN; + } + else if (Long.TYPE.equals(type)) { + return Opcodes.LRETURN; + } + else if (Double.TYPE.equals(type)) { + return Opcodes.DRETURN; + } + } + return Opcodes.ARETURN; + } + + /** + * Calclates the proper STORE instruction opcode for the given type + * + * @param type the type to get stored + * @return the proper Opcode ISTORE, ASTORE, LSTORE, etc + */ + public static int getStoreInsn(Class<?> type) { + if (type.equals(Void.TYPE)) { + throw new IllegalArgumentException("Void type cannot be stored"); + } + if (type.isPrimitive()) { + if (Integer.TYPE.equals(type)) { + return Opcodes.ISTORE; + } + else if (Boolean.TYPE.equals(type)) { + return Opcodes.ISTORE; + } + else if (Character.TYPE.equals(type)) { + return Opcodes.ISTORE; + } + else if (Byte.TYPE.equals(type)) { + return Opcodes.ISTORE; + } + else if (Short.TYPE.equals(type)) { + return Opcodes.ISTORE; + } + else if (Float.TYPE.equals(type)) { + return Opcodes.FSTORE; + } + else if (Long.TYPE.equals(type)) { + return Opcodes.LSTORE; + } + else if (Double.TYPE.equals(type)) { + return Opcodes.DSTORE; + } + } + + return Opcodes.ASTORE; + } + + + /** + * Calclates the proper LOAD instruction opcode for the given type. + * This is the appropriate bytecode instruction to load a value from a variable to the stack. + * + * @param type the type to get loaded + * @return the proper Opcode ILOAD, ALOAD, LLOAD, etc + */ + public static int getLoadInsn(Class<?> type) { + if (type.equals(Void.TYPE)) { + throw new IllegalArgumentException("Void type cannot be loaded"); + } + if (type.isPrimitive()) { + if (Integer.TYPE.equals(type)) { + return Opcodes.ILOAD; + } + else if (Boolean.TYPE.equals(type)) { + return Opcodes.ILOAD; + } + else if (Character.TYPE.equals(type)) { + return Opcodes.ILOAD; + } + else if (Byte.TYPE.equals(type)) { + return Opcodes.ILOAD; + } + else if (Short.TYPE.equals(type)) { + return Opcodes.ILOAD; + } + else if (Float.TYPE.equals(type)) { + return Opcodes.FLOAD; + } + else if (Long.TYPE.equals(type)) { + return Opcodes.LLOAD; + } + else if (Double.TYPE.equals(type)) { + return Opcodes.DLOAD; + } + } + + return Opcodes.ALOAD; + } + + /** + * Get the internal names for the given classes + * @see Type#getInternalName(Class) + */ + public static String[] getInternalNames(Class[] classes) { + String[] internalNames = new String[classes.length]; + + for (int i=0; i<classes.length; i++) { + internalNames[i] = Type.getInternalName(classes[i]); + } + return internalNames; + } + + + /** + * get the ASM Types for the given classes + * @see Type#getType(Method) + */ + public static Type[] getParamTypes(Class[] params) { + Type[] types = new Type[params.length]; + for (int i=0; i<types.length; i++) { + types[i] = Type.getType(params[i]); + } + + return types; + } + +}