OPENJPA-2909 implement ASM proxy for Collections

This commit is contained in:
Mark Struberg 2023-05-03 12:58:26 +02:00
parent 87458c708b
commit 12f780eb76
4 changed files with 651 additions and 179 deletions

View File

@ -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>

View File

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

View File

@ -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(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, 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(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 {

View File

@ -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;
}
}