1 package annotations.util; 2 3 import java.io.IOException; 4 import java.io.PrintWriter; 5 import java.io.Writer; 6 import java.util.Map; 7 import java.util.Set; 8 9 import com.sun.tools.javac.util.Pair; 10 11 import annotations.el.ABlock; 12 import annotations.el.AClass; 13 import annotations.el.ADeclaration; 14 import annotations.el.AElement; 15 import annotations.el.AExpression; 16 import annotations.el.AField; 17 import annotations.el.AMethod; 18 import annotations.el.AScene; 19 import annotations.el.ATypeElement; 20 import annotations.el.ATypeElementWithType; 21 import annotations.el.AnnotationDef; 22 import annotations.el.DefException; 23 import annotations.el.ElementVisitor; 24 import annotations.io.IndexFileParser; 25 import annotations.io.IndexFileWriter; 26 import annotations.util.coll.VivifyingMap; 27 28 /** 29 * Algebraic operations on scenes. 30 * 31 * Also includes a {@link #main(String[])} method that lets these 32 * operations be performed from the command line. 33 * 34 * @author dbro 35 */ 36 public class SceneOps { 37 private SceneOps() {} 38 39 /** 40 * Run an operation on a subcommand-specific number of JAIFs. 41 * Currently the only available subcommand is "diff", which must be 42 * the first of three arguments, followed in order by the "minuend" 43 * and the "subtrahend" (see {@link #diff(AScene, AScene)}). If 44 * successful, the diff subcommand writes the scene it calculates to 45 * {@link System#out}. 46 * 47 * @throws IOException 48 */ 49 public static void main(String[] args) throws IOException { 50 if (args.length != 3 || !"diff".equals(args[0])) { 51 System.err.println( 52 "usage: java annotations.util.SceneOps diff first.jaif second.jaif"); 53 System.exit(1); 54 } 55 56 AScene s1 = new AScene(); 57 AScene s2 = new AScene(); 58 59 try { 60 IndexFileParser.parseFile(args[1], s1); 61 IndexFileParser.parseFile(args[2], s2); 62 AScene diff = diff(s1, s2); 63 64 try (Writer w = new PrintWriter(System.out)) { 65 IndexFileWriter.write(diff, w); 66 } catch (DefException e) { 67 exitWithException(e); 68 } 69 } catch (IOException e) { 70 exitWithException(e); 71 } 72 } 73 74 /** 75 * Compute the difference of two scenes, that is, a scene containing 76 * all and only those insertion specifications that exist in the first 77 * but not in the second. 78 * 79 * @param s1 the "minuend" 80 * @param s2 the "subtrahend" 81 * @return s1 - s2 ("set difference") 82 */ 83 public static AScene diff(AScene s1, AScene s2) { 84 AScene diff = new AScene(); 85 new DiffVisitor().visitScene(s1, s2, diff); 86 diff.prune(); 87 return diff; 88 } 89 90 /** Print stack trace (for debugging) and exit with return code 1. */ 91 private static void exitWithException(Exception e) { 92 e.printStackTrace(); 93 System.exit(1); 94 } 95 96 // TODO: integrate into scene-lib test suite 97 public static void testDiffEmpties() { 98 assert new AScene().equals(diff(new AScene(), new AScene())); 99 } 100 /** Test that X-X=0, for several scenes X. */ 101 public static void testDiffSame() throws IOException { 102 String dirname = 103 "test/annotations/tests/classfile/cases"; 104 String[] testcases = { "ClassEmpty", "ClassNonEmpty", "FieldGeneric", 105 "FieldSimple", "LocalVariableGenericArray", "MethodReceiver", 106 "MethodReturnTypeGenericArray", "ObjectCreationGenericArray", 107 "ObjectCreation", "TypecastGenericArray", "Typecast" }; 108 AScene emptyScene = new AScene(); 109 for (String testcase : testcases) { 110 AScene scene1 = new AScene(); 111 AScene scene2 = new AScene(); 112 String filename = dirname+"/Test"+testcase+".jaif"; 113 IndexFileParser.parseFile(filename, scene1); 114 IndexFileParser.parseFile(filename, scene2); 115 assert emptyScene.equals(diff(scene1, scene1)); 116 assert emptyScene.equals(diff(scene1, scene2)); 117 } 118 } 119 } 120 121 /** 122 * Visitor for calculating "set difference" of scenes. 123 * Visitor methods fill in a scene instead of returning one because an 124 * {@link AElement} can be created only inside an {@link AScene}. 125 * 126 * @author dbro 127 */ 128 class DiffVisitor 129 implements ElementVisitor<Void, Pair<AElement, AElement>> { 130 131 /** 132 * Adds all annotations that are in {@code minuend} but not in 133 * {@code subtrahend} to {@code difference}. 134 */ 135 public void visitScene(AScene minuend, AScene subtrahend, 136 AScene difference) { 137 visitElements(minuend.packages, subtrahend.packages, 138 difference.packages); 139 diff(minuend.imports, subtrahend.imports, difference.imports); 140 visitElements(minuend.classes, subtrahend.classes, 141 difference.classes); 142 } 143 144 // Never used, as annotations and definitions don't get duplicated. 145 @Override 146 public Void visitAnnotationDef(AnnotationDef minuend, 147 Pair<AElement, AElement> eltPair) { 148 throw new IllegalStateException( 149 "BUG: DiffVisitor.visitAnnotationDef invoked"); 150 } 151 152 /** 153 * Calculates difference between {@code minuend} and first component 154 * of {@code eltPair}, adding results to second component of {@code eltPair}. 155 */ 156 @Override 157 public Void visitBlock(ABlock minuend, Pair<AElement, AElement> eltPair) { 158 ABlock subtrahend = (ABlock) eltPair.fst; 159 ABlock difference = (ABlock) eltPair.snd; 160 visitElements(minuend.locals, subtrahend.locals, difference.locals); 161 return visitExpression(minuend, eltPair); 162 } 163 164 /** 165 * Calculates difference between {@code minuend} and first component 166 * of {@code eltPair}, adding results to second component of {@code eltPair}. 167 */ 168 @Override 169 public Void visitClass(AClass minuend, Pair<AElement, AElement> eltPair) { 170 AClass subtrahend = (AClass) eltPair.fst; 171 AClass difference = (AClass) eltPair.snd; 172 visitElements(minuend.bounds, subtrahend.bounds, difference.bounds); 173 visitElements(minuend.extendsImplements, 174 subtrahend.extendsImplements, difference.extendsImplements); 175 visitElements(minuend.methods, subtrahend.methods, 176 difference.methods); 177 visitElements(minuend.staticInits, subtrahend.staticInits, 178 difference.staticInits); 179 visitElements(minuend.instanceInits, subtrahend.instanceInits, 180 difference.instanceInits); 181 visitElements(minuend.fields, subtrahend.fields, difference.fields); 182 visitElements(minuend.fieldInits, subtrahend.fieldInits, 183 difference.fieldInits); 184 return visitDeclaration(minuend, eltPair); 185 } 186 187 /** 188 * Calculates difference between {@code minuend} and first component 189 * of {@code eltPair}, adding results to second component of {@code eltPair}. 190 */ 191 @Override 192 public Void visitDeclaration(ADeclaration minuend, 193 Pair<AElement, AElement> eltPair) { 194 ADeclaration subtrahend = (ADeclaration) eltPair.fst; 195 ADeclaration difference = (ADeclaration) eltPair.snd; 196 visitElements(minuend.insertAnnotations, 197 subtrahend.insertAnnotations, difference.insertAnnotations); 198 visitElements(minuend.insertTypecasts, subtrahend.insertTypecasts, 199 difference.insertTypecasts); 200 return visitElement(minuend, eltPair); 201 } 202 203 /** 204 * Calculates difference between {@code minuend} and first component 205 * of {@code eltPair}, adding results to second component of {@code eltPair}. 206 */ 207 @Override 208 public Void visitExpression(AExpression minuend, 209 Pair<AElement, AElement> eltPair) { 210 AExpression subtrahend = (AExpression) eltPair.fst; 211 AExpression difference = (AExpression) eltPair.snd; 212 visitElements(minuend.typecasts, subtrahend.typecasts, 213 difference.typecasts); 214 visitElements(minuend.instanceofs, subtrahend.instanceofs, 215 difference.instanceofs); 216 visitElements(minuend.news, subtrahend.news, difference.news); 217 visitElements(minuend.calls, subtrahend.calls, difference.calls); 218 visitElements(minuend.refs, subtrahend.refs, difference.refs); 219 visitElements(minuend.funs, subtrahend.funs, difference.funs); 220 return visitElement(minuend, eltPair); 221 } 222 223 /** 224 * Calculates difference between {@code minuend} and first component 225 * of {@code eltPair}, adding results to second component of {@code eltPair}. 226 */ 227 @Override 228 public Void visitField(AField minuend, Pair<AElement, AElement> eltPair) { 229 return visitDeclaration(minuend, eltPair); 230 } 231 232 /** 233 * Calculates difference between {@code minuend} and first component 234 * of {@code eltPair}, adding results to second component of {@code eltPair}. 235 */ 236 @Override 237 public Void visitMethod(AMethod minuend, 238 Pair<AElement, AElement> eltPair) { 239 AMethod subtrahend = (AMethod) eltPair.fst; 240 AMethod difference = (AMethod) eltPair.snd; 241 visitElements(minuend.bounds, subtrahend.bounds, difference.bounds); 242 visitElements(minuend.parameters, subtrahend.parameters, 243 difference.parameters); 244 visitElements(minuend.throwsException, subtrahend.throwsException, 245 difference.throwsException); 246 visitElements(minuend.parameters, subtrahend.parameters, 247 difference.parameters); 248 visitBlock(minuend.body, 249 elemPair(subtrahend.body, difference.body)); 250 if (minuend.returnType != null) { 251 minuend.returnType.accept(this, 252 elemPair(subtrahend.returnType, difference.returnType)); 253 } 254 if (minuend.receiver != null) { 255 minuend.receiver.accept(this, 256 elemPair(subtrahend.receiver, difference.receiver)); 257 } 258 return visitDeclaration(minuend, eltPair); 259 } 260 261 /** 262 * Calculates difference between {@code minuend} and first component 263 * of {@code eltPair}, adding results to second component of {@code eltPair}. 264 */ 265 @Override 266 public Void visitTypeElement(ATypeElement minuend, 267 Pair<AElement, AElement> eltPair) { 268 ATypeElement subtrahend = (ATypeElement) eltPair.fst; 269 ATypeElement difference = (ATypeElement) eltPair.snd; 270 visitElements(minuend.innerTypes, subtrahend.innerTypes, 271 difference.innerTypes); 272 return visitElement(minuend, eltPair); 273 } 274 275 /** 276 * Calculates difference between {@code minuend} and first component 277 * of {@code eltPair}, adding results to second component of {@code eltPair}. 278 */ 279 @Override 280 public Void visitTypeElementWithType(ATypeElementWithType minuend, 281 Pair<AElement, AElement> eltPair) { 282 return visitTypeElement(minuend, eltPair); 283 } 284 285 /** 286 * Calculates difference between {@code minuend} and first component 287 * of {@code eltPair}, adding results to second component of {@code eltPair}. 288 */ 289 @Override 290 public Void visitElement(AElement minuend, 291 Pair<AElement, AElement> eltPair) { 292 AElement subtrahend = eltPair.fst; 293 AElement difference = eltPair.snd; 294 diff(minuend.tlAnnotationsHere, subtrahend.tlAnnotationsHere, 295 difference.tlAnnotationsHere); 296 if (minuend.type != null) { 297 AElement stype = subtrahend.type; 298 AElement dtype = difference.type; 299 minuend.type.accept(this, elemPair(stype, dtype)); 300 } 301 return null; 302 } 303 304 /** 305 * Calculates difference between {@code minuend} and first component 306 * of {@code eltPair}, adding results to second component of {@code eltPair}. 307 */ 308 private <K, V extends AElement> 309 void visitElements(VivifyingMap<K, V> minuend, 310 VivifyingMap<K, V> subtrahend, VivifyingMap<K, V> difference) { 311 if (minuend != null) { 312 for (Map.Entry<K, V> e : minuend.entrySet()) { 313 K key = e.getKey(); 314 V mval = e.getValue(); 315 V sval = subtrahend.get(key); 316 if (sval == null) { 317 difference.put(key, mval); 318 } else { 319 mval.accept(this, elemPair(sval, difference.vivify(key))); 320 } 321 } 322 } 323 } 324 325 /** 326 * Calculates difference between {@code minuend} and 327 * {@code subtrahend}, adding the result to {@code difference}. 328 */ 329 private static <T> void diff(Set<T> minuend, Set<T> subtrahend, 330 Set<T> difference) { 331 if (minuend != null) { 332 for (T t : minuend) { 333 if (!subtrahend.contains(t)) { 334 difference.add(t); 335 } 336 } 337 } 338 } 339 340 /** 341 * Calculates difference between {@code minuend} and 342 * {@code subtrahend}, adding the results to {@code difference}. 343 */ 344 private static <K, V> void diff(Map<K, Set<V>> minuend, 345 Map<K, Set<V>> subtrahend, Map<K, Set<V>> difference) { 346 if (minuend != null) { 347 for (K key : minuend.keySet()) { 348 Set<V> mval = minuend.get(key); 349 Set<V> sval = subtrahend.get(key); 350 if (sval == null) { 351 difference.put(key, mval); 352 } else if (!sval.equals(mval)) { 353 try { 354 @SuppressWarnings("unchecked") 355 Set<V> set = (Set<V>) sval.getClass().newInstance(); 356 diff(mval, sval, set); 357 if (!set.isEmpty()) { 358 difference.put(key, set); 359 } 360 } catch (InstantiationException e) { 361 e.printStackTrace(); 362 System.exit(1); 363 } catch (IllegalAccessException e) { 364 e.printStackTrace(); 365 System.exit(1); 366 } 367 } 368 } 369 } 370 } 371 372 /** 373 * Convenience method for ensuring returned {@link Pair} is of the 374 * most general type. 375 */ 376 private Pair<AElement, AElement> elemPair(AElement stype, 377 AElement dtype) { 378 return Pair.of(stype, dtype); 379 } 380 } 381