| /* |
| * 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.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.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.asm.AsmHelper; |
| 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.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; |
| |
| |
| /** |
| * 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); |
| public static final Type TYPE_OBJECT = Type.getType(Object.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 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 = generateAndLoadProxyMap(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 = generateAndLoadProxyCalendar(type, true, l); |
| proxy = (ProxyCalendar) instantiateProxy(pcls, null, null); |
| _proxies.put(type, proxy); |
| } |
| 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. |
| */ |
| 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) { |
| pcls = generateAndLoadProxyBean(type, true, 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); |
| } |
| } |
| |
| /** |
| * Return the name of the proxy class to generate for the given type. |
| */ |
| protected 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; |
| |
| } |
| |
| |
| private Class generateAndLoadProxyDate(Class type, boolean runtime, ClassLoader l) { |
| final String proxyClassName = getProxyClassName(type, runtime); |
| final byte[] classBytes = generateProxyDateBytecode(type, runtime, proxyClassName); |
| if (classBytes == null) { |
| return null; |
| } |
| |
| return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyDate.class, l); |
| } |
| |
| private Class generateAndLoadProxyCalendar(Class type, boolean runtime, ClassLoader l) { |
| final String proxyClassName = getProxyClassName(type, runtime); |
| final byte[] classBytes = generateProxyCalendarBytecode(type, runtime, proxyClassName); |
| if (classBytes == null) { |
| return null; |
| } |
| |
| 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); |
| if (classBytes == null) { |
| return null; |
| } |
| |
| return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyCollection.class, l); |
| } |
| |
| private Class generateAndLoadProxyMap(Class type, boolean runtime, ClassLoader l) { |
| final String proxyClassName = getProxyClassName(type, runtime); |
| final byte[] classBytes = generateProxyMapBytecode(type, runtime, proxyClassName); |
| if (classBytes == null) { |
| return null; |
| } |
| |
| return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyMap.class, l); |
| } |
| |
| private Class generateAndLoadProxyBean(Class type, boolean runtime, ClassLoader l) { |
| final String proxyClassName = getProxyClassName(type, runtime); |
| final byte[] classBytes = generateProxyBeanBytecode(type, runtime, proxyClassName); |
| if (classBytes == null) { |
| return null; |
| } |
| |
| return GeneratedClasses.loadAsmClass(proxyClassName, classBytes, ProxyBean.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.getInternalName(type); |
| 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); |
| |
| ClassWriterTracker ct = new ClassWriterTracker(cw); |
| String classFileName = runtime ? type.getName() : proxyClassDef; |
| cw.visitSource(classFileName + ".java", null); |
| |
| delegateConstructors(ct, type, superClassFileNname); |
| addInstanceVariables(ct); |
| addProxyMethods(ct, true, proxyClassDef, type); |
| addProxyDateMethods(ct, proxyClassDef, type); |
| proxySetters(ct, proxyClassDef, type); |
| addWriteReplaceMethod(ct, proxyClassDef, runtime); |
| |
| return cw.toByteArray(); |
| } |
| |
| /** |
| * Generate the bytecode for a calendar proxy for the given type. |
| */ |
| protected byte[] generateProxyCalendarBytecode(Class type, boolean runtime, String proxyClassName) { |
| assertNotFinal(type); |
| String proxyClassDef = proxyClassName.replace('.', '/'); |
| String superClassFileNname = Type.getInternalName(type); |
| String[] interfaceNames = new String[]{Type.getInternalName(ProxyCalendar.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, 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(); |
| } |
| |
| /** |
| * Generate the bytecode for a map proxy for the given type. |
| */ |
| protected byte[] generateProxyMapBytecode(Class type, boolean runtime, String proxyClassName) { |
| assertNotFinal(type); |
| String proxyClassDef = proxyClassName.replace('.', '/'); |
| String superClassFileNname = Type.getInternalName(type); |
| String[] interfaceNames = new String[]{Type.getInternalName(ProxyMap.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); |
| addProxyMapMethods(ct, proxyClassDef, type); |
| proxyRecognizedMethods(ct, proxyClassDef, type, ProxyMaps.class, ProxyMap.class); |
| proxySetters(ct, proxyClassDef, type); |
| addWriteReplaceMethod(ct, proxyClassDef, runtime); |
| |
| return cw.toByteArray(); |
| } |
| |
| /** |
| * Generate the bytecode for a bean proxy for the given type. |
| */ |
| protected byte[] generateProxyBeanBytecode(Class type, boolean runtime, String proxyClassName) { |
| 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; |
| } |
| |
| String proxyClassDef = proxyClassName.replace('.', '/'); |
| String superClassFileNname = Type.getInternalName(type); |
| String[] interfaceNames = new String[]{Type.getInternalName(ProxyBean.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, true, proxyClassDef, type); |
| addProxyBeanMethods(ct, proxyClassDef, type, cons); |
| if (!proxySetters(ct, proxyClassDef, type)) { |
| return null; |
| } |
| addWriteReplaceMethod(ct, proxyClassDef, runtime); |
| |
| return cw.toByteArray(); |
| } |
| |
| private void addProxyBeanMethods(ClassWriterTracker ct, String proxyClassDef, Class type, Constructor cons) { |
| // bean copy |
| { |
| 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); |
| |
| Class[] params = cons.getParameterTypes(); |
| if (params.length == 1) { |
| 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, AsmHelper.getParamTypes(params)), false); |
| int beanVarPos = params.length+2; // params+DUP |
| |
| if (params.length == 0) { |
| mv.visitVarInsn(Opcodes.ASTORE, beanVarPos); |
| copyBeanProperties(mv, type, beanVarPos); |
| mv.visitVarInsn(Opcodes.ALOAD, beanVarPos); |
| } |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| } |
| |
| // new instance factory |
| { |
| MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "newInstance", |
| Type.getMethodDescriptor(Type.getType(ProxyBean.class), Type.getType(Object.class)) |
| , null, null); |
| mv.visitCode(); |
| mv.visitTypeInsn(Opcodes.NEW, proxyClassDef); |
| mv.visitInsn(Opcodes.DUP); |
| |
| |
| Class[] params = cons.getParameterTypes(); |
| if (params.length == 1) { |
| mv.visitVarInsn(Opcodes.ALOAD, 1); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(params[0])); |
| } |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, proxyClassDef, "<init>", |
| Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.getParamTypes(params)), false); |
| int beanVarPos = params.length+2; // params+DUP |
| |
| if (params.length == 0) { |
| mv.visitVarInsn(Opcodes.ASTORE, beanVarPos); |
| copyBeanProperties(mv, type, beanVarPos); |
| mv.visitVarInsn(Opcodes.ALOAD, beanVarPos); |
| } |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| |
| } |
| } |
| |
| private void copyBeanProperties(MethodVisitor mv, Class type, int copyVarPos) { |
| 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()); |
| mv.visitVarInsn(Opcodes.ALOAD, copyVarPos); |
| mv.visitVarInsn(Opcodes.ALOAD, copyVarPos-1); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(type)); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), getter.getName(), |
| Type.getMethodDescriptor(getter), false); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), meth.getName(), |
| Type.getMethodDescriptor(meth), false); |
| } |
| } |
| |
| 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.getType(Collection.class)), false); |
| 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 addProxyMapMethods(ClassWriterTracker ct, String proxyClassDef, Class type) { |
| // change tracker |
| { |
| ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, |
| "changeTracker", Type.getDescriptor(MapChangeTracker.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(MapChangeTracker.class)); |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| } |
| |
| // 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(); |
| |
| 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(SortedMap.class)); |
| mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getInternalName(SortedMap.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(Map.class)); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "putAll", |
| Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Map.class)), false); |
| } |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| } |
| |
| // key type |
| { |
| ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, |
| "keyType", Type.getDescriptor(Class.class), null, null).visitEnd(); |
| |
| MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "getKeyType", |
| Type.getMethodDescriptor(Type.getType(Class.class)) |
| , null, null); |
| mv.visitCode(); |
| mv.visitVarInsn(Opcodes.ALOAD, 0); |
| mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassDef, "keyType", Type.getDescriptor(Class.class)); |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| } |
| |
| // value type |
| { |
| ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, |
| "valueType", Type.getDescriptor(Class.class), null, null).visitEnd(); |
| |
| MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "getValueType", |
| Type.getMethodDescriptor(Type.getType(Class.class)) |
| , null, null); |
| mv.visitCode(); |
| mv.visitVarInsn(Opcodes.ALOAD, 0); |
| mv.visitFieldInsn(Opcodes.GETFIELD, proxyClassDef, "valueType", 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(ProxyMap.class), |
| Type.getType(Class.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, 3); |
| } |
| |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, proxyClassDef, "<init>", |
| Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.getParamTypes(params)), false); |
| |
| mv.visitVarInsn(Opcodes.ASTORE, 6); |
| mv.visitVarInsn(Opcodes.ALOAD, 6); |
| mv.visitVarInsn(Opcodes.ALOAD, 1); |
| mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassDef, "keyType", Type.getDescriptor(Class.class)); |
| |
| mv.visitVarInsn(Opcodes.ALOAD, 6); |
| mv.visitVarInsn(Opcodes.ALOAD, 2); |
| mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassDef, "valueType", Type.getDescriptor(Class.class)); |
| |
| mv.visitVarInsn(Opcodes.ILOAD, 4); |
| Label lNotTrack = new Label(); |
| mv.visitJumpInsn(Opcodes.IFEQ, lNotTrack); |
| mv.visitVarInsn(Opcodes.ALOAD, 6); |
| mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(MapChangeTrackerImpl.class)); |
| |
| mv.visitInsn(Opcodes.DUP); |
| mv.visitVarInsn(Opcodes.ALOAD, 6); |
| |
| mv.visitVarInsn(Opcodes.ILOAD, 5); |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(MapChangeTrackerImpl.class), "<init>", |
| Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(Map.class), Type.BOOLEAN_TYPE), |
| false); |
| mv.visitFieldInsn(Opcodes.PUTFIELD, proxyClassDef, "changeTracker", Type.getDescriptor(MapChangeTracker.class)); |
| |
| mv.visitLabel(lNotTrack); |
| mv.visitVarInsn(Opcodes.ALOAD, 6); |
| |
| 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(ClassWriterTracker ct) { |
| // variable #1, the state manager |
| ct.getCw().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_TRANSIENT, |
| "sm", Type.getDescriptor(OpenJPAStateManager.class), null, null).visitEnd(); |
| |
| // variable #2, the state manager |
| ct.getCw().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(ClassWriterTracker ct, Class type, String superClassFileNname) { |
| Constructor[] constructors = type.getConstructors(); |
| |
| for (Constructor constructor : constructors) { |
| Class[] params = constructor.getParameterTypes(); |
| String[] exceptionTypes = AsmHelper.getInternalNames(constructor.getExceptionTypes()); |
| String descriptor = Type.getConstructorDescriptor(constructor); |
| MethodVisitor mv = ct.visitMethod(Opcodes.ACC_PUBLIC, "<init>", descriptor, null, exceptionTypes); |
| mv.visitCode(); |
| mv.visitVarInsn(Opcodes.ALOAD, 0); |
| int stackPos = 1; |
| for (Class param : params) { |
| mv.visitVarInsn(AsmHelper.getLoadInsn(param), stackPos); |
| stackPos += Type.getType(param).getSize(); |
| } |
| 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 defaultChangeTracker whether to implement a null change tracker; if false |
| * the change tracker method is left unimplemented |
| * @param proxyClassDef |
| */ |
| private void addProxyMethods(ClassWriterTracker ct, boolean defaultChangeTracker, String proxyClassDef, Class<?> parentClass) { |
| |
| { |
| MethodVisitor mv = ct.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 = ct.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 = ct.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 = ct.visitMethod(Modifier.PUBLIC, "clone", |
| Type.getMethodDescriptor(TYPE_OBJECT) |
| , null, null); |
| mv.visitCode(); |
| |
| mv.visitVarInsn(Opcodes.ALOAD, 0); |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(parentClass), "clone", |
| Type.getMethodDescriptor(TYPE_OBJECT), 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 (defaultChangeTracker) { |
| MethodVisitor mv = ct.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(ClassWriterTracker ct, 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 = ct.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 = 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] == long.class) { |
| // call getTime on the given Date if the current type has a long constructor |
| 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.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) { |
| 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 = ct.visitMethod(Modifier.PUBLIC, "newInstance", |
| Type.getMethodDescriptor(Type.getType(ProxyDate.class)) |
| , null, null); |
| mv.visitCode(); |
| 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(); |
| } |
| } |
| |
| 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 = 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); |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), "<init>", |
| Type.getMethodDescriptor(Type.VOID_TYPE, AsmHelper.getParamTypes(params)), false); |
| |
| // timeInMillis |
| mv.visitInsn(Opcodes.DUP); |
| mv.visitVarInsn(Opcodes.ALOAD, 1); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Calendar.class)); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Calendar.class), "getTimeInMillis", |
| Type.getMethodDescriptor(Type.LONG_TYPE), false); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "setTimeInMillis", |
| Type.getMethodDescriptor(Type.VOID_TYPE, Type.LONG_TYPE), false); |
| |
| // lenient |
| mv.visitInsn(Opcodes.DUP); |
| mv.visitVarInsn(Opcodes.ALOAD, 1); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Calendar.class)); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Calendar.class), "isLenient", |
| Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "setLenient", |
| Type.getMethodDescriptor(Type.VOID_TYPE, Type.BOOLEAN_TYPE), false); |
| |
| // firstDayOfWeek |
| mv.visitInsn(Opcodes.DUP); |
| mv.visitVarInsn(Opcodes.ALOAD, 1); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Calendar.class)); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Calendar.class), "getFirstDayOfWeek", |
| Type.getMethodDescriptor(Type.INT_TYPE), false); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "setFirstDayOfWeek", |
| Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false); |
| |
| // minimalDaysInFirstWeek |
| mv.visitInsn(Opcodes.DUP); |
| mv.visitVarInsn(Opcodes.ALOAD, 1); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Calendar.class)); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Calendar.class), "getMinimalDaysInFirstWeek", |
| Type.getMethodDescriptor(Type.INT_TYPE), false); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "setMinimalDaysInFirstWeek", |
| Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE), false); |
| |
| // timeZone |
| mv.visitInsn(Opcodes.DUP); |
| mv.visitVarInsn(Opcodes.ALOAD, 1); |
| mv.visitTypeInsn(Opcodes.CHECKCAST, Type.getInternalName(Calendar.class)); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(Calendar.class), "getTimeZone", |
| Type.getMethodDescriptor(Type.getType(TimeZone.class)), false); |
| mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, Type.getInternalName(type), "setTimeZone", |
| Type.getMethodDescriptor(Type.VOID_TYPE, Type.getType(TimeZone.class)), false); |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| } |
| |
| // newInstance factory |
| { |
| MethodVisitor mv = ct.visitMethod(Modifier.PUBLIC, "newInstance", |
| Type.getMethodDescriptor(Type.getType(ProxyCalendar.class)) |
| , null, null); |
| mv.visitCode(); |
| 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 the protected computeFields method b/c it is called on |
| // mutate, and some setters are final and therefore not proxyable |
| { |
| MethodVisitor mv = ct.visitMethod(Modifier.PROTECTED, "computeFields", |
| Type.getMethodDescriptor(Type.VOID_TYPE) |
| , 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); |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), "computeFields", |
| Type.getMethodDescriptor(Type.VOID_TYPE), false); |
| |
| mv.visitInsn(Opcodes.RETURN); |
| 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(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(ct, type, meth); |
| } |
| } |
| return setters > 0; |
| } |
| |
| private void proxySetter(ClassWriterTracker ct, Class type, Method meth) { |
| Class[] params = meth.getParameterTypes(); |
| Class ret = meth.getReturnType(); |
| |
| 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); |
| 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 |
| int stackPos = 1; |
| for (int i = 1; i <= params.length; i++) { |
| Class param = params[i-1]; |
| mv.visitVarInsn(AsmHelper.getLoadInsn(param), stackPos); |
| stackPos += Type.getType(param).getSize(); |
| } |
| |
| mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(type), meth.getName(), |
| methodDescriptor, false); |
| |
| mv.visitInsn(AsmHelper.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(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(); |
| 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_OBJECT, Type.getType(Proxy.class), Type.BOOLEAN_TYPE), false); |
| |
| mv.visitInsn(Opcodes.ARETURN); |
| mv.visitMaxs(-1, -1); |
| mv.visitEnd(); |
| } |
| |
| private boolean hasConstructor(Class type, Class<?>... paramTypes) { |
| try { |
| return type.getDeclaredConstructor(paramTypes) != null; |
| } |
| catch (NoSuchMethodException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * 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()); |
| } |
| |
| /** |
| * 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; |
| } |
| |
| /** |
| * 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()))); |
| } |
| |
| /** |
| * 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]* |
| * <class name>+<br /> |
| * Where the following options are recognized: |
| * <ul> |
| * <li><i>-utils/-u <number></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; |
| 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 |
| } |
| |
| |
| 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); |
| } |
| else if (Map.class.isAssignableFrom(cls)) { |
| bytes = mgr.generateProxyMapBytecode(cls, false, proxyClassName); |
| } |
| else { |
| bytes = mgr.generateProxyBeanBytecode(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); |
| } |
| } |
| } |
| } |