speedup: call force() only at the end of the build, between chunk builds drop only...
[idea/community.git] / jps / model / src / org / jetbrains / ether / dependencyView / Mappings.java
1 package org.jetbrains.ether.dependencyView;
2
3 import com.intellij.openapi.diagnostic.Logger;
4 import com.intellij.openapi.util.Pair;
5 import com.intellij.openapi.util.io.FileUtil;
6 import org.jetbrains.annotations.Nullable;
7 import org.objectweb.asm.ClassReader;
8 import org.objectweb.asm.Opcodes;
9
10 import java.io.File;
11 import java.io.IOException;
12 import java.lang.annotation.ElementType;
13 import java.lang.annotation.RetentionPolicy;
14 import java.util.*;
15
16 /**
17  * Created by IntelliJ IDEA.
18  * User: db
19  * Date: 28.01.11
20  * Time: 16:20
21  * To change this template use File | Settings | File Templates.
22  */
23 public class Mappings {
24   private final static Logger LOG = Logger.getInstance("#org.jetbrains.ether.dependencyView.Mappings");
25
26   private final static String CLASS_TO_SUBCLASSES = "classToSubclasses.tab";
27   private final static String CLASS_TO_CLASS = "classToClass.tab";
28   private final static String SOURCE_TO_CLASS = "sourceToClass.tab";
29   private final static String SOURCE_TO_ANNOTATIONS = "sourceToAnnotations.tab";
30   private final static String SOURCE_TO_USAGES = "sourceToUsages.tab";
31   private final static String CLASS_TO_SOURCE = "classToSource.tab";
32
33   private final boolean myIsDelta;
34   private final boolean myDeltaIsTransient;
35
36   private final File myRootDir;
37   private DependencyContext myContext;
38   private org.jetbrains.ether.dependencyView.Logger<DependencyContext.S> myDebugS;
39
40   private static void debug(final String s) {
41     LOG.debug(s);
42   }
43
44   private void debug(final String comment, final DependencyContext.S s) {
45     myDebugS.debug(comment, s);
46   }
47
48   private void debug(final String comment, final String s) {
49     myDebugS.debug(comment, s);
50   }
51
52   private void debug(final String comment, final boolean s) {
53     myDebugS.debug(comment, s);
54   }
55
56   private MultiMaplet<DependencyContext.S, DependencyContext.S> myClassToSubclasses;
57   private MultiMaplet<DependencyContext.S, DependencyContext.S> myClassToClassDependency;
58
59   private MultiMaplet<DependencyContext.S, ClassRepr> mySourceFileToClasses;
60   private MultiMaplet<DependencyContext.S, UsageRepr.Usage> mySourceFileToAnnotationUsages;
61
62   private MultiMaplet<DependencyContext.S, UsageRepr.Cluster> mySourceFileToUsages;
63   private Maplet<DependencyContext.S, DependencyContext.S> myClassToSourceFile;
64
65   private static final TransientMultiMaplet.CollectionConstructor<ClassRepr> ourClassSetConstructor =
66     new TransientMultiMaplet.CollectionConstructor<ClassRepr>() {
67       public Set<ClassRepr> create() {
68         return new HashSet<ClassRepr>();
69       }
70     };
71
72   private static final TransientMultiMaplet.CollectionConstructor<UsageRepr.Cluster> ourUsageClusterSetConstructor =
73     new TransientMultiMaplet.CollectionConstructor<UsageRepr.Cluster>() {
74       public Set<UsageRepr.Cluster> create() {
75         return new HashSet<UsageRepr.Cluster>();
76       }
77     };
78
79   private static final TransientMultiMaplet.CollectionConstructor<UsageRepr.Usage> ourUsageSetConstructor =
80     new TransientMultiMaplet.CollectionConstructor<UsageRepr.Usage>() {
81       public Set<UsageRepr.Usage> create() {
82         return new HashSet<UsageRepr.Usage>();
83       }
84     };
85
86   private static final TransientMultiMaplet.CollectionConstructor<DependencyContext.S> ourStringSetConstructor =
87     new TransientMultiMaplet.CollectionConstructor<DependencyContext.S>() {
88       public Set<DependencyContext.S> create() {
89         return new HashSet<DependencyContext.S>();
90       }
91     };
92
93   private Mappings(final Mappings base) throws IOException {
94     myIsDelta = true;
95     myDeltaIsTransient = base.myDeltaIsTransient;
96     myRootDir = new File(FileUtil.toSystemIndependentName(base.myRootDir.getAbsolutePath()) + File.separatorChar + "delta");
97     myContext = base.myContext;
98     myDebugS = base.myDebugS;
99     myRootDir.mkdirs();
100     createImplementation();
101   }
102
103   public Mappings(final File rootDir, final boolean transientDelta) throws IOException {
104     myIsDelta = false;
105     myDeltaIsTransient = transientDelta;
106     myRootDir = rootDir;
107     createImplementation();
108   }
109
110   private void createImplementation() throws IOException {
111     if (!myIsDelta) {
112       myContext = new DependencyContext(myRootDir);
113       myDebugS = myContext.getLogger(LOG);
114     }
115
116     if (myIsDelta && myDeltaIsTransient) {
117       myClassToSubclasses = new TransientMultiMaplet<DependencyContext.S, DependencyContext.S>(ourStringSetConstructor);
118       myClassToClassDependency = new TransientMultiMaplet<DependencyContext.S, DependencyContext.S>(ourStringSetConstructor);
119       mySourceFileToClasses = new TransientMultiMaplet<DependencyContext.S, ClassRepr>(ourClassSetConstructor);
120       mySourceFileToAnnotationUsages = new TransientMultiMaplet<DependencyContext.S, UsageRepr.Usage>(ourUsageSetConstructor);
121       mySourceFileToUsages = new TransientMultiMaplet<DependencyContext.S, UsageRepr.Cluster>(ourUsageClusterSetConstructor);
122       myClassToSourceFile = new TransientMaplet<DependencyContext.S, DependencyContext.S>();
123     }
124     else {
125       myClassToSubclasses =
126         new PersistentMultiMaplet<DependencyContext.S, DependencyContext.S>(DependencyContext.getTableFile(myRootDir, CLASS_TO_SUBCLASSES),
127                                                                             DependencyContext.descriptorS, DependencyContext.descriptorS,
128                                                                             ourStringSetConstructor);
129
130       myClassToClassDependency =
131         new PersistentMultiMaplet<DependencyContext.S, DependencyContext.S>(DependencyContext.getTableFile(myRootDir, CLASS_TO_CLASS),
132                                                                             DependencyContext.descriptorS, DependencyContext.descriptorS,
133                                                                             ourStringSetConstructor);
134
135       mySourceFileToClasses =
136         new PersistentMultiMaplet<DependencyContext.S, ClassRepr>(DependencyContext.getTableFile(myRootDir, SOURCE_TO_CLASS),
137                                                                   DependencyContext.descriptorS, ClassRepr.externalizer(myContext),
138                                                                   ourClassSetConstructor);
139
140       mySourceFileToAnnotationUsages =
141         new PersistentMultiMaplet<DependencyContext.S, UsageRepr.Usage>(DependencyContext.getTableFile(myRootDir, SOURCE_TO_ANNOTATIONS),
142                                                                         DependencyContext.descriptorS, UsageRepr.externalizer(myContext),
143                                                                         ourUsageSetConstructor);
144
145       mySourceFileToUsages =
146         new PersistentMultiMaplet<DependencyContext.S, UsageRepr.Cluster>(DependencyContext.getTableFile(myRootDir, SOURCE_TO_USAGES),
147                                                                           DependencyContext.descriptorS,
148                                                                           UsageRepr.Cluster.clusterExternalizer(myContext),
149                                                                           ourUsageClusterSetConstructor);
150
151       myClassToSourceFile =
152         new PersistentMaplet<DependencyContext.S, DependencyContext.S>(DependencyContext.getTableFile(myRootDir, CLASS_TO_SOURCE),
153                                                                        DependencyContext.descriptorS, DependencyContext.descriptorS);
154     }
155   }
156
157   public Mappings createDelta() {
158     try {
159       return new Mappings(this);
160     }
161     catch (IOException e) {
162       throw new RuntimeException(e);
163     }
164   }
165
166   private void compensateRemovedContent(final Collection<File> compiled) {
167     for (File file : compiled) {
168       final DependencyContext.S key = myContext.get(FileUtil.toSystemIndependentName(file.getAbsolutePath()));
169       if (!mySourceFileToClasses.containsKey(key)) {
170         mySourceFileToClasses.put(key, new HashSet<ClassRepr>());
171       }
172     }
173   }
174
175   @Nullable
176   private ClassRepr getReprByName(final DependencyContext.S name) {
177     final DependencyContext.S source = myClassToSourceFile.get(name);
178
179     if (source != null) {
180       final Collection<ClassRepr> reprs = mySourceFileToClasses.get(source);
181
182       if (reprs != null) {
183         for (ClassRepr repr : reprs) {
184           if (repr.name.equals(name)) {
185             return repr;
186           }
187         }
188       }
189     }
190
191     return null;
192   }
193
194   public void clean() throws IOException {
195     if (myRootDir != null) {
196       close();
197       FileUtil.delete(myRootDir);
198       createImplementation();
199     }
200   }
201
202   private static class Option<X> {
203     final X myValue;
204
205     Option(final X value) {
206       this.myValue = value;
207     }
208
209     Option() {
210       myValue = null;
211     }
212
213     boolean isNone() {
214       return myValue == null;
215     }
216
217     boolean isValue() {
218       return myValue != null;
219     }
220
221     X value() {
222       return myValue;
223     }
224   }
225
226   private static ClassRepr myMockClass = null;
227   private static MethodRepr myMockMethod = null;
228
229   private class Util {
230     final Mappings myDelta;
231
232     private Util() {
233       myDelta = null;
234     }
235
236     private Util(Mappings delta) {
237       this.myDelta = delta;
238     }
239
240     void appendDependents(final Set<ClassRepr> classes, final Set<DependencyContext.S> result) {
241       if (classes == null) {
242         return;
243       }
244
245       for (ClassRepr c : classes) {
246         final Collection<DependencyContext.S> depClasses = myDelta.myClassToClassDependency.get(c.name);
247
248         if (depClasses != null) {
249           for (DependencyContext.S className : depClasses) {
250             result.add(className);
251           }
252         }
253       }
254     }
255
256     void propagateMemberAccessRec(final Collection<DependencyContext.S> acc,
257                                   final boolean isField,
258                                   final boolean root,
259                                   final DependencyContext.S name,
260                                   final DependencyContext.S reflcass) {
261       final ClassRepr repr = reprByName(reflcass);
262
263       if (repr != null) {
264         if (!root) {
265           final Collection members = isField ? repr.fields : repr.methods;
266
267           for (Object o : members) {
268             final ProtoMember m = (ProtoMember)o;
269
270             if (m.name.equals(name)) {
271               return;
272             }
273           }
274
275           acc.add(reflcass);
276         }
277
278         final Collection<DependencyContext.S> subclasses = myClassToSubclasses.get(reflcass);
279
280         if (subclasses != null) {
281           for (DependencyContext.S subclass : subclasses) {
282             propagateMemberAccessRec(acc, isField, false, name, subclass);
283           }
284         }
285       }
286     }
287
288     Collection<DependencyContext.S> propagateMemberAccess(final boolean isField,
289                                                           final DependencyContext.S name,
290                                                           final DependencyContext.S className) {
291       final Set<DependencyContext.S> acc = new HashSet<DependencyContext.S>();
292
293       propagateMemberAccessRec(acc, isField, true, name, className);
294
295       return acc;
296     }
297
298     Collection<DependencyContext.S> propagateFieldAccess(final DependencyContext.S name, final DependencyContext.S className) {
299       return propagateMemberAccess(true, name, className);
300     }
301
302     Collection<DependencyContext.S> propagateMethodAccess(final DependencyContext.S name, final DependencyContext.S className) {
303       return propagateMemberAccess(false, name, className);
304     }
305
306     MethodRepr.Predicate lessSpecific(final MethodRepr than) {
307       return new MethodRepr.Predicate() {
308         @Override
309         public boolean satisfy(final MethodRepr m) {
310           if (!m.name.equals(than.name) || m.argumentTypes.length != than.argumentTypes.length) {
311             return false;
312           }
313
314           for (int i = 0; i < than.argumentTypes.length; i++) {
315             final Option<Boolean> subtypeOf = isSubtypeOf(than.argumentTypes[i], m.argumentTypes[i]);
316             if (subtypeOf.isValue() && !subtypeOf.value()) {
317               return false;
318             }
319           }
320
321           return true;
322         }
323       };
324     }
325
326     Collection<Pair<MethodRepr, ClassRepr>> findOverridingMethods(final MethodRepr m, final ClassRepr c, final boolean bySpecificity) {
327       final Set<Pair<MethodRepr, ClassRepr>> result = new HashSet<Pair<MethodRepr, ClassRepr>>();
328       final MethodRepr.Predicate predicate = bySpecificity ? lessSpecific(m) : MethodRepr.equalByJavaRules(m);
329
330       new Object() {
331         public void run(final ClassRepr c) {
332           final Collection<DependencyContext.S> subClasses = myClassToSubclasses.get(c.name);
333
334           if (subClasses != null) {
335             for (DependencyContext.S subClassName : subClasses) {
336               final ClassRepr r = reprByName(subClassName);
337
338               if (r != null) {
339                 boolean cont = true;
340
341                 final Collection<MethodRepr> methods = r.findMethods(predicate);
342
343                 for (MethodRepr mm : methods) {
344                   if (isVisibleIn(c, m, r)) {
345                     result.add(new Pair<MethodRepr, ClassRepr>(mm, r));
346                     cont = false;
347                   }
348                 }
349
350                 if (cont) {
351                   run(r);
352                 }
353               }
354             }
355           }
356         }
357       }.run(c);
358
359       return result;
360     }
361
362     Collection<Pair<MethodRepr, ClassRepr>> findOverridenMethods(final MethodRepr m, final ClassRepr c) {
363       return findOverridenMethods(m, c, false);
364     }
365
366     Collection<Pair<MethodRepr, ClassRepr>> findOverridenMethods(final MethodRepr m, final ClassRepr c, final boolean bySpecificity) {
367       final Set<Pair<MethodRepr, ClassRepr>> result = new HashSet<Pair<MethodRepr, ClassRepr>>();
368       final MethodRepr.Predicate predicate = bySpecificity ? lessSpecific(m) : MethodRepr.equalByJavaRules(m);
369
370       new Object() {
371         public void run(final ClassRepr c) {
372           final DependencyContext.S[] supers = c.getSupers();
373
374           for (DependencyContext.S succName : supers) {
375             final ClassRepr r = reprByName(succName);
376
377             if (r != null) {
378               boolean cont = true;
379
380               final Collection<MethodRepr> methods = r.findMethods(predicate);
381
382               for (MethodRepr mm : methods) {
383                 if (isVisibleIn(r, mm, c)) {
384                   result.add(new Pair<MethodRepr, ClassRepr>(mm, r));
385                   cont = false;
386                 }
387               }
388
389               if (cont) {
390                 run(r);
391               }
392             }
393             else {
394               result.add(new Pair<MethodRepr, ClassRepr>(myMockMethod, myMockClass));
395             }
396           }
397         }
398       }.run(c);
399
400       return result;
401     }
402
403     Collection<Pair<MethodRepr, ClassRepr>> findAllMethodsBySpecificity(final MethodRepr m, final ClassRepr c) {
404       final Collection<Pair<MethodRepr, ClassRepr>> result = findOverridenMethods(m, c, true);
405
406       result.addAll(findOverridingMethods(m, c, true));
407
408       return result;
409     }
410
411     Collection<Pair<FieldRepr, ClassRepr>> findOverridenFields(final FieldRepr f, final ClassRepr c) {
412       final Set<Pair<FieldRepr, ClassRepr>> result = new HashSet<Pair<FieldRepr, ClassRepr>>();
413
414       new Object() {
415         public void run(final ClassRepr c) {
416           final DependencyContext.S[] supers = c.getSupers();
417
418           for (DependencyContext.S succName : supers) {
419             final ClassRepr r = reprByName(succName);
420
421             if (r != null) {
422               boolean cont = true;
423
424               if (r.fields.contains(f)) {
425                 final FieldRepr ff = r.findField(f.name);
426
427                 if (ff != null) {
428                   if (isVisibleIn(r, ff, c)) {
429                     result.add(new Pair<FieldRepr, ClassRepr>(ff, r));
430                     cont = false;
431                   }
432                 }
433               }
434
435               if (cont) {
436                 run(r);
437               }
438             }
439           }
440         }
441       }.run(c);
442
443       return result;
444     }
445
446     ClassRepr reprByName(final DependencyContext.S name) {
447       if (myDelta != null) {
448         final ClassRepr r = myDelta.getReprByName(name);
449
450         if (r != null) {
451           return r;
452         }
453       }
454
455       return getReprByName(name);
456     }
457
458     Option<Boolean> isInheritorOf(final DependencyContext.S who, final DependencyContext.S whom) {
459       if (who.equals(whom)) {
460         return new Option<Boolean>(true);
461       }
462
463       final ClassRepr repr = reprByName(who);
464
465       if (repr != null) {
466         for (DependencyContext.S s : repr.getSupers()) {
467           final Option<Boolean> inheritorOf = isInheritorOf(s, whom);
468           if (inheritorOf.isValue() && inheritorOf.value()) {
469             return inheritorOf;
470           }
471         }
472       }
473
474       return new Option<Boolean>();
475     }
476
477     Option<Boolean> isSubtypeOf(final TypeRepr.AbstractType who, final TypeRepr.AbstractType whom) {
478       if (who.equals(whom)) {
479         return new Option<Boolean>(true);
480       }
481
482       if (who instanceof TypeRepr.PrimitiveType || whom instanceof TypeRepr.PrimitiveType) {
483         return new Option<Boolean>(false);
484       }
485
486       if (who instanceof TypeRepr.ArrayType) {
487         if (whom instanceof TypeRepr.ArrayType) {
488           return isSubtypeOf(((TypeRepr.ArrayType)who).elementType, ((TypeRepr.ArrayType)whom).elementType);
489         }
490
491         final String descr = whom.getDescr(myContext);
492
493         if (descr.equals("Ljava/lang/Cloneable") || descr.equals("Ljava/lang/Object") || descr.equals("Ljava/io/Serializable")) {
494           return new Option<Boolean>(true);
495         }
496
497         return new Option<Boolean>(false);
498       }
499
500       if (whom instanceof TypeRepr.ClassType) {
501         return isInheritorOf(((TypeRepr.ClassType)who).className, ((TypeRepr.ClassType)whom).className);
502       }
503
504       return new Option<Boolean>(false);
505     }
506
507     boolean methodVisible(final DependencyContext.S className, final MethodRepr m) {
508       final ClassRepr r = reprByName(className);
509
510       if (r != null) {
511         if (r.findMethods(MethodRepr.equalByJavaRules(m)).size() > 0) {
512           return true;
513         }
514
515         return findOverridenMethods(m, r).size() > 0;
516       }
517
518       return false;
519     }
520
521     boolean fieldVisible(final DependencyContext.S className, final FieldRepr field) {
522       final ClassRepr r = reprByName(className);
523
524       if (r != null) {
525         if (r.fields.contains(field)) {
526           return true;
527         }
528
529         return findOverridenFields(field, r).size() > 0;
530       }
531
532       return true;
533     }
534
535     void affectSubclasses(final DependencyContext.S className,
536                           final Collection<File> affectedFiles,
537                           final Collection<UsageRepr.Usage> affectedUsages,
538                           final Collection<DependencyContext.S> dependants,
539                           final boolean usages) {
540       debug("Affecting subclasses of class: ", className);
541
542       final DependencyContext.S fileName = myClassToSourceFile.get(className);
543
544       if (fileName == null) {
545         debug("No source file detected for class ", className);
546         debug("End of affectSubclasses");
547         return;
548       }
549
550       debug("Source file name: ", fileName);
551
552       if (usages) {
553         debug("Class usages affection requested");
554
555         final ClassRepr classRepr = reprByName(className);
556
557         if (classRepr != null) {
558           debug("Added class usage for ", classRepr.name);
559           affectedUsages.add(classRepr.createUsage());
560         }
561       }
562
563       final Collection<DependencyContext.S> depClasses = myClassToClassDependency.get(fileName);
564
565       if (depClasses != null) {
566         dependants.addAll(depClasses);
567       }
568
569       affectedFiles.add(new File(myContext.getValue(fileName)));
570
571       final Collection<DependencyContext.S> directSubclasses = myClassToSubclasses.get(className);
572
573       if (directSubclasses != null) {
574         for (DependencyContext.S subClass : directSubclasses) {
575           affectSubclasses(subClass, affectedFiles, affectedUsages, dependants, usages);
576         }
577       }
578     }
579
580     void affectFieldUsages(final FieldRepr field,
581                            final Collection<DependencyContext.S> subclasses,
582                            final UsageRepr.Usage rootUsage,
583                            final Set<UsageRepr.Usage> affectedUsages,
584                            final Set<DependencyContext.S> dependents) {
585       affectedUsages.add(rootUsage);
586
587       for (DependencyContext.S p : subclasses) {
588         final Collection<DependencyContext.S> deps = myClassToClassDependency.get(p);
589
590         if (deps != null) {
591           dependents.addAll(deps);
592         }
593
594         debug("Affect field usage referenced of class ", p);
595         affectedUsages
596           .add(rootUsage instanceof UsageRepr.FieldAssignUsage ? field.createAssignUsage(myContext, p) : field.createUsage(myContext, p));
597       }
598     }
599
600     void affectMethodUsages(final MethodRepr method,
601                             final Collection<DependencyContext.S> subclasses,
602                             final UsageRepr.Usage rootUsage,
603                             final Set<UsageRepr.Usage> affectedUsages,
604                             final Set<DependencyContext.S> dependents) {
605       affectedUsages.add(rootUsage);
606
607       for (DependencyContext.S p : subclasses) {
608         final Collection<DependencyContext.S> deps = myClassToClassDependency.get(p);
609
610         if (deps != null) {
611           dependents.addAll(deps);
612         }
613
614         debug("Affect method usage referenced of class ", p);
615
616         affectedUsages
617           .add(rootUsage instanceof UsageRepr.MetaMethodUsage ? method.createMetaUsage(myContext, p) : method.createUsage(myContext, p));
618       }
619     }
620
621     void affectAll(final DependencyContext.S className, final Collection<File> affectedFiles) {
622       final Set<DependencyContext.S> dependants = (Set<DependencyContext.S>)myClassToClassDependency.get(className);
623       final DependencyContext.S sourceFile = myClassToSourceFile.get(className);
624
625       if (dependants != null) {
626         for (DependencyContext.S depClass : dependants) {
627           final DependencyContext.S depFile = myClassToSourceFile.get(depClass);
628
629           if (depFile != null && sourceFile != null && !depFile.equals(sourceFile)) {
630             affectedFiles.add(new File(myContext.getValue(depFile)));
631           }
632         }
633       }
634     }
635
636     public abstract class UsageConstraint {
637       public abstract boolean checkResidence(final DependencyContext.S residence);
638     }
639
640     public class PackageConstraint extends UsageConstraint {
641       public final String packageName;
642
643       public PackageConstraint(final String packageName) {
644         this.packageName = packageName;
645       }
646
647       @Override
648       public boolean checkResidence(final DependencyContext.S residence) {
649         return !ClassRepr.getPackageName(myContext.getValue(residence)).equals(packageName);
650       }
651     }
652
653     public class InheritanceConstraint extends PackageConstraint {
654       public final DependencyContext.S rootClass;
655
656       public InheritanceConstraint(final DependencyContext.S rootClass) {
657         super(ClassRepr.getPackageName(myContext.getValue(rootClass)));
658         this.rootClass = rootClass;
659       }
660
661       @Override
662       public boolean checkResidence(final DependencyContext.S residence) {
663         final Option<Boolean> inheritorOf = isInheritorOf(residence, rootClass);
664         return inheritorOf.isNone() || !inheritorOf.value() || super.checkResidence(residence);
665       }
666     }
667
668     public class NegationConstraint extends UsageConstraint {
669       final UsageConstraint x;
670
671       public NegationConstraint(UsageConstraint x) {
672         this.x = x;
673       }
674
675       @Override
676       public boolean checkResidence(final DependencyContext.S residence) {
677         return !x.checkResidence(residence);
678       }
679     }
680
681     public class IntersectionConstraint extends UsageConstraint {
682       final UsageConstraint x;
683       final UsageConstraint y;
684
685       public IntersectionConstraint(final UsageConstraint x, final UsageConstraint y) {
686         this.x = x;
687         this.y = y;
688       }
689
690       @Override
691       public boolean checkResidence(final DependencyContext.S residence) {
692         return x.checkResidence(residence) && y.checkResidence(residence);
693       }
694     }
695   }
696
697   private static boolean isPackageLocal(final int access) {
698     return (access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC)) == 0;
699   }
700
701   private static boolean weakerAccess(final int me, final int then) {
702     return ((me & Opcodes.ACC_PRIVATE) > 0 && (then & Opcodes.ACC_PRIVATE) == 0) ||
703            ((me & Opcodes.ACC_PROTECTED) > 0 && (then & Opcodes.ACC_PUBLIC) > 0) ||
704            (isPackageLocal(me) && (then & Opcodes.ACC_PROTECTED) > 0);
705   }
706
707   private static boolean isVisibleIn(final ClassRepr c, final ProtoMember m, final ClassRepr scope) {
708     final boolean privacy = ((m.access & Opcodes.ACC_PRIVATE) > 0) && !c.name.equals(scope.name);
709     final boolean packageLocality = isPackageLocal(m.access) && !c.getPackageName().equals(scope.getPackageName());
710
711     return !privacy && !packageLocality;
712   }
713
714   private boolean empty(final DependencyContext.S s) {
715     return s.equals(myContext.get(""));
716   }
717
718   private Collection<DependencyContext.S> getAllSubclasses(final DependencyContext.S root) {
719     final Collection<DependencyContext.S> result = new HashSet<DependencyContext.S>();
720
721     addAllSubclasses(root, result);
722
723     return result;
724   }
725
726   private void addAllSubclasses(final DependencyContext.S root, final Collection<DependencyContext.S> acc) {
727     final Collection<DependencyContext.S> directSubclasses = myClassToSubclasses.get(root);
728
729     acc.add(root);
730
731     if (directSubclasses != null) {
732       for (final DependencyContext.S s : directSubclasses) {
733         if (acc.contains(s)) {
734           continue;
735         }
736
737         addAllSubclasses(s, acc);
738       }
739     }
740   }
741
742   private boolean incrementalDecision(final DependencyContext.S owner, final Proto member, final Collection<File> affectedFiles) {
743     final boolean isField = member instanceof FieldRepr;
744     final Util self = new Util(this);
745
746     // Public branch --- hopeless
747     if ((member.access & Opcodes.ACC_PUBLIC) > 0) {
748       debug("Public access, switching to a non-incremental mode");
749       return false;
750     }
751
752     // Protected branch
753     if ((member.access & Opcodes.ACC_PROTECTED) > 0) {
754       debug("Protected access, softening non-incremental decision: adding all relevant subclasses for a recompilation");
755       debug("Root class: ", owner);
756
757       final Collection<DependencyContext.S> propagated = self.propagateFieldAccess(isField ? member.name : myContext.get(""), owner);
758
759       for (DependencyContext.S className : propagated) {
760         final String fileName = myContext.getValue(myClassToSourceFile.get(className));
761         debug("Adding ", fileName);
762         affectedFiles.add(new File(fileName));
763       }
764     }
765
766     final String packageName = ClassRepr.getPackageName(myContext.getValue(isField ? owner : member.name));
767
768     debug("Softening non-incremental decision: adding all package classes for a recompilation");
769     debug("Package name: ", packageName);
770
771     // Package-local branch    
772     for (Map.Entry<DependencyContext.S, DependencyContext.S> e : myClassToSourceFile.entrySet()) {
773       final DependencyContext.S className = e.getKey();
774       final DependencyContext.S fileName = e.getValue();
775
776       if (ClassRepr.getPackageName(myContext.getValue(className)).equals(packageName)) {
777         final String f = myContext.getValue(fileName);
778         debug("Adding: ", f);
779         affectedFiles.add(new File(f));
780       }
781     }
782
783     return true;
784   }
785
786   public boolean differentiate(final Mappings delta,
787                                final Collection<String> removed,
788                                final Collection<File> filesToCompile,
789                                final Collection<File> compiledFiles,
790                                final Collection<File> affectedFiles) {
791     debug("Begin of Differentiate:");
792
793     delta.compensateRemovedContent(filesToCompile);
794
795     final Util u = new Util(delta);
796     final Util self = new Util(this);
797     final Util o = new Util();
798
799     if (removed != null) {
800       for (String file : removed) {
801         final Collection<ClassRepr> classes = mySourceFileToClasses.get(myContext.get(file));
802
803         if (classes != null) {
804           for (ClassRepr c : classes) {
805             u.affectAll(c.name, affectedFiles);
806           }
807         }
808       }
809     }
810
811     for (DependencyContext.S fileName : delta.mySourceFileToClasses.keyCollection()) {
812       final Set<ClassRepr> classes = (Set<ClassRepr>)delta.mySourceFileToClasses.get(fileName);
813       final Set<ClassRepr> pastClasses = (Set<ClassRepr>)mySourceFileToClasses.get(fileName);
814       final Set<DependencyContext.S> dependants = new HashSet<DependencyContext.S>();
815
816       self.appendDependents(pastClasses, dependants);
817
818       final Set<UsageRepr.Usage> affectedUsages = new HashSet<UsageRepr.Usage>();
819       final Set<UsageRepr.AnnotationUsage> annotationQuery = new HashSet<UsageRepr.AnnotationUsage>();
820       final Map<UsageRepr.Usage, Util.UsageConstraint> usageConstraints = new HashMap<UsageRepr.Usage, Util.UsageConstraint>();
821
822       final Difference.Specifier<ClassRepr> classDiff = Difference.make(pastClasses, classes);
823
824       debug("Processing changed classes:");
825       for (Pair<ClassRepr, Difference> changed : classDiff.changed()) {
826         final ClassRepr it = changed.first;
827         final ClassRepr.Diff diff = (ClassRepr.Diff)changed.second;
828
829         debug("Changed: ", it.name);
830
831         final int addedModifiers = diff.addedModifiers();
832         final int removedModifiers = diff.removedModifiers();
833
834         final boolean superClassChanged = (diff.base() & Difference.SUPERCLASS) > 0;
835         final boolean interfacesChanged = !diff.interfaces().unchanged();
836         final boolean signatureChanged = (diff.base() & Difference.SIGNATURE) > 0;
837
838         if (superClassChanged || interfacesChanged || signatureChanged) {
839           debug("Superclass changed: ", superClassChanged);
840           debug("Interfaces changed: ", interfacesChanged);
841           debug("Signature changed ", signatureChanged);
842
843           final boolean extendsChanged = superClassChanged && !diff.extendsAdded();
844           final boolean interfacesRemoved = interfacesChanged && !diff.interfaces().removed().isEmpty();
845
846           debug("Extends changed: ", extendsChanged);
847           debug("Interfaces removed: ", interfacesRemoved);
848
849           u.affectSubclasses(it.name, affectedFiles, affectedUsages, dependants, extendsChanged || interfacesRemoved || signatureChanged);
850         }
851
852         if ((diff.addedModifiers() & Opcodes.ACC_INTERFACE) > 0 || (diff.removedModifiers() & Opcodes.ACC_INTERFACE) > 0) {
853           debug("Class-to-interface or interface-to-class conversion detected, added class usage to affected usages");
854           affectedUsages.add(it.createUsage());
855         }
856
857         if (it.isAnnotation() && it.policy == RetentionPolicy.SOURCE) {
858           debug("Annotation, retention policy = SOURCE => a switch to non-incremental mode requested");
859           if (!incrementalDecision(it.outerClassName, it, affectedFiles)) {
860             debug("End of Differentiate, returning false");
861             return false;
862           }
863         }
864
865         if ((addedModifiers & Opcodes.ACC_PROTECTED) > 0) {
866           debug("Introduction of 'protected' modifier detected, adding class usage + inheritance constraint to affected usages");
867           final UsageRepr.Usage usage = it.createUsage();
868
869           affectedUsages.add(usage);
870           usageConstraints.put(usage, u.new InheritanceConstraint(it.name));
871         }
872
873         if (diff.packageLocalOn()) {
874           debug("Introduction of 'package local' access detected, adding class usage + package constraint to affected usages");
875           final UsageRepr.Usage usage = it.createUsage();
876
877           affectedUsages.add(usage);
878           usageConstraints.put(usage, u.new PackageConstraint(it.getPackageName()));
879         }
880
881         if ((addedModifiers & Opcodes.ACC_FINAL) > 0 || (addedModifiers & Opcodes.ACC_PRIVATE) > 0) {
882           debug("Introduction of 'private' or 'final' modifier(s) detected, adding class usage to affected usages");
883           affectedUsages.add(it.createUsage());
884         }
885
886         if ((addedModifiers & Opcodes.ACC_ABSTRACT) > 0 || (addedModifiers & Opcodes.ACC_STATIC) > 0) {
887           debug("Introduction of 'abstract' or 'static' modifier(s) detected, adding class new usage to affected usages");
888           affectedUsages.add(UsageRepr.createClassNewUsage(myContext, it.name));
889         }
890
891         if (it.isAnnotation()) {
892           debug("Class is annotation, performing annotation-specific analysis");
893
894           if (diff.retentionChanged()) {
895             debug("Retention policy change detected, adding class usage to affected usages");
896             affectedUsages.add(it.createUsage());
897           }
898           else {
899             final Collection<ElementType> removedtargets = diff.targets().removed();
900
901             if (removedtargets.contains(ElementType.LOCAL_VARIABLE)) {
902               debug("Removed target contains LOCAL_VARIABLE => a switch to non-incremental mode requested");
903               if (!incrementalDecision(it.outerClassName, it, affectedFiles)) {
904                 debug("End of Differentiate, returning false");
905                 return false;
906               }
907             }
908
909             if (!removedtargets.isEmpty()) {
910               debug("Removed some annotation targets, adding annotation query");
911               annotationQuery.add((UsageRepr.AnnotationUsage)UsageRepr
912                 .createAnnotationUsage(myContext, TypeRepr.createClassType(myContext, it.name), null, removedtargets));
913             }
914
915             for (MethodRepr m : diff.methods().added()) {
916               if (!m.hasValue()) {
917                 debug("Added method with no default value: ", m.name);
918                 debug("Adding class usage to affected usages");
919                 affectedUsages.add(it.createUsage());
920               }
921             }
922           }
923
924           debug("End of annotation-specific analysis");
925         }
926
927         debug("Processing added methods: ");
928         for (MethodRepr m : diff.methods().added()) {
929           debug("Method: ", m.name);
930
931           if (it.isAnnotation()) {
932             debug("Class is annotation, skipping method analysis");
933             continue;
934           }
935
936           if ((it.access & Opcodes.ACC_INTERFACE) > 0 ||
937               (it.access & Opcodes.ACC_ABSTRACT) > 0 ||
938               (m.access & Opcodes.ACC_ABSTRACT) > 0) {
939             debug("Class is abstract, or is interface, or added method in abstract => affecting all subclasses");
940             u.affectSubclasses(it.name, affectedFiles, affectedUsages, dependants, false);
941           }
942
943           Collection<DependencyContext.S> propagated = null;
944
945           if ((m.access & Opcodes.ACC_PRIVATE) == 0 && !myContext.getValue(m.name).equals("<init>")) {
946             final ClassRepr oldIt = getReprByName(it.name);
947
948             if (oldIt != null && self.findOverridenMethods(m, oldIt).size() > 0) {
949
950             }
951             else {
952               propagated = u.propagateMethodAccess(m.name, it.name);
953               debug("Conservative case on overriding methods, affecting method usages");
954               u.affectMethodUsages(m, propagated, m.createMetaUsage(myContext, it.name), affectedUsages, dependants);
955             }
956           }
957
958           if ((m.access & Opcodes.ACC_PRIVATE) == 0) {
959             final Collection<Pair<MethodRepr, ClassRepr>> affectedMethods = u.findAllMethodsBySpecificity(m, it);
960             final MethodRepr.Predicate overrides = MethodRepr.equalByJavaRules(m);
961
962             if (propagated == null) {
963               propagated = u.propagateMethodAccess(m.name, it.name);
964             }
965
966             final Collection<MethodRepr> lessSpecific = it.findMethods(u.lessSpecific(m));
967
968             for (MethodRepr mm : lessSpecific) {
969               if (!mm.equals(m)) {
970                 debug("Found less specific method, affecting method usages");
971                 u.affectMethodUsages(mm, propagated, mm.createUsage(myContext, it.name), affectedUsages, dependants);
972               }
973             }
974
975             debug("Processing affected by specificity methods");
976             for (Pair<MethodRepr, ClassRepr> p : affectedMethods) {
977               final MethodRepr mm = p.first;
978               final ClassRepr cc = p.second;
979
980               if (cc == myMockClass) {
981
982               }
983               else {
984                 debug("Method: ", mm.name);
985                 debug("Class : ", cc.name);
986
987                 if (overrides.satisfy(mm)) {
988                   debug("Current method overrides that found");
989
990                   final Option<Boolean> subtypeOf = u.isSubtypeOf(mm.type, m.type);
991
992                   if (weakerAccess(mm.access, m.access) ||
993                       ((m.access & Opcodes.ACC_STATIC) > 0 && (mm.access & Opcodes.ACC_STATIC) == 0) ||
994                       ((m.access & Opcodes.ACC_STATIC) == 0 && (mm.access & Opcodes.ACC_STATIC) > 0) ||
995                       ((m.access & Opcodes.ACC_FINAL) > 0) ||
996                       !m.exceptions.equals(mm.exceptions) ||
997                       (subtypeOf.isNone() || !subtypeOf.value()) ||
998                       !empty(mm.signature) || !empty(m.signature)) {
999                     final DependencyContext.S file = myClassToSourceFile.get(cc.name);
1000
1001                     if (file != null) {
1002                       final String f = myContext.getValue(file);
1003                       debug("Complex condition is satisfied, affecting file ", f);
1004                       affectedFiles.add(new File(f));
1005                     }
1006                   }
1007                 }
1008                 else {
1009                   debug("Current method does not override that found");
1010
1011                   final Collection<DependencyContext.S> yetPropagated = self.propagateMethodAccess(mm.name, cc.name);
1012                   final Collection<DependencyContext.S> deps = myClassToClassDependency.get(cc.name);
1013
1014                   if (deps != null) {
1015                     dependants.addAll(deps);
1016                   }
1017
1018                   debug("Affecting method usages for that found");
1019                   u.affectMethodUsages(mm, yetPropagated, mm.createUsage(myContext, cc.name), affectedUsages, dependants);
1020                 }
1021               }
1022             }
1023
1024             final Collection<DependencyContext.S> subClasses = getAllSubclasses(it.name);
1025
1026             if (subClasses != null) {
1027               for (final DependencyContext.S subClass : subClasses) {
1028                 final ClassRepr r = u.reprByName(subClass);
1029                 final DependencyContext.S sourceFileName = myClassToSourceFile.get(subClass);
1030
1031                 if (r != null && sourceFileName != null) {
1032                   final DependencyContext.S outerClass = r.outerClassName;
1033
1034                   if (u.methodVisible(outerClass, m)) {
1035                     final String f = myContext.getValue(sourceFileName);
1036                     debug("Affecting file due to local overriding: ", f);
1037                     affectedFiles.add(new File(f));
1038                   }
1039                 }
1040               }
1041             }
1042           }
1043         }
1044         debug("End of added methods processing");
1045
1046         debug("Processing removed methods:");
1047         for (MethodRepr m : diff.methods().removed()) {
1048           debug("Method ", m.name);
1049
1050           final Collection<Pair<MethodRepr, ClassRepr>> overridenMethods = u.findOverridenMethods(m, it);
1051           final Collection<DependencyContext.S> propagated = u.propagateMethodAccess(m.name, it.name);
1052
1053           if (overridenMethods.size() == 0) {
1054             debug("No overridden methods found, affecting method usages");
1055             u.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), affectedUsages, dependants);
1056           }
1057           else {
1058             boolean clear = true;
1059
1060             loop:
1061             for (Pair<MethodRepr, ClassRepr> overriden : overridenMethods) {
1062               final MethodRepr mm = overriden.first;
1063
1064               if (mm == myMockMethod || !mm.type.equals(m.type) || !empty(mm.signature) || !empty(m.signature)) {
1065                 clear = false;
1066                 break loop;
1067               }
1068             }
1069
1070             if (!clear) {
1071               debug("No clearly overridden methods found, affecting method usages");
1072               u.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), affectedUsages, dependants);
1073             }
1074           }
1075
1076           if ((m.access & Opcodes.ACC_ABSTRACT) == 0) {
1077             for (DependencyContext.S p : propagated) {
1078               final ClassRepr s = u.reprByName(p);
1079
1080               if (s != null) {
1081                 final Collection<Pair<MethodRepr, ClassRepr>> overridenInS = u.findOverridenMethods(m, s);
1082
1083                 overridenInS.addAll(overridenMethods);
1084
1085                 boolean allAbstract = true;
1086                 boolean visited = false;
1087
1088                 for (Pair<MethodRepr, ClassRepr> pp : overridenInS) {
1089                   final ClassRepr cc = pp.second;
1090
1091                   if (cc == myMockClass) {
1092                     visited = true;
1093                     continue;
1094                   }
1095
1096                   if (cc.name.equals(it.name)) {
1097                     continue;
1098                   }
1099
1100                   visited = true;
1101                   allAbstract = ((pp.first.access & Opcodes.ACC_ABSTRACT) > 0) || ((cc.access & Opcodes.ACC_INTERFACE) > 0);
1102
1103                   if (!allAbstract) {
1104                     break;
1105                   }
1106                 }
1107
1108                 if (allAbstract && visited) {
1109                   final DependencyContext.S source = myClassToSourceFile.get(p);
1110
1111                   if (source != null) {
1112                     final String f = myContext.getValue(source);
1113                     debug(
1114                       "Removed method is not abstract & is overrides some abstract method which is not then over-overriden in subclass ",
1115                       p);
1116                     debug("Affecting subclass source file ", f);
1117                     affectedFiles.add(new File(f));
1118                   }
1119                 }
1120               }
1121             }
1122           }
1123         }
1124         debug("End of removed methods processing");
1125
1126         debug("Processing changed methods:");
1127         for (Pair<MethodRepr, Difference> mr : diff.methods().changed()) {
1128           final MethodRepr m = mr.first;
1129           final MethodRepr.Diff d = (MethodRepr.Diff)mr.second;
1130           final boolean throwsChanged = (d.exceptions().added().size() > 0) || (d.exceptions().changed().size() > 0);
1131
1132           debug("Method: ", m.name);
1133
1134           if (it.isAnnotation()) {
1135             if (d.defaultRemoved()) {
1136               debug("Class is annotation, default value is removed => adding annotation query");
1137               final List<DependencyContext.S> l = new LinkedList<DependencyContext.S>();
1138               l.add(m.name);
1139               annotationQuery.add((UsageRepr.AnnotationUsage)UsageRepr
1140                 .createAnnotationUsage(myContext, TypeRepr.createClassType(myContext, it.name), l, null));
1141             }
1142           }
1143           else if (d.base() != Difference.NONE || throwsChanged) {
1144             final Collection<DependencyContext.S> propagated = u.propagateMethodAccess(m.name, it.name);
1145
1146             boolean affected = false;
1147             boolean constrained = false;
1148
1149             final Set<UsageRepr.Usage> usages = new HashSet<UsageRepr.Usage>();
1150
1151             if (d.packageLocalOn()) {
1152               debug("Method became package-local, affecting method usages outside the package");
1153               u.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, dependants);
1154
1155               for (UsageRepr.Usage usage : usages) {
1156                 usageConstraints.put(usage, u.new InheritanceConstraint(it.name));
1157               }
1158
1159               affectedUsages.addAll(usages);
1160               affected = true;
1161               constrained = true;
1162             }
1163
1164             if ((d.base() & Difference.TYPE) > 0 || (d.base() & Difference.SIGNATURE) > 0 || throwsChanged) {
1165               if (!affected) {
1166                 debug("Return type, throws list or signature changed --- affecting method usages");
1167                 u.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, dependants);
1168                 affectedUsages.addAll(usages);
1169               }
1170             }
1171             else if ((d.base() & Difference.ACCESS) > 0) {
1172               if ((d.addedModifiers() & Opcodes.ACC_STATIC) > 0 ||
1173                   (d.removedModifiers() & Opcodes.ACC_STATIC) > 0 ||
1174                   (d.addedModifiers() & Opcodes.ACC_PRIVATE) > 0) {
1175                 if (!affected) {
1176                   debug("Added static or private specifier or removed static specifier --- affecting method usages");
1177                   u.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, dependants);
1178                   affectedUsages.addAll(usages);
1179                 }
1180
1181                 if ((d.addedModifiers() & Opcodes.ACC_STATIC) > 0) {
1182                   debug("Added static specifier --- affecting subclasses");
1183                   u.affectSubclasses(it.name, affectedFiles, affectedUsages, dependants, false);
1184                 }
1185               }
1186               else {
1187                 if ((d.addedModifiers() & Opcodes.ACC_FINAL) > 0 ||
1188                     (d.addedModifiers() & Opcodes.ACC_PUBLIC) > 0 ||
1189                     (d.addedModifiers() & Opcodes.ACC_ABSTRACT) > 0) {
1190                   debug("Added final, public or abstract specifier --- affecting subclasses");
1191                   u.affectSubclasses(it.name, affectedFiles, affectedUsages, dependants, false);
1192                 }
1193
1194                 if ((d.addedModifiers() & Opcodes.ACC_PROTECTED) > 0 && !((d.removedModifiers() & Opcodes.ACC_PRIVATE) > 0)) {
1195                   if (!constrained) {
1196                     debug("Added public or package-local method became protected --- affect method usages with protected constraint");
1197                     if (!affected) {
1198                       u.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, dependants);
1199                       affectedUsages.addAll(usages);
1200                     }
1201
1202                     for (UsageRepr.Usage usage : usages) {
1203                       usageConstraints.put(usage, u.new InheritanceConstraint(it.name));
1204                     }
1205                   }
1206                 }
1207               }
1208             }
1209           }
1210         }
1211         debug("End of changed methods processing");
1212
1213         final int mask = Opcodes.ACC_STATIC | Opcodes.ACC_FINAL;
1214
1215         debug("Processing added fields");
1216         for (FieldRepr f : diff.fields().added()) {
1217           debug("Field: ", f.name);
1218
1219           final boolean fPrivate = (f.access & Opcodes.ACC_PRIVATE) > 0;
1220           final boolean fProtected = (f.access & Opcodes.ACC_PROTECTED) > 0;
1221           final boolean fPublic = (f.access & Opcodes.ACC_PUBLIC) > 0;
1222           final boolean fPLocal = !fPrivate && !fProtected && !fPublic;
1223
1224           if (!fPrivate) {
1225             final Collection<DependencyContext.S> subClasses = getAllSubclasses(it.name);
1226
1227             for (final DependencyContext.S subClass : subClasses) {
1228               final ClassRepr r = u.reprByName(subClass);
1229               final DependencyContext.S sourceFileName = myClassToSourceFile.get(subClass);
1230
1231               if (r != null && sourceFileName != null) {
1232                 if (r.isLocal) {
1233                   debug("Affecting local subclass (introduced field can potentially hide surrounding method parameters/local variables): ",
1234                         sourceFileName);
1235                   affectedFiles.add(new File(myContext.getValue(sourceFileName)));
1236                 }
1237                 else {
1238                   final DependencyContext.S outerClass = r.outerClassName;
1239
1240                   if (!empty(outerClass) && u.fieldVisible(outerClass, f)) {
1241                     debug("Affecting inner subclass (introduced field can potentially hide surrounding class fields): ", sourceFileName);
1242                     affectedFiles.add(new File(myContext.getValue(sourceFileName)));
1243                   }
1244                 }
1245               }
1246
1247               debug("Affecting field usages referenced from subclass ", subClass);
1248               final Collection<DependencyContext.S> propagated = u.propagateFieldAccess(f.name, subClass);
1249               u.affectFieldUsages(f, propagated, f.createUsage(myContext, subClass), affectedUsages, dependants);
1250
1251               final Collection<DependencyContext.S> deps = myClassToClassDependency.get(subClass);
1252
1253               if (deps != null) {
1254                 dependants.addAll(deps);
1255               }
1256             }
1257           }
1258
1259           final Collection<Pair<FieldRepr, ClassRepr>> overridden = u.findOverridenFields(f, it);
1260
1261           for (Pair<FieldRepr, ClassRepr> p : overridden) {
1262             final FieldRepr ff = p.first;
1263             final ClassRepr cc = p.second;
1264
1265             final boolean ffPrivate = (ff.access & Opcodes.ACC_PRIVATE) > 0;
1266             final boolean ffProtected = (ff.access & Opcodes.ACC_PROTECTED) > 0;
1267             final boolean ffPublic = (ff.access & Opcodes.ACC_PUBLIC) > 0;
1268             final boolean ffPLocal = isPackageLocal(ff.access);
1269
1270             if (!ffPrivate) {
1271               final Collection<DependencyContext.S> propagated = o.propagateFieldAccess(ff.name, cc.name);
1272               final Set<UsageRepr.Usage> localUsages = new HashSet<UsageRepr.Usage>();
1273
1274               debug("Affecting usages of overridden field in class ", cc.name);
1275               u.affectFieldUsages(ff, propagated, ff.createUsage(myContext, cc.name), localUsages, dependants);
1276
1277               if (fPrivate || (fPublic && (ffPublic || ffPLocal)) || (fProtected && ffProtected) || (fPLocal && ffPLocal)) {
1278
1279               }
1280               else {
1281                 Util.UsageConstraint constaint;
1282
1283                 if ((ffProtected && fPublic) || (fProtected && ffPublic) || (ffPLocal && fProtected)) {
1284                   constaint = u.new NegationConstraint(u.new InheritanceConstraint(cc.name));
1285                 }
1286                 else if (ffPublic && ffPLocal) {
1287                   constaint = u.new NegationConstraint(u.new PackageConstraint(cc.getPackageName()));
1288                 }
1289                 else {
1290                   constaint = u.new IntersectionConstraint(u.new NegationConstraint(u.new InheritanceConstraint(cc.name)),
1291                                                            u.new NegationConstraint(u.new PackageConstraint(cc.getPackageName())));
1292                 }
1293
1294                 for (UsageRepr.Usage usage : localUsages) {
1295                   usageConstraints.put(usage, constaint);
1296                 }
1297               }
1298
1299               affectedUsages.addAll(localUsages);
1300             }
1301           }
1302         }
1303         debug("End of added fields processing");
1304
1305         debug("Processing removed fields:");
1306         for (FieldRepr f : diff.fields().removed()) {
1307           debug("Field: ", it.name);
1308
1309           if ((f.access & Opcodes.ACC_PRIVATE) == 0 && (f.access & mask) == mask && f.hasValue()) {
1310             debug("Field had value and was (non-private) final static => a switch to non-incremental mode requested");
1311             if (!incrementalDecision(it.name, f, affectedFiles)) {
1312               debug("End of Differentiate, returning false");
1313               return false;
1314             }
1315           }
1316
1317           final Collection<DependencyContext.S> propagated = u.propagateFieldAccess(f.name, it.name);
1318           u.affectFieldUsages(f, propagated, f.createUsage(myContext, it.name), affectedUsages, dependants);
1319         }
1320         debug("End of removed fields processing");
1321
1322         debug("Processing changed fields:");
1323         for (Pair<FieldRepr, Difference> f : diff.fields().changed()) {
1324           final Difference d = f.second;
1325           final FieldRepr field = f.first;
1326
1327           debug("Field: ", it.name);
1328
1329           if ((field.access & Opcodes.ACC_PRIVATE) == 0 && (field.access & mask) == mask) {
1330             if ((d.base() & Difference.ACCESS) > 0 || (d.base() & Difference.VALUE) > 0) {
1331               debug("Inline field changed it's access or value => a switch to non-incremental mode requested");
1332               if (!incrementalDecision(it.name, field, affectedFiles)) {
1333                 debug("End of Differentiate, returning false");
1334                 return false;
1335               }
1336             }
1337           }
1338
1339           if (d.base() != Difference.NONE) {
1340             final Collection<DependencyContext.S> propagated = u.propagateFieldAccess(field.name, it.name);
1341
1342             if ((d.base() & Difference.TYPE) > 0 || (d.base() & Difference.SIGNATURE) > 0) {
1343               debug("Type or signature changed --- affecting field usages");
1344               u.affectFieldUsages(field, propagated, field.createUsage(myContext, it.name), affectedUsages, dependants);
1345             }
1346             else if ((d.base() & Difference.ACCESS) > 0) {
1347               if ((d.addedModifiers() & Opcodes.ACC_STATIC) > 0 ||
1348                   (d.removedModifiers() & Opcodes.ACC_STATIC) > 0 ||
1349                   (d.addedModifiers() & Opcodes.ACC_PRIVATE) > 0 ||
1350                   (d.addedModifiers() & Opcodes.ACC_VOLATILE) > 0) {
1351                 debug("Added/removed static modifier or added private/volatile modifier --- affecting field usages");
1352                 u.affectFieldUsages(field, propagated, field.createUsage(myContext, it.name), affectedUsages, dependants);
1353               }
1354               else {
1355                 boolean affected = false;
1356                 final Set<UsageRepr.Usage> usages = new HashSet<UsageRepr.Usage>();
1357
1358                 if ((d.addedModifiers() & Opcodes.ACC_FINAL) > 0) {
1359                   debug("Added final modifier --- affecting field assign usages");
1360                   u.affectFieldUsages(field, propagated, field.createAssignUsage(myContext, it.name), usages, dependants);
1361                   affectedUsages.addAll(usages);
1362                   affected = true;
1363                 }
1364
1365                 if ((d.removedModifiers() & Opcodes.ACC_PUBLIC) > 0) {
1366                   debug("Removed public modifier, affecting field usages with appropriate constraint");
1367                   if (!affected) {
1368                     u.affectFieldUsages(field, propagated, field.createUsage(myContext, it.name), usages, dependants);
1369                     affectedUsages.addAll(usages);
1370                   }
1371
1372                   for (UsageRepr.Usage usage : usages) {
1373                     if ((d.addedModifiers() & Opcodes.ACC_PROTECTED) > 0) {
1374                       usageConstraints.put(usage, u.new InheritanceConstraint(it.name));
1375                     }
1376                     else {
1377                       usageConstraints.put(usage, u.new PackageConstraint(it.getPackageName()));
1378                     }
1379                   }
1380                 }
1381               }
1382             }
1383           }
1384         }
1385         debug("End of changed fields processing");
1386       }
1387       debug("End of changed classes processing");
1388
1389       debug("Processing removed classes:");
1390       for (ClassRepr c : classDiff.removed()) {
1391         debug("Adding usages of class ", c.name);
1392         affectedUsages.add(c.createUsage());
1393       }
1394       debug("End of removed classes processing.");
1395
1396       debug("Processing added classes:");
1397       for (ClassRepr c : classDiff.added()) {
1398         final Collection<DependencyContext.S> depClasses = myClassToClassDependency.get(c.name);
1399
1400         if (depClasses != null) {
1401           for (DependencyContext.S depClass : depClasses) {
1402             final DependencyContext.S fName = myClassToSourceFile.get(depClass);
1403
1404             if (fName != null) {
1405               final String f = myContext.getValue(fName);
1406               debug("Adding dependent file ", f);
1407               affectedFiles.add(new File(f));
1408             }
1409           }
1410         }
1411       }
1412       debug("End of added classes processing.");
1413
1414       debug("Checking dependent files:");
1415       if (dependants != null) {
1416         final Set<DependencyContext.S> dependentFiles = new HashSet<DependencyContext.S>();
1417
1418         for (DependencyContext.S depClass : dependants) {
1419           final DependencyContext.S file = myClassToSourceFile.get(depClass);
1420
1421           if (file != null) {
1422             dependentFiles.add(file);
1423           }
1424         }
1425
1426         filewise:
1427         for (DependencyContext.S depFile : dependentFiles) {
1428           final File theFile = new File(myContext.getValue(depFile));
1429
1430           if (affectedFiles.contains(theFile) || compiledFiles.contains(theFile)) {
1431             continue filewise;
1432           }
1433
1434           debug("Dependent file: ", depFile);
1435           final Collection<UsageRepr.Cluster> depClusters = mySourceFileToUsages.get(depFile);
1436
1437           for (UsageRepr.Cluster depCluster : depClusters) {
1438             final Set<UsageRepr.Usage> depUsages = depCluster.getUsages();
1439
1440             if (depUsages != null) {
1441               final Set<UsageRepr.Usage> usages = new HashSet<UsageRepr.Usage>(depUsages);
1442
1443               usages.retainAll(affectedUsages);
1444
1445               if (!usages.isEmpty()) {
1446                 for (UsageRepr.Usage usage : usages) {
1447                   final Util.UsageConstraint constraint = usageConstraints.get(usage);
1448
1449                   if (constraint == null) {
1450                     debug("Added file with no constraints");
1451                     affectedFiles.add(theFile);
1452                     continue filewise;
1453                   }
1454                   else {
1455                     final Set<DependencyContext.S> residenceClasses = depCluster.getResidence(usage);
1456                     for (DependencyContext.S residentName : residenceClasses) {
1457                       if (constraint.checkResidence(residentName)) {
1458                         debug("Added file with satisfied constraint");
1459                         affectedFiles.add(theFile);
1460                         continue filewise;
1461                       }
1462                     }
1463
1464                   }
1465                 }
1466               }
1467
1468               if (annotationQuery.size() > 0) {
1469                 final Collection<UsageRepr.Usage> annotationUsages = mySourceFileToAnnotationUsages.get(depFile);
1470
1471                 for (UsageRepr.Usage usage : annotationUsages) {
1472                   for (UsageRepr.AnnotationUsage query : annotationQuery) {
1473                     if (query.satisfies(usage)) {
1474                       debug("Added file due to annotation query");
1475                       affectedFiles.add(theFile);
1476                       continue filewise;
1477                     }
1478                   }
1479                 }
1480               }
1481             }
1482           }
1483         }
1484       }
1485     }
1486
1487     if (removed != null) {
1488       for (String r : removed) {
1489         affectedFiles.remove(new File(r));
1490       }
1491     }
1492
1493     debug("End of Differentiate, returning true");
1494     return true;
1495   }
1496
1497   public void integrate(final Mappings delta, final Collection<File> compiled, final Collection<String> removed) {
1498     try {
1499       if (removed != null) {
1500         for (String file : removed) {
1501           final DependencyContext.S key = myContext.get(file);
1502           final Set<ClassRepr> classes = (Set<ClassRepr>)mySourceFileToClasses.get(key);
1503           final Collection<UsageRepr.Cluster> clusters = mySourceFileToUsages.get(key);
1504
1505           if (classes != null) {
1506             for (ClassRepr cr : classes) {
1507               myClassToSubclasses.remove(cr.name);
1508               myClassToSourceFile.remove(cr.name);
1509               myClassToClassDependency.remove(cr.name);
1510
1511               for (DependencyContext.S superSomething : cr.getSupers()) {
1512                 myClassToSubclasses.removeFrom(superSomething, cr.name);
1513               }
1514
1515               for (UsageRepr.Cluster cluster : clusters) {
1516                 final Set<UsageRepr.Usage> usages = cluster.getUsages();
1517                 if (usages != null) {
1518                   for (UsageRepr.Usage u : usages) {
1519                     if (u instanceof UsageRepr.ClassUsage) {
1520                       final Set<DependencyContext.S> residents = cluster.getResidence(u);
1521
1522                       if (residents != null && residents.contains(cr.name)) {
1523                         myClassToClassDependency.removeFrom(((UsageRepr.ClassUsage)u).className, cr.name);
1524                       }
1525                     }
1526                   }
1527                 }
1528               }
1529             }
1530           }
1531
1532           mySourceFileToClasses.remove(key);
1533           mySourceFileToUsages.remove(key);
1534           mySourceFileToAnnotationUsages.remove(key);
1535         }
1536       }
1537
1538       myClassToSubclasses.putAll(delta.myClassToSubclasses);
1539       mySourceFileToClasses.putAll(delta.mySourceFileToClasses);
1540       mySourceFileToUsages.putAll(delta.mySourceFileToUsages);
1541       mySourceFileToAnnotationUsages.putAll(delta.mySourceFileToAnnotationUsages);
1542       myClassToSourceFile.putAll(delta.myClassToSourceFile);
1543
1544       for (DependencyContext.S file : delta.myClassToClassDependency.keyCollection()) {
1545         final Collection<DependencyContext.S> now = delta.myClassToClassDependency.get(file);
1546         final Collection<DependencyContext.S> past = myClassToClassDependency.get(file);
1547
1548         if (past == null) {
1549           myClassToClassDependency.put(file, now);
1550         }
1551         else {
1552           final Collection<DependencyContext.S> removeSet = new HashSet<DependencyContext.S>();
1553
1554           for (File c : compiled) {
1555             removeSet.add(myContext.get(FileUtil.toSystemIndependentName(c.getAbsolutePath())));
1556           }
1557
1558           removeSet.removeAll(now);
1559
1560           past.addAll(now);
1561           past.removeAll(removeSet);
1562
1563           myClassToClassDependency.remove(file);
1564           myClassToClassDependency.put(file, past);
1565         }
1566       }
1567     }
1568     finally {
1569       delta.close();
1570     }
1571   }
1572
1573   public Callbacks.Backend getCallback() {
1574     return new Callbacks.Backend() {
1575       public Collection<String> getClassFiles() {
1576         final HashSet<String> result = new HashSet<String>();
1577
1578         for (DependencyContext.S s : myClassToSourceFile.keyCollection()) {
1579           result.add(myContext.getValue(s));
1580         }
1581
1582         return result;
1583       }
1584
1585       public void associate(final String classFileName, final Callbacks.SourceFileNameLookup sourceFileName, final ClassReader cr) {
1586         final DependencyContext.S classFileNameS = myContext.get(classFileName);
1587         final Pair<ClassRepr, Pair<UsageRepr.Cluster, Set<UsageRepr.Usage>>> result =
1588           new ClassfileAnalyzer(myContext).analyze(classFileNameS, cr);
1589         final ClassRepr repr = result.first;
1590         final UsageRepr.Cluster localUsages = result.second.first;
1591         final Set<UsageRepr.Usage> localAnnotationUsages = result.second.second;
1592
1593         final String srcFileName = sourceFileName.get(repr == null ? null : myContext.getValue(repr.getSourceFileName()));
1594         final DependencyContext.S sourceFileNameS = myContext.get(srcFileName);
1595
1596         if (repr != null) {
1597           final DependencyContext.S className = repr.name;
1598
1599           myClassToSourceFile.put(repr.name, sourceFileNameS);
1600           mySourceFileToClasses.put(sourceFileNameS, repr);
1601
1602           for (DependencyContext.S s : repr.getSupers()) {
1603             myClassToSubclasses.put(s, repr.name);
1604           }
1605
1606           for (UsageRepr.Usage u : localUsages.getUsages()) {
1607             final DependencyContext.S owner = u.getOwner();
1608
1609             if (!owner.equals(className)) {
1610               final DependencyContext.S sourceFile = repr.getSourceFileName();
1611               final DependencyContext.S ownerSourceFile = myClassToSourceFile.get(owner);
1612
1613               if (ownerSourceFile != null) {
1614                 if (!ownerSourceFile.equals(sourceFile)) {
1615                   myClassToClassDependency.put(owner, className);
1616                 }
1617               }
1618               else {
1619                 myClassToClassDependency.put(owner, className);
1620               }
1621             }
1622           }
1623         }
1624
1625         if (!localUsages.isEmpty()) {
1626           mySourceFileToUsages.put(sourceFileNameS, localUsages);
1627         }
1628
1629         if (!localAnnotationUsages.isEmpty()) {
1630           mySourceFileToAnnotationUsages.put(sourceFileNameS, localAnnotationUsages);
1631         }
1632       }
1633     };
1634   }
1635
1636   @Nullable
1637   public Set<ClassRepr> getClasses(final String sourceFileName) {
1638     return (Set<ClassRepr>)mySourceFileToClasses.get(myContext.get(sourceFileName));
1639   }
1640
1641   public void close() {
1642     myClassToSubclasses.close();
1643     myClassToClassDependency.close();
1644     mySourceFileToClasses.close();
1645     mySourceFileToAnnotationUsages.close();
1646     mySourceFileToUsages.close();
1647     myClassToSourceFile.close();
1648
1649     if (!myIsDelta) {
1650       // only close if you own the context
1651       final DependencyContext context = myContext;
1652       if (context != null) {
1653         context.close();
1654         myContext = null;
1655       }
1656     }
1657     else {
1658       FileUtil.delete(myRootDir);
1659     }
1660   }
1661
1662   public void flush(final boolean memoryCachesOnly) {
1663     myClassToSubclasses.flush(memoryCachesOnly);
1664     myClassToClassDependency.flush(memoryCachesOnly);
1665     mySourceFileToClasses.flush(memoryCachesOnly);
1666     mySourceFileToAnnotationUsages.flush(memoryCachesOnly);
1667     mySourceFileToUsages.flush(memoryCachesOnly);
1668     myClassToSourceFile.flush(memoryCachesOnly);
1669
1670     if (!myIsDelta) {
1671       // flush if you own the context
1672       final DependencyContext context = myContext;
1673       if (context != null) {
1674         if (memoryCachesOnly) {
1675           context.clearMemoryCaches();
1676         }
1677         else {
1678           context.flush();
1679         }
1680       }
1681     }
1682   }
1683 }