diff options
Diffstat (limited to 'scene-lib/src/annotations/util/SceneOps.java')
-rw-r--r-- | scene-lib/src/annotations/util/SceneOps.java | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/scene-lib/src/annotations/util/SceneOps.java b/scene-lib/src/annotations/util/SceneOps.java new file mode 100644 index 0000000..5ea448b --- /dev/null +++ b/scene-lib/src/annotations/util/SceneOps.java @@ -0,0 +1,380 @@ +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<Void, Pair<AElement, AElement>> { + + /** + * 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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<AElement, AElement> 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 <K, V extends AElement> + void visitElements(VivifyingMap<K, V> minuend, + VivifyingMap<K, V> subtrahend, VivifyingMap<K, V> difference) { + if (minuend != null) { + for (Map.Entry<K, V> 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 <T> void diff(Set<T> minuend, Set<T> subtrahend, + Set<T> 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 <K, V> void diff(Map<K, Set<V>> minuend, + Map<K, Set<V>> subtrahend, Map<K, Set<V>> difference) { + if (minuend != null) { + for (K key : minuend.keySet()) { + Set<V> mval = minuend.get(key); + Set<V> sval = subtrahend.get(key); + if (sval == null) { + difference.put(key, mval); + } else if (!sval.equals(mval)) { + try { + @SuppressWarnings("unchecked") + Set<V> set = (Set<V>) 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<AElement, AElement> elemPair(AElement stype, + AElement dtype) { + return Pair.of(stype, dtype); + } +} |