Mercurial > hg > blitz_condensed
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 } |