/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ package EDU.purdue.cs.bloat.tools; import java.io.*; import java.util.*; import EDU.purdue.cs.bloat.benchmark.*; import EDU.purdue.cs.bloat.cfg.*; import EDU.purdue.cs.bloat.codegen.*; import EDU.purdue.cs.bloat.context.*; import EDU.purdue.cs.bloat.editor.*; import EDU.purdue.cs.bloat.file.*; import EDU.purdue.cs.bloat.inline.*; import EDU.purdue.cs.bloat.optimize.*; import EDU.purdue.cs.bloat.reflect.*; import EDU.purdue.cs.bloat.trans.*; import EDU.purdue.cs.bloat.tree.*; import EDU.purdue.cs.bloat.util.*; /** * This program is used to BLOAT Java programs. In particular, we use it to * BLOAT the programs used to benchmark BLOAT. This program is intended to be * run multiple times to generate multiple BLOATed programs. */ public class BloatBenchmark { public static boolean TRACE = false; private static boolean INLINE = false; private static boolean INTRA = false; private static boolean PEEPHOLE = false; private static boolean VERIFY = true; private static boolean SPECIALIZE = false; private static boolean SUN = false; private static boolean USE1_1 = false; private static boolean CHECK = true; private static boolean TIMES = false; private static final PrintWriter err = new PrintWriter(System.err, true); private static final Set CLASSES = new HashSet(); private static String statsFile = null; private static String timesFile = null; private static int DEPTH = 2; // No. calls deep private static int SIZE = 1000; // Max size of methods private static int MORPH = -1; // Max "morphosity" of virtual calls private static int CALLEE_SIZE = -1; private static final List SKIP = new ArrayList(); // Classes that are // specifically not // optimized private static void tr(final String s) { if (BloatBenchmark.TRACE) { System.out.println(s); } } private static void usage() { BloatBenchmark.err .println("java TestSpecialize [options] classNames outputDir"); BloatBenchmark.err.println("where [options] are:"); BloatBenchmark.err .println(" -calleeSize size Max method size to inline"); BloatBenchmark.err .println(" -classpath path Classpath is always prepended"); BloatBenchmark.err.println(" -depth depth Max inline depth"); BloatBenchmark.err .println(" -inline Inline calls to static methods"); BloatBenchmark.err .println(" -intra Intraprocedural BLOAT"); BloatBenchmark.err .println(" -lookIn dir Look for classes here"); BloatBenchmark.err .println(" -morph morph Max morphosity of call sites"); BloatBenchmark.err.println(" -no-verify Don't verify CFG"); BloatBenchmark.err .println(" -no-opt-stack Don't optimize stack usage"); BloatBenchmark.err .println(" -no-stack-vars Don't use stack vars in CFG"); BloatBenchmark.err .println(" -no-stack-alloc Don't try to push locals onto the operand stack"); BloatBenchmark.err .println(" -peel-loops <n|all>" + "\n Peel innermost loops to enable code hoisting" + "\n (n >= 0 is the maximum loop level to peel)"); BloatBenchmark.err .println(" -no-pre Don't perform partial redundency elimination"); BloatBenchmark.err .println(" -no-dce Don't perform dead code elimination"); BloatBenchmark.err .println(" -no-prop Don't perform copy and constant propagation"); BloatBenchmark.err .println(" -no-color Don't do graph coloring"); BloatBenchmark.err .println(" -peephole Perform peephole after inter"); BloatBenchmark.err.println(" -size size Max method size"); BloatBenchmark.err .println(" -specialize Specialize virtual method calls"); BloatBenchmark.err.println(" -stats statsFile Generate stats"); BloatBenchmark.err.println(" -sun Include sun packages"); BloatBenchmark.err.println(" -times timesFile Print timings"); BloatBenchmark.err .println(" -trace Print trace information"); BloatBenchmark.err .println(" -no-check Don't check that my options 'make sense'"); BloatBenchmark.err.println(" -skip <class|package.*>" + "\n Skip the given class or package"); BloatBenchmark.err.println(" -1.1 BLOAT for JDK1.1"); BloatBenchmark.err.println(" -1.2 BLOAT for JDK1.2"); BloatBenchmark.err.println(""); System.exit(1); } public static void main(final String[] args) { String CLASSPATH = null; String outputDirName = null; String lookIn = null; // Parse the command line for (int i = 0; i < args.length; i++) { if (args[i].equals("-trace")) { BloatBenchmark.TRACE = true; PersistentBloatContext.DB_COMMIT = true; } else if (args[i].equals("-calleeSize")) { if (++i >= args.length) { BloatBenchmark.err.println("** No callee size specified"); BloatBenchmark.usage(); } try { BloatBenchmark.CALLEE_SIZE = Integer.parseInt(args[i]); } catch (final NumberFormatException ex33) { BloatBenchmark.err.println("** Bad number: " + args[i]); BloatBenchmark.usage(); } } else if (args[i].startsWith("-classpath")) { if (++i >= args.length) { BloatBenchmark.err.println("** No classpath specified"); BloatBenchmark.usage(); } // If there is more than one -classpath append it to the // current one. That way the CLASSPATH reflects the order in // which the options came on the command line. if (CLASSPATH == null) { CLASSPATH = args[i]; } else { CLASSPATH += File.pathSeparator + args[i]; } } else if (args[i].equals("-no-stack-alloc")) { Main.STACK_ALLOC = false; } else if (args[i].equals("-peel-loops")) { if (++i >= args.length) { BloatBenchmark.usage(); } final String n = args[i]; if (n.equals("all")) { FlowGraph.PEEL_LOOPS_LEVEL = FlowGraph.PEEL_ALL_LOOPS; } else { try { FlowGraph.PEEL_LOOPS_LEVEL = Integer.parseInt(n); if (FlowGraph.PEEL_LOOPS_LEVEL < 0) { BloatBenchmark.usage(); } } catch (final NumberFormatException ex) { BloatBenchmark.usage(); } } } else if (args[i].equals("-no-color")) { Liveness.UNIQUE = true; } else if (args[i].equals("-no-dce")) { Main.DCE = false; } else if (args[i].equals("-no-prop")) { Main.PROP = false; } else if (args[i].equals("-no-pre")) { Main.PRE = false; } else if (args[i].equals("-no-check")) { BloatBenchmark.CHECK = false; } else if (args[i].equals("-depth")) { if (++i >= args.length) { BloatBenchmark.err.println("** No depth specified"); BloatBenchmark.usage(); } try { BloatBenchmark.DEPTH = Integer.parseInt(args[i]); } catch (final NumberFormatException ex33) { BloatBenchmark.err.println("** Bad number: " + args[i]); BloatBenchmark.usage(); } } else if (args[i].equals("-inline")) { // Inline calls to static methods BloatBenchmark.INLINE = true; } else if (args[i].equals("-intra")) { BloatBenchmark.INTRA = true; } else if (args[i].equals("-lookIn")) { if (++i >= args.length) { BloatBenchmark.err.println("** No directory specified"); BloatBenchmark.usage(); } if (lookIn != null) { lookIn += File.pathSeparator + args[i]; } else { lookIn = args[i]; } } else if (args[i].equals("-morph")) { if (++i >= args.length) { BloatBenchmark.err.println("** No morphosity specified"); BloatBenchmark.usage(); } try { BloatBenchmark.MORPH = Integer.parseInt(args[i]); } catch (final NumberFormatException ex33) { BloatBenchmark.err.println("** Bad number: " + args[i]); BloatBenchmark.usage(); } } else if (args[i].equals("-noinline")) { // Don't perform inlining, just specialize BloatBenchmark.INLINE = false; } else if (args[i].equals("-peephole")) { // Perform peephole optimizations when doing interprocedural // stuff BloatBenchmark.PEEPHOLE = true; } else if (args[i].equals("-size")) { if (++i >= args.length) { BloatBenchmark.err.println("** No size specified"); BloatBenchmark.usage(); } try { BloatBenchmark.SIZE = Integer.parseInt(args[i]); } catch (final NumberFormatException ex33) { BloatBenchmark.err.println("** Bad number: " + args[i]); BloatBenchmark.usage(); } } else if (args[i].equals("-specialize")) { // Specialize virtual method call sites BloatBenchmark.SPECIALIZE = true; } else if (args[i].equals("-stats")) { if (++i >= args.length) { BloatBenchmark.err.println("** No stats file specified"); BloatBenchmark.usage(); } BloatBenchmark.statsFile = args[i]; } else if (args[i].equals("-sun")) { // Optimize sun packages BloatBenchmark.SUN = true; } else if (args[i].equals("-times")) { BloatBenchmark.TIMES = true; if (++i >= args.length) { BloatBenchmark.err.println("** No times file specified"); BloatBenchmark.usage(); } BloatBenchmark.timesFile = args[i]; } else if (args[i].equals("-no-verify")) { BloatBenchmark.VERIFY = false; } else if (args[i].equals("-no-opt-stack")) { CodeGenerator.OPT_STACK = false; } else if (args[i].equals("-no-stack-vars")) { Tree.USE_STACK = false; } else if (args[i].equals("-skip")) { if (++i >= args.length) { BloatBenchmark.usage(); } String pkg = args[i]; // Account for class file name on command line if (pkg.endsWith(".class")) { pkg = pkg.substring(0, pkg.lastIndexOf('.')); } BloatBenchmark.SKIP.add(pkg.replace('.', '/')); } else if (args[i].equals("-1.1")) { // There are some classes that we don't want to be pre-live. // They don't exist in JDK1.1. BloatBenchmark.USE1_1 = true; CallGraph.USE1_2 = false; } else if (args[i].equals("-1.2")) { CallGraph.USE1_2 = true; if (lookIn != null) { lookIn += File.separator + "1.2"; } } else if (args[i].startsWith("-")) { BloatBenchmark.err .println("** Unrecognized option: " + args[i]); BloatBenchmark.usage(); } else if (i == args.length - 1) { outputDirName = args[i]; } else { BloatBenchmark.CLASSES.add(args[i]); } } if (BloatBenchmark.CLASSES.isEmpty()) { BloatBenchmark.err.println("** No classes specified"); BloatBenchmark.usage(); } if (outputDirName == null) { BloatBenchmark.err.println("** No output directory specified"); BloatBenchmark.usage(); } // Make sure the options the user entered make sense if (BloatBenchmark.CHECK) { BloatBenchmark.checkOptions(); } if (BloatBenchmark.USE1_1) { // Don't generate stats for 1.1 BloatBenchmark.statsFile = null; } if (lookIn != null) { CLASSPATH = lookIn + File.pathSeparator + CLASSPATH; } final StringBuffer sb = new StringBuffer(); for (int i = 0; i < args.length; i++) { sb.append(args[i] + " "); } BloatBenchmark.tr("BLOATing with command line: " + sb); BloatContext context = null; float systemStart = 0.0F; float systemDelta = 0.0F; float systemEnd = 0.0F; float systemTotal = 0.0F; float userStart = 0.0F; float userDelta = 0.0F; float userEnd = 0.0F; float userTotal = 0.0F; PrintWriter times = null; if (BloatBenchmark.TIMES) { try { times = new PrintWriter( new FileWriter(BloatBenchmark.timesFile), true); } catch (final IOException ex) { times = new PrintWriter(System.out, true); } } if (BloatBenchmark.INTRA) { BloatBenchmark.tr("Intraprocedural BLOAT"); // First compute the roots of the call graph. Figure out which // methods are live. context = BloatBenchmark.makeContext(CLASSPATH, null); final Collection liveMethods = BloatBenchmark.liveMethods( BloatBenchmark.CLASSES, context); // Run intraprocedural BLOAT on the live methods. BloatBenchmark.tr(liveMethods.size() + " live methods"); context = BloatBenchmark.makeContext(CLASSPATH, outputDirName); BloatBenchmark.intraBloat(liveMethods, context); } else { BloatBenchmark.tr("Interprocedural BLOAT"); if (BloatBenchmark.TIMES) { Times.snapshot(); systemStart = Times.systemTime(); userStart = Times.userTime(); } // Do the interprocedural BLOATing context = BloatBenchmark.makeContext(CLASSPATH, outputDirName); BloatBenchmark.liveMethods(BloatBenchmark.CLASSES, context); if (BloatBenchmark.TIMES) { // Take a measurement Times.snapshot(); systemEnd = Times.systemTime(); userEnd = Times.userTime(); systemDelta = systemEnd - systemStart; userDelta = userEnd - userStart; systemStart = systemEnd; userStart = userEnd; systemTotal += systemDelta; userTotal += userDelta; times.println("Call graph construction"); times.println(" User: " + userDelta); times.println(" System: " + systemDelta); } if (BloatBenchmark.SPECIALIZE) { BloatBenchmark.specialize(context); } if (BloatBenchmark.TIMES) { // Take a measurement Times.snapshot(); systemEnd = Times.systemTime(); userEnd = Times.userTime(); systemDelta = systemEnd - systemStart; userDelta = userEnd - userStart; systemStart = systemEnd; userStart = userEnd; systemTotal += systemDelta; userTotal += userDelta; times.println("Call site specialization"); times.println(" User: " + userDelta); times.println(" System: " + systemDelta); } if (BloatBenchmark.INLINE) { BloatBenchmark.inline(context); } if (BloatBenchmark.TIMES) { // Take a measurement Times.snapshot(); systemEnd = Times.systemTime(); userEnd = Times.userTime(); systemDelta = systemEnd - systemStart; userDelta = userEnd - userStart; systemStart = systemEnd; userStart = userEnd; systemTotal += systemDelta; userTotal += userDelta; times.println("Method inlining"); times.println(" User: " + userDelta); times.println(" System: " + systemDelta); } if (BloatBenchmark.PEEPHOLE) { BloatBenchmark.peephole(context); } } // Commit dirty data BloatBenchmark.tr("Committing dirty methods"); context.commitDirty(); if (BloatBenchmark.TIMES) { // Take a measurement Times.snapshot(); systemEnd = Times.systemTime(); userEnd = Times.userTime(); systemDelta = systemEnd - systemStart; userDelta = userEnd - userStart; systemStart = systemEnd; userStart = userEnd; systemTotal += systemDelta; userTotal += userDelta; times.println("Committal"); times.println(" User: " + userDelta); times.println(" System: " + systemDelta); } if (BloatBenchmark.TIMES) { times.println("Total"); times.println(" User: " + userTotal); times.println(" System: " + systemTotal); } if (BloatBenchmark.statsFile != null) { final InlineStats stats = context.getInlineStats(); PrintWriter statsOut = null; try { statsOut = new PrintWriter(new FileWriter( BloatBenchmark.statsFile), true); } catch (final IOException ex) { statsOut = new PrintWriter(System.out, true); } stats.printSummary(statsOut); } BloatBenchmark.tr("Finished"); } /** * Creates a <tt>BloatContext</tt> that loads classes from a given * CLASSPATH. */ static BloatContext makeContext(final String classpath, final String outputDirName) { final ClassFileLoader loader = new ClassFileLoader(); if (classpath != null) { loader.prependClassPath(classpath); } // if(TRACE) { // loader.setVerbose(true); // } BloatBenchmark.tr(" Creating a BloatContext for CLASSPATH: " + loader.getClassPath()); if (outputDirName != null) { loader.setOutputDir(new File(outputDirName)); } final BloatContext context = new CachingBloatContext(loader, BloatBenchmark.CLASSES, true); // Always ignore the sun packages and the opj stuff for // interprocedural stuff if (!BloatBenchmark.SUN) { context.addIgnorePackage("sun"); } context.addIgnorePackage("java.lang.ref"); context.addIgnorePackage("org.opj.system"); if (BloatBenchmark.USE1_1) { // Toba can't deal with java.lang.Character context.addIgnoreClass(Type.getType("Ljava/lang/Character;")); } return (context); } /** * Returns the live methods of a program whose root methods are the * <tt>main</tt> method of a set of classes. * * @param classes * Names of classes containing root methods * @param context * Repository for accessing BLOAT stuff * @return The <tt>MemberRef</tt>s of the live methods */ private static Collection liveMethods(final Collection classes, final BloatContext context) { // Determine the roots of the call graph final Set roots = new HashSet(); Iterator iter = classes.iterator(); while (iter.hasNext()) { final String className = (String) iter.next(); try { final ClassEditor ce = context.editClass(className); final MethodInfo[] methods = ce.methods(); for (int i = 0; i < methods.length; i++) { final MethodEditor me = context.editMethod(methods[i]); if (!me.name().equals("main")) { continue; } BloatBenchmark.tr(" Root " + ce.name() + "." + me.name() + me.type()); roots.add(me.memberRef()); } } catch (final ClassNotFoundException ex1) { BloatBenchmark.err.println("** Could not find class: " + ex1.getMessage()); System.exit(1); } } if (roots.isEmpty()) { BloatBenchmark.err.print("** No main method found in classes: "); iter = classes.iterator(); while (iter.hasNext()) { final String name = (String) iter.next(); BloatBenchmark.err.print(name); if (iter.hasNext()) { BloatBenchmark.err.print(", "); } } BloatBenchmark.err.println(""); } context.setRootMethods(roots); final CallGraph cg = context.getCallGraph(); final Set liveMethods = new TreeSet(new MemberRefComparator()); liveMethods.addAll(cg.liveMethods()); return (liveMethods); } /** * Specializes the live methods in a program. */ private static void specialize(final BloatContext context) { final CallGraph cg = context.getCallGraph(); final Set liveMethods = new TreeSet(new MemberRefComparator()); liveMethods.addAll(cg.liveMethods()); // Specialize all possible methods final InlineStats stats = context.getInlineStats(); if (BloatBenchmark.statsFile != null) { Specialize.STATS = true; stats.setConfigName("BloatBenchmark"); } if (BloatBenchmark.MORPH != -1) { Specialize.MAX_MORPH = BloatBenchmark.MORPH; } final Specialize spec = new Specialize(context); if (Specialize.STATS) { stats.noteLiveMethods(liveMethods.size()); stats.noteLiveClasses(cg.liveClasses().size()); } BloatBenchmark.tr("Specializing live methods"); final Iterator iter = liveMethods.iterator(); for (int count = 0; iter.hasNext(); count++) { try { final MethodEditor live = context.editMethod((MemberRef) iter .next()); if (context.ignoreMethod(live.memberRef())) { // Don't display ignored methods, it's misleading. continue; } BloatBenchmark.tr(" " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type()); spec.specialize(live); } catch (final NoSuchMethodException ex2) { BloatBenchmark.err.println("** Could not find method " + ex2.getMessage()); System.exit(1); } } } /** * Inlines calls to static methods in the live methods of a given program. */ private static void inline(final BloatContext context) { final Set liveMethods = new TreeSet(new MemberRefComparator()); final CallGraph cg = context.getCallGraph(); liveMethods.addAll(cg.liveMethods()); BloatBenchmark.tr("Inlining " + liveMethods.size() + " live methods"); if (BloatBenchmark.CALLEE_SIZE != -1) { Inline.CALLEE_SIZE = BloatBenchmark.CALLEE_SIZE; } final Iterator iter = liveMethods.iterator(); for (int count = 0; BloatBenchmark.INLINE && iter.hasNext(); count++) { try { final MethodEditor live = context.editMethod((MemberRef) iter .next()); if (context.ignoreMethod(live.memberRef())) { // Don't display ignored methods, it's misleading. continue; } BloatBenchmark.tr(" " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type()); final Inline inline = new Inline(context, BloatBenchmark.SIZE); inline.setMaxCallDepth(BloatBenchmark.DEPTH); inline.inline(live); // Commit here in an attempt to conserve memory context.commit(live.methodInfo()); context.release(live.methodInfo()); } catch (final NoSuchMethodException ex3) { BloatBenchmark.err.println("** Could not find method " + ex3.getMessage()); System.exit(1); } } } /** * Performs peephole optimizations on a program's live methods. */ private static void peephole(final BloatContext context) { final Set liveMethods = new TreeSet(new MemberRefComparator()); final CallGraph cg = context.getCallGraph(); liveMethods.addAll(cg.liveMethods()); // Perform peephole optimizations. We do this separately because // some peephole optimizations do things to the stack that // inlining doesn't like. For instance, a peephole optimizations // might make it so that a method has a non-empty stack upon // return. Inlining will barf at the sight of this. BloatBenchmark.tr("Performing peephole optimizations"); final Iterator iter = liveMethods.iterator(); while (BloatBenchmark.PEEPHOLE && iter.hasNext()) { try { final MethodEditor live = context.editMethod((MemberRef) iter .next()); Peephole.transform(live); context.commit(live.methodInfo()); context.release(live.methodInfo()); } catch (final NoSuchMethodException ex314) { BloatBenchmark.err.println("** Could not find method " + ex314.getMessage()); ex314.printStackTrace(System.err); System.exit(1); } } } /** * Performs intraprocedural BLOAT on a program's live methods. * * @param liveMethods * Should be alphabetized. This way we can commit a class once * we've BLOATed all of its methods. */ private static void intraBloat(final Collection liveMethods, final BloatContext context) { ClassEditor prevClass = null; final Iterator iter = liveMethods.iterator(); for (int count = 0; iter.hasNext(); count++) { MethodEditor live = null; ClassEditor ce = null; // Hack to make sure commit happens try { live = context.editMethod((MemberRef) iter.next()); ce = context.editClass(live.declaringClass().classInfo()); } catch (final NoSuchMethodException ex3) { BloatBenchmark.err.println("** Could not find method " + ex3.getMessage()); System.exit(1); } /* So we can skip classes or packages */ final String name = ce.type().className(); final String qual = ce.type().qualifier() + "/*"; boolean skip = false; for (int i = 0; i < BloatBenchmark.SKIP.size(); i++) { final String pkg = (String) BloatBenchmark.SKIP.get(i); if (name.equals(pkg) || qual.equals(pkg)) { skip = true; break; } } if (context.ignoreMethod(live.memberRef()) || skip) { // Don't display ignored methods, it's misleading. context.release(live.methodInfo()); continue; } final Runtime runtime = Runtime.getRuntime(); runtime.gc(); final Date start = new Date(); BloatBenchmark.tr(" " + count + ") " + live.declaringClass().name() + "." + live.name() + live.type()); BloatBenchmark.tr(" Start: " + start); try { EDU.purdue.cs.bloat.optimize.Main.TRACE = BloatBenchmark.TRACE; if (!BloatBenchmark.VERIFY) { EDU.purdue.cs.bloat.optimize.Main.VERIFY = false; } EDU.purdue.cs.bloat.optimize.Main.bloatMethod(live, context); } catch (final Exception oops) { BloatBenchmark.err .println("******************************************"); BloatBenchmark.err.println("Exception while BLOATing " + live.declaringClass().name() + "." + live.name() + live.type()); BloatBenchmark.err.println(oops.getMessage()); oops.printStackTrace(System.err); BloatBenchmark.err .println("******************************************"); } // Commit here in an attempt to conserve memory context.commit(live.methodInfo()); context.release(live.methodInfo()); if (prevClass == null) { prevClass = ce; } else if (!prevClass.equals(ce)) { // We've finished BLOATed the methods for prevClass, commit // prevClass and move on BloatBenchmark.tr(prevClass.type() + " != " + ce.type()); context.commit(prevClass.classInfo()); context.release(prevClass.classInfo()); // context.commitDirty(); // tr(context.toString()); prevClass = ce; } else { context.release(ce.classInfo()); } final Date end = new Date(); BloatBenchmark.tr(" Ellapsed time: " + (end.getTime() - start.getTime()) + " ms"); } context.commitDirty(); } /** * Checks to make sure that the chosen options make sense. */ private static void checkOptions() { if (!BloatBenchmark.INTRA && !BloatBenchmark.SPECIALIZE && !BloatBenchmark.INLINE) { BloatBenchmark.err.println("** There is nothing to do!"); BloatBenchmark.usage(); } else if ((BloatBenchmark.MORPH != -1) && !BloatBenchmark.SPECIALIZE) { BloatBenchmark.err .println("** Must specialize when setting morphosity"); BloatBenchmark.usage(); } } private static class MemberRefComparator implements Comparator { public int compare(final Object o1, final Object o2) { Assert.isTrue(o1 instanceof MemberRef, o1 + " is not a MemberRef!"); Assert.isTrue(o2 instanceof MemberRef, o2 + " is not a MemberRef!"); final MemberRef me1 = (MemberRef) o1; final MemberRef me2 = (MemberRef) o2; final String s1 = me1.declaringClass() + "." + me1.name() + me1.type(); final String s2 = me2.declaringClass() + "." + me2.name() + me2.type(); return (s1.compareTo(s2)); } public boolean equals(final Object other) { return (true); } } }