package annotations.util; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.Map; import java.util.Set; import com.sun.tools.javac.util.Pair; import annotations.el.ABlock; import annotations.el.AClass; import annotations.el.ADeclaration; import annotations.el.AElement; import annotations.el.AExpression; import annotations.el.AField; import annotations.el.AMethod; import annotations.el.AScene; import annotations.el.ATypeElement; import annotations.el.ATypeElementWithType; import annotations.el.AnnotationDef; import annotations.el.DefException; import annotations.el.ElementVisitor; import annotations.io.IndexFileParser; import annotations.io.IndexFileWriter; import annotations.util.coll.VivifyingMap; /** * Algebraic operations on scenes. * * Also includes a {@link #main(String[])} method that lets these * operations be performed from the command line. * * @author dbro */ public class SceneOps { private SceneOps() {} /** * Run an operation on a subcommand-specific number of JAIFs. * Currently the only available subcommand is "diff", which must be * the first of three arguments, followed in order by the "minuend" * and the "subtrahend" (see {@link #diff(AScene, AScene)}). If * successful, the diff subcommand writes the scene it calculates to * {@link System#out}. * * @throws IOException */ public static void main(String[] args) throws IOException { if (args.length != 3 || !"diff".equals(args[0])) { System.err.println( "usage: java annotations.util.SceneOps diff first.jaif second.jaif"); System.exit(1); } AScene s1 = new AScene(); AScene s2 = new AScene(); try { IndexFileParser.parseFile(args[1], s1); IndexFileParser.parseFile(args[2], s2); AScene diff = diff(s1, s2); try (Writer w = new PrintWriter(System.out)) { IndexFileWriter.write(diff, w); } catch (DefException e) { exitWithException(e); } } catch (IOException e) { exitWithException(e); } } /** * Compute the difference of two scenes, that is, a scene containing * all and only those insertion specifications that exist in the first * but not in the second. * * @param s1 the "minuend" * @param s2 the "subtrahend" * @return s1 - s2 ("set difference") */ public static AScene diff(AScene s1, AScene s2) { AScene diff = new AScene(); new DiffVisitor().visitScene(s1, s2, diff); diff.prune(); return diff; } /** Print stack trace (for debugging) and exit with return code 1. */ private static void exitWithException(Exception e) { e.printStackTrace(); System.exit(1); } // TODO: integrate into scene-lib test suite public static void testDiffEmpties() { assert new AScene().equals(diff(new AScene(), new AScene())); } /** Test that X-X=0, for several scenes X. */ public static void testDiffSame() throws IOException { String dirname = "test/annotations/tests/classfile/cases"; String[] testcases = { "ClassEmpty", "ClassNonEmpty", "FieldGeneric", "FieldSimple", "LocalVariableGenericArray", "MethodReceiver", "MethodReturnTypeGenericArray", "ObjectCreationGenericArray", "ObjectCreation", "TypecastGenericArray", "Typecast" }; AScene emptyScene = new AScene(); for (String testcase : testcases) { AScene scene1 = new AScene(); AScene scene2 = new AScene(); String filename = dirname+"/Test"+testcase+".jaif"; IndexFileParser.parseFile(filename, scene1); IndexFileParser.parseFile(filename, scene2); assert emptyScene.equals(diff(scene1, scene1)); assert emptyScene.equals(diff(scene1, scene2)); } } } /** * Visitor for calculating "set difference" of scenes. * Visitor methods fill in a scene instead of returning one because an * {@link AElement} can be created only inside an {@link AScene}. * * @author dbro */ class DiffVisitor implements ElementVisitor> { /** * Adds all annotations that are in {@code minuend} but not in * {@code subtrahend} to {@code difference}. */ public void visitScene(AScene minuend, AScene subtrahend, AScene difference) { visitElements(minuend.packages, subtrahend.packages, difference.packages); diff(minuend.imports, subtrahend.imports, difference.imports); visitElements(minuend.classes, subtrahend.classes, difference.classes); } // Never used, as annotations and definitions don't get duplicated. @Override public Void visitAnnotationDef(AnnotationDef minuend, Pair eltPair) { throw new IllegalStateException( "BUG: DiffVisitor.visitAnnotationDef invoked"); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitBlock(ABlock minuend, Pair eltPair) { ABlock subtrahend = (ABlock) eltPair.fst; ABlock difference = (ABlock) eltPair.snd; visitElements(minuend.locals, subtrahend.locals, difference.locals); return visitExpression(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitClass(AClass minuend, Pair eltPair) { AClass subtrahend = (AClass) eltPair.fst; AClass difference = (AClass) eltPair.snd; visitElements(minuend.bounds, subtrahend.bounds, difference.bounds); visitElements(minuend.extendsImplements, subtrahend.extendsImplements, difference.extendsImplements); visitElements(minuend.methods, subtrahend.methods, difference.methods); visitElements(minuend.staticInits, subtrahend.staticInits, difference.staticInits); visitElements(minuend.instanceInits, subtrahend.instanceInits, difference.instanceInits); visitElements(minuend.fields, subtrahend.fields, difference.fields); visitElements(minuend.fieldInits, subtrahend.fieldInits, difference.fieldInits); return visitDeclaration(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitDeclaration(ADeclaration minuend, Pair eltPair) { ADeclaration subtrahend = (ADeclaration) eltPair.fst; ADeclaration difference = (ADeclaration) eltPair.snd; visitElements(minuend.insertAnnotations, subtrahend.insertAnnotations, difference.insertAnnotations); visitElements(minuend.insertTypecasts, subtrahend.insertTypecasts, difference.insertTypecasts); return visitElement(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitExpression(AExpression minuend, Pair eltPair) { AExpression subtrahend = (AExpression) eltPair.fst; AExpression difference = (AExpression) eltPair.snd; visitElements(minuend.typecasts, subtrahend.typecasts, difference.typecasts); visitElements(minuend.instanceofs, subtrahend.instanceofs, difference.instanceofs); visitElements(minuend.news, subtrahend.news, difference.news); visitElements(minuend.calls, subtrahend.calls, difference.calls); visitElements(minuend.refs, subtrahend.refs, difference.refs); visitElements(minuend.funs, subtrahend.funs, difference.funs); return visitElement(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitField(AField minuend, Pair eltPair) { return visitDeclaration(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitMethod(AMethod minuend, Pair eltPair) { AMethod subtrahend = (AMethod) eltPair.fst; AMethod difference = (AMethod) eltPair.snd; visitElements(minuend.bounds, subtrahend.bounds, difference.bounds); visitElements(minuend.parameters, subtrahend.parameters, difference.parameters); visitElements(minuend.throwsException, subtrahend.throwsException, difference.throwsException); visitElements(minuend.parameters, subtrahend.parameters, difference.parameters); visitBlock(minuend.body, elemPair(subtrahend.body, difference.body)); if (minuend.returnType != null) { minuend.returnType.accept(this, elemPair(subtrahend.returnType, difference.returnType)); } if (minuend.receiver != null) { minuend.receiver.accept(this, elemPair(subtrahend.receiver, difference.receiver)); } return visitDeclaration(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitTypeElement(ATypeElement minuend, Pair eltPair) { ATypeElement subtrahend = (ATypeElement) eltPair.fst; ATypeElement difference = (ATypeElement) eltPair.snd; visitElements(minuend.innerTypes, subtrahend.innerTypes, difference.innerTypes); return visitElement(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitTypeElementWithType(ATypeElementWithType minuend, Pair eltPair) { return visitTypeElement(minuend, eltPair); } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ @Override public Void visitElement(AElement minuend, Pair eltPair) { AElement subtrahend = eltPair.fst; AElement difference = eltPair.snd; diff(minuend.tlAnnotationsHere, subtrahend.tlAnnotationsHere, difference.tlAnnotationsHere); if (minuend.type != null) { AElement stype = subtrahend.type; AElement dtype = difference.type; minuend.type.accept(this, elemPair(stype, dtype)); } return null; } /** * Calculates difference between {@code minuend} and first component * of {@code eltPair}, adding results to second component of {@code eltPair}. */ private void visitElements(VivifyingMap minuend, VivifyingMap subtrahend, VivifyingMap difference) { if (minuend != null) { for (Map.Entry e : minuend.entrySet()) { K key = e.getKey(); V mval = e.getValue(); V sval = subtrahend.get(key); if (sval == null) { difference.put(key, mval); } else { mval.accept(this, elemPair(sval, difference.vivify(key))); } } } } /** * Calculates difference between {@code minuend} and * {@code subtrahend}, adding the result to {@code difference}. */ private static void diff(Set minuend, Set subtrahend, Set difference) { if (minuend != null) { for (T t : minuend) { if (!subtrahend.contains(t)) { difference.add(t); } } } } /** * Calculates difference between {@code minuend} and * {@code subtrahend}, adding the results to {@code difference}. */ private static void diff(Map> minuend, Map> subtrahend, Map> difference) { if (minuend != null) { for (K key : minuend.keySet()) { Set mval = minuend.get(key); Set sval = subtrahend.get(key); if (sval == null) { difference.put(key, mval); } else if (!sval.equals(mval)) { try { @SuppressWarnings("unchecked") Set set = (Set) sval.getClass().newInstance(); diff(mval, sval, set); if (!set.isEmpty()) { difference.put(key, set); } } catch (InstantiationException e) { e.printStackTrace(); System.exit(1); } catch (IllegalAccessException e) { e.printStackTrace(); System.exit(1); } } } } } /** * Convenience method for ensuring returned {@link Pair} is of the * most general type. */ private Pair elemPair(AElement stype, AElement dtype) { return Pair.of(stype, dtype); } }