| /* |
| * 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 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 |
| */ |
| 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 = GeneratedClasses.loadBCClass( |
| generateProxyDateBytecode(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 org.apache.openjpa.util.DelayedArrayListProxy.class; |
| } |
| if (type.equals(java.util.HashSet.class)) { |
| return org.apache.openjpa.util.DelayedHashSetProxy.class; |
| } |
| if (type.equals(java.util.LinkedList.class)) { |
| return org.apache.openjpa.util.DelayedLinkedListProxy.class; |
| } |
| if (type.equals(java.util.Vector.class)) { |
| return org.apache.openjpa.util.DelayedVectorProxy.class; |
| } |
| if (type.equals(java.util.LinkedHashSet.class)) { |
| return org.apache.openjpa.util.DelayedLinkedHashSetProxy.class; |
| } |
| if (type.equals(java.util.SortedSet.class) || type.equals(java.util.TreeSet.class)) { |
| return org.apache.openjpa.util.DelayedTreeSetProxy.class; |
| } |
| if (type.equals(java.util.PriorityQueue.class)) { |
| return org.apache.openjpa.util.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; |
| } |
| |
| /** |
| * Generate the bytecode for a date proxy for the given type. |
| */ |
| protected BCClass generateProxyDateBytecode(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(ProxyDate.class); |
| |
| delegateConstructors(bc, type); |
| addProxyMethods(bc, true); |
| addProxyDateMethods(bc, type); |
| proxySetters(bc, type); |
| addWriteReplaceMethod(bc, runtime); |
| return bc; |
| } |
| |
| /** |
| * 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 ProxyDate} interface. |
| */ |
| private void addProxyDateMethods(BCClass bc, Class type) { |
| boolean hasDefaultCons = bc.getDeclaredMethod("<init>", |
| (Class[]) null) != null; |
| boolean hasMillisCons = bc.getDeclaredMethod("<init>", |
| new Class[] { long.class }) != null; |
| if (!hasDefaultCons && !hasMillisCons) |
| throw new UnsupportedException(_loc.get("no-date-cons", type)); |
| |
| // add a default constructor that delegates to the millis constructor |
| BCMethod m; |
| Code code; |
| if (!hasDefaultCons) { |
| m = bc.declareMethod("<init>", void.class, null); |
| m.makePublic(); |
| code = m.getCode(true); |
| code.aload().setThis(); |
| code.invokestatic().setMethod(System.class, "currentTimeMillis", |
| long.class, null); |
| code.invokespecial().setMethod(type, "<init>", void.class, |
| new Class[] { long.class }); |
| code.vreturn(); |
| code.calculateMaxStack(); |
| code.calculateMaxLocals(); |
| } |
| |
| // 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]; |
| |
| 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) { |
| if (params[0] == long.class) { |
| code.aload().setParam(0); |
| code.checkcast().setType(Date.class); |
| code.invokevirtual().setMethod(Date.class, "getTime", |
| long.class, null); |
| } else { |
| 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(Date.class); |
| code.invokevirtual().setMethod(Date.class, "getTime", long.class, |
| null); |
| code.invokevirtual().setMethod(type, "setTime", void.class, |
| new Class[] { long.class }); |
| } |
| if ((params.length == 0 || params[0] == long.class) |
| && Timestamp.class.isAssignableFrom(type)) { |
| code.dup(); |
| code.aload().setParam(0); |
| code.checkcast().setType(Timestamp.class); |
| code.invokevirtual().setMethod(Timestamp.class, "getNanos", |
| int.class, null); |
| code.invokevirtual().setMethod(type, "setNanos", void.class, |
| new Class[] { int.class }); |
| } |
| code.areturn(); |
| code.calculateMaxStack(); |
| code.calculateMaxLocals(); |
| |
| // new instance factory |
| m = bc.declareMethod("newInstance", ProxyDate.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(); |
| } |
| |
| /** |
| * 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]* |
| * <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; |
| 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 |
| } |
| |
| if (Collection.class.isAssignableFrom(cls)) |
| bc = mgr.generateProxyCollectionBytecode(cls, false); |
| else if (Map.class.isAssignableFrom(cls)) |
| bc = mgr.generateProxyMapBytecode(cls, false); |
| else if (Date.class.isAssignableFrom(cls)) |
| bc = mgr.generateProxyDateBytecode(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")); |
| } |
| } |
| } |