001: /* Copyright (C) 2004 - 2007 db4objects Inc. http://www.db4o.com
002:
003: This file is part of the db4o open source object database.
004:
005: db4o is free software; you can redistribute it and/or modify it under
006: the terms of version 2 of the GNU General Public License as published
007: by the Free Software Foundation and as clarified by db4objects' GPL
008: interpretation policy, available at
009: http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
010: Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
011: Suite 350, San Mateo, CA 94403, USA.
012:
013: db4o is distributed in the hope that it will be useful, but WITHOUT ANY
014: WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: for more details.
017:
018: You should have received a copy of the GNU General Public License along
019: with this program; if not, write to the Free Software Foundation, Inc.,
020: 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
021: package EDU.purdue.cs.bloat.inline;
022:
023: import java.io.*;
024: import java.util.*;
025:
026: import EDU.purdue.cs.bloat.editor.*;
027: import EDU.purdue.cs.bloat.reflect.*;
028: import EDU.purdue.cs.bloat.util.*;
029:
030: /**
031: * Grants access to certain information about a Java program. At least one root
032: * method must be specified. From these root methods, the call graph and
033: * information such as the classes that are instantiated during the Java program
034: * is computed.
035: *
036: * <p>
037: *
038: * The construction of the call graph is in the spirit of the "Program
039: * Virtual-call Graph" presented in [Bacon97]. However, certain changes have
040: * been made to tailor it to BLOAT and Java and to make the overall
041: * representation smaller.
042: *
043: * <p>
044: *
045: * Rapid type analysis is integrated into the construction of the call graph. A
046: * virtual method is not examined until we know that its declaring class has
047: * been instantiated.
048: *
049: * <p>
050: *
051: * Some classes are created internally by the VM and are missed by our analysis.
052: * So, we maintain a set of "pre-live" classes. We consider all of their
053: * constructors to be live.
054: */
055: public class CallGraph {
056: public static boolean DEBUG = false;
057:
058: private static Set preLive; // "Pre-live" classes
059:
060: public static boolean USEPRELIVE = true;
061:
062: public static boolean USE1_2 = true;
063:
064: private Set roots; // Root methods (MethodRefs)
065:
066: private Map calls; // Maps methods to the methods they
067:
068: // call (virtual calls are not resolved)
069: private Set liveClasses; // Classes (Types) that have been instantiated
070:
071: private Map resolvesTo; // Maps methods to the methods they resolve to
072:
073: private Map blocked; // Maps types to methods blocked on those types
074:
075: List worklist; // Methods to process
076:
077: Set liveMethods; // Methods that may be executed
078:
079: InlineContext context;
080:
081: private ClassHierarchy hier;
082:
083: static void db(final String s) {
084: if (CallGraph.DEBUG) {
085: System.out.println(s);
086: }
087: }
088:
089: /**
090: * Initialize the set of classes that are "pre-live"
091: */
092: private static void init() {
093: // We can't do this in the static initializer because USE1_2 might
094: // not have the desired value.
095:
096: CallGraph.preLive = new HashSet();
097:
098: CallGraph.preLive.add("java.lang.Boolean");
099: CallGraph.preLive.add("java.lang.Class");
100: CallGraph.preLive.add("java.lang.ClassLoader");
101: CallGraph.preLive.add("java.lang.Compiler");
102: CallGraph.preLive.add("java.lang.Integer");
103: CallGraph.preLive.add("java.lang.SecurityManager");
104: CallGraph.preLive.add("java.lang.String");
105: CallGraph.preLive.add("java.lang.StringBuffer");
106: CallGraph.preLive.add("java.lang.System");
107: CallGraph.preLive.add("java.lang.StackOverflowError");
108: CallGraph.preLive.add("java.lang.Thread");
109: CallGraph.preLive.add("java.lang.ThreadGroup");
110:
111: CallGraph.preLive.add("java.io.BufferedInputStream");
112: CallGraph.preLive.add("java.io.BufferedReader");
113: CallGraph.preLive.add("java.io.BufferedOutputStream");
114: CallGraph.preLive.add("java.io.BufferedWriter");
115: CallGraph.preLive.add("java.io.File");
116: CallGraph.preLive.add("java.io.FileDescriptor");
117: CallGraph.preLive.add("java.io.InputStreamReader");
118: CallGraph.preLive.add("java.io.ObjectStreamClass");
119: CallGraph.preLive.add("java.io.OutputStreamWriter");
120: CallGraph.preLive.add("java.io.PrintStream");
121: CallGraph.preLive.add("java.io.PrintWriter");
122:
123: CallGraph.preLive.add("java.net.URL");
124:
125: CallGraph.preLive.add("java.security.Provider");
126: CallGraph.preLive.add("java.security.Security");
127:
128: CallGraph.preLive.add("java.util.Hashtable");
129: CallGraph.preLive.add("java.util.ListResourceBundle");
130: CallGraph.preLive.add("java.util.Locale");
131: CallGraph.preLive.add("java.util.Properties");
132: CallGraph.preLive.add("java.util.Stack");
133: CallGraph.preLive.add("java.util.Vector");
134:
135: CallGraph.preLive.add("java.util.zip.ZipFile");
136:
137: // Some pre-live classes are only available on JDK1.2.
138: if (CallGraph.USE1_2) {
139: CallGraph.preLive.add("java.lang.Package");
140:
141: CallGraph.preLive.add("java.lang.ref.Finalizer");
142: CallGraph.preLive.add("java.lang.ref.ReferenceQueue");
143:
144: CallGraph.preLive.add("java.io.FilePermission");
145: CallGraph.preLive.add("java.io.UnixFileSystem");
146:
147: CallGraph.preLive.add("java.net.URLClassLoader");
148:
149: CallGraph.preLive.add("java.security.SecureClassLoader");
150: CallGraph.preLive.add("java.security.AccessController");
151:
152: CallGraph.preLive.add("java.text.resources.LocaleElements");
153: CallGraph.preLive
154: .add("java.text.resources.LocaleElements_en");
155:
156: CallGraph.preLive.add("java.util.HashMap");
157:
158: CallGraph.preLive.add("java.util.jar.JarFile");
159: }
160: }
161:
162: /**
163: * Adds (the name of) a class to the set of classes that are considered to
164: * be "pre-live"
165: */
166: public static void addPreLive(final String name) {
167: if (CallGraph.preLive == null) {
168: CallGraph.init();
169: }
170: CallGraph.preLive.add(name);
171: }
172:
173: /**
174: * Removes a class from the set of "pre-live" classes
175: *
176: * @return <tt>true</tt> if the class was "pre-live"
177: */
178: public static boolean removePreLive(final String name) {
179: if (CallGraph.preLive == null) {
180: CallGraph.init();
181: }
182: return (CallGraph.preLive.remove(name));
183: }
184:
185: /**
186: * Constructor.
187: *
188: * @param context
189: * <Tt>InlineContext</tt> used to examine classes and methods.
190: *
191: * @param roots
192: * The methods (represented as <tt>MemberRef</tt>s) considered
193: * to the roots (that is, the "main" methods) of the call graph.
194: * Presumably, only static methods or constructors can be root
195: * methods.
196: */
197: public CallGraph(final InlineContext context, final Set roots) {
198: Assert.isTrue(roots != null, "A call graph must have roots");
199: Assert.isTrue(roots.size() > 0, "A call graph must have roots");
200:
201: if (CallGraph.preLive == null) {
202: CallGraph.init();
203: }
204:
205: this .context = context;
206: this .hier = context.getHierarchy();
207: this .roots = roots;
208:
209: this .liveClasses = new HashSet();
210: this .resolvesTo = new HashMap();
211: this .calls = new HashMap();
212: this .blocked = new HashMap();
213: this .worklist = new LinkedList(this .roots);
214: this .liveMethods = new HashSet();
215:
216: // To save space, make one InstructionVisitor and use it on every
217: // Instruction.
218: final CallVisitor visitor = new CallVisitor(this );
219:
220: CallGraph.db("Adding pre-live classes");
221: doPreLive();
222:
223: CallGraph.db("Constructing call graph");
224:
225: // Examine each method in the worklist. At each constructor
226: // invocation make note of the type that was created. At each
227: // method call determine all possible methods that it can resolve
228: // to. Add the methods of classes that have been instantiated to
229: // the worklist.
230: while (!worklist.isEmpty()) {
231: final MemberRef caller = (MemberRef) worklist.remove(0);
232:
233: if (liveMethods.contains(caller)) {
234: // We've already handled this method
235: continue;
236: }
237:
238: MethodEditor callerMethod = null;
239: try {
240: callerMethod = context.editMethod(caller);
241:
242: } catch (final NoSuchMethodException ex1) {
243: System.err.println("** Could not find method: "
244: + caller);
245: ex1.printStackTrace(System.err);
246: System.exit(1);
247: }
248:
249: // If the method is abstract or native, we can't do anything
250: // with it.
251: if (callerMethod.isAbstract()) {
252: continue;
253: }
254:
255: liveMethods.add(caller);
256:
257: if (callerMethod.isNative()) {
258: // We still want native methods to be live
259: continue;
260: }
261:
262: CallGraph.db("\n Examining method " + caller);
263:
264: final Set callees = new HashSet(); // Methods called by caller
265: calls.put(caller, callees);
266:
267: // If the method is static or is a constructor, the classes
268: // static initializer method must have been called. Make note
269: // of this.
270: if (callerMethod.isStatic() || callerMethod.isConstructor()) {
271: addClinit(callerMethod.declaringClass().type());
272: }
273:
274: // Examine the instructions in the caller method.
275: final Iterator code = callerMethod.code().iterator();
276: visitor.setCaller(callerMethod);
277: while (code.hasNext()) {
278: final Object o = code.next();
279: if (o instanceof Instruction) {
280: final Instruction inst = (Instruction) o;
281: inst.visit(visitor);
282: }
283: }
284: }
285:
286: // We're done constructing the call graph. Try to free up some
287: // memory.
288: blocked = null;
289: }
290:
291: /**
292: * Helper method to add the static initializers and all constructors of the
293: * pre-live classes to the worklist, etc.
294: */
295: private void doPreLive() {
296: if (!CallGraph.USEPRELIVE) {
297: return;
298: }
299:
300: CallGraph.db("Making constructors of pre-live classes live");
301:
302: final Iterator iter = CallGraph.preLive.iterator();
303: while (iter.hasNext()) {
304: String name = (String) iter.next();
305: CallGraph.db(" " + name + " is pre-live");
306: name = name.replace('.', '/');
307:
308: ClassEditor ce = null;
309: try {
310: ce = context.editClass(name);
311:
312: } catch (final ClassNotFoundException ex1) {
313: System.err.println("** Cannot find pre-live class: "
314: + name);
315: ex1.printStackTrace(System.err);
316: System.exit(1);
317: }
318:
319: // Make class and static initializer live
320: liveClasses.add(ce.type());
321: addClinit(ce.type());
322:
323: // Make all constructors live
324: final MethodInfo[] methods = ce.methods();
325: for (int i = 0; i < methods.length; i++) {
326: final MethodEditor method = context
327: .editMethod(methods[i]);
328: if (method.name().equals("<init>")) {
329: CallGraph.db(" " + method);
330: worklist.add(method.memberRef());
331: }
332: }
333: }
334: }
335:
336: /**
337: * Adds the static initializer for a given <tt>Type</tt> to the worklist.
338: */
339: void addClinit(final Type type) {
340: try {
341: final ClassEditor ce = context.editClass(type);
342:
343: final MethodInfo[] methods = ce.methods();
344: for (int i = 0; i < methods.length; i++) {
345: final MethodEditor clinit = context
346: .editMethod(methods[i]);
347: if (clinit.name().equals("<clinit>")) {
348: worklist.add(clinit.memberRef());
349: context.release(clinit.methodInfo());
350: break;
351: }
352: context.release(clinit.methodInfo());
353: }
354: context.release(ce.classInfo());
355:
356: } catch (final ClassNotFoundException ex1) {
357: System.err.println("** Could not find class for " + type);
358: ex1.printStackTrace(System.err);
359: System.exit(1);
360: }
361: }
362:
363: /**
364: * Handles a virtual call. Determines all possible methods the call could
365: * resolve to. Adds the method whose declaring classes are live to the
366: * worklist. Blocks the rest on their declaring types.
367: */
368: void doVirtual(final MethodEditor caller, final MemberRef callee) {
369: // Figure out which methods the callee can resolve to.
370: final Iterator resolvesToWith = hier.resolvesToWith(callee)
371: .iterator();
372:
373: while (resolvesToWith.hasNext()) {
374: final ClassHierarchy.ResolvesToWith rtw = (ClassHierarchy.ResolvesToWith) resolvesToWith
375: .next();
376: CallGraph.db(" resolves to " + rtw.method);
377:
378: // Add all possible non-abstract methods to the call graph.
379: // This way, when a blocked method becomes unblocked, it will
380: // still be in the call graph.
381: addCall(caller, rtw.method);
382:
383: Iterator rTypes = rtw.rTypes.iterator();
384: boolean isLive = false; // Is one of the rTypes live?
385: while (rTypes.hasNext()) {
386: final Type rType = (Type) rTypes.next();
387: if (liveClasses.contains(rType)) {
388: isLive = true;
389: CallGraph.db(" Method " + rtw.method
390: + " is live");
391: worklist.add(rtw.method);
392: break;
393: }
394: }
395:
396: if (!isLive) {
397: // If none of the receiver types is live, then the method is
398: // blocked on all possible receiver types.
399: rTypes = rtw.rTypes.iterator();
400: final StringBuffer sb = new StringBuffer();
401: while (rTypes.hasNext()) {
402: final Type rType = (Type) rTypes.next();
403: Set blockedMethods = (Set) blocked.get(rType);
404: if (blockedMethods == null) {
405: blockedMethods = new HashSet();
406: blocked.put(rType, blockedMethods);
407: }
408: blockedMethods.add(rtw.method);
409: sb.append(rType.toString());
410: if (rTypes.hasNext()) {
411: sb.append(',');
412: }
413: }
414: CallGraph.db(" Blocked " + rtw.method + " on "
415: + sb);
416: }
417: }
418: }
419:
420: /**
421: * Makes note of one method calling another. This does not make the method
422: * live.
423: */
424: void addCall(final MethodEditor callerMethod, final MemberRef callee) {
425: // Just maintain the calls mapping
426: final MemberRef caller = callerMethod.memberRef();
427: Set callees = (Set) this .calls.get(caller);
428: if (callees == null) {
429: callees = new HashSet();
430: this .calls.put(caller, callees);
431: }
432: callees.add(callee);
433: }
434:
435: /**
436: * Marks a <tt>Type</tt> as being lives. It also unblocks any methods that
437: * were blocked on the type.
438: */
439: void makeLive(final Type type) {
440: if (this .liveClasses.contains(type)) {
441: return;
442: }
443:
444: // Make type live and unblock all methods blocked on it
445: CallGraph.db(" Making " + type + " live");
446: liveClasses.add(type);
447: final Set blockedMethods = (Set) blocked.remove(type);
448: if (blockedMethods != null) {
449: final Iterator iter = blockedMethods.iterator();
450: while (iter.hasNext()) {
451: final MemberRef method = (MemberRef) iter.next();
452: CallGraph.db(" Unblocking " + method);
453: worklist.add(method);
454: }
455: }
456: }
457:
458: /**
459: * Returns the methods (<tt>MemberRef</tt>s) to which a given method
460: * could resolve. Only live methods are taken into account. The methods are
461: * sorted such that overriding methods appear before overriden methods.
462: */
463: public Set resolvesTo(final MemberRef method) {
464: TreeSet resolvesTo = (TreeSet) this .resolvesTo.get(method);
465:
466: if (resolvesTo == null) {
467: resolvesTo = new TreeSet(new MemberRefComparator(context));
468: this .resolvesTo.put(method, resolvesTo);
469:
470: final Set liveMethods = this .liveMethods();
471: final Iterator rtws = hier.resolvesToWith(method)
472: .iterator();
473: while (rtws.hasNext()) {
474: final ClassHierarchy.ResolvesToWith rtw = (ClassHierarchy.ResolvesToWith) rtws
475: .next();
476: if (liveMethods.contains(rtw.method)) {
477: resolvesTo.add(rtw.method);
478: }
479: }
480: }
481:
482: // Return a clone so that the set may safely be modified
483: return ((Set) resolvesTo.clone());
484: }
485:
486: /**
487: * Returns the methods (<tt>MemberRef</tt>s) to which a given method
488: * could resolve given that the receiver is in a certain set of types. Only
489: * live methods are taken into account. The methods are sorted such that
490: * overriding methods appear before overriden methods.
491: */
492: public Set resolvesTo(final MemberRef method, final Set rTypes) {
493: if (rTypes.isEmpty()) {
494: return (resolvesTo(method));
495: }
496:
497: // Since we're only dealing with a subset of types, don't bother
498: // with the caching stuff.
499: final TreeSet resolvesTo = new TreeSet(new MemberRefComparator(
500: context));
501:
502: final Set liveMethods = this .liveMethods();
503: final Iterator rtws = hier.resolvesToWith(method).iterator();
504: while (rtws.hasNext()) {
505: final ClassHierarchy.ResolvesToWith rtw = (ClassHierarchy.ResolvesToWith) rtws
506: .next();
507: if (liveMethods.contains(rtw.method)) {
508: final HashSet clone = (HashSet) rtw.rTypes.clone();
509:
510: clone.retainAll(rTypes);
511: if (!clone.isEmpty()) {
512: // Only keep method that have at least one possible
513: // receiver type in rTypes
514: resolvesTo.add(rtw.method);
515: }
516: }
517: }
518:
519: // Return a clone so that the set may safely be modified
520: return ((Set) resolvesTo.clone());
521: }
522:
523: /**
524: * Returns the set of methods (<tt>MemberRef</tt>s) that the
525: * construction algorithm has deemed to be live.
526: */
527: public Set liveMethods() {
528: // Not all of the methods in the calls mapping are necessarily
529: // live. So, we have to maintain a separate set.
530: return (this .liveMethods);
531: }
532:
533: /**
534: * Returns the root methods (<tt>MemberRef</tt>s) of the call graph.
535: */
536: public Set roots() {
537: return (this .roots);
538: }
539:
540: /**
541: * Returns the set of classes (<tt>Type</tt>s) that are instantiated in
542: * the program.
543: */
544: public Set liveClasses() {
545: return (this .liveClasses);
546: }
547:
548: /**
549: * Prints a textual prepresentation of the <tt>CallGraph</tt> to a
550: * <tt>PrintWriter</tt>.
551: *
552: * @param out
553: * To where we print
554: * @param printLeaves
555: * If <tt>true</tt>, leaf methods (methods that do not call
556: * any other methods) are printed
557: */
558: public void print(final PrintWriter out, boolean printLeaves) {
559:
560: final Iterator callers = calls.keySet().iterator();
561: while (callers.hasNext()) {
562: final MemberRef caller = (MemberRef) callers.next();
563:
564: final Iterator callees = ((Set) calls.get(caller))
565: .iterator();
566:
567: if (!printLeaves && !callees.hasNext()) {
568: continue;
569: }
570:
571: out.print(caller.declaringClass() + "." + caller.name()
572: + caller.type());
573: if (roots.contains(caller)) {
574: out.print(" (root)");
575: }
576: out.println("");
577:
578: while (callees.hasNext()) {
579: final MemberRef callee = (MemberRef) callees.next();
580:
581: // Only print live methods
582: if (!calls.containsKey(callee)) {
583: continue;
584: }
585:
586: out.println(" " + callee.declaringClass() + "."
587: + callee.name() + callee.type());
588: }
589:
590: out.println("");
591: }
592: }
593:
594: /**
595: * Prints a summary of the call graph. Including the classes that are live
596: * and which methods are blocked.
597: */
598: public void printSummary(final PrintWriter out) {
599: out.println("Instantiated classes:");
600: final Iterator instantiated = this .liveClasses.iterator();
601: while (instantiated.hasNext()) {
602: final Type type = (Type) instantiated.next();
603: out.println(" " + type.toString());
604: }
605:
606: out.println("\nBlocked methods:");
607: if (blocked != null) {
608: final Iterator types = blocked.keySet().iterator();
609: while (types.hasNext()) {
610: final Type type = (Type) types.next();
611: out.println(" " + type);
612:
613: final Set set = (Set) blocked.get(type);
614: if (set != null) {
615: final Iterator methods = set.iterator();
616: while (methods.hasNext()) {
617: final MemberRef method = (MemberRef) methods
618: .next();
619: out.println(" " + method);
620: }
621: }
622: }
623: }
624:
625: out.println("\nCall graph:");
626: this .print(out, false);
627: }
628:
629: }
630:
631: /**
632: * <tt>CallVisitor</tt> examines the instructions in a method and notices what
633: * methods are called and which classes are created.
634: */
635: class CallVisitor extends InstructionAdapter {
636: MethodEditor caller;
637:
638: CallGraph cg;
639:
640: boolean firstSpecial; // Are we dealing with the first invokespecial?
641:
642: private static void db(final String s) {
643: CallGraph.db(s);
644: }
645:
646: public CallVisitor(final CallGraph cg) {
647: this .cg = cg;
648: }
649:
650: public void setCaller(final MethodEditor caller) {
651: this .caller = caller;
652: if (caller.isConstructor()) {
653: this .firstSpecial = true;
654: } else {
655: this .firstSpecial = false;
656: }
657: }
658:
659: public void visit_invokevirtual(final Instruction inst) {
660: CallVisitor.db("\n Visiting Call: " + inst);
661:
662: this .firstSpecial = false;
663:
664: // Call doVirtual to determine which methods this call may resolve
665: // to are live.
666: final MemberRef callee = (MemberRef) inst.operand();
667: cg.doVirtual(caller, callee);
668: }
669:
670: public void visit_invokeinterface(final Instruction inst) {
671: CallVisitor.db("\n Visiting Call: " + inst);
672:
673: this .firstSpecial = false;
674:
675: // Pretty much the same as invokevirtual
676: final MemberRef callee = (MemberRef) inst.operand();
677: cg.doVirtual(caller, callee);
678: }
679:
680: public void visit_invokestatic(final Instruction inst) {
681: CallVisitor.db("\n Visiting call: " + inst);
682:
683: this .firstSpecial = false;
684:
685: // There's not a lot to do with static methods since there is no
686: // dynamic dispatch.
687: final MemberRef callee = (MemberRef) inst.operand();
688: cg.addCall(caller, callee);
689: cg.worklist.add(callee);
690: }
691:
692: public void visit_invokespecial(final Instruction inst) {
693: CallVisitor.db("\n Visiting call: " + inst);
694:
695: // Recall that invokespecial is used to call constructors, private
696: // methods, and "super" methods. There is no dynamic dispatch for
697: // special methods.
698: final MemberRef callee = (MemberRef) inst.operand();
699:
700: MethodEditor calleeMethod = null;
701:
702: try {
703: calleeMethod = cg.context.editMethod(callee);
704:
705: } catch (final NoSuchMethodException ex1) {
706: System.err.println("** Couldn't find method: " + callee);
707: System.exit(1);
708: }
709:
710: if (calleeMethod.isSynchronized() || calleeMethod.isNative()) {
711: // Calls to synchronized and native methods are virtual
712: cg.doVirtual(caller, callee);
713:
714: } else {
715: // Calls to everything else (superclass methods, private
716: // methods, etc.) do not involve a dynamic dispatch and can be
717: // treated like a static method.
718: cg.addCall(caller, callee);
719: cg.worklist.add(callee);
720: }
721:
722: cg.context.release(calleeMethod.methodInfo());
723: }
724:
725: public void visit_getstatic(final Instruction inst) {
726: // Referencing a static fields implies that its class's static
727: // initializer has been invoked.
728: CallVisitor.db("\n Referencing static field " + inst);
729: final MemberRef field = (MemberRef) inst.operand();
730: cg.addClinit(field.declaringClass());
731: }
732:
733: public void visit_putstatic(final Instruction inst) {
734: // Referencing a static field implies that its class's static
735: // initializer has been invoked.
736: CallVisitor.db("\n Referencing static field " + inst);
737: final MemberRef field = (MemberRef) inst.operand();
738: cg.addClinit(field.declaringClass());
739: }
740:
741: public void visit_new(final Instruction inst) {
742: // The new instruction instantiates a type and thus makes it live.
743: final Type type = (Type) inst.operand();
744: cg.makeLive(type);
745: }
746: }
747:
748: /**
749: * Compares <tt>MemberRef</tt>s such that overriding methods are less than
750: * overridden methods.
751: */
752: class MemberRefComparator implements Comparator {
753: TypeComparator c;
754:
755: public MemberRefComparator(final InlineContext context) {
756: c = new TypeComparator(context);
757: }
758:
759: public int compare(final Object o1, final Object o2) {
760: Assert.isTrue(o1 instanceof MemberRef, o1
761: + " is not a MemberRef");
762: Assert.isTrue(o2 instanceof MemberRef, o2
763: + " is not a MemberRef");
764:
765: final MemberRef ref1 = (MemberRef) o1;
766: final MemberRef ref2 = (MemberRef) o2;
767:
768: final Type type1 = ref1.declaringClass();
769: final Type type2 = ref2.declaringClass();
770:
771: return (c.compare(type1, type2));
772: }
773:
774: public boolean compareTo(final Object other) {
775: return (other instanceof MemberRefComparator);
776: }
777: }
|