1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.openjpa.util;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.ObjectStreamException;
24 import java.lang.reflect.Array;
25 import java.lang.reflect.Constructor;
26 import java.lang.reflect.Method;
27 import java.lang.reflect.Modifier;
28 import java.security.AccessController;
29 import java.security.PrivilegedAction;
30 import java.security.PrivilegedActionException;
31 import java.sql.Timestamp;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Calendar;
35 import java.util.Collection;
36 import java.util.Comparator;
37 import java.util.Date;
38 import java.util.GregorianCalendar;
39 import java.util.HashMap;
40 import java.util.HashSet;
41 import java.util.LinkedList;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.SortedMap;
46 import java.util.SortedSet;
47 import java.util.TimeZone;
48 import java.util.TreeMap;
49 import java.util.TreeSet;
50 import java.util.Queue;
51
52 import org.apache.commons.lang.StringUtils;
53 import org.apache.openjpa.kernel.OpenJPAStateManager;
54 import org.apache.openjpa.lib.util.Files;
55 import org.apache.openjpa.lib.util.J2DoPrivHelper;
56 import org.apache.openjpa.lib.util.JavaVersions;
57 import org.apache.openjpa.lib.util.Localizer;
58 import org.apache.openjpa.lib.util.Options;
59 import org.apache.openjpa.lib.util.concurrent.NullSafeConcurrentHashMap;
60
61 import java.util.concurrent.ConcurrentHashMap;
62 import serp.bytecode.BCClass;
63 import serp.bytecode.BCField;
64 import serp.bytecode.BCMethod;
65 import serp.bytecode.Code;
66 import serp.bytecode.JumpInstruction;
67 import serp.bytecode.Project;
68 import serp.util.Strings;
69
70 /**
71 * Default implementation of the {@link ProxyManager} interface.
72 *
73 * @author Abe White
74 */
75 public class ProxyManagerImpl
76 implements ProxyManager {
77
78 private static final String PROXY_SUFFIX = "$proxy";
79
80 private static final Localizer _loc = Localizer.forPackage
81 (ProxyManagerImpl.class);
82
83 private static long _proxyId = 0L;
84 private static final Map _stdCollections = new HashMap();
85 private static final Map _stdMaps = new HashMap();
86 static {
87 _stdCollections.put(Collection.class, ArrayList.class);
88 _stdCollections.put(Set.class, HashSet.class);
89 _stdCollections.put(SortedSet.class, TreeSet.class);
90 _stdCollections.put(List.class, ArrayList.class);
91 _stdCollections.put(Queue.class, LinkedList.class);
92 _stdMaps.put(Map.class, HashMap.class);
93 _stdMaps.put(SortedMap.class, TreeMap.class);
94 }
95
96 private final Set _unproxyable = new HashSet();
97 private final Map _proxies = new NullSafeConcurrentHashMap();
98 private boolean _trackChanges = true;
99 private boolean _assertType = false;
100
101 public ProxyManagerImpl() {
102 _unproxyable.add(TimeZone.class.getName());
103 }
104
105 /**
106 * Whether proxies produced by this factory will use {@link ChangeTracker}s
107 * to try to cut down on data store operations at the cost of some extra
108 * bookkeeping overhead. Defaults to true.
109 */
110 public boolean getTrackChanges() {
111 return _trackChanges;
112 }
113
114 /**
115 * Whether proxies produced by this factory will use {@link ChangeTracker}s
116 * to try to cut down on data store operations at the cost of some extra
117 * bookkeeping overhead. Defaults to true.
118 */
119 public void setTrackChanges(boolean track) {
120 _trackChanges = track;
121 }
122
123 /**
124 * Whether to perform runtime checks to ensure that all elements
125 * added to collection and map proxies are the proper element/key/value
126 * type as defined by the metadata. Defaults to false.
127 */
128 public boolean getAssertAllowedType() {
129 return _assertType;
130 }
131
132 /**
133 * Whether to perform runtime checks to ensure that all elements
134 * added to collection and map proxies are the proper element/key/value
135 * type as defined by the metadata. Defaults to false.
136 */
137 public void setAssertAllowedType(boolean assertType) {
138 _assertType = assertType;
139 }
140
141 /**
142 * Return a mutable view of class names we know cannot be proxied
143 * correctly by this manager.
144 */
145 public Collection getUnproxyable() {
146 return _unproxyable;
147 }
148
149 /**
150 * Provided for auto-configuration. Add the given semicolon-separated
151 * class names to the set of class names we know cannot be proxied correctly
152 * by this manager.
153 */
154 public void setUnproxyable(String clsNames) {
155 if (clsNames != null)
156 _unproxyable.addAll(Arrays.asList(Strings.split(clsNames, ";", 0)));
157 }
158
159 public Object copyArray(Object orig) {
160 if (orig == null)
161 return null;
162
163 try {
164 int length = Array.getLength(orig);
165 Object array = Array.newInstance(orig.getClass().
166 getComponentType(), length);
167
168 System.arraycopy(orig, 0, array, 0, length);
169 return array;
170 } catch (Exception e) {
171 throw new UnsupportedException(_loc.get("bad-array",
172 e.getMessage()), e);
173 }
174 }
175
176 public Collection copyCollection(Collection orig) {
177 if (orig == null)
178 return null;
179 if (orig instanceof Proxy)
180 return (Collection) ((Proxy) orig).copy(orig);
181
182 ProxyCollection proxy = getFactoryProxyCollection(orig.getClass());
183 return (Collection) proxy.copy(orig);
184 }
185
186 public Proxy newCollectionProxy(Class type, Class elementType,
187 Comparator compare) {
188 type = toProxyableCollectionType(type);
189 ProxyCollection proxy = getFactoryProxyCollection(type);
190 return proxy.newInstance((_assertType) ? elementType : null, compare,
191 _trackChanges);
192 }
193
194 public Map copyMap(Map orig) {
195 if (orig == null)
196 return null;
197 if (orig instanceof Proxy)
198 return (Map) ((Proxy) orig).copy(orig);
199
200 ProxyMap proxy = getFactoryProxyMap(orig.getClass());
201 return (Map) proxy.copy(orig);
202 }
203
204 public Proxy newMapProxy(Class type, Class keyType,
205 Class elementType, Comparator compare) {
206 type = toProxyableMapType(type);
207 ProxyMap proxy = getFactoryProxyMap(type);
208 return proxy.newInstance((_assertType) ? keyType : null,
209 (_assertType) ? elementType : null, compare, _trackChanges);
210 }
211
212 public Date copyDate(Date orig) {
213 if (orig == null)
214 return null;
215 if (orig instanceof Proxy)
216 return (Date) ((Proxy) orig).copy(orig);
217
218 ProxyDate proxy = getFactoryProxyDate(orig.getClass());
219 return (Date) proxy.copy(orig);
220 }
221
222 public Proxy newDateProxy(Class type) {
223 ProxyDate proxy = getFactoryProxyDate(type);
224 return proxy.newInstance();
225 }
226
227 public Calendar copyCalendar(Calendar orig) {
228 if (orig == null)
229 return null;
230 if (orig instanceof Proxy)
231 return (Calendar) ((Proxy) orig).copy(orig);
232
233 ProxyCalendar proxy = getFactoryProxyCalendar(orig.getClass());
234 return (Calendar) proxy.copy(orig);
235 }
236
237 public Proxy newCalendarProxy(Class type, TimeZone zone) {
238 if (type == Calendar.class)
239 type = GregorianCalendar.class;
240 ProxyCalendar proxy = getFactoryProxyCalendar(type);
241 ProxyCalendar cal = proxy.newInstance();
242 if (zone != null)
243 ((Calendar) cal).setTimeZone(zone);
244 return cal;
245 }
246
247 public Object copyCustom(Object orig) {
248 if (orig == null)
249 return null;
250 if (orig instanceof Proxy)
251 return ((Proxy) orig).copy(orig);
252 if (ImplHelper.isManageable(orig))
253 return null;
254 if (orig instanceof Collection)
255 return copyCollection((Collection) orig);
256 if (orig instanceof Map)
257 return copyMap((Map) orig);
258 if (orig instanceof Date)
259 return copyDate((Date) orig);
260 if (orig instanceof Calendar)
261 return copyCalendar((Calendar) orig);
262 ProxyBean proxy = getFactoryProxyBean(orig);
263 return (proxy == null) ? null : proxy.copy(orig);
264 }
265
266 public Proxy newCustomProxy(Object orig) {
267 if (orig == null)
268 return null;
269 if (orig instanceof Proxy)
270 return (Proxy) orig;
271 if (ImplHelper.isManageable(orig))
272 return null;
273 if (orig instanceof Collection) {
274 Comparator comp = (orig instanceof SortedSet)
275 ? ((SortedSet) orig).comparator() : null;
276 Collection c = (Collection) newCollectionProxy(orig.getClass(),
277 null, comp);
278 c.addAll((Collection) orig);
279 return (Proxy) c;
280 }
281 if (orig instanceof Map) {
282 Comparator comp = (orig instanceof SortedMap)
283 ? ((SortedMap) orig).comparator() : null;
284 Map m = (Map) newMapProxy(orig.getClass(), null, null, comp);
285 m.putAll((Map) orig);
286 return (Proxy) m;
287 }
288 if (orig instanceof Date) {
289 Date d = (Date) newDateProxy(orig.getClass());
290 d.setTime(((Date) orig).getTime());
291 if (orig instanceof Timestamp)
292 ((Timestamp) d).setNanos(((Timestamp) orig).getNanos());
293 return (Proxy) d;
294 }
295 if (orig instanceof Calendar) {
296 Calendar c = (Calendar) newCalendarProxy(orig.getClass(),
297 ((Calendar) orig).getTimeZone());
298 c.setTimeInMillis(((Calendar) orig).getTimeInMillis());
299 return (Proxy) c;
300 }
301
302 ProxyBean proxy = getFactoryProxyBean(orig);
303 return (proxy == null) ? null : proxy.newInstance(orig);
304 }
305
306 /**
307 * Return the concrete type for proxying.
308 */
309 protected Class toProxyableCollectionType(Class type) {
310 if (type.getName().endsWith(PROXY_SUFFIX))
311 type = type.getSuperclass();
312 else if (type.isInterface()) {
313 type = toConcreteType(type, _stdCollections);
314 if (type == null)
315 throw new UnsupportedException(_loc.get("no-proxy-intf", type));
316 } else if (Modifier.isAbstract(type.getModifiers()))
317 throw new UnsupportedException(_loc.get("no-proxy-abstract", type));
318 return type;
319 }
320
321 /**
322 * Return the concrete type for proxying.
323 */
324 protected Class toProxyableMapType(Class type) {
325 if (type.getName().endsWith(PROXY_SUFFIX))
326 type = type.getSuperclass();
327 else if (type.isInterface()) {
328 type = toConcreteType(type, _stdMaps);
329 if (type == null)
330 throw new UnsupportedException(_loc.get("no-proxy-intf", type));
331 } else if (Modifier.isAbstract(type.getModifiers()))
332 throw new UnsupportedException(_loc.get("no-proxy-abstract", type));
333 return type;
334 }
335
336 /**
337 * Locate a concrete type to proxy for the given collection interface.
338 */
339 private static Class toConcreteType(Class intf, Map concretes) {
340 Class concrete = (Class) concretes.get(intf);
341 if (concrete != null)
342 return concrete;
343 Class[] intfs = intf.getInterfaces();
344 for (int i = 0; i < intfs.length; i++) {
345 concrete = toConcreteType(intfs[i], concretes);
346 if (concrete != null)
347 return concrete;
348 }
349 return null;
350 }
351
352 /**
353 * Return the cached factory proxy for the given collection type.
354 */
355 private ProxyCollection getFactoryProxyCollection(Class type) {
356 // we don't lock here; ok if two proxies get generated for same type
357 ProxyCollection proxy = (ProxyCollection) _proxies.get(type);
358 if (proxy == null) {
359 ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
360 ProxyCollection.class);
361 Class pcls = loadBuildTimeProxy(type, l);
362 if (pcls == null)
363 pcls = GeneratedClasses.loadBCClass(
364 generateProxyCollectionBytecode(type, true), l);
365 proxy = (ProxyCollection) instantiateProxy(pcls, null, null);
366 _proxies.put(type, proxy);
367 }
368 return proxy;
369 }
370
371 /**
372 * Return the cached factory proxy for the given map type.
373 */
374 private ProxyMap getFactoryProxyMap(Class type) {
375 // we don't lock here; ok if two proxies get generated for same type
376 ProxyMap proxy = (ProxyMap) _proxies.get(type);
377 if (proxy == null) {
378 ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
379 ProxyMap.class);
380 Class pcls = loadBuildTimeProxy(type, l);
381 if (pcls == null)
382 pcls = GeneratedClasses.loadBCClass(
383 generateProxyMapBytecode(type, true), l);
384 proxy = (ProxyMap) instantiateProxy(pcls, null, null);
385 _proxies.put(type, proxy);
386 }
387 return proxy;
388 }
389
390 /**
391 * Return the cached factory proxy for the given date type.
392 */
393 private ProxyDate getFactoryProxyDate(Class type) {
394 // we don't lock here; ok if two proxies get generated for same type
395 ProxyDate proxy = (ProxyDate) _proxies.get(type);
396 if (proxy == null) {
397 ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
398 ProxyDate.class);
399 Class pcls = loadBuildTimeProxy(type, l);
400 if (pcls == null)
401 pcls = GeneratedClasses.loadBCClass(
402 generateProxyDateBytecode(type, true), l);
403 proxy = (ProxyDate) instantiateProxy(pcls, null, null);
404 _proxies.put(type, proxy);
405 }
406 return proxy;
407 }
408
409 /**
410 * Return the cached factory proxy for the given calendar type.
411 */
412 private ProxyCalendar getFactoryProxyCalendar(Class type) {
413 // we don't lock here; ok if two proxies get generated for same type
414 ProxyCalendar proxy = (ProxyCalendar) _proxies.get(type);
415 if (proxy == null) {
416 ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
417 ProxyCalendar.class);
418 Class pcls = loadBuildTimeProxy(type, l);
419 if (pcls == null)
420 pcls = GeneratedClasses.loadBCClass(
421 generateProxyCalendarBytecode(type, true), l);
422 proxy = (ProxyCalendar) instantiateProxy(pcls, null, null);
423 _proxies.put(type, proxy);
424 }
425 return proxy;
426 }
427
428 /**
429 * Return the cached factory proxy for the given bean type.
430 */
431 private ProxyBean getFactoryProxyBean(Object orig) {
432 final Class type = orig.getClass();
433 if (isUnproxyable(type))
434 return null;
435
436 // we don't lock here; ok if two proxies get generated for same type
437 ProxyBean proxy = (ProxyBean) _proxies.get(type);
438 if (proxy == null && !_proxies.containsKey(type)) {
439 ClassLoader l = GeneratedClasses.getMostDerivedLoader(type,
440 ProxyBean.class);
441 Class pcls = loadBuildTimeProxy(type, l);
442 if (pcls == null) {
443 BCClass bc = (BCClass) AccessController
444 .doPrivileged(new PrivilegedAction() {
445 public Object run() {
446 return generateProxyBeanBytecode(type, true);
447 }
448 });
449 if (bc != null)
450 pcls = GeneratedClasses.loadBCClass(bc, l);
451 }
452 if (pcls != null)
453 proxy = (ProxyBean) instantiateProxy(pcls,
454 findCopyConstructor(type), new Object[] {orig});
455 _proxies.put(type, proxy);
456 }
457 return proxy;
458 }
459
460 /**
461 * Return whether the given type is known to be unproxyable.
462 */
463 protected boolean isUnproxyable(Class type) {
464 for (; type != null && type != Object.class;
465 type = type.getSuperclass()) {
466 if (_unproxyable.contains(type.getName()))
467 return true;
468 }
469 return false;
470 }
471
472 /**
473 * Load the proxy class generated at build time for the given type,
474 * returning null if none exists.
475 */
476 protected Class loadBuildTimeProxy(Class type, ClassLoader loader) {
477 try {
478 return Class.forName(getProxyClassName(type, false), true, loader);
479 } catch (Throwable t) {
480 return null;
481 }
482 }
483
484 /**
485 * Instantiate the given proxy class.
486 */
487 private Proxy instantiateProxy(Class cls, Constructor cons, Object[] args) {
488 try {
489 if (cons != null)
490 return (Proxy) cls.getConstructor(cons.getParameterTypes()).
491 newInstance(args);
492 return (Proxy) AccessController.doPrivileged(
493 J2DoPrivHelper.newInstanceAction(cls));
494 } catch (InstantiationException ie) {
495 throw new UnsupportedException(_loc.get("cant-newinstance",
496 cls.getSuperclass().getName()));
497 } catch (PrivilegedActionException pae) {
498 Exception e = pae.getException();
499 if (e instanceof InstantiationException)
500 throw new UnsupportedException(_loc.get("cant-newinstance",
501 cls.getSuperclass().getName()));
502 else
503 throw new GeneralException(cls.getName()).setCause(e);
504 } catch (Throwable t) {
505 throw new GeneralException(cls.getName()).setCause(t);
506 }
507 }
508
509 /**
510 * Generate the bytecode for a collection proxy for the given type.
511 */
512 protected BCClass generateProxyCollectionBytecode(Class type,
513 boolean runtime) {
514 assertNotFinal(type);
515 Project project = new Project();
516 BCClass bc = (BCClass) AccessController.doPrivileged(J2DoPrivHelper
517 .loadProjectClassAction(project, getProxyClassName(type, runtime)));
518 bc.setSuperclass(type);
519 bc.declareInterface(ProxyCollection.class);
520
521 delegateConstructors(bc, type);
522 addProxyMethods(bc, false);
523 addProxyCollectionMethods(bc, type);
524 proxyRecognizedMethods(bc, type, ProxyCollections.class,
525 ProxyCollection.class);
526 proxySetters(bc, type);
527 addWriteReplaceMethod(bc, runtime);
528 return bc;
529 }
530
531 /**
532 * Return the name of the proxy class to generate for the given type.
533 */
534 private static String getProxyClassName(Class type, boolean runtime) {
535 String id = (runtime) ? "$" + nextProxyId() : "";
536 return Strings.getPackageName(ProxyManagerImpl.class) + "."
537 + type.getName().replace('.', '$') + id + PROXY_SUFFIX;
538 }
539
540 /**
541 * Throw appropriate exception if the given type is final.
542 */
543 private static void assertNotFinal(Class type) {
544 if (Modifier.isFinal(type.getModifiers()))
545 throw new UnsupportedException(_loc.get("no-proxy-final", type));
546 }
547
548 /**
549 * Generate the bytecode for a map proxy for the given type.
550 */
551 protected BCClass generateProxyMapBytecode(Class type, boolean runtime) {
552 assertNotFinal(type);
553 Project project = new Project();
554 BCClass bc = (BCClass) AccessController.doPrivileged(J2DoPrivHelper
555 .loadProjectClassAction(project, getProxyClassName(type, runtime)));
556 bc.setSuperclass(type);
557 bc.declareInterface(ProxyMap.class);
558
559 delegateConstructors(bc, type);
560 addProxyMethods(bc, false);
561 addProxyMapMethods(bc, type);
562 proxyRecognizedMethods(bc, type, ProxyMaps.class, ProxyMap.class);
563 proxySetters(bc, type);
564 addWriteReplaceMethod(bc, runtime);
565 return bc;
566 }
567
568 /**
569 * Generate the bytecode for a date proxy for the given type.
570 */
571 protected BCClass generateProxyDateBytecode(Class type, boolean runtime) {
572 assertNotFinal(type);
573 Project project = new Project();
574 BCClass bc = (BCClass) AccessController.doPrivileged(J2DoPrivHelper
575 .loadProjectClassAction(project, getProxyClassName(type, runtime)));
576 bc.setSuperclass(type);
577 bc.declareInterface(ProxyDate.class);
578
579 delegateConstructors(bc, type);
580 addProxyMethods(bc, true);
581 addProxyDateMethods(bc, type);
582 proxySetters(bc, type);
583 addWriteReplaceMethod(bc, runtime);
584 return bc;
585 }
586
587 /**
588 * Generate the bytecode for a calendar proxy for the given type.
589 */
590 protected BCClass generateProxyCalendarBytecode(Class type,
591 boolean runtime) {
592 assertNotFinal(type);
593 Project project = new Project();
594 BCClass bc = (BCClass) AccessController.doPrivileged(J2DoPrivHelper
595 .loadProjectClassAction(project, getProxyClassName(type, runtime)));
596 bc.setSuperclass(type);
597 bc.declareInterface(ProxyCalendar.class);
598
599 delegateConstructors(bc, type);
600 addProxyMethods(bc, true);
601 addProxyCalendarMethods(bc, type);
602 proxySetters(bc, type);
603 addWriteReplaceMethod(bc, runtime);
604 return bc;
605 }
606
607 /**
608 * Generate the bytecode for a bean proxy for the given type.
609 */
610 protected BCClass generateProxyBeanBytecode(Class type, boolean runtime) {
611 if (Modifier.isFinal(type.getModifiers()))
612 return null;
613 if (ImplHelper.isManagedType(null, type))
614 return null;
615
616 // we can only generate a valid proxy if there is a copy constructor
617 // or a default constructor
618 Constructor cons = findCopyConstructor(type);
619 if (cons == null) {
620 Constructor[] cs = type.getConstructors();
621 for (int i = 0; cons == null && i < cs.length; i++)
622 if (cs[i].getParameterTypes().length == 0)
623 cons = cs[i];
624 if (cons == null)
625 return null;
626 }
627
628 Project project = new Project();
629 BCClass bc = (BCClass) AccessController.doPrivileged(J2DoPrivHelper
630 .loadProjectClassAction(project, getProxyClassName(type, runtime)));
631 bc.setSuperclass(type);
632 bc.declareInterface(ProxyBean.class);
633
634 delegateConstructors(bc, type);
635 addProxyMethods(bc, true);
636 addProxyBeanMethods(bc, type, cons);
637 if (!proxySetters(bc, type))
638 return null;
639 addWriteReplaceMethod(bc, runtime);
640 return bc;
641 }
642
643 /**
644 * Create pass-through constructors to base type.
645 */
646 private void delegateConstructors(BCClass bc, Class type) {
647 Constructor[] cons = type.getConstructors();
648 Class[] params;
649 BCMethod m;
650 Code code;
651 for (int i = 0; i < cons.length; i++) {
652 params = cons[i].getParameterTypes();
653 m = bc.declareMethod("<init>", void.class, params);
654 m.makePublic();
655
656 code = m.getCode(true);
657 code.aload().setThis();
658 for (int j = 0; j < params.length; j++)
659 code.xload().setParam(j).setType(params[j]);
660 code.invokespecial().setMethod(cons[i]);
661 code.vreturn();
662 code.calculateMaxStack();
663 code.calculateMaxLocals();
664 }
665 }
666
667 /**
668 * Implement the methods in the {@link Proxy} interface, with the exception
669 * of {@link Proxy#copy}.
670 *
671 * @param changeTracker whether to implement a null change tracker; if false
672 * the change tracker method is left unimplemented
673 */
674 private void addProxyMethods(BCClass bc, boolean changeTracker) {
675 BCField sm = bc.declareField("sm", OpenJPAStateManager.class);
676 sm.setTransient(true);
677 BCField field = bc.declareField("field", int.class);
678 field.setTransient(true);
679
680 BCMethod m = bc.declareMethod("setOwner", void.class, new Class[] {
681 OpenJPAStateManager.class, int.class });
682 m.makePublic();
683 Code code = m.getCode(true);
684 code.aload().setThis();
685 code.aload().setParam(0);
686 code.putfield().setField(sm);
687 code.aload().setThis();
688 code.iload().setParam(1);
689 code.putfield().setField(field);
690 code.vreturn();
691 code.calculateMaxStack();
692 code.calculateMaxLocals();
693
694 m = bc.declareMethod("getOwner", OpenJPAStateManager.class, null);
695 m.makePublic();
696 code = m.getCode(true);
697 code.aload().setThis();
698 code.getfield().setField(sm);
699 code.areturn();
700 code.calculateMaxStack();
701 code.calculateMaxLocals();
702
703 m = bc.declareMethod("getOwnerField", int.class, null);
704 m.makePublic();
705 code = m.getCode(true);
706 code.aload().setThis();
707 code.getfield().setField(field);
708 code.ireturn();
709 code.calculateMaxStack();
710 code.calculateMaxLocals();
711
712 /*
713 * clone (return detached proxy object)
714 * Note: This method is only being provided to satisfy a quirk with
715 * the IBM JDK -- while comparing Calendar objects, the clone() method
716 * was invoked. So, we are now overriding the clone() method so as to
717 * provide a detached proxy object (null out the StateManager).
718 */
719 m = bc.declareMethod("clone", Object.class, null);
720 m.makePublic();
721 code = m.getCode(true);
722 code.aload().setThis();
723 code.invokespecial().setMethod(bc.getSuperclassType(), "clone",
724 Object.class, null);
725 code.checkcast().setType(Proxy.class);
726 int other = code.getNextLocalsIndex();
727 code.astore().setLocal(other);
728 code.aload().setLocal(other);
729 code.constant().setNull();
730 code.constant().setValue(0);
731 code.invokeinterface().setMethod(Proxy.class, "setOwner", void.class,
732 new Class[] { OpenJPAStateManager.class, int.class });
733 code.aload().setLocal(other);
734 code.areturn();
735 code.calculateMaxStack();
736 code.calculateMaxLocals();
737
738 if (changeTracker) {
739 m = bc.declareMethod("getChangeTracker", ChangeTracker.class, null);
740 m.makePublic();
741 code = m.getCode(true);
742 code.constant().setNull();
743 code.areturn();
744 code.calculateMaxStack();
745 code.calculateMaxLocals();
746 }
747 }
748
749 /**
750 * Implement the methods in the {@link ProxyCollection} interface.
751 */
752 private void addProxyCollectionMethods(BCClass bc, Class type) {
753 // change tracker
754 BCField changeTracker = bc.declareField("changeTracker",
755 CollectionChangeTracker.class);
756 changeTracker.setTransient(true);
757 BCMethod m = bc.declareMethod("getChangeTracker", ChangeTracker.class,
758 null);
759 m.makePublic();
760 Code code = m.getCode(true);
761 code.aload().setThis();
762 code.getfield().setField(changeTracker);
763 code.areturn();
764 code.calculateMaxStack();
765 code.calculateMaxLocals();
766
767 // collection copy
768 Constructor cons = findCopyConstructor(type);
769 if (cons == null && SortedSet.class.isAssignableFrom(type))
770 cons = findComparatorConstructor(type);
771 Class[] params = (cons == null) ? new Class[0]
772 : cons.getParameterTypes();
773
774 m = bc.declareMethod("copy", Object.class, new Class[] {Object.class});
775 m.makePublic();
776 code = m.getCode(true);
777
778 code.anew().setType(type);
779 code.dup();
780 if (params.length == 1) {
781 code.aload().setParam(0);
782 if (params[0] == Comparator.class) {
783 code.checkcast().setType(SortedSet.class);
784 code.invokeinterface().setMethod(SortedSet.class, "comparator",
785 Comparator.class, null);
786 } else
787 code.checkcast().setType(params[0]);
788 }
789 code.invokespecial().setMethod(type, "<init>", void.class, params);
790 if (params.length == 0 || params[0] == Comparator.class) {
791 code.dup();
792 code.aload().setParam(0);
793 code.checkcast().setType(Collection.class);
794 code.invokevirtual().setMethod(type, "addAll", boolean.class,
795 new Class[] { Collection.class });
796 code.pop();
797 }
798 code.areturn();
799 code.calculateMaxStack();
800 code.calculateMaxLocals();
801
802 // element type
803 BCField elementType = bc.declareField("elementType", Class.class);
804 elementType.setTransient(true);
805 m = bc.declareMethod("getElementType", Class.class, null);
806 m.makePublic();
807 code = m.getCode(true);
808 code.aload().setThis();
809 code.getfield().setField(elementType);
810 code.areturn();
811 code.calculateMaxStack();
812 code.calculateMaxLocals();
813
814 // new instance factory
815 m = bc.declareMethod("newInstance", ProxyCollection.class,
816 new Class[] { Class.class, Comparator.class, boolean.class });
817 m.makePublic();
818 code = m.getCode(true);
819
820 code.anew().setType(bc);
821 code.dup();
822 cons = findComparatorConstructor(type);
823 params = (cons == null) ? new Class[0] : cons.getParameterTypes();
824 if (params.length == 1)
825 code.aload().setParam(1);
826 code.invokespecial().setMethod("<init>", void.class, params);
827 int ret = code.getNextLocalsIndex();
828 code.astore().setLocal(ret);
829
830 // set element type
831 code.aload().setLocal(ret);
832 code.aload().setParam(0);
833 code.putfield().setField(elementType);
834
835 // create change tracker and set it
836 code.iload().setParam(2);
837 JumpInstruction ifins = code.ifeq();
838 code.aload().setLocal(ret);
839 code.anew().setType(CollectionChangeTrackerImpl.class);
840 code.dup();
841 code.aload().setLocal(ret);
842 code.constant().setValue(allowsDuplicates(type));
843 code.constant().setValue(isOrdered(type));
844 code.invokespecial().setMethod(CollectionChangeTrackerImpl.class,
845 "<init>", void.class, new Class[] { Collection.class,
846 boolean.class, boolean.class });
847 code.putfield().setField(changeTracker);
848
849 ifins.setTarget(code.aload().setLocal(ret));
850 code.areturn();
851 code.calculateMaxStack();
852 code.calculateMaxLocals();
853 }
854
855 /**
856 * Return whether the given collection type allows duplicates.
857 */
858 protected boolean allowsDuplicates(Class type) {
859 return !Set.class.isAssignableFrom(type);
860 }
861
862 /**
863 * Return whether the given collection type maintains an artificial
864 * ordering.
865 */
866 protected boolean isOrdered(Class type) {
867 return List.class.isAssignableFrom(type)
868 || "java.util.LinkedHashSet".equals(type.getName());
869 }
870
871 /**
872 * Implement the methods in the {@link ProxyMap} interface.
873 */
874 private void addProxyMapMethods(BCClass bc, Class type) {
875 // change tracker
876 BCField changeTracker = bc.declareField("changeTracker",
877 MapChangeTracker.class);
878 changeTracker.setTransient(true);
879 BCMethod m = bc.declareMethod("getChangeTracker", ChangeTracker.class,
880 null);
881 m.makePublic();
882 Code code = m.getCode(true);
883 code.aload().setThis();
884 code.getfield().setField(changeTracker);
885 code.areturn();
886 code.calculateMaxStack();
887 code.calculateMaxLocals();
888
889 // map copy
890 Constructor cons = findCopyConstructor(type);
891 if (cons == null && SortedMap.class.isAssignableFrom(type))
892 cons = findComparatorConstructor(type);
893 Class[] params = (cons == null) ? new Class[0]
894 : cons.getParameterTypes();
895
896 m = bc.declareMethod("copy", Object.class, new Class[] {Object.class});
897 m.makePublic();
898 code = m.getCode(true);
899
900 code.anew().setType(type);
901 code.dup();
902 if (params.length == 1) {
903 code.aload().setParam(0);
904 if (params[0] == Comparator.class) {
905 code.checkcast().setType(SortedMap.class);
906 code.invokeinterface().setMethod(SortedMap.class, "comparator",
907 Comparator.class, null);
908 } else
909 code.checkcast().setType(params[0]);
910 }
911 code.invokespecial().setMethod(type, "<init>", void.class, params);
912 if (params.length == 0 || params[0] == Comparator.class) {
913 code.dup();
914 code.aload().setParam(0);
915 code.checkcast().setType(Map.class);
916 code.invokevirtual().setMethod(type, "putAll", void.class,
917 new Class[] { Map.class });
918 }
919 code.areturn();
920 code.calculateMaxStack();
921 code.calculateMaxLocals();
922
923 // key type
924 BCField keyType = bc.declareField("keyType", Class.class);
925 keyType.setTransient(true);
926 m = bc.declareMethod("getKeyType", Class.class, null);
927 m.makePublic();
928 code = m.getCode(true);
929 code.aload().setThis();
930 code.getfield().setField(keyType);
931 code.areturn();
932 code.calculateMaxStack();
933 code.calculateMaxLocals();
934
935 // value type
936 BCField valueType = bc.declareField("valueType", Class.class);
937 valueType.setTransient(true);
938 m = bc.declareMethod("getValueType", Class.class, null);
939 m.makePublic();
940 code = m.getCode(true);
941 code.aload().setThis();
942 code.getfield().setField(valueType);
943 code.areturn();
944 code.calculateMaxStack();
945 code.calculateMaxLocals();
946
947 // new instance factory
948 m = bc.declareMethod("newInstance", ProxyMap.class,
949 new Class[] { Class.class, Class.class, Comparator.class,
950 boolean.class });
951 m.makePublic();
952 code = m.getCode(true);
953
954 code.anew().setType(bc);
955 code.dup();
956 cons = findComparatorConstructor(type);
957 params = (cons == null) ? new Class[0] : cons.getParameterTypes();
958 if (params.length == 1)
959 code.aload().setParam(2);
960 code.invokespecial().setMethod("<init>", void.class, params);
961 int ret = code.getNextLocalsIndex();
962 code.astore().setLocal(ret);
963
964 // set key and value types
965 code.aload().setLocal(ret);
966 code.aload().setParam(0);
967 code.putfield().setField(keyType);
968 code.aload().setLocal(ret);
969 code.aload().setParam(1);
970 code.putfield().setField(valueType);
971
972 // create change tracker and set it
973 code.iload().setParam(3);
974 JumpInstruction ifins = code.ifeq();
975 code.aload().setLocal(ret);
976 code.anew().setType(MapChangeTrackerImpl.class);
977 code.dup();
978 code.aload().setLocal(ret);
979 code.invokespecial().setMethod(MapChangeTrackerImpl.class,
980 "<init>", void.class, new Class[] { Map.class });
981 code.putfield().setField(changeTracker);
982
983 ifins.setTarget(code.aload().setLocal(ret));
984 code.areturn();
985 code.calculateMaxStack();
986 code.calculateMaxLocals();
987 }
988
989 /**
990 * Implement the methods in the {@link ProxyDate} interface.
991 */
992 private void addProxyDateMethods(BCClass bc, Class type) {
993 boolean hasDefaultCons = bc.getDeclaredMethod("<init>",
994 (Class[]) null) != null;
995 boolean hasMillisCons = bc.getDeclaredMethod("<init>",
996 new Class[] { long.class }) != null;
997 if (!hasDefaultCons && !hasMillisCons)
998 throw new UnsupportedException(_loc.get("no-date-cons", type));
999
1000 // add a default constructor that delegates to the millis constructor
1001 BCMethod m;
1002 Code code;
1003 if (!hasDefaultCons) {
1004 m = bc.declareMethod("<init>", void.class, null);
1005 m.makePublic();
1006 code = m.getCode(true);
1007 code.aload().setThis();
1008 code.invokestatic().setMethod(System.class, "currentTimeMillis",
1009 long.class, null);
1010 code.invokespecial().setMethod(type, "<init>", void.class,
1011 new Class[] { long.class });
1012 code.vreturn();
1013 code.calculateMaxStack();
1014 code.calculateMaxLocals();
1015 }
1016
1017 // date copy
1018 Constructor cons = findCopyConstructor(type);
1019 Class[] params;
1020 if (cons != null)
1021 params = cons.getParameterTypes();
1022 else if (hasMillisCons)
1023 params = new Class[] { long.class };
1024 else
1025 params = new Class[0];
1026
1027 m = bc.declareMethod("copy", Object.class, new Class[] {Object.class});
1028 m.makePublic();
1029 code = m.getCode(true);
1030
1031 code.anew().setType(type);
1032 code.dup();
1033 if (params.length == 1) {
1034 if (params[0] == long.class) {
1035 code.aload().setParam(0);
1036 code.checkcast().setType(Date.class);
1037 code.invokevirtual().setMethod(Date.class, "getTime",
1038 long.class, null);
1039 } else {
1040 code.aload().setParam(0);
1041 code.checkcast().setType(params[0]);
1042 }
1043 }
1044 code.invokespecial().setMethod(type, "<init>", void.class, params);
1045 if (params.length == 0) {
1046 code.dup();
1047 code.aload().setParam(0);
1048 code.checkcast().setType(Date.class);
1049 code.invokevirtual().setMethod(Date.class, "getTime", long.class,
1050 null);
1051 code.invokevirtual().setMethod(type, "setTime", void.class,
1052 new Class[] { long.class });
1053 }
1054 if ((params.length == 0 || params[0] == long.class)
1055 && Timestamp.class.isAssignableFrom(type)) {
1056 code.dup();
1057 code.aload().setParam(0);
1058 code.checkcast().setType(Timestamp.class);
1059 code.invokevirtual().setMethod(Timestamp.class, "getNanos",
1060 int.class, null);
1061 code.invokevirtual().setMethod(type, "setNanos", void.class,
1062 new Class[] { int.class });
1063 }
1064 code.areturn();
1065 code.calculateMaxStack();
1066 code.calculateMaxLocals();
1067
1068 // new instance factory
1069 m = bc.declareMethod("newInstance", ProxyDate.class, null);
1070 m.makePublic();
1071 code = m.getCode(true);
1072 code.anew().setType(bc);
1073 code.dup();
1074 code.invokespecial().setMethod("<init>", void.class, null);
1075 code.areturn();
1076 code.calculateMaxStack();
1077 code.calculateMaxLocals();
1078 }
1079
1080 /**
1081 * Implement the methods in the {@link ProxyCalendar} interface.
1082 */
1083 private void addProxyCalendarMethods(BCClass bc, Class type) {
1084 // calendar copy
1085 Constructor cons = findCopyConstructor(type);
1086 Class[] params = (cons == null) ? new Class[0]
1087 : cons.getParameterTypes();
1088
1089 BCMethod m = bc.declareMethod("copy", Object.class,
1090 new Class[] {Object.class});
1091 m.makePublic();
1092 Code code = m.getCode(true);
1093
1094 code.anew().setType(type);
1095 code.dup();
1096 if (params.length == 1) {
1097 code.aload().setParam(0);
1098 code.checkcast().setType(params[0]);
1099 }
1100 code.invokespecial().setMethod(type, "<init>", void.class, params);
1101 if (params.length == 0) {
1102 code.dup();
1103 code.aload().setParam(0);
1104 code.checkcast().setType(Calendar.class);
1105 code.invokevirtual().setMethod(Calendar.class, "getTimeInMillis",
1106 long.class, null);
1107 code.invokevirtual().setMethod(type, "setTimeInMillis", void.class,
1108 new Class[] { long.class });
1109
1110 code.dup();
1111 code.aload().setParam(0);
1112 code.checkcast().setType(Calendar.class);
1113 code.invokevirtual().setMethod(Calendar.class, "isLenient",
1114 boolean.class, null);
1115 code.invokevirtual().setMethod(type, "setLenient", void.class,
1116 new Class[] { boolean.class });
1117
1118 code.dup();
1119 code.aload().setParam(0);
1120 code.checkcast().setType(Calendar.class);
1121 code.invokevirtual().setMethod(Calendar.class, "getFirstDayOfWeek",
1122 int.class, null);
1123 code.invokevirtual().setMethod(type, "setFirstDayOfWeek",
1124 void.class, new Class[] { int.class });
1125
1126 code.dup();
1127 code.aload().setParam(0);
1128 code.checkcast().setType(Calendar.class);
1129 code.invokevirtual().setMethod(Calendar.class,
1130 "getMinimalDaysInFirstWeek", int.class, null);
1131 code.invokevirtual().setMethod(type, "setMinimalDaysInFirstWeek",
1132 void.class, new Class[] { int.class });
1133
1134 code.dup();
1135 code.aload().setParam(0);
1136 code.checkcast().setType(Calendar.class);
1137 code.invokevirtual().setMethod(Calendar.class, "getTimeZone",
1138 TimeZone.class, null);
1139 code.invokevirtual().setMethod(type, "setTimeZone", void.class,
1140 new Class[] { TimeZone.class });
1141 }
1142 code.areturn();
1143 code.calculateMaxStack();
1144 code.calculateMaxLocals();
1145
1146 // new instance factory
1147 m = bc.declareMethod("newInstance", ProxyCalendar.class, null);
1148 m.makePublic();
1149 code = m.getCode(true);
1150 code.anew().setType(bc);
1151 code.dup();
1152 code.invokespecial().setMethod("<init>", void.class, null);
1153 code.areturn();
1154 code.calculateMaxStack();
1155 code.calculateMaxLocals();
1156
1157 // proxy the protected computeFields method b/c it is called on
1158 // mutate, and some setters are final and therefore not proxyable
1159 m = bc.declareMethod("computeFields", void.class, null);
1160 m.makeProtected();
1161 code = m.getCode(true);
1162 code.aload().setThis();
1163 code.constant().setValue(true);
1164 code.invokestatic().setMethod(Proxies.class, "dirty", void.class,
1165 new Class[] { Proxy.class, boolean.class });
1166 code.aload().setThis();
1167 code.invokespecial().setMethod(type, "computeFields", void.class, null);
1168 code.vreturn();
1169 code.calculateMaxStack();
1170 code.calculateMaxLocals();
1171 }
1172
1173 /**
1174 * Implement the methods in the {@link ProxyBean} interface.
1175 */
1176 private void addProxyBeanMethods(BCClass bc, Class type, Constructor cons) {
1177 // bean copy
1178 BCMethod m = bc.declareMethod("copy", Object.class,
1179 new Class[] { Object.class });
1180 m.makePublic();
1181 Code code = m.getCode(true);
1182
1183 code.anew().setType(type);
1184 code.dup();
1185 Class[] params = cons.getParameterTypes();
1186 if (params.length == 1) {
1187 code.aload().setParam(0);
1188 code.checkcast().setType(params[0]);
1189 }
1190 code.invokespecial().setMethod(cons);
1191 if (params.length == 0)
1192 copyProperties(type, code);
1193 code.areturn();
1194 code.calculateMaxStack();
1195 code.calculateMaxLocals();
1196
1197 // new instance factory
1198 m = bc.declareMethod("newInstance", ProxyBean.class,
1199 new Class[] { Object.class });
1200 m.makePublic();
1201 code = m.getCode(true);
1202 code.anew().setType(bc);
1203 code.dup();
1204 if (params.length == 1) {
1205 code.aload().setParam(0);
1206 code.checkcast().setType(params[0]);
1207 }
1208 code.invokespecial().setMethod("<init>", void.class, params);
1209 if (params.length == 0)
1210 copyProperties(type, code);
1211 code.areturn();
1212 code.calculateMaxStack();
1213 code.calculateMaxLocals();
1214 }
1215
1216 /**
1217 * Copy bean properties. Called with the copy object on the stack. Must
1218 * return with the copy object on the stack.
1219 */
1220 private void copyProperties(Class type, Code code) {
1221 int copy = code.getNextLocalsIndex();
1222 code.astore().setLocal(copy);
1223
1224 Method[] meths = type.getMethods();
1225 Method getter;
1226 int mods;
1227 for (int i = 0; i < meths.length; i++) {
1228 mods = meths[i].getModifiers();
1229 if (!Modifier.isPublic(mods) || Modifier.isStatic(mods))
1230 continue;
1231 if (!startsWith(meths[i].getName(), "set")
1232 || meths[i].getParameterTypes().length != 1)
1233 continue;
1234 getter = findGetter(type, meths[i]);
1235 if (getter == null)
1236 continue;
1237
1238 // copy.setXXX(orig.getXXX());
1239 code.aload().setLocal(copy);
1240 code.aload().setParam(0);
1241 code.checkcast().setType(type);
1242 code.invokevirtual().setMethod(getter);
1243 code.invokevirtual().setMethod(meths[i]);
1244 }
1245 code.aload().setLocal(copy);
1246 }
1247
1248 /**
1249 * Proxy recognized methods to invoke helpers in given helper class.
1250 */
1251 private void proxyRecognizedMethods(BCClass bc, Class type, Class helper,
1252 Class proxyType) {
1253 Method[] meths = type.getMethods();
1254 Class[] params;
1255 Class[] afterParams;
1256 Method match;
1257 Method after;
1258 for (int i = 0; i < meths.length; i++) {
1259 params = toHelperParameters(meths[i].getParameterTypes(),
1260 proxyType);
1261
1262 // first check for overriding method
1263 try {
1264 match = helper.getMethod(meths[i].getName(), params);
1265 proxyOverrideMethod(bc, meths[i], match, params);
1266 continue;
1267 } catch (NoSuchMethodException nsme) {
1268 } catch (Exception e) {
1269 throw new GeneralException(e);
1270 }
1271
1272 // check for before and after methods, either of which may not
1273 // exist
1274 match = null;
1275 try {
1276 match = helper.getMethod("before"
1277 + StringUtils.capitalize(meths[i].getName()), params);
1278 } catch (NoSuchMethodException nsme) {
1279 } catch (Exception e) {
1280 throw new GeneralException(e);
1281 }
1282 after = null;
1283 afterParams = null;
1284 try {
1285 afterParams = toHelperAfterParameters(params,
1286 meths[i].getReturnType(), (match == null)
1287 ? void.class : match.getReturnType());
1288 after = helper.getMethod("after"
1289 + StringUtils.capitalize(meths[i].getName()), afterParams);
1290 } catch (NoSuchMethodException nsme) {
1291 } catch (Exception e) {
1292 throw new GeneralException(e);
1293 }
1294 if (match != null || after != null)
1295 proxyBeforeAfterMethod(bc, type, meths[i], match, params, after,
1296 afterParams);
1297 }
1298 }
1299
1300 /**
1301 * Return the parameter types to the corresponding helper class method.
1302 */
1303 private static Class[] toHelperParameters(Class[] cls, Class helper) {
1304 Class[] params = new Class[cls.length + 1];
1305 params[0] = helper;
1306 System.arraycopy(cls, 0, params, 1, cls.length);
1307 return params;
1308 }
1309
1310 /**
1311 * Return the parameter types to the corresponding helper class "after"
1312 * method.
1313 */
1314 private static Class[] toHelperAfterParameters(Class[] cls, Class ret,
1315 Class beforeRet) {
1316 if (ret == void.class && beforeRet == void.class)
1317 return cls;
1318 int len = cls.length;
1319 if (ret != void.class)
1320 len++;
1321 if (beforeRet != void.class)
1322 len++;
1323 Class[] params = new Class[len];
1324 System.arraycopy(cls, 0, params, 0, cls.length);
1325 int pos = cls.length;
1326 if (ret != void.class)
1327 params[pos++] = ret;
1328 if (beforeRet != void.class)
1329 params[pos++] = beforeRet;
1330 return params;
1331 }
1332
1333 /**
1334 * Proxy setter methods of the given type.
1335 *
1336 * @return true if we find any setters, false otherwise
1337 */
1338 private boolean proxySetters(BCClass bc, Class type) {
1339 Method[] meths = type.getMethods();
1340 int setters = 0;
1341 for (int i = 0; i < meths.length; i++) {
1342 if (isSetter(meths[i]) && !Modifier.isFinal(meths[i].getModifiers())
1343 && bc.getDeclaredMethod(meths[i].getName(),
1344 meths[i].getParameterTypes()) == null) {
1345 setters++;
1346 proxySetter(bc, type, meths[i]);
1347 }
1348 }
1349 return setters > 0;
1350 }
1351
1352 /**
1353 * Proxy the given method with one that overrides it by calling into the
1354 * given helper.
1355 */
1356 private void proxyOverrideMethod(BCClass bc, Method meth,
1357 Method helper, Class[] params) {
1358 BCMethod m = bc.declareMethod(meth.getName(), meth.getReturnType(),
1359 meth.getParameterTypes());
1360 m.makePublic();
1361 Code code = m.getCode(true);
1362
1363 code.aload().setThis();
1364 for (int i = 1; i < params.length; i++)
1365 code.xload().setParam(i - 1).setType(params[i]);
1366 code.invokestatic().setMethod(helper);
1367 code.xreturn().setType(meth.getReturnType());
1368
1369 code.calculateMaxStack();
1370 code.calculateMaxLocals();
1371 }
1372
1373 /**
1374 * Proxy the given method with one that overrides it by calling into the
1375 * given helper.
1376 */
1377 private void proxyBeforeAfterMethod(BCClass bc, Class type, Method meth,
1378 Method before, Class[] params, Method after, Class[] afterParams) {
1379 BCMethod m = bc.declareMethod(meth.getName(), meth.getReturnType(),
1380 meth.getParameterTypes());
1381 m.makePublic();
1382 Code code = m.getCode(true);
1383
1384 // invoke before
1385 int beforeRet = -1;
1386 if (before != null) {
1387 code.aload().setThis();
1388 for (int i = 1; i < params.length; i++)
1389 code.xload().setParam(i - 1).setType(params[i]);
1390 code.invokestatic().setMethod(before);
1391 if (after != null && before.getReturnType() != void.class) {
1392 beforeRet = code.getNextLocalsIndex();
1393 code.xstore().setLocal(beforeRet).
1394 setType(before.getReturnType());
1395 }
1396 }
1397
1398 // invoke super
1399 code.aload().setThis();
1400 for (int i = 1; i < params.length; i++)
1401 code.xload().setParam(i - 1).setType(params[i]);
1402 code.invokespecial().setMethod(type, meth.getName(),
1403 meth.getReturnType(), meth.getParameterTypes());
1404
1405 // invoke after
1406 if (after != null) {
1407 int ret = -1;
1408 if (meth.getReturnType() != void.class) {
1409 ret = code.getNextLocalsIndex();
1410 code.xstore().setLocal(ret).setType(meth.getReturnType());
1411 }
1412 code.aload().setThis();
1413 for (int i = 1; i < params.length; i++)
1414 code.xload().setParam(i - 1).setType(params[i]);
1415 if (ret != -1)
1416 code.xload().setLocal(ret).setType(meth.getReturnType());
1417 if (beforeRet != -1)
1418 code.xload().setLocal(beforeRet).
1419 setType(before.getReturnType());
1420 code.invokestatic().setMethod(after);
1421 }
1422 code.xreturn().setType(meth.getReturnType());
1423
1424 code.calculateMaxStack();
1425 code.calculateMaxLocals();
1426 }
1427
1428 /**
1429 * Return whether the given method is a setter.
1430 */
1431 protected boolean isSetter(Method meth) {
1432 return startsWith(meth.getName(), "set")
1433 || startsWith(meth.getName(), "add")
1434 || startsWith(meth.getName(), "remove")
1435 || startsWith(meth.getName(), "insert")
1436 || startsWith(meth.getName(), "clear")
1437 || startsWith(meth.getName(), "roll"); // used by Calendar
1438 }
1439
1440 /**
1441 * Return the getter corresponding to the given setter, or null.
1442 */
1443 protected Method findGetter(Class type, Method setter) {
1444 String name = setter.getName().substring(3);
1445 Class param = setter.getParameterTypes()[0];
1446 Method getter;
1447 try {
1448 getter = type.getMethod("get" + name, (Class[]) null);
1449 if (getter.getReturnType().isAssignableFrom(param)
1450 || param.isAssignableFrom(getter.getReturnType()))
1451 return getter;
1452 } catch (NoSuchMethodException nsme) {
1453 } catch (Exception e) {
1454 throw new GeneralException(e);
1455 }
1456
1457 if (param == boolean.class || param == Boolean.class) {
1458 try {
1459 getter = type.getMethod("is" + name, (Class[]) null);
1460 if (getter.getReturnType().isAssignableFrom(param)
1461 || param.isAssignableFrom(getter.getReturnType()))
1462 return getter;
1463 } catch (NoSuchMethodException nsme) {
1464 } catch (Exception e) {
1465 throw new GeneralException(e);
1466 }
1467 }
1468 return null;
1469 }
1470
1471 /**
1472 * Return whether the target string stars with the given token.
1473 */
1474 private static boolean startsWith(String str, String token) {
1475 return str.startsWith(token)
1476 && (str.length() == token.length()
1477 || Character.isUpperCase(str.charAt(token.length())));
1478 }
1479
1480 /**
1481 * Proxy the given setter method to dirty the proxy owner.
1482 */
1483 private void proxySetter(BCClass bc, Class type, Method meth) {
1484 Class[] params = meth.getParameterTypes();
1485 Class ret = meth.getReturnType();
1486 BCMethod m = bc.declareMethod(meth.getName(), ret, params);
1487 m.makePublic();
1488 Code code = m.getCode(true);
1489 code.aload().setThis();
1490 code.constant().setValue(true);
1491 code.invokestatic().setMethod(Proxies.class, "dirty", void.class,
1492 new Class[] { Proxy.class, boolean.class });
1493 code.aload().setThis();
1494 for (int i = 0; i < params.length; i++)
1495 code.xload().setParam(i).setType(params[i]);
1496 code.invokespecial().setMethod(type, meth.getName(), ret, params);
1497 code.xreturn().setType(ret);
1498 code.calculateMaxStack();
1499 code.calculateMaxLocals();
1500 }
1501
1502 /**
1503 * Add a writeReplace implementation that serializes to a non-proxy type
1504 * unless detached and this is a build-time generated class.
1505 */
1506 private void addWriteReplaceMethod(BCClass bc, boolean runtime) {
1507 BCMethod m = bc.declareMethod("writeReplace", Object.class, null);
1508 m.makeProtected();
1509 m.getExceptions(true).addException(ObjectStreamException.class);
1510 Code code = m.getCode(true);
1511 code.aload().setThis();
1512 code.constant().setValue(!runtime);
1513 code.invokestatic().setMethod(Proxies.class, "writeReplace",
1514 Object.class, new Class[] { Proxy.class, boolean.class });
1515 code.areturn();
1516 code.calculateMaxLocals();
1517 code.calculateMaxStack();
1518 }
1519
1520 /**
1521 * Create a unique id to avoid proxy class name conflicts.
1522 */
1523 private static synchronized long nextProxyId() {
1524 return _proxyId++;
1525 }
1526
1527 /**
1528 * Find an appropriate copy constructor for the given type, or return null
1529 * if none.
1530 */
1531 protected Constructor findCopyConstructor(Class cls) {
1532 Constructor[] cons = cls.getConstructors();
1533 Constructor match = null;
1534 Class matchParam = null;
1535 Class[] params;
1536 for (int i = 0; i < cons.length; i++) {
1537 params = cons[i].getParameterTypes();
1538 if (params.length != 1)
1539 continue;
1540
1541 // quit immediately on exact match
1542 if (params[0] == cls)
1543 return cons[i];
1544
1545 if (params[0].isAssignableFrom(cls) && (matchParam == null
1546 || matchParam.isAssignableFrom(params[0]))) {
1547 // track most derived collection constructor
1548 match = cons[i];
1549 matchParam = params[0];
1550 }
1551 }
1552 return match;
1553 }
1554
1555 /**
1556 * Return the constructor that takes a comparator for the given type, or
1557 * null if none.
1558 */
1559 private static Constructor findComparatorConstructor(Class cls) {
1560 try {
1561 return cls.getConstructor(new Class[] { Comparator.class });
1562 } catch (NoSuchMethodException nsme) {
1563 return null;
1564 } catch (Exception e) {
1565 throw new GeneralException(e);
1566 }
1567 }
1568
1569 /**
1570 * Usage: java org.apache.openjpa.util.proxy.ProxyManagerImpl [option]*
1571 * <class name>+<br />
1572 * Where the following options are recognized:
1573 * <ul>
1574 * <li><i>-utils/-u <number></i>: Generate proxies for the standard
1575 * java.util collection, map, date, and calendar classes of the given Java
1576 * version. Use 4 for Java 1.4, 5 for Java 5, etc.</li>
1577 * </ul>
1578 *
1579 * The main method generates .class files for the proxies to the classes
1580 * given on the command line. It writes the generated classes to beside the
1581 * ProxyManagerImpl.class file if possible; otherwise it writes to the
1582 * current directory. The proxy manager looks for these classes
1583 * before generating its own proxies at runtime.
1584 */
1585 public static void main(String[] args)
1586 throws ClassNotFoundException, IOException {
1587 File dir = Files.getClassFile(ProxyManagerImpl.class);
1588 dir = (dir == null) ? new File((String) AccessController.doPrivileged(
1589 J2DoPrivHelper.getPropertyAction("user.dir")))
1590 : dir.getParentFile();
1591
1592 Options opts = new Options();
1593 args = opts.setFromCmdLine(args);
1594
1595 List types = new ArrayList();
1596 types.addAll(Arrays.asList(args));
1597 int utils = opts.removeIntProperty("utils", "u", 0);
1598 if (utils >= 4) {
1599 types.addAll(Arrays.asList(new String[] {
1600 java.sql.Date.class.getName(),
1601 java.sql.Time.class.getName(),
1602 java.sql.Timestamp.class.getName(),
1603 java.util.ArrayList.class.getName(),
1604 java.util.Date.class.getName(),
1605 java.util.GregorianCalendar.class.getName(),
1606 java.util.HashMap.class.getName(),
1607 java.util.HashSet.class.getName(),
1608 java.util.Hashtable.class.getName(),
1609 java.util.LinkedList.class.getName(),
1610 java.util.Properties.class.getName(),
1611 java.util.TreeMap.class.getName(),
1612 java.util.TreeSet.class.getName(),
1613 java.util.Vector.class.getName(),
1614 }));
1615 }
1616 if (utils >= 5) {
1617 types.addAll(Arrays.asList(new String[] {
1618 "java.util.EnumMap",
1619 "java.util.IdentityHashMap",
1620 "java.util.LinkedHashMap",
1621 "java.util.LinkedHashSet",
1622 "java.util.PriorityQueue",
1623 }));
1624 }
1625
1626 final ProxyManagerImpl mgr = new ProxyManagerImpl();
1627 Class cls;
1628 BCClass bc;
1629 for (int i = 0; i < types.size(); i++) {
1630 cls = Class.forName((String) types.get(i));
1631 try {
1632 if (Class.forName(getProxyClassName(cls, false), true,
1633 GeneratedClasses.getMostDerivedLoader(cls, Proxy.class))
1634 != null)
1635 continue;
1636 } catch (Throwable t) {
1637 // expected if the class hasn't been generated
1638 }
1639
1640 if (Collection.class.isAssignableFrom(cls))
1641 bc = mgr.generateProxyCollectionBytecode(cls, false);
1642 else if (Map.class.isAssignableFrom(cls))
1643 bc = mgr.generateProxyMapBytecode(cls, false);
1644 else if (Date.class.isAssignableFrom(cls))
1645 bc = mgr.generateProxyDateBytecode(cls, false);
1646 else if (Calendar.class.isAssignableFrom(cls))
1647 bc = mgr.generateProxyCalendarBytecode(cls, false);
1648 else {
1649 final Class fCls = cls;
1650 bc = (BCClass) AccessController
1651 .doPrivileged(new PrivilegedAction() {
1652 public Object run() {
1653 return mgr.generateProxyBeanBytecode(fCls, false);
1654 }
1655 });
1656 }
1657
1658 System.out.println(bc.getName());
1659 bc.write(new File(dir, bc.getClassName() + ".class"));
1660 }
1661 }
1662 }