You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by st...@apache.org on 2023/05/06 07:18:35 UTC
[openjpa] 10/17: OPENJPA-2909 implement ASM proxy for Collections
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 <st...@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:struberg@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:struberg@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;
+ }
+
+}