blob: 47f75f9bb5b3a7a1a24db6a979daa8efedf38fac [file] [log] [blame]
/*
* 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.io.File;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.openjpa.enhance.AsmAdaptor;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.lib.util.ClassUtil;
import org.apache.openjpa.lib.util.Files;
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.proxy.DelayedArrayListProxy;
import org.apache.openjpa.util.proxy.DelayedHashSetProxy;
import org.apache.openjpa.util.proxy.DelayedLinkedHashSetProxy;
import org.apache.openjpa.util.proxy.DelayedLinkedListProxy;
import org.apache.openjpa.util.proxy.DelayedPriorityQueueProxy;
import org.apache.openjpa.util.proxy.DelayedTreeSetProxy;
import org.apache.openjpa.util.proxy.DelayedVectorProxy;
import org.apache.openjpa.util.proxy.ProxyBean;
import org.apache.openjpa.util.proxy.ProxyCalendar;
import org.apache.openjpa.util.proxy.ProxyCollection;
import org.apache.openjpa.util.proxy.ProxyCollections;
import org.apache.openjpa.util.proxy.ProxyConcurrentMaps;
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.MethodVisitor;
import org.apache.xbean.asm9.Opcodes;
import org.apache.xbean.asm9.Type;
import serp.bytecode.BCClass;
import serp.bytecode.BCField;
import serp.bytecode.BCMethod;
import serp.bytecode.Code;
import serp.bytecode.JumpInstruction;
import serp.bytecode.Project;
/**
* Default implementation of the {@link ProxyManager} interface.
*
* @author Abe White
* @author Mark Struberg
*/
public class ProxyManagerImpl
implements ProxyManager {
private static final String PROXY_SUFFIX = "$proxy";
private static final Localizer _loc = Localizer.forPackage
(ProxyManagerImpl.class);
private static long _proxyId = 0L;
private static final Map _stdCollections = new HashMap();
private static final Map _stdMaps = new HashMap();
static {
_stdCollections.put(Collection.class, ArrayList.class);
_stdCollections.put(Set.class, HashSet.class);
_stdCollections.put(SortedSet.class, TreeSet.class);
_stdCollections.put(List.class, ArrayList.class);
_stdCollections.put(Queue.class, LinkedList.class);
_stdMaps.put(Map.class, HashMap.class);
_stdMaps.put(SortedMap.class, TreeMap.class);
}
private final Set<String> _unproxyable = new HashSet<>();
private final Map<Class<?>, Proxy> _proxies = new ConcurrentHashMap<>();
private boolean _trackChanges = true;
private boolean _assertType = false;
private boolean _delayedCollectionLoading = false;
public ProxyManagerImpl() {
_unproxyable.add(TimeZone.class.getName());
}
/**
* Whether proxies produced by this factory will use {@link ChangeTracker}s
* to try to cut down on data store operations at the cost of some extra
* bookkeeping overhead. Defaults to true.
*/
public boolean getTrackChanges() {
return _trackChanges;
}
/**
* Whether proxies produced by this factory will use {@link ChangeTracker}s
* to try to cut down on data store operations at the cost of some extra
* bookkeeping overhead. Defaults to true.
*/
public void setTrackChanges(boolean track) {
_trackChanges = track;
}
/**
* Whether to perform runtime checks to ensure that all elements
* added to collection and map proxies are the proper element/key/value
* type as defined by the metadata. Defaults to false.
*/
public boolean getAssertAllowedType() {
return _assertType;
}
/**
* Whether to perform runtime checks to ensure that all elements
* added to collection and map proxies are the proper element/key/value
* type as defined by the metadata. Defaults to false.
*/
public void setAssertAllowedType(boolean assertType) {
_assertType = assertType;
}
/**
* Whether loading of collections should be delayed until an operation
* is performed that requires them to be loaded. This property only
* applies to proxies that implement java.util.Collection (ie. not arrays
* or maps). Defaults to false.
*/
@Override
public boolean getDelayCollectionLoading() {
return _delayedCollectionLoading;
}
/**
* Whether loading of collections should be delayed until an operation
* is performed that requires them to be loaded. Defaults to false.
*/
public void setDelayCollectionLoading(boolean delay) {
_delayedCollectionLoading = delay;
}
/**
* Return a mutable view of class names we know cannot be proxied
* correctly by this manager.
*/
public Collection getUnproxyable() {
return _unproxyable;
}
/**
* Provided for auto-configuration. Add the given semicolon-separated
* class names to the set of class names we know cannot be proxied correctly
* by this manager.
*/
public void setUnproxyable(String clsNames) {
if (clsNames != null)
_unproxyable.addAll(Arrays.asList(StringUtil.split(clsNames, ";", 0)));
}
@Override
public Object copyArray(Object orig) {
if (orig == null)
return null;
try {
int length = Array.getLength(orig);
Object array = Array.newInstance(orig.getClass().
getComponentType(), length);
System.arraycopy(orig, 0, array, 0, length);
return array;
} catch (Exception e) {
throw new UnsupportedException(_loc.get("bad-array",
e.getMessage()), e);
}
}
@Override
public Collection copyCollection(Collection orig) {
if (orig == null)
return null;
if (orig instanceof Proxy)
return (Collection) ((Proxy) orig).copy(orig);
ProxyCollection proxy = getFactoryProxyCollection(orig.getClass());
return (Collection) proxy.copy(orig);
}
@Override
public Proxy newCollectionProxy(Class type, Class elementType,
Comparator compare, boolean autoOff) {
type = toProxyableCollectionType(type);
ProxyCollection proxy = getFactoryProxyCollection(type);
return proxy.newInstance((_assertType) ? elementType : null, compare,
_trackChanges, autoOff);
}
@Override
public Map copyMap(Map orig) {
if (orig == null)
return null;
if (orig instanceof Proxy)
return (Map) ((Proxy) orig).copy(orig);
ProxyMap proxy = getFactoryProxyMap(orig.getClass());
return (Map) proxy.copy(orig);
}
@Override
public Proxy newMapProxy(Class type, Class keyType,
Class elementType, Comparator compare,boolean autoOff) {
type = toProxyableMapType(type);
ProxyMap proxy = getFactoryProxyMap(type);
return proxy.newInstance((_assertType) ? keyType : null,
(_assertType) ? elementType : null, compare, _trackChanges, autoOff);
}
@Override
public Date copyDate(Date orig) {
if (orig == null)
return null;
if (orig instanceof Proxy)
return (Date) ((Proxy) orig).copy(orig);
ProxyDate proxy = getFactoryProxyDate(orig.getClass());
return (Date) proxy.copy(orig);
}
@Override
public Proxy newDateProxy(Class type) {
ProxyDate proxy = getFactoryProxyDate(type);
return proxy.newInstance();
}
@Override
public Calendar copyCalendar(Calendar orig) {
if (orig == null)
return null;
if (orig instanceof Proxy)
return (Calendar) ((Proxy) orig).copy(orig);
ProxyCalendar proxy = getFactoryProxyCalendar(orig.getClass());
return (Calendar) proxy.copy(orig);
}
@Override
public Proxy newCalendarProxy(Class type, TimeZone zone) {
if (type == Calendar.class)
type = GregorianCalendar.class;
ProxyCalendar proxy = getFactoryProxyCalendar(type);
ProxyCalendar cal = proxy.newInstance();
if (zone != null)
((Calendar) cal).setTimeZone(zone);
return cal;
}
@Override
public Object copyCustom(Object orig) {
if (orig == null)
return null;
if (orig instanceof Proxy)
return ((Proxy) orig).copy(orig);
if (ImplHelper.isManageable(orig))
return null;
if (orig instanceof Collection)
return copyCollection((Collection) orig);
if (orig instanceof Map)
return copyMap((Map) orig);
if (orig instanceof Date)
return copyDate((Date) orig);
if (orig instanceof Calendar)
return copyCalendar((Calendar) orig);
ProxyBean proxy = getFactoryProxyBean(orig);
return (proxy == null) ? null : proxy.copy(orig);
}
@Override
public Proxy newCustomProxy(Object orig, boolean autoOff) {
if (orig == null)
return null;
if (orig instanceof Proxy)
return (Proxy) orig;
if (ImplHelper.isManageable(orig))
return null;
if (!isProxyable(orig.getClass()))
return null;
if (orig instanceof Collection) {
Comparator comp = (orig instanceof SortedSet)
? ((SortedSet) orig).comparator() : null;
Collection c = (Collection) newCollectionProxy(orig.getClass(),
null, comp, autoOff);
c.addAll((Collection) orig);
return (Proxy) c;
}
if (orig instanceof Map) {
Comparator comp = (orig instanceof SortedMap)
? ((SortedMap) orig).comparator() : null;
Map m = (Map) newMapProxy(orig.getClass(), null, null, comp, autoOff);
m.putAll((Map) orig);
return (Proxy) m;
}
if (orig instanceof Date) {
Date d = (Date) newDateProxy(orig.getClass());
d.setTime(((Date) orig).getTime());
if (orig instanceof Timestamp)
((Timestamp) d).setNanos(((Timestamp) orig).getNanos());
return (Proxy) d;
}
if (orig instanceof Calendar) {
Calendar c = (Calendar) newCalendarProxy(orig.getClass(),
((Calendar) orig).getTimeZone());
c.setTimeInMillis(((Calendar) orig).getTimeInMillis());
return (Proxy) c;
}
ProxyBean proxy = getFactoryProxyBean(orig);
return (proxy == null) ? null : proxy.newInstance(orig);
}
/**
* Return the concrete type for proxying.
*/
protected Class toProxyableCollectionType(Class type) {
if (type.getName().endsWith(PROXY_SUFFIX))
type = type.getSuperclass();
else if (type.isInterface()) {
type = toConcreteType(type, _stdCollections);
if (type == null)
throw new UnsupportedException(_loc.get("no-proxy-intf", type));
} else if (Modifier.isAbstract(type.getModifiers()))
throw new UnsupportedException(_loc.get("no-proxy-abstract", type));
return type;
}
/**
* Return the concrete type for proxying.
*/
protected Class toProxyableMapType(Class type) {
if (type.getName().endsWith(PROXY_SUFFIX))
type = type.getSuperclass();
else if (type.isInterface()) {
type = toConcreteType(type, _stdMaps);
if (type == null)
throw new UnsupportedException(_loc.get("no-proxy-intf", type));
} else if (Modifier.isAbstract(type.getModifiers()))
throw new UnsupportedException(_loc.get("no-proxy-abstract", type));
return type;
}
/**
* Locate a concrete type to proxy for the given collection interface.
*/
private static Class toConcreteType(Class intf, Map concretes) {
Class concrete = (Class) concretes.get(intf);
if (concrete != null)
return concrete;
Class[] intfs = intf.getInterfaces();
for (Class aClass : intfs) {
concrete = toConcreteType(aClass, concretes);
if (concrete != null)
return concrete;
}
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.
*/
private ProxyMap getFactoryProxyMap(Class type) {
// we don't lock here; ok if two proxies get generated for same type
ProxyMap proxy = (ProxyMap) _proxies.get(type);
if (proxy == null) {
ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
ProxyMap.class);
Class pcls = loadBuildTimeProxy(type, l);
if (pcls == null)
pcls = GeneratedClasses.loadBCClass(
generateProxyMapBytecode(type, true), l);
proxy = (ProxyMap) instantiateProxy(pcls, null, null);
_proxies.put(type, proxy);
}
return proxy;
}
/**
* Return the cached factory proxy for the given date type.
*/
private ProxyDate getFactoryProxyDate(Class type) {
// we don't lock here; ok if two proxies get generated for same type
ProxyDate proxy = (ProxyDate) _proxies.get(type);
if (proxy == null) {
ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
ProxyDate.class);
Class pcls = loadBuildTimeProxy(type, l);
if (pcls == null)
pcls = generateAndLoadProxyDate(type, true, l);
proxy = (ProxyDate) instantiateProxy(pcls, null, null);
_proxies.put(type, proxy);
}
return proxy;
}
/**
* Return the cached factory proxy for the given calendar type.
*/
private ProxyCalendar getFactoryProxyCalendar(Class type) {
// we don't lock here; ok if two proxies get generated for same type
ProxyCalendar proxy = (ProxyCalendar) _proxies.get(type);
if (proxy == null) {
ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
ProxyCalendar.class);
Class pcls = loadBuildTimeProxy(type, l);
if (pcls == null)
pcls = GeneratedClasses.loadBCClass(
generateProxyCalendarBytecode(type, true), l);
proxy = (ProxyCalendar) instantiateProxy(pcls, null, null);
_proxies.put(type, proxy);
}
return proxy;
}
/**
* Return the cached factory proxy for the given bean type.
*/
private ProxyBean getFactoryProxyBean(Object orig) {
final Class<?> type = orig.getClass();
if (isUnproxyable(type))
return null;
// we don't lock here; ok if two proxies get generated for same type
ProxyBean proxy = (ProxyBean) _proxies.get(type);
if (proxy == null) {
ClassLoader l = GeneratedClasses.getMostDerivedLoader(type, ProxyBean.class);
Class<?> pcls = loadBuildTimeProxy(type, l);
if (pcls == null) {
// TODO Move this to J2DOPrivHelper?
BCClass bc = AccessController.doPrivileged(new PrivilegedAction<BCClass>() {
@Override
public BCClass run() {
return generateProxyBeanBytecode(type, true);
}
});
if (bc != null)
pcls = GeneratedClasses.loadBCClass(bc, l);
}
if (pcls != null)
proxy = (ProxyBean) instantiateProxy(pcls, findCopyConstructor(type), new Object[] { orig });
if (proxy == null) {
_unproxyable.add(type.getName());
} else {
_proxies.put(type, proxy);
}
}
return proxy;
}
/**
* Return whether the given type is known to be unproxyable.
*/
protected boolean isUnproxyable(Class type) {
for (; type != null && type != Object.class;
type = type.getSuperclass()) {
if (_unproxyable.contains(type.getName()))
return true;
}
return false;
}
/**
* Load the proxy class generated at build time for the given type,
* returning null if none exists.
*/
protected Class loadBuildTimeProxy(Class type, ClassLoader loader) {
try {
Class<?> proxyClass = null;
if (_delayedCollectionLoading) {
proxyClass = loadDelayedProxy(type);
if (proxyClass != null) {
return proxyClass;
}
}
return Class.forName(getProxyClassName(type, false), true, loader);
} catch (Throwable t) {
return null;
}
}
protected Class<?> loadDelayedProxy(Class<?> type) {
if (type.equals(java.util.ArrayList.class)) {
return DelayedArrayListProxy.class;
}
if (type.equals(java.util.HashSet.class)) {
return DelayedHashSetProxy.class;
}
if (type.equals(java.util.LinkedList.class)) {
return DelayedLinkedListProxy.class;
}
if (type.equals(java.util.Vector.class)) {
return DelayedVectorProxy.class;
}
if (type.equals(java.util.LinkedHashSet.class)) {
return DelayedLinkedHashSetProxy.class;
}
if (type.equals(java.util.SortedSet.class) || type.equals(java.util.TreeSet.class)) {
return DelayedTreeSetProxy.class;
}
if (type.equals(java.util.PriorityQueue.class)) {
return DelayedPriorityQueueProxy.class;
}
return null;
}
/**
* Instantiate the given proxy class.
*/
private Proxy instantiateProxy(Class cls, Constructor cons, Object[] args) {
try {
if (cons != null)
return (Proxy) cls.getConstructor(cons.getParameterTypes()).
newInstance(args);
return (Proxy) AccessController.doPrivileged(
J2DoPrivHelper.newInstanceAction(cls));
} catch (InstantiationException ie) {
throw new UnsupportedException(_loc.get("cant-newinstance",
cls.getSuperclass().getName()));
} catch (PrivilegedActionException pae) {
Exception e = pae.getException();
if (e instanceof InstantiationException)
throw new UnsupportedException(_loc.get("cant-newinstance",
cls.getSuperclass().getName()));
else
throw new GeneralException(cls.getName()).setCause(e);
} catch (Throwable t) {
throw new GeneralException(cls.getName()).setCause(t);
}
}
/**
* Generate the bytecode for a collection proxy for the given type.
*/
protected BCClass generateProxyCollectionBytecode(Class type,
boolean runtime) {
assertNotFinal(type);
Project project = new Project();
BCClass bc = AccessController.doPrivileged(J2DoPrivHelper
.loadProjectClassAction(project, getProxyClassName(type, runtime)));
bc.setSuperclass(type);
bc.declareInterface(ProxyCollection.class);
delegateConstructors(bc, type);
addProxyMethods(bc, false);
addProxyCollectionMethods(bc, type);
proxyRecognizedMethods(bc, type, ProxyCollections.class,
ProxyCollection.class);
proxySetters(bc, type);
addWriteReplaceMethod(bc, runtime);
return bc;
}
/**
* Return the name of the proxy class to generate for the given type.
*/
private static String getProxyClassName(Class type, boolean runtime) {
String id = (runtime) ? "$" + nextProxyId() : "";
return ClassUtil.getPackageName(ProxyManagerImpl.class) + "."
+ type.getName().replace('.', '$') + id + PROXY_SUFFIX;
}
/**
* Throw appropriate exception if the given type is final.
*/
private static void assertNotFinal(Class type) {
if (Modifier.isFinal(type.getModifiers()))
throw new UnsupportedException(_loc.get("no-proxy-final", type));
}
private static boolean isProxyable(Class<?> cls){
int mod = cls.getModifiers();
if(Modifier.isFinal(mod))
return false;
if(Modifier.isProtected(mod) || Modifier.isPublic(mod))
return true;
// Default scoped class, we can only extend if it is in the same package as the generated proxy. Ideally
// we'd fix the code gen portion and place proxies in the same pacakge as the types being proxied.
if(cls.getPackage().getName().equals("org.apache.openjpa.util"))
return true;
return false;
}
/**
* Generate the bytecode for a map proxy for the given type.
*/
protected BCClass generateProxyMapBytecode(Class type, boolean runtime) {
assertNotFinal(type);
Project project = new Project();
BCClass bc = AccessController.doPrivileged(J2DoPrivHelper
.loadProjectClassAction(project, getProxyClassName(type, runtime)));
bc.setSuperclass(type);
bc.declareInterface(ProxyMap.class);
delegateConstructors(bc, type);
addProxyMethods(bc, false);
addProxyMapMethods(bc, type);
Class<? extends ProxyMaps> mapProxyClassType = ProxyConcurrentMaps.class;
proxyRecognizedMethods(bc, type, mapProxyClassType, ProxyMap.class);
proxySetters(bc, type);
addWriteReplaceMethod(bc, runtime);
return bc;
}
private Class generateAndLoadProxyDate(Class type, boolean runtime, ClassLoader l) {
final String proxyClassName = getProxyClassName(type, runtime);
final byte[] classBytes = generateProxyDateBytecode(type, runtime, proxyClassName);
return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyDate.class, l);
}
/**
* Generate the bytecode for a date proxy for the given type.
*/
protected byte[] generateProxyDateBytecode(Class type, boolean runtime, String proxyClassName) {
assertNotFinal(type);
String proxyClassDef = proxyClassName.replace('.', '/');
String superClassFileNname = type.getName().replace('.', '/');
String[] interfaceNames = new String[]{Type.getInternalName(ProxyDate.class)};
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
cw.visit(Opcodes.V11, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER, proxyClassDef,
null, superClassFileNname, interfaceNames);
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);
return cw.toByteArray();
}
/**
* add the instance variables to the class to be generated
*/
private void addInstanceVariables(ClassWriter cw) {
// variable #1, the state manager
cw.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,
"field", Type.getDescriptor(int.class), null, null).visitEnd();
}
/**
* Create pass-through constructors to base type.
*/
private void delegateConstructors(ClassWriter cw, Class type, String superClassFileNname) {
Constructor[] constructors = type.getConstructors();
for (Constructor constructor : constructors) {
Class[] params = constructor.getParameterTypes();
String[] exceptionTypes = getInternalNames(constructor.getExceptionTypes());
String descriptor = Type.getConstructorDescriptor(constructor);
MethodVisitor mv = cw.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.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassFileNname, "<init>", descriptor, false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
}
/**
* 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
* the change tracker method is left unimplemented
* @param proxyClassDef
*/
private void addProxyMethods(ClassWriter cw, boolean changeTracker, String proxyClassDef, Class<?> parentClass) {
{
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "setOwner",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OpenJPAStateManager.class), Type.INT_TYPE)
, null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassDef, "sm", Type.getDescriptor(OpenJPAStateManager.class));
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitVarInsn(Opcodes.ILOAD, 2);
mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassDef, "field", Type.getDescriptor(Integer.TYPE));
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "getOwner",
Type.getMethodDescriptor(Type.getType(OpenJPAStateManager.class))
, null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassDef, "sm", Type.getDescriptor(OpenJPAStateManager.class));
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "getOwnerField",
Type.getMethodDescriptor(Type.INT_TYPE)
, null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassDef, "field", Type.INT_TYPE.getDescriptor());
mv.visitInsn(Opcodes.IRETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
{
/*
* clone (return detached proxy object)
* Note: This method is only being provided to satisfy a quirk with
* the IBM JDK -- while comparing Calendar objects, the clone() method
* 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",
Type.getMethodDescriptor(Type.getType(Object.class))
, null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(parentClass), "clone",
Type.getMethodDescriptor(Type.getType(Object.class)), false);
mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Proxy.class));
mv.visitVarInsn(Opcodes.ASTORE, 1);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.ICONST_0);
mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(Proxy.class), "setOwner",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(OpenJPAStateManager.class), Type.INT_TYPE), true);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
if (changeTracker) {
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "getChangeTracker",
Type.getMethodDescriptor(Type.getType(ChangeTracker.class))
, null, null);
mv.visitCode();
mv.visitInsn(Opcodes.ACONST_NULL);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
}
/**
* Implement the methods in the {@link ProxyDate} interface.
*/
private void addProxyDateMethods(ClassWriter cw, String proxyClassDef, Class type) {
final boolean hasDefaultCons = hasConstructor(type);
final boolean hasMillisCons = hasConstructor(type, long.class);
if (!hasDefaultCons && !hasMillisCons) {
throw new UnsupportedException(_loc.get("no-date-cons", type));
}
// add a default constructor that delegates to the millis constructor
if (!hasDefaultCons) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE), null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(System.class), "currentTimeMillis",
Type.getMethodDescriptor(Type.LONG_TYPE), false);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE), false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
{
// date copy
Constructor cons = findCopyConstructor(type);
Class[] params;
if (cons != null) {
params = cons.getParameterTypes();
}
else if (hasMillisCons) {
params = new Class[]{long.class};
}
else {
params = new Class[0];
}
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "copy",
Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Object.class))
, null, null);
mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(type));
mv.visitInsn(Opcodes.DUP);
if (params.length == 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);
if (params.length == 0) {
mv.visitInsn(Opcodes.DUP);
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);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "setTime",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE), false);
}
if ((params.length == 0 || params[0] == long.class)
&& Timestamp.class.isAssignableFrom(type)) {
mv.visitInsn(Opcodes.DUP);
mv.visitVarInsn(Opcodes.ALOAD, 1);
mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Timestamp.class));
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Timestamp.class), "getNanos",
Type.getMethodDescriptor(Type.INT_TYPE), false);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "setNanos",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false);
}
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
{
// new instance factory
MethodVisitor mv = cw.visitMethod(Modifier.PUBLIC, "newInstance",
Type.getMethodDescriptor(Type.getType(ProxyDate.class))
, null, null);
mv.visitTypeInsn(Opcodes.NEW, proxyClassDef);
mv.visitInsn(Opcodes.DUP);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, proxyClassDef, "<init>",
Type.getMethodDescriptor(Type.VOID_TYPE), false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
}
/**
* Proxy setter methods of the given type.
*
* @return true if we generated any setters, false otherwise
*/
private boolean proxySetters(ClassWriter cw, 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);
}
}
return setters > 0;
}
private void proxySetter(ClassWriter cw, 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);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(Opcodes.ICONST_1);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Proxies.class), "dirty",
Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Proxy.class), Type.BOOLEAN_TYPE), false);
mv.visitVarInsn(Opcodes.ALOAD, 0);
// push all the method params to the stack
for (int i = 1; i <= params.length; i++)
{
mv.visitVarInsn(getVarInsn(params[i-1]), i);
}
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), meth.getName(),
methodDescriptor, false);
mv.visitInsn(getReturnInsn(ret));
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
/**
* 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",
Type.getMethodDescriptor(Type.getType(Object.class))
, null, new String[]{Type.getInternalName(ObjectStreamException.class)});
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitInsn(runtime ? Opcodes.ICONST_0 : Opcodes.ICONST_1); // !runtime
mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(Proxies.class), "writeReplace",
Type.getMethodDescriptor(Type.getType(Object.class), Type.getType(Proxy.class), Type.BOOLEAN_TYPE), false);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
/* 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 {
return type.getDeclaredConstructor(paramTypes) != null;
}
catch (NoSuchMethodException e) {
return false;
}
}
/**
* 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 */
/**
* Generate the bytecode for a calendar proxy for the given type.
*/
protected BCClass generateProxyCalendarBytecode(Class type,
boolean runtime) {
assertNotFinal(type);
Project project = new Project();
BCClass bc = AccessController.doPrivileged(J2DoPrivHelper
.loadProjectClassAction(project, getProxyClassName(type, runtime)));
bc.setSuperclass(type);
bc.declareInterface(ProxyCalendar.class);
delegateConstructors(bc, type);
addProxyMethods(bc, true);
addProxyCalendarMethods(bc, type);
proxySetters(bc, type);
addWriteReplaceMethod(bc, runtime);
return bc;
}
/**
* Generate the bytecode for a bean proxy for the given type.
*/
protected BCClass generateProxyBeanBytecode(Class type, boolean runtime) {
if (Modifier.isFinal(type.getModifiers()))
return null;
if (ImplHelper.isManagedType(null, type))
return null;
// we can only generate a valid proxy if there is a copy constructor
// or a default constructor
Constructor cons = findCopyConstructor(type);
if (cons == null) {
Constructor[] cs = type.getConstructors();
for (int i = 0; cons == null && i < cs.length; i++)
if (cs[i].getParameterTypes().length == 0)
cons = cs[i];
if (cons == null)
return null;
}
Project project = new Project();
BCClass bc = AccessController.doPrivileged(J2DoPrivHelper
.loadProjectClassAction(project, getProxyClassName(type, runtime)));
bc.setSuperclass(type);
bc.declareInterface(ProxyBean.class);
delegateConstructors(bc, type);
addProxyMethods(bc, true);
addProxyBeanMethods(bc, type, cons);
if (!proxySetters(bc, type))
return null;
addWriteReplaceMethod(bc, runtime);
return bc;
}
/**
* Create pass-through constructors to base type.
*/
private void delegateConstructors(BCClass bc, Class type) {
Constructor[] cons = type.getConstructors();
Class[] params;
BCMethod m;
Code code;
for (Constructor con : cons) {
params = con.getParameterTypes();
m = bc.declareMethod("<init>", void.class, params);
m.makePublic();
code = m.getCode(true);
code.aload().setThis();
for (int j = 0; j < params.length; j++)
code.xload().setParam(j).setType(params[j]);
code.invokespecial().setMethod(con);
code.vreturn();
code.calculateMaxStack();
code.calculateMaxLocals();
}
}
/**
* 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
* the change tracker method is left unimplemented
*/
private void addProxyMethods(BCClass bc, boolean changeTracker) {
BCField sm = bc.declareField("sm", OpenJPAStateManager.class);
sm.setTransient(true);
BCField field = bc.declareField("field", int.class);
field.setTransient(true);
BCMethod m = bc.declareMethod("setOwner", void.class, new Class[] {
OpenJPAStateManager.class, int.class });
m.makePublic();
Code code = m.getCode(true);
code.aload().setThis();
code.aload().setParam(0);
code.putfield().setField(sm);
code.aload().setThis();
code.iload().setParam(1);
code.putfield().setField(field);
code.vreturn();
code.calculateMaxStack();
code.calculateMaxLocals();
m = bc.declareMethod("getOwner", OpenJPAStateManager.class, null);
m.makePublic();
code = m.getCode(true);
code.aload().setThis();
code.getfield().setField(sm);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
m = bc.declareMethod("getOwnerField", int.class, null);
m.makePublic();
code = m.getCode(true);
code.aload().setThis();
code.getfield().setField(field);
code.ireturn();
code.calculateMaxStack();
code.calculateMaxLocals();
/*
* clone (return detached proxy object)
* Note: This method is only being provided to satisfy a quirk with
* the IBM JDK -- while comparing Calendar objects, the clone() method
* was invoked. So, we are now overriding the clone() method so as to
* provide a detached proxy object (null out the StateManager).
*/
m = bc.declareMethod("clone", Object.class, null);
m.makePublic();
code = m.getCode(true);
code.aload().setThis();
code.invokespecial().setMethod(bc.getSuperclassType(), "clone",
Object.class, null);
code.checkcast().setType(Proxy.class);
int other = code.getNextLocalsIndex();
code.astore().setLocal(other);
code.aload().setLocal(other);
code.constant().setNull();
code.constant().setValue(0);
code.invokeinterface().setMethod(Proxy.class, "setOwner", void.class,
new Class[] { OpenJPAStateManager.class, int.class });
code.aload().setLocal(other);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
if (changeTracker) {
m = bc.declareMethod("getChangeTracker", ChangeTracker.class, null);
m.makePublic();
code = m.getCode(true);
code.constant().setNull();
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
}
}
/**
* Implement the methods in the {@link ProxyCollection} interface.
*/
private void addProxyCollectionMethods(BCClass bc, Class type) {
// change tracker
BCField changeTracker = bc.declareField("changeTracker",
CollectionChangeTracker.class);
changeTracker.setTransient(true);
BCMethod m = bc.declareMethod("getChangeTracker", ChangeTracker.class,
null);
m.makePublic();
Code code = m.getCode(true);
code.aload().setThis();
code.getfield().setField(changeTracker);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// 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();
m = bc.declareMethod("copy", Object.class, new Class[] {Object.class});
m.makePublic();
code = m.getCode(true);
code.anew().setType(type);
code.dup();
if (params.length == 1) {
code.aload().setParam(0);
if (params[0] == Comparator.class) {
code.checkcast().setType(SortedSet.class);
code.invokeinterface().setMethod(SortedSet.class, "comparator",
Comparator.class, null);
} else
code.checkcast().setType(params[0]);
}
code.invokespecial().setMethod(type, "<init>", void.class, params);
if (params.length == 0 || params[0] == Comparator.class) {
code.dup();
code.aload().setParam(0);
code.checkcast().setType(Collection.class);
code.invokevirtual().setMethod(type, "addAll", boolean.class,
new Class[] { Collection.class });
code.pop();
}
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// element type
BCField elementType = bc.declareField("elementType", Class.class);
elementType.setTransient(true);
m = bc.declareMethod("getElementType", Class.class, null);
m.makePublic();
code = m.getCode(true);
code.aload().setThis();
code.getfield().setField(elementType);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// new instance factory
m = bc.declareMethod("newInstance", ProxyCollection.class,
new Class[] { Class.class, Comparator.class, boolean.class, boolean.class });
m.makePublic();
code = m.getCode(true);
code.anew().setType(bc);
code.dup();
cons = findComparatorConstructor(type);
params = (cons == null) ? new Class[0] : cons.getParameterTypes();
if (params.length == 1)
code.aload().setParam(1);
code.invokespecial().setMethod("<init>", void.class, params);
int ret = code.getNextLocalsIndex();
code.astore().setLocal(ret);
// set element type
code.aload().setLocal(ret);
code.aload().setParam(0);
code.putfield().setField(elementType);
// create change tracker and set it
code.iload().setParam(2);
JumpInstruction ifins = code.ifeq();
code.aload().setLocal(ret);
code.anew().setType(CollectionChangeTrackerImpl.class);
code.dup();
code.aload().setLocal(ret);
code.constant().setValue(allowsDuplicates(type));
code.constant().setValue(isOrdered(type));
code.aload().setParam(3);
code.invokespecial().setMethod(CollectionChangeTrackerImpl.class,
"<init>", void.class, new Class[] { Collection.class,
boolean.class, boolean.class, boolean.class });
code.putfield().setField(changeTracker);
ifins.setTarget(code.aload().setLocal(ret));
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
}
/**
* Return whether the given collection type allows duplicates.
*/
protected boolean allowsDuplicates(Class type) {
return !Set.class.isAssignableFrom(type);
}
/**
* Return whether the given collection type maintains an artificial
* ordering.
*/
protected boolean isOrdered(Class type) {
return List.class.isAssignableFrom(type)
|| "java.util.LinkedHashSet".equals(type.getName());
}
/**
* Implement the methods in the {@link ProxyMap} interface.
*/
private void addProxyMapMethods(BCClass bc, Class type) {
// change tracker
BCField changeTracker = bc.declareField("changeTracker",
MapChangeTracker.class);
changeTracker.setTransient(true);
BCMethod m = bc.declareMethod("getChangeTracker", ChangeTracker.class,
null);
m.makePublic();
Code code = m.getCode(true);
code.aload().setThis();
code.getfield().setField(changeTracker);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// map copy
Constructor cons = findCopyConstructor(type);
if (cons == null && SortedMap.class.isAssignableFrom(type))
cons = findComparatorConstructor(type);
Class[] params = (cons == null) ? new Class[0]
: cons.getParameterTypes();
m = bc.declareMethod("copy", Object.class, new Class[] {Object.class});
m.makePublic();
code = m.getCode(true);
code.anew().setType(type);
code.dup();
if (params.length == 1) {
code.aload().setParam(0);
if (params[0] == Comparator.class) {
code.checkcast().setType(SortedMap.class);
code.invokeinterface().setMethod(SortedMap.class, "comparator",
Comparator.class, null);
} else
code.checkcast().setType(params[0]);
}
code.invokespecial().setMethod(type, "<init>", void.class, params);
if (params.length == 0 || params[0] == Comparator.class) {
code.dup();
code.aload().setParam(0);
code.checkcast().setType(Map.class);
code.invokevirtual().setMethod(type, "putAll", void.class,
new Class[] { Map.class });
}
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// key type
BCField keyType = bc.declareField("keyType", Class.class);
keyType.setTransient(true);
m = bc.declareMethod("getKeyType", Class.class, null);
m.makePublic();
code = m.getCode(true);
code.aload().setThis();
code.getfield().setField(keyType);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// value type
BCField valueType = bc.declareField("valueType", Class.class);
valueType.setTransient(true);
m = bc.declareMethod("getValueType", Class.class, null);
m.makePublic();
code = m.getCode(true);
code.aload().setThis();
code.getfield().setField(valueType);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// new instance factory
m = bc.declareMethod("newInstance", ProxyMap.class,
new Class[] { Class.class, Class.class, Comparator.class,
boolean.class,boolean.class });
m.makePublic();
code = m.getCode(true);
code.anew().setType(bc);
code.dup();
cons = findComparatorConstructor(type);
params = (cons == null) ? new Class[0] : cons.getParameterTypes();
if (params.length == 1)
code.aload().setParam(2);
code.invokespecial().setMethod("<init>", void.class, params);
int ret = code.getNextLocalsIndex();
code.astore().setLocal(ret);
// set key and value types
code.aload().setLocal(ret);
code.aload().setParam(0);
code.putfield().setField(keyType);
code.aload().setLocal(ret);
code.aload().setParam(1);
code.putfield().setField(valueType);
// create change tracker and set it
code.iload().setParam(3);
JumpInstruction ifins = code.ifeq();
code.aload().setLocal(ret);
code.anew().setType(MapChangeTrackerImpl.class);
code.dup();
code.aload().setLocal(ret);
code.aload().setParam(4);
code.invokespecial().setMethod(MapChangeTrackerImpl.class,
"<init>", void.class, new Class[] { Map.class, boolean.class });
code.putfield().setField(changeTracker);
ifins.setTarget(code.aload().setLocal(ret));
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
}
/**
* Implement the methods in the {@link ProxyCalendar} interface.
*/
private void addProxyCalendarMethods(BCClass bc, Class type) {
// calendar copy
Constructor cons = findCopyConstructor(type);
Class[] params = (cons == null) ? new Class[0]
: cons.getParameterTypes();
BCMethod m = bc.declareMethod("copy", Object.class,
new Class[] {Object.class});
m.makePublic();
Code code = m.getCode(true);
code.anew().setType(type);
code.dup();
if (params.length == 1) {
code.aload().setParam(0);
code.checkcast().setType(params[0]);
}
code.invokespecial().setMethod(type, "<init>", void.class, params);
if (params.length == 0) {
code.dup();
code.aload().setParam(0);
code.checkcast().setType(Calendar.class);
code.invokevirtual().setMethod(Calendar.class, "getTimeInMillis",
long.class, null);
code.invokevirtual().setMethod(type, "setTimeInMillis", void.class,
new Class[] { long.class });
code.dup();
code.aload().setParam(0);
code.checkcast().setType(Calendar.class);
code.invokevirtual().setMethod(Calendar.class, "isLenient",
boolean.class, null);
code.invokevirtual().setMethod(type, "setLenient", void.class,
new Class[] { boolean.class });
code.dup();
code.aload().setParam(0);
code.checkcast().setType(Calendar.class);
code.invokevirtual().setMethod(Calendar.class, "getFirstDayOfWeek",
int.class, null);
code.invokevirtual().setMethod(type, "setFirstDayOfWeek",
void.class, new Class[] { int.class });
code.dup();
code.aload().setParam(0);
code.checkcast().setType(Calendar.class);
code.invokevirtual().setMethod(Calendar.class,
"getMinimalDaysInFirstWeek", int.class, null);
code.invokevirtual().setMethod(type, "setMinimalDaysInFirstWeek",
void.class, new Class[] { int.class });
code.dup();
code.aload().setParam(0);
code.checkcast().setType(Calendar.class);
code.invokevirtual().setMethod(Calendar.class, "getTimeZone",
TimeZone.class, null);
code.invokevirtual().setMethod(type, "setTimeZone", void.class,
new Class[] { TimeZone.class });
}
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// new instance factory
m = bc.declareMethod("newInstance", ProxyCalendar.class, null);
m.makePublic();
code = m.getCode(true);
code.anew().setType(bc);
code.dup();
code.invokespecial().setMethod("<init>", void.class, null);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// 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();
}
/**
* Implement the methods in the {@link ProxyBean} interface.
*/
private void addProxyBeanMethods(BCClass bc, Class type, Constructor cons) {
// bean copy
BCMethod m = bc.declareMethod("copy", Object.class,
new Class[] { Object.class });
m.makePublic();
Code code = m.getCode(true);
code.anew().setType(type);
code.dup();
Class[] params = cons.getParameterTypes();
if (params.length == 1) {
code.aload().setParam(0);
code.checkcast().setType(params[0]);
}
code.invokespecial().setMethod(cons);
if (params.length == 0)
copyProperties(type, code);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
// new instance factory
m = bc.declareMethod("newInstance", ProxyBean.class,
new Class[] { Object.class });
m.makePublic();
code = m.getCode(true);
code.anew().setType(bc);
code.dup();
if (params.length == 1) {
code.aload().setParam(0);
code.checkcast().setType(params[0]);
}
code.invokespecial().setMethod("<init>", void.class, params);
if (params.length == 0)
copyProperties(type, code);
code.areturn();
code.calculateMaxStack();
code.calculateMaxLocals();
}
/**
* Copy bean properties. Called with the copy object on the stack. Must
* return with the copy object on the stack.
*/
private void copyProperties(Class type, Code code) {
int copy = code.getNextLocalsIndex();
code.astore().setLocal(copy);
Method[] meths = type.getMethods();
Method getter;
int mods;
for (Method meth : meths) {
mods = meth.getModifiers();
if (!Modifier.isPublic(mods) || Modifier.isStatic(mods))
continue;
if (!startsWith(meth.getName(), "set")
|| meth.getParameterTypes().length != 1)
continue;
getter = findGetter(type, meth);
if (getter == null)
continue;
// copy.setXXX(orig.getXXX());
code.aload().setLocal(copy);
code.aload().setParam(0);
code.checkcast().setType(type);
code.invokevirtual().setMethod(getter);
code.invokevirtual().setMethod(meth);
}
code.aload().setLocal(copy);
}
/**
* Proxy recognized methods to invoke helpers in given helper class.
*/
private void proxyRecognizedMethods(BCClass bc, Class type, Class helper,
Class proxyType) {
Method[] meths = type.getMethods();
Class[] params;
Class[] afterParams;
Method match;
Method after;
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;
params = toHelperParameters(meth.getParameterTypes(),
proxyType);
// first check for overriding method
try {
match = helper.getMethod(meth.getName(), params);
proxyOverrideMethod(bc, meth, match, params);
continue;
}
catch (NoSuchMethodException nsme) {
}
catch (Exception e) {
throw new GeneralException(e);
}
// check for before and after methods, either of which may not
// exist
match = null;
try {
match = helper.getMethod("before"
+ StringUtil.capitalize(meth.getName()), params);
}
catch (NoSuchMethodException nsme) {
}
catch (Exception e) {
throw new GeneralException(e);
}
after = null;
afterParams = null;
try {
afterParams = toHelperAfterParameters(params,
meth.getReturnType(), (match == null)
? void.class : match.getReturnType());
after = helper.getMethod("after"
+ StringUtil.capitalize(meth.getName()), afterParams);
}
catch (NoSuchMethodException nsme) {
}
catch (Exception e) {
throw new GeneralException(e);
}
if (match != null || after != null)
proxyBeforeAfterMethod(bc, type, meth, match, params, after,
afterParams);
}
}
/**
* Return the parameter types to the corresponding helper class method.
*/
private static Class[] toHelperParameters(Class[] cls, Class helper) {
Class[] params = new Class[cls.length + 1];
params[0] = helper;
System.arraycopy(cls, 0, params, 1, cls.length);
return params;
}
/**
* Return the parameter types to the corresponding helper class "after"
* method.
*/
private static Class[] toHelperAfterParameters(Class[] cls, Class ret,
Class beforeRet) {
if (ret == void.class && beforeRet == void.class)
return cls;
int len = cls.length;
if (ret != void.class)
len++;
if (beforeRet != void.class)
len++;
Class[] params = new Class[len];
System.arraycopy(cls, 0, params, 0, cls.length);
int pos = cls.length;
if (ret != void.class)
params[pos++] = ret;
if (beforeRet != void.class)
params[pos++] = beforeRet;
return params;
}
/**
* Proxy setter methods of the given type.
*
* @return true if we find any setters, false otherwise
*/
private boolean proxySetters(BCClass bc, Class type) {
Method[] meths = type.getMethods();
int setters = 0;
for (Method meth : meths) {
if (isSetter(meth) && !Modifier.isFinal(meth.getModifiers())
&& bc.getDeclaredMethod(meth.getName(),
meth.getParameterTypes()) == null) {
setters++;
proxySetter(bc, type, meth);
}
}
return setters > 0;
}
/**
* Proxy the given method with one that overrides it by calling into the
* given helper.
*/
private void proxyOverrideMethod(BCClass bc, Method meth,
Method helper, Class[] params) {
BCMethod m = bc.declareMethod(meth.getName(), meth.getReturnType(),
meth.getParameterTypes());
m.makePublic();
Code code = m.getCode(true);
code.aload().setThis();
for (int i = 1; i < params.length; i++)
code.xload().setParam(i - 1).setType(params[i]);
code.invokestatic().setMethod(helper);
code.xreturn().setType(meth.getReturnType());
code.calculateMaxStack();
code.calculateMaxLocals();
}
/**
* Proxy the given method with one that overrides it by calling into the
* given helper.
*/
private void proxyBeforeAfterMethod(BCClass bc, Class type, Method meth,
Method before, Class[] params, Method after, Class[] afterParams) {
BCMethod m = bc.declareMethod(meth.getName(), meth.getReturnType(),
meth.getParameterTypes());
m.makePublic();
Code code = m.getCode(true);
// invoke before
int beforeRet = -1;
if (before != null) {
code.aload().setThis();
for (int i = 1; i < params.length; i++)
code.xload().setParam(i - 1).setType(params[i]);
code.invokestatic().setMethod(before);
if (after != null && before.getReturnType() != void.class) {
beforeRet = code.getNextLocalsIndex();
code.xstore().setLocal(beforeRet).
setType(before.getReturnType());
}
}
// invoke super
code.aload().setThis();
for (int i = 1; i < params.length; i++)
code.xload().setParam(i - 1).setType(params[i]);
code.invokespecial().setMethod(type, meth.getName(),
meth.getReturnType(), meth.getParameterTypes());
// invoke after
if (after != null) {
int ret = -1;
if (meth.getReturnType() != void.class) {
ret = code.getNextLocalsIndex();
code.xstore().setLocal(ret).setType(meth.getReturnType());
}
code.aload().setThis();
for (int i = 1; i < params.length; i++)
code.xload().setParam(i - 1).setType(params[i]);
if (ret != -1)
code.xload().setLocal(ret).setType(meth.getReturnType());
if (beforeRet != -1)
code.xload().setLocal(beforeRet).
setType(before.getReturnType());
code.invokestatic().setMethod(after);
}
code.xreturn().setType(meth.getReturnType());
code.calculateMaxStack();
code.calculateMaxLocals();
}
/**
* Return whether the given method is a setter.
*/
protected boolean isSetter(Method meth) {
return startsWith(meth.getName(), "set")
|| startsWith(meth.getName(), "add")
|| startsWith(meth.getName(), "remove")
|| startsWith(meth.getName(), "insert")
|| startsWith(meth.getName(), "clear")
|| startsWith(meth.getName(), "roll"); // used by Calendar
}
/**
* Return the getter corresponding to the given setter, or null.
*/
protected Method findGetter(Class type, Method setter) {
String name = setter.getName().substring(3);
Class param = setter.getParameterTypes()[0];
Method getter;
try {
getter = type.getMethod("get" + name, (Class[]) null);
if (getter.getReturnType().isAssignableFrom(param)
|| param.isAssignableFrom(getter.getReturnType()))
return getter;
} catch (NoSuchMethodException nsme) {
} catch (Exception e) {
throw new GeneralException(e);
}
if (param == boolean.class || param == Boolean.class) {
try {
getter = type.getMethod("is" + name, (Class[]) null);
if (getter.getReturnType().isAssignableFrom(param)
|| param.isAssignableFrom(getter.getReturnType()))
return getter;
} catch (NoSuchMethodException nsme) {
} catch (Exception e) {
throw new GeneralException(e);
}
}
return null;
}
/**
* Return whether the target string stars with the given token.
*/
private static boolean startsWith(String str, String token) {
return str.startsWith(token)
&& (str.length() == token.length()
|| Character.isUpperCase(str.charAt(token.length())));
}
/**
* Proxy the given setter method to dirty the proxy owner.
*/
private void proxySetter(BCClass bc, Class type, Method meth) {
Class[] params = meth.getParameterTypes();
Class ret = meth.getReturnType();
BCMethod m = bc.declareMethod(meth.getName(), ret, params);
m.makePublic();
Code 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();
for (int i = 0; i < params.length; i++)
code.xload().setParam(i).setType(params[i]);
code.invokespecial().setMethod(type, meth.getName(), ret, params);
code.xreturn().setType(ret);
code.calculateMaxStack();
code.calculateMaxLocals();
}
/**
* Add a writeReplace implementation that serializes to a non-proxy type
* unless detached and this is a build-time generated class.
*/
private void addWriteReplaceMethod(BCClass bc, boolean runtime) {
BCMethod m = bc.declareMethod("writeReplace", Object.class, null);
m.makeProtected();
m.getExceptions(true).addException(ObjectStreamException.class);
Code code = m.getCode(true);
code.aload().setThis();
code.constant().setValue(!runtime);
code.invokestatic().setMethod(Proxies.class, "writeReplace",
Object.class, new Class[] { Proxy.class, boolean.class });
code.areturn();
code.calculateMaxLocals();
code.calculateMaxStack();
}
/**
* Create a unique id to avoid proxy class name conflicts.
*/
private static synchronized long nextProxyId() {
return _proxyId++;
}
/**
* Find an appropriate copy constructor for the given type, or return null
* if none.
*/
protected Constructor findCopyConstructor(Class cls) {
Constructor[] cons = cls.getConstructors();
Constructor match = null;
Class matchParam = null;
Class[] params;
for (Constructor con : cons) {
params = con.getParameterTypes();
if (params.length != 1)
continue;
// quit immediately on exact match
if (params[0] == cls)
return con;
if (params[0].isAssignableFrom(cls) && (matchParam == null
|| matchParam.isAssignableFrom(params[0]))) {
// track most derived collection constructor
match = con;
matchParam = params[0];
}
}
return match;
}
/**
* Return the constructor that takes a comparator for the given type, or
* null if none.
*/
private static Constructor findComparatorConstructor(Class cls) {
try {
return cls.getConstructor(new Class[] { Comparator.class });
} catch (NoSuchMethodException nsme) {
return null;
} catch (Exception e) {
throw new GeneralException(e);
}
}
/**
* Usage: java org.apache.openjpa.util.proxy.ProxyManagerImpl [option]*
* &lt;class name&gt;+<br />
* Where the following options are recognized:
* <ul>
* <li><i>-utils/-u &lt;number&gt;</i>: Generate proxies for the standard
* java.util collection, map, date, and calendar classes of the given Java
* version. Use 4 for Java 1.4, 5 for Java 5, etc.</li>
* </ul>
*
* The main method generates .class files for the proxies to the classes
* given on the command line. It writes the generated classes to beside the
* ProxyManagerImpl.class file if possible; otherwise it writes to the
* current directory. The proxy manager looks for these classes
* before generating its own proxies at runtime.
*/
public static void main(String[] args)
throws ClassNotFoundException, IOException {
File dir = Files.getClassFile(ProxyManagerImpl.class);
dir = (dir == null) ? new File(AccessController.doPrivileged(
J2DoPrivHelper.getPropertyAction("user.dir")))
: dir.getParentFile();
Options opts = new Options();
args = opts.setFromCmdLine(args);
List types = new ArrayList(Arrays.asList(args));
int utils = opts.removeIntProperty("utils", "u", 0);
if (utils >= 4) {
types.addAll(Arrays.asList(new String[] {
java.sql.Date.class.getName(),
java.sql.Time.class.getName(),
java.sql.Timestamp.class.getName(),
java.util.ArrayList.class.getName(),
java.util.Date.class.getName(),
java.util.GregorianCalendar.class.getName(),
java.util.HashMap.class.getName(),
java.util.HashSet.class.getName(),
java.util.Hashtable.class.getName(),
java.util.LinkedList.class.getName(),
java.util.Properties.class.getName(),
java.util.TreeMap.class.getName(),
java.util.TreeSet.class.getName(),
java.util.Vector.class.getName(),
}));
}
if (utils >= 5) {
types.addAll(Arrays.asList(new String[] {
"java.util.EnumMap",
"java.util.IdentityHashMap",
"java.util.LinkedHashMap",
"java.util.LinkedHashSet",
"java.util.PriorityQueue",
}));
}
final ProxyManagerImpl mgr = new ProxyManagerImpl();
Class cls;
BCClass bc;
for (Object type : types) {
cls = Class.forName((String) type);
try {
if (Class.forName(getProxyClassName(cls, false), true,
GeneratedClasses.getMostDerivedLoader(cls, Proxy.class))
!= null)
continue;
}
catch (Throwable t) {
// expected if the class hasn't been generated
}
// ASM generated proxies
if (Date.class.isAssignableFrom(cls)) {
final String proxyClassName = getProxyClassName(cls, false);
byte[] bytes = null;
if (Date.class.isAssignableFrom(cls)) {
bytes = mgr.generateProxyDateBytecode(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);
}
continue;
}
if (Collection.class.isAssignableFrom(cls))
bc = mgr.generateProxyCollectionBytecode(cls, false);
else if (Map.class.isAssignableFrom(cls))
bc = mgr.generateProxyMapBytecode(cls, false);
else if (Calendar.class.isAssignableFrom(cls))
bc = mgr.generateProxyCalendarBytecode(cls, false);
else {
final Class fCls = cls;
// TODO Move this to J2DOPrivHelper
bc = AccessController
.doPrivileged(new PrivilegedAction<BCClass>() {
@Override
public BCClass run() {
return mgr.generateProxyBeanBytecode(fCls, false);
}
});
}
// START - ALLOW PRINT STATEMENTS
System.out.println(bc.getName());
// STOP - ALLOW PRINT STATEMENTS
AsmAdaptor.write(bc, new File(dir, bc.getClassName() + ".class"));
}
}
}