comparison src/com/go/trove/util/MergedClass.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3dc0c5604566
1 /* ====================================================================
2 * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
3 * ====================================================================
4 * The Tea Software License, Version 1.1
5 *
6 * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution,
21 * if any, must include the following acknowledgment:
22 * "This product includes software developed by the
23 * Walt Disney Internet Group (http://opensource.go.com/)."
24 * Alternately, this acknowledgment may appear in the software itself,
25 * if and wherever such third-party acknowledgments normally appear.
26 *
27 * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
28 * not be used to endorse or promote products derived from this
29 * software without prior written permission. For written
30 * permission, please contact opensource@dig.com.
31 *
32 * 5. Products derived from this software may not be called "Tea",
33 * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
34 * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
35 * written permission of the Walt Disney Internet Group.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
41 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
45 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 * ====================================================================
49 *
50 * For more information about Tea, please see http://opensource.go.com/.
51 */
52
53 package com.go.trove.util;
54
55 import java.lang.reflect.*;
56 import java.util.*;
57 import java.io.OutputStream;
58 import java.io.IOException;
59 import com.go.trove.classfile.*;
60
61 /******************************************************************************
62 * Merges several classes together, producing a new class that has all of the
63 * methods of the combined classes. All methods in the combined class delegate
64 * to instances of the source classes. If multiple classes implement the same
65 * method, the first one provided is used. The merged class implements all of
66 * the interfaces provided by the source classes or interfaces.
67 *
68 * <p>This class performs a function almost the same as the Proxy class
69 * introduced in JDK1.3. It differs in that it supports classes as well as
70 * interfaces as input, and it binds to wrapped objects without using
71 * runtime reflection. It is less flexible than Proxy in that there isn't way
72 * to customize the method delegation, and so it isn't suitable for creating
73 * dynamically generated interface implementations.
74 *
75 * @author Brian S O'Neill
76 * @version
77 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 01/02/20 <!-- $-->
78 */
79 public class MergedClass {
80 // Maps ClassInjectors to Maps that map to merged class names.
81 // Map<ClassInjector, Map<MultiKey, String>>
82 // The MultiKey is composed of class names and method prefixes. By storing
83 // class names into the maps instead of classes, the classes may be
84 // reclaimed by the garbage collector.
85 private static Map cMergedMap;
86
87 static {
88 try {
89 cMergedMap = new IdentityMap(7);
90 }
91 catch (LinkageError e) {
92 cMergedMap = new HashMap(7);
93 }
94 catch (Exception e) {
95 // Microsoft VM sometimes throws an undeclared
96 // ClassNotFoundException instead of doing the right thing and
97 // throwing some form of a LinkageError if the class couldn't
98 // be found.
99 cMergedMap = new HashMap(7);
100 }
101 }
102
103 /**
104 * Returns the constructor for a class that merges all of the given source
105 * classes. The constructor's parameter types match the source classes.
106 * An IllegalArgumentException is thrown if any of the given conditions
107 * are not satisfied:
108 *
109 * <ul>
110 * <li>None of the given classes can represent a primitive type
111 * <li>A source class can only be provided once
112 * <li>Any non-public classes must be in the same common package
113 * <li>Duplicate methods cannot have conflicting return types
114 * <li>The given classes must be loadable from the given injector
115 * <li>At most 254 classes may be merged
116 * </ul>
117 *
118 * Note: Because a constructor is limited to 254 parameters, if more than
119 * 254 classes are to be merged together, call {@link #getConstructor2}.
120 *
121 * @param injector ClassInjector that will receive class definition
122 * @param classes Source classes used to derive merged class
123 */
124 public static Constructor getConstructor(ClassInjector injector,
125 Class[] classes)
126 throws IllegalArgumentException
127 {
128 return getConstructor(injector, classes, null);
129 }
130
131 /**
132 * Returns the constructor for a class that merges all of the given source
133 * classes. The constructor's parameter types match the source classes.
134 * An IllegalArgumentException is thrown if any of the given conditions
135 * are not satisfied:
136 *
137 * <ul>
138 * <li>None of the given classes can represent a primitive type
139 * <li>A source class can only be provided once, unless paired with a
140 * unique prefix.
141 * <li>Any non-public classes must be in the same common package
142 * <li>Duplicate methods cannot have conflicting return types, unless a
143 * prefix is provided.
144 * <li>The given classes must be loadable from the given injector
145 * <li>At most 254 classes may be merged
146 * </ul>
147 *
148 * To help resolve ambiguities, a method prefix can be specified for each
149 * passed in class. For each prefixed method, the non-prefixed method is
150 * also generated, unless that method conflicts with the return type of
151 * another method. If any passed in classes are or have interfaces, then
152 * those interfaces will be implemented only if there are no conflicts.
153 *
154 * <p>The array of prefixes may be null, have null elements or
155 * be shorter than the array of classes. A prefix of "" is treated as null.
156 * <p>
157 * Note: Because a constructor is limited to 254 parameters, if more than
158 * 254 classes are to be merged together, call {@link #getConstructor2}.
159 *
160 * @param injector ClassInjector that will receive class definition
161 * @param classes Source classes used to derive merged class
162 * @param prefixes Optional prefixes to apply to methods of each generated
163 * class to eliminate duplicate method names
164 */
165 public static Constructor getConstructor(ClassInjector injector,
166 Class[] classes,
167 String[] prefixes)
168 throws IllegalArgumentException
169 {
170 if (classes.length > 254) {
171 throw new IllegalArgumentException
172 ("More than 254 merged classes: " + classes.length);
173 }
174
175 Class clazz = getMergedClass(injector, classes, prefixes);
176
177 try {
178 return clazz.getConstructor(classes);
179 }
180 catch (NoSuchMethodException e) {
181 throw new InternalError(e.toString());
182 }
183 }
184
185 /**
186 * Returns the constructor for a class that merges all of the given source
187 * classes. The constructor accepts one parameter type: an
188 * {@link InstanceFactory}. Merged instances are requested only when
189 * first needed. An IllegalArgumentException is thrown if any of the given
190 * conditions are not satisfied:
191 *
192 * <ul>
193 * <li>None of the given classes can represent a primitive type
194 * <li>A source class can only be provided once
195 * <li>Any non-public classes must be in the same common package
196 * <li>Duplicate methods cannot have conflicting return types
197 * <li>The given classes must be loadable from the given injector
198 * </ul>
199 *
200 * @param injector ClassInjector that will receive class definition
201 * @param classes Source classes used to derive merged class
202 */
203 public static Constructor getConstructor2(ClassInjector injector,
204 Class[] classes)
205 throws IllegalArgumentException
206 {
207 return getConstructor2(injector, classes, null);
208 }
209
210 /**
211 * Returns the constructor for a class that merges all of the given source
212 * classes. The constructor accepts one parameter type: an
213 * {@link InstanceFactory}. Merged instances are requested only when
214 * first needed. An IllegalArgumentException is thrown if any of the given
215 * conditions are not satisfied:
216 *
217 * <ul>
218 * <li>None of the given classes can represent a primitive type
219 * <li>A source class can only be provided once, unless paired with a
220 * unique prefix.
221 * <li>Any non-public classes must be in the same common package
222 * <li>Duplicate methods cannot have conflicting return types, unless a
223 * prefix is provided.
224 * <li>The given classes must be loadable from the given injector
225 * </ul>
226 *
227 * To help resolve ambiguities, a method prefix can be specified for each
228 * passed in class. For each prefixed method, the non-prefixed method is
229 * also generated, unless that method conflicts with the return type of
230 * another method. If any passed in classes are or have interfaces, then
231 * those interfaces will be implemented only if there are no conflicts.
232 *
233 * <p>The array of prefixes may be null, have null elements or
234 * be shorter than the array of classes. A prefix of "" is treated as null.
235 *
236 * @param injector ClassInjector that will receive class definition
237 * @param classes Source classes used to derive merged class
238 * @param prefixes Optional prefixes to apply to methods of each generated
239 * class to eliminate duplicate method names
240 */
241 public static Constructor getConstructor2(ClassInjector injector,
242 Class[] classes,
243 String[] prefixes)
244 throws IllegalArgumentException
245 {
246 Class clazz = getMergedClass(injector, classes, prefixes);
247
248 try {
249 return clazz.getConstructor(new Class[]{InstanceFactory.class});
250 }
251 catch (NoSuchMethodException e) {
252 throw new InternalError(e.toString());
253 }
254 }
255
256 private static Class getMergedClass(ClassInjector injector,
257 Class[] classes,
258 String[] prefixes)
259 throws IllegalArgumentException
260 {
261 ClassEntry[] classEntries = new ClassEntry[classes.length];
262 for (int i=0; i<classes.length; i++) {
263 // Load the classes from the ClassInjector, just like they will be
264 // when the generated class is resolved.
265 try {
266 classes[i] = injector.loadClass(classes[i].getName());
267 }
268 catch (ClassNotFoundException e) {
269 throw new IllegalArgumentException
270 ("Unable to load class from injector: " + classes[i]);
271 }
272
273 if (prefixes == null || i >= prefixes.length) {
274 classEntries[i] = new ClassEntry(classes[i]);
275 }
276 else {
277 String prefix = prefixes[i];
278 if (prefix != null && prefix.length() == 0) {
279 prefix = null;
280 }
281 classEntries[i] = new ClassEntry(classes[i], prefix);
282 }
283 }
284
285 return getMergedClass(injector, classEntries);
286 }
287
288 /**
289 * Creates a class with a public constructor whose parameter types match
290 * the given source classes. Another constructor is also created that
291 * accepts an InstanceFactory.
292 */
293 private static synchronized Class getMergedClass(ClassInjector injector,
294 ClassEntry[] classEntries)
295 throws IllegalArgumentException
296 {
297 Map classListMap = (Map)cMergedMap.get(injector);
298 if (classListMap == null) {
299 classListMap = new HashMap(7);
300 cMergedMap.put(injector, classListMap);
301 }
302
303 Object key = generateKey(classEntries);
304 String mergedName = (String)classListMap.get(key);
305 if (mergedName != null) {
306 try {
307 return injector.loadClass(mergedName);
308 }
309 catch (ClassNotFoundException e) {
310 }
311 }
312
313 ClassFile cf;
314 try {
315 cf = createClassFile(injector, classEntries);
316 }
317 catch (IllegalArgumentException e) {
318 e.fillInStackTrace();
319 throw e;
320 }
321
322 /*
323 try {
324 java.io.FileOutputStream out =
325 new java.io.FileOutputStream(cf.getClassName() + ".class");
326 cf.writeTo(out);
327 out.close();
328 }
329 catch (Exception e) {
330 e.printStackTrace();
331 }
332 */
333
334 try {
335 OutputStream stream = injector.getStream(cf.getClassName());
336 cf.writeTo(stream);
337 stream.close();
338 }
339 catch (IOException e) {
340 throw new InternalError(e.toString());
341 }
342
343 Class merged;
344 try {
345 merged = injector.loadClass(cf.getClassName());
346 }
347 catch (ClassNotFoundException e) {
348 throw new InternalError(e.toString());
349 }
350
351 classListMap.put(key, merged.getName());
352 return merged;
353 }
354
355 private static Object generateKey(ClassEntry[] classEntries) {
356 int length = classEntries.length;
357 Object[] mainElements = new Object[length];
358 for (int i=0; i<length; i++) {
359 ClassEntry classEntry = classEntries[i];
360 mainElements[i] = new MultiKey(new Object[] {
361 classEntry.getClazz().getName(),
362 classEntry.getMethodPrefix()
363 });
364 }
365 return new MultiKey(mainElements);
366 }
367
368 private static ClassFile createClassFile(ClassInjector injector,
369 ClassEntry[] classEntries)
370 throws IllegalArgumentException
371 {
372 Set classSet = new HashSet(classEntries.length * 2 + 1);
373 Set nonConflictingClasses = new HashSet(classEntries.length * 2 + 1);
374 String commonPackage = null;
375 Map methodMap = new HashMap();
376
377 for (int i=0; i<classEntries.length; i++) {
378 ClassEntry classEntry = classEntries[i];
379 Class clazz = classEntry.getClazz();
380
381 if (clazz.isPrimitive()) {
382 throw new IllegalArgumentException
383 ("Merged classes cannot be primitive: " + clazz);
384 }
385
386 if (!classSet.add(classEntry)) {
387 throw new IllegalArgumentException
388 ("Class is specified more than once: " + clazz);
389 }
390
391 if (!Modifier.isPublic(clazz.getModifiers())) {
392 String classPackage = clazz.getName();
393 int index = classPackage.lastIndexOf('.');
394 if (index < 0) {
395 classPackage = "";
396 }
397 else {
398 classPackage = classPackage.substring(0, index);
399 }
400
401 if (commonPackage == null) {
402 commonPackage = classPackage;
403 }
404 else if (!commonPackage.equals(classPackage)) {
405 throw new IllegalArgumentException
406 ("Not all non-public classes defined in same " +
407 "package: " + commonPackage);
408 }
409 }
410
411 // Innocent until proven guilty.
412 nonConflictingClasses.add(classEntry);
413
414 Method[] methods = clazz.getMethods();
415 String prefix = classEntry.getMethodPrefix();
416
417 for (int j=0; j<methods.length; j++) {
418 Method method = methods[j];
419 String name = method.getName();
420 // Workaround for JDK1.2 bug #4187388.
421 if ("<clinit>".equals(name)) {
422 continue;
423 }
424
425 MethodEntry methodEntry = new MethodEntry(method, name);
426 MethodEntry existing = (MethodEntry)methodMap.get(methodEntry);
427
428 if (existing == null) {
429 methodMap.put(methodEntry, methodEntry);
430 }
431 else if (existing.returnTypeDiffers(methodEntry)) {
432 nonConflictingClasses.remove(classEntry);
433 if (prefix == null) {
434 throw new IllegalArgumentException
435 ("Conflicting return types: " +
436 existing + ", " + methodEntry);
437 }
438 }
439
440 if (prefix != null) {
441 name = prefix + name;
442
443 methodEntry = new MethodEntry(method, name);
444 existing = (MethodEntry)methodMap.get(methodEntry);
445
446 if (existing == null) {
447 methodMap.put(methodEntry, methodEntry);
448 }
449 else if (existing.returnTypeDiffers(methodEntry)) {
450 nonConflictingClasses.remove(classEntry);
451 throw new IllegalArgumentException
452 ("Conflicting return types: " +
453 existing + ", " + methodEntry);
454 }
455 }
456 }
457 }
458
459 int id = 0;
460 Iterator it = classSet.iterator();
461 while (it.hasNext()) {
462 id = id * 31 + it.next().hashCode();
463 }
464
465 String className = "MergedClass$";
466 try {
467 while (true) {
468 className = "MergedClass$" + (id & 0xffffffffL);
469 if (commonPackage != null && commonPackage.length() > 0) {
470 className = commonPackage + '.' + className;
471 }
472 try {
473 injector.loadClass(className);
474 }
475 catch (LinkageError e) {
476 }
477 id++;
478 }
479 }
480 catch (ClassNotFoundException e) {
481 }
482
483 ClassFile cf = new ClassFile(className);
484 cf.getAccessFlags().setFinal(true);
485 cf.markSynthetic();
486
487 for (int i=0; i<classEntries.length; i++) {
488 ClassEntry classEntry = classEntries[i];
489 if (nonConflictingClasses.contains(classEntry)) {
490 addAllInterfaces(cf, classEntry.getClazz());
491 }
492 }
493
494 AccessFlags privateAccess = new AccessFlags();
495 privateAccess.setPrivate(true);
496 AccessFlags privateFinalAccess = new AccessFlags();
497 privateFinalAccess.setPrivate(true);
498 privateFinalAccess.setFinal(true);
499 AccessFlags publicAccess = new AccessFlags();
500 publicAccess.setPublic(true);
501
502 // Add field to store optional InstanceFactory.
503 TypeDescriptor instanceFactoryType =
504 new TypeDescriptor(InstanceFactory.class);
505 cf.addField(privateAccess,
506 "instanceFactory", instanceFactoryType).markSynthetic();
507
508 Method instanceFactoryMethod;
509 try {
510 instanceFactoryMethod =
511 InstanceFactory.class.getMethod
512 ("getInstance", new Class[]{int.class});
513 }
514 catch (NoSuchMethodException e) {
515 throw new InternalError(e.toString());
516 }
517
518 // Define fields which point to wrapped objects, and define methods
519 // to access the fields.
520 String[] fieldNames = new String[classEntries.length];
521 TypeDescriptor[] types = new TypeDescriptor[classEntries.length];
522 for (int i=0; i<classEntries.length; i++) {
523 Class clazz = classEntries[i].getClazz();
524 String fieldName = "m$" + i;
525 TypeDescriptor type = new TypeDescriptor(clazz);
526 cf.addField(privateAccess, fieldName, type).markSynthetic();
527 fieldNames[i] = fieldName;
528 types[i] = type;
529
530 // Create method that returns field, calling the InstanceFactory
531 // if necessary to initialize for the first time.
532 MethodInfo mi = cf.addMethod
533 (privateFinalAccess, fieldName, type, null);
534 mi.markSynthetic();
535 CodeBuilder builder = new CodeBuilder(mi);
536 builder.loadThis();
537 builder.loadField(fieldName, type);
538 builder.dup();
539 Label isNull = builder.createLabel();
540 builder.ifNullBranch(isNull, true);
541 // Return the initialized field.
542 builder.returnValue(Object.class);
543 isNull.setLocation();
544 // Discard null field value.
545 builder.pop();
546 builder.loadThis();
547 builder.loadField("instanceFactory", instanceFactoryType);
548 builder.dup();
549 Label haveInstanceFactory = builder.createLabel();
550 builder.ifNullBranch(haveInstanceFactory, false);
551 // No instanceFactory: return null.
552 builder.loadConstant(null);
553 builder.returnValue(Object.class);
554 haveInstanceFactory.setLocation();
555 builder.loadConstant(i);
556 builder.invoke(instanceFactoryMethod);
557 builder.checkCast(type);
558 builder.dup();
559 builder.loadThis();
560 builder.swap();
561 builder.storeField(fieldName, type);
562 builder.returnValue(Object.class);
563 }
564
565 // Define a constructor that initializes fields from an Object array.
566 if (classEntries.length <= 254) {
567 MethodInfo mi = cf.addConstructor(publicAccess, types);
568
569 CodeBuilder builder = new CodeBuilder(mi);
570 builder.loadThis();
571 builder.invokeSuperConstructor(null);
572 LocalVariable[] params = builder.getParameters();
573
574 for (int i=0; i<classEntries.length; i++) {
575 builder.loadThis();
576 builder.loadLocal(params[i]);
577 builder.storeField(fieldNames[i], types[i]);
578 }
579
580 builder.returnVoid();
581 builder = null;
582 }
583
584 // Define a constructor that saves an InstanceFactory.
585 MethodInfo mi = cf.addConstructor
586 (publicAccess, new TypeDescriptor[]{instanceFactoryType});
587
588 CodeBuilder builder = new CodeBuilder(mi);
589 builder.loadThis();
590 builder.invokeSuperConstructor(null);
591 builder.loadThis();
592 builder.loadLocal(builder.getParameters()[0]);
593 builder.storeField("instanceFactory", instanceFactoryType);
594
595 builder.returnVoid();
596 builder = null;
597
598 Set methodSet = methodMap.keySet();
599
600 // Define all the wrapper methods.
601 for (int i=0; i<classEntries.length; i++) {
602 ClassEntry classEntry = classEntries[i];
603 String prefix = classEntry.getMethodPrefix();
604 String fieldName = fieldNames[i];
605 TypeDescriptor type = types[i];
606
607 Method[] methods = classEntry.getClazz().getMethods();
608 for (int j=0; j<methods.length; j++) {
609 Method method = methods[j];
610 // Workaround for JDK1.2 bug #4187388.
611 if ("<clinit>".equals(method.getName())) {
612 continue;
613 }
614
615 MethodEntry methodEntry = new MethodEntry(method);
616 if (methodSet.contains(methodEntry)) {
617 methodSet.remove(methodEntry);
618 addWrapperMethod(cf, methodEntry, fieldName, type);
619 }
620
621 if (prefix != null) {
622 methodEntry = new MethodEntry
623 (method, prefix + method.getName());
624 if (methodSet.contains(methodEntry)) {
625 methodSet.remove(methodEntry);
626 addWrapperMethod(cf, methodEntry, fieldName, type);
627 }
628 }
629 }
630 }
631
632 return cf;
633 }
634
635 private static void addAllInterfaces(ClassFile cf, Class clazz) {
636 if (clazz == null) {
637 return;
638 }
639
640 if (clazz.isInterface()) {
641 cf.addInterface(clazz);
642 }
643
644 addAllInterfaces(cf, clazz.getSuperclass());
645
646 Class[] interfaces = clazz.getInterfaces();
647 for (int i=0; i<interfaces.length; i++) {
648 addAllInterfaces(cf, interfaces[i]);
649 }
650 }
651
652 private static void addWrapperMethod(ClassFile cf,
653 MethodEntry methodEntry,
654 String fieldName,
655 TypeDescriptor type) {
656 Method method = methodEntry.getMethod();
657
658 // Don't override any methods in Object, especially final ones.
659 if (isDefinedInObject(method)) {
660 return;
661 }
662
663 AccessFlags flags = new AccessFlags(method.getModifiers());
664 flags.setAbstract(false);
665 flags.setFinal(true);
666 flags.setSynchronized(false);
667 flags.setNative(false);
668 flags.setStatic(false);
669
670 AccessFlags staticFlags = (AccessFlags)flags.clone();
671 staticFlags.setStatic(true);
672
673 TypeDescriptor ret = new TypeDescriptor(method.getReturnType());
674
675 Class[] paramClasses = method.getParameterTypes();
676 TypeDescriptor[] params = new TypeDescriptor[paramClasses.length];
677 for (int i=0; i<params.length; i++) {
678 params[i] = new TypeDescriptor(paramClasses[i]);
679 }
680
681 MethodInfo mi;
682 if (Modifier.isStatic(method.getModifiers())) {
683 mi = cf.addMethod
684 (staticFlags, methodEntry.getName(), ret, params);
685 }
686 else {
687 mi = cf.addMethod(flags, methodEntry.getName(), ret, params);
688 }
689
690 // Exception stuff...
691 Class[] exceptions = method.getExceptionTypes();
692 for (int i=0; i<exceptions.length; i++) {
693 mi.addException(exceptions[i].getName());
694 }
695
696 // Delegate to wrapped object.
697 CodeBuilder builder = new CodeBuilder(mi);
698
699 if (!Modifier.isStatic(method.getModifiers())) {
700 builder.loadThis();
701 builder.loadField(fieldName, type);
702 Label isNonNull = builder.createLabel();
703 builder.dup();
704 builder.ifNullBranch(isNonNull, false);
705 // Discard null field value.
706 builder.pop();
707 // Call the field access method, which in turn calls the
708 // InstanceFactory.
709 builder.loadThis();
710 builder.invokePrivate(fieldName, type, null);
711 isNonNull.setLocation();
712 }
713 LocalVariable[] locals = builder.getParameters();
714 for (int i=0; i<locals.length; i++) {
715 builder.loadLocal(locals[i]);
716 }
717 builder.invoke(method);
718
719 if (method.getReturnType() == void.class) {
720 builder.returnVoid();
721 }
722 else {
723 builder.returnValue(method.getReturnType());
724 }
725 }
726
727 private static boolean isDefinedInObject(Method method) {
728 if (method.getDeclaringClass() == Object.class) {
729 return true;
730 }
731
732 Class[] types = method.getParameterTypes();
733 String name = method.getName();
734
735 if (types.length == 0) {
736 return
737 "hashCode".equals(name) ||
738 "clone".equals(name) ||
739 "toString".equals(name) ||
740 "finalize".equals(name);
741 }
742 else {
743 return
744 types.length == 1 &&
745 types[0] == Object.class &&
746 "equals".equals(name);
747 }
748 }
749
750 private MergedClass() {
751 }
752
753 /**
754 * InstanceFactory allows merged class instances to be requested only
755 * when first needed.
756 */
757 public interface InstanceFactory {
758 /**
759 * Return a merged class instance by index. This index corresponds to
760 * the class index used when defining the MergedClass.
761 */
762 public Object getInstance(int index);
763 }
764
765 private static class ClassEntry {
766 private final Class mClazz;
767 private final String mPrefix;
768
769 public ClassEntry(Class clazz) {
770 this(clazz, null);
771 }
772
773 public ClassEntry(Class clazz, String prefix) {
774 mClazz = clazz;
775 mPrefix = prefix;
776 }
777
778 public Class getClazz() {
779 return mClazz;
780 }
781
782 public String getMethodPrefix() {
783 return mPrefix;
784 }
785
786 public int hashCode() {
787 int hash = mClazz.getName().hashCode();
788 return (mPrefix == null) ? hash : hash ^ mPrefix.hashCode();
789 }
790
791 public boolean equals(Object other) {
792 if (other instanceof ClassEntry) {
793 ClassEntry classEntry = (ClassEntry)other;
794 if (mClazz == classEntry.mClazz) {
795 if (mPrefix == null) {
796 return classEntry.mPrefix == null;
797 }
798 else {
799 return mPrefix.equals(classEntry.mPrefix);
800 }
801 }
802 }
803 return false;
804 }
805
806 public String toString() {
807 return mClazz.toString();
808 }
809 }
810
811 private static class MethodEntry {
812 private final Method mMethod;
813 private final String mName;
814 private List mParams;
815 private int mHashCode;
816
817 public MethodEntry(Method method) {
818 this(method, method.getName());
819 }
820
821 public MethodEntry(Method method, String name) {
822 mMethod = method;
823 mName = name;
824 mParams = Arrays.asList(method.getParameterTypes());
825 mHashCode = mName.hashCode() ^ mParams.hashCode();
826 }
827
828 public Method getMethod() {
829 return mMethod;
830 }
831
832 public String getName() {
833 return mName;
834 }
835
836 public boolean returnTypeDiffers(MethodEntry methodEntry) {
837 return getMethod().getReturnType() !=
838 methodEntry.getMethod().getReturnType();
839 }
840
841 public int hashCode() {
842 return mHashCode;
843 }
844
845 public boolean equals(Object other) {
846 if (!(other instanceof MethodEntry)) {
847 return false;
848 }
849 MethodEntry methodEntry = (MethodEntry)other;
850 return mName.equals(methodEntry.mName) &&
851 mParams.equals(methodEntry.mParams);
852 }
853
854 public String toString() {
855 return mMethod.toString();
856 }
857 }
858 }