blob: 3e3a4793cbb695c155588ce9d6be613368edbe7d [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jps.builders.java.dependencyView;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.io.IntInlineKeyDescriptor;
import gnu.trove.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.builders.storage.BuildDataCorruptedException;
import org.jetbrains.jps.incremental.storage.FileKeyDescriptor;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.Opcodes;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.annotation.RetentionPolicy;
import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @author: db
* Date: 28.01.11
*/
public class Mappings {
private final static Logger LOG = Logger.getInstance("#org.jetbrains.ether.dependencyView.Mappings");
private final static String CLASS_TO_SUBCLASSES = "classToSubclasses.tab";
private final static String CLASS_TO_CLASS = "classToClass.tab";
private final static String SHORT_NAMES = "shortNames.tab";
private final static String SOURCE_TO_CLASS = "sourceToClass.tab";
private final static String CLASS_TO_SOURCE = "classToSource.tab";
private static final IntInlineKeyDescriptor INT_KEY_DESCRIPTOR = new IntInlineKeyDescriptor();
private static final int DEFAULT_SET_CAPACITY = 32;
private static final float DEFAULT_SET_LOAD_FACTOR = 0.98f;
private static final CollectionFactory<ClassRepr> ourClassSetConstructor = new CollectionFactory<ClassRepr>() {
public Set<ClassRepr> create() {
// for IDEA codebase on average there is no more than 2.5 classes out of one source file, so we use smaller estimate
return new THashSet<ClassRepr>(5, DEFAULT_SET_LOAD_FACTOR);
}
};
private final boolean myIsDelta;
private final boolean myDeltaIsTransient;
private boolean myIsDifferentiated = false;
private boolean myIsRebuild = false;
private final TIntHashSet myChangedClasses;
private final THashSet<File> myChangedFiles;
private final Set<Pair<ClassRepr, File>> myDeletedClasses;
private final Set<ClassRepr> myAddedClasses;
private final Object myLock;
private final File myRootDir;
private DependencyContext myContext;
private final int myInitName;
private final int myEmptyName;
private final int myObjectClassName;
private LoggerWrapper<Integer> myDebugS;
private IntIntMultiMaplet myClassToSubclasses;
/**
key: the name of a class who is used;
values: class names that use the class registered as the key
*/
private IntIntMultiMaplet myClassToClassDependency;
private ObjectObjectMultiMaplet<File, ClassRepr> mySourceFileToClasses;
private IntObjectMultiMaplet<File> myClassToSourceFile;
/**
* [short className] -> list of FQ names
*/
private IntIntMultiMaplet myShortClassNameIndex;
private IntIntTransientMultiMaplet myRemovedSuperClasses;
private IntIntTransientMultiMaplet myAddedSuperClasses;
@Nullable
private Collection<String> myRemovedFiles;
private Mappings(final Mappings base) throws IOException {
myLock = base.myLock;
myIsDelta = true;
myChangedClasses = new TIntHashSet(DEFAULT_SET_CAPACITY, DEFAULT_SET_LOAD_FACTOR);
myChangedFiles = new THashSet(FileUtil.FILE_HASHING_STRATEGY);
myDeletedClasses = new HashSet<Pair<ClassRepr, File>>(DEFAULT_SET_CAPACITY, DEFAULT_SET_LOAD_FACTOR);
myAddedClasses = new HashSet<ClassRepr>(DEFAULT_SET_CAPACITY, DEFAULT_SET_LOAD_FACTOR);
myDeltaIsTransient = base.myDeltaIsTransient;
myRootDir = new File(FileUtil.toSystemIndependentName(base.myRootDir.getAbsolutePath()) + File.separatorChar + "myDelta");
myContext = base.myContext;
myInitName = myContext.get("<init>");
myEmptyName = myContext.get("");
myObjectClassName = myContext.get("java/lang/Object");
myDebugS = base.myDebugS;
createImplementation();
}
public Mappings(final File rootDir, final boolean transientDelta) throws IOException {
myLock = new Object();
myIsDelta = false;
myChangedClasses = null;
myChangedFiles = null;
myDeletedClasses = null;
myAddedClasses = null;
myDeltaIsTransient = transientDelta;
myRootDir = rootDir;
createImplementation();
myInitName = myContext.get("<init>");
myEmptyName = myContext.get("");
myObjectClassName = myContext.get("java/lang/Object");
}
private void createImplementation() throws IOException {
if (!myIsDelta) {
myContext = new DependencyContext(myRootDir);
myDebugS = myContext.getLogger(LOG);
}
myRemovedSuperClasses = myIsDelta ? new IntIntTransientMultiMaplet() : null;
myAddedSuperClasses = myIsDelta ? new IntIntTransientMultiMaplet() : null;
final CollectionFactory<File> fileCollectionFactory = new CollectionFactory<File>() {
public Collection<File> create() {
return new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY); // todo: do we really need set and not a list here?
}
};
if (myIsDelta && myDeltaIsTransient) {
myClassToSubclasses = new IntIntTransientMultiMaplet();
myClassToClassDependency = new IntIntTransientMultiMaplet();
myShortClassNameIndex = null;
mySourceFileToClasses = new ObjectObjectTransientMultiMaplet<File, ClassRepr>(FileUtil.FILE_HASHING_STRATEGY, ourClassSetConstructor);
myClassToSourceFile = new IntObjectTransientMultiMaplet<File>(fileCollectionFactory);
}
else {
if (myIsDelta) {
myRootDir.mkdirs();
}
myClassToSubclasses = new IntIntPersistentMultiMaplet(DependencyContext.getTableFile(myRootDir, CLASS_TO_SUBCLASSES), INT_KEY_DESCRIPTOR);
myClassToClassDependency = new IntIntPersistentMultiMaplet(DependencyContext.getTableFile(myRootDir, CLASS_TO_CLASS), INT_KEY_DESCRIPTOR);
myShortClassNameIndex = myIsDelta? null : new IntIntPersistentMultiMaplet(DependencyContext.getTableFile(myRootDir, SHORT_NAMES), INT_KEY_DESCRIPTOR);
mySourceFileToClasses = new ObjectObjectPersistentMultiMaplet<File, ClassRepr>(
DependencyContext.getTableFile(myRootDir, SOURCE_TO_CLASS), new FileKeyDescriptor(), ClassRepr.externalizer(myContext),
ourClassSetConstructor
);
myClassToSourceFile = new IntObjectPersistentMultiMaplet<File>(DependencyContext.getTableFile(myRootDir, CLASS_TO_SOURCE), INT_KEY_DESCRIPTOR, new FileKeyDescriptor(), fileCollectionFactory);
}
}
public String valueOf(final int name) {
return myContext.getValue(name);
}
public int getName(final String string) {
return myContext.get(string);
}
public Mappings createDelta() {
synchronized (myLock) {
try {
return new Mappings(this);
}
catch (IOException e) {
throw new BuildDataCorruptedException(e);
}
}
}
private void compensateRemovedContent(final Collection<File> compiled) {
if (compiled != null) {
for (final File file : compiled) {
if (!mySourceFileToClasses.containsKey(file)) {
mySourceFileToClasses.put(file, new HashSet<ClassRepr>());
}
}
}
}
@Nullable
private ClassRepr getReprByName(final @Nullable File source, final int qName) {
final Collection<File> sources = source != null? Collections.singleton(source) : myClassToSourceFile.get(qName);
if (sources != null) {
for (File src : sources) {
final Collection<ClassRepr> reprs = mySourceFileToClasses.get(src);
if (reprs != null) {
for (ClassRepr repr : reprs) {
if (repr.name == qName) {
return repr;
}
}
}
}
}
return null;
}
public void clean() throws IOException {
if (myRootDir != null) {
synchronized (myLock) {
close();
FileUtil.delete(myRootDir);
createImplementation();
}
}
}
public IntIntTransientMultiMaplet getRemovedSuperClasses() {
return myRemovedSuperClasses;
}
public IntIntTransientMultiMaplet getAddedSuperClasses() {
return myAddedSuperClasses;
}
private final LinkedBlockingQueue<Runnable> myPostPasses = new LinkedBlockingQueue<Runnable>();
private void runPostPasses() {
final Set<Pair<ClassRepr, File>> deleted = myDeletedClasses;
if (deleted != null) {
for (Pair<ClassRepr, File> pair : deleted) {
myChangedClasses.remove(pair.first.name);
}
}
for (Runnable pass = myPostPasses.poll(); pass != null; pass = myPostPasses.poll()) {
pass.run();
}
}
private static final ClassRepr MOCK_CLASS = null;
private static final MethodRepr MOCK_METHOD = null;
private interface MemberComparator {
boolean isSame(ProtoMember member);
}
private class Util {
@Nullable
private final Mappings myMappings;
private Util() {
myMappings = null;
}
private Util(@NotNull Mappings mappings) {
myMappings = mappings;
}
void appendDependents(final ClassRepr c, final TIntHashSet result) {
final TIntHashSet depClasses = myClassToClassDependency.get(c.name);
if (depClasses != null) {
addAll(result, depClasses);
}
}
void propagateMemberAccessRec(final TIntHashSet acc, final boolean isField, final boolean root, final MemberComparator comparator, final int reflcass) {
final ClassRepr repr = reprByName(reflcass);
if (repr != null) {
if (!root) {
final Set<? extends ProtoMember> members = isField ? repr.getFields() : repr.getMethods();
for (ProtoMember m : members) {
if (comparator.isSame(m)) {
return;
}
}
acc.add(reflcass);
}
final TIntHashSet subclasses = myClassToSubclasses.get(reflcass);
if (subclasses != null) {
subclasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int subclass) {
propagateMemberAccessRec(acc, isField, false, comparator, subclass);
return true;
}
});
}
}
}
TIntHashSet propagateMemberAccess(final boolean isField, final MemberComparator comparator, final int className) {
final TIntHashSet acc = new TIntHashSet(DEFAULT_SET_CAPACITY, DEFAULT_SET_LOAD_FACTOR);
propagateMemberAccessRec(acc, isField, true, comparator, className);
return acc;
}
TIntHashSet propagateFieldAccess(final int name, final int className) {
return propagateMemberAccess(true, new MemberComparator() {
@Override
public boolean isSame(ProtoMember member) {
return member.name == name;
}
}, className);
}
TIntHashSet propagateMethodAccess(final MethodRepr m, final int className) {
return propagateMemberAccess(false, new MemberComparator() {
@Override
public boolean isSame(ProtoMember member) {
if (member instanceof MethodRepr) {
final MethodRepr memberMethod = (MethodRepr)member;
return memberMethod.name == m.name && Arrays.equals(memberMethod.myArgumentTypes, m.myArgumentTypes);
}
return member.name == m.name;
}
}, className);
}
MethodRepr.Predicate lessSpecific(final MethodRepr than) {
return new MethodRepr.Predicate() {
@Override
public boolean satisfy(final MethodRepr m) {
if (m.name == myInitName || m.name != than.name || m.myArgumentTypes.length != than.myArgumentTypes.length) {
return false;
}
for (int i = 0; i < than.myArgumentTypes.length; i++) {
final Boolean subtypeOf = isSubtypeOf(than.myArgumentTypes[i], m.myArgumentTypes[i]);
if (subtypeOf != null && !subtypeOf) {
return false;
}
}
return true;
}
};
}
private void addOverridingMethods(final MethodRepr m, final ClassRepr fromClass, final MethodRepr.Predicate predicate, final Collection<Pair<MethodRepr, ClassRepr>> container) {
final TIntHashSet subClasses = myClassToSubclasses.get(fromClass.name);
if (subClasses == null) {
return;
}
subClasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int subClassName) {
final ClassRepr r = reprByName(subClassName);
if (r != null) {
boolean cont = true;
final Collection<MethodRepr> methods = r.findMethods(predicate);
for (MethodRepr mm : methods) {
if (isVisibleIn(fromClass, m, r)) {
container.add(Pair.create(mm, r));
cont = false;
}
}
if (cont) {
addOverridingMethods(m, r, predicate, container);
}
}
return true;
}
});
}
private Collection<Pair<MethodRepr, ClassRepr>> findAllMethodsBySpecificity(final MethodRepr m, final ClassRepr c) {
final MethodRepr.Predicate predicate = lessSpecific(m);
final Collection<Pair<MethodRepr, ClassRepr>> result = new HashSet<Pair<MethodRepr, ClassRepr>>();
addOverridenMethods(c, predicate, result);
addOverridingMethods(m, c, predicate, result);
return result;
}
private Collection<Pair<MethodRepr, ClassRepr>> findOverriddenMethods(final MethodRepr m, final ClassRepr c) {
final Collection<Pair<MethodRepr, ClassRepr>> result = new HashSet<Pair<MethodRepr, ClassRepr>>();
addOverridenMethods(c, MethodRepr.equalByJavaRules(m), result);
return result;
}
private boolean hasOverriddenMethods(final ClassRepr fromClass, final MethodRepr.Predicate predicate) {
for (int superName : fromClass.getSupers()) {
final ClassRepr superClass = reprByName(superName);
if (superClass == null) {
return true; // assumption
}
for (MethodRepr mm : superClass.findMethods(predicate)) {
if (isVisibleIn(superClass, mm, fromClass)) {
return true;
}
}
if (hasOverriddenMethods(superClass, predicate)) {
return true;
}
}
return false;
}
private void addOverridenMethods(final ClassRepr fromClass, final MethodRepr.Predicate predicate, final Collection<Pair<MethodRepr, ClassRepr>> container) {
for (int superName : fromClass.getSupers()) {
final ClassRepr superClass = reprByName(superName);
if (superClass != null) {
boolean cont = true;
final Collection<MethodRepr> methods = superClass.findMethods(predicate);
for (MethodRepr mm : methods) {
if (isVisibleIn(superClass, mm, fromClass)) {
container.add(Pair.create(mm, superClass));
cont = false;
}
}
if (cont) {
addOverridenMethods(superClass, predicate, container);
}
}
else {
container.add(Pair.create(MOCK_METHOD, MOCK_CLASS));
}
}
}
void addOverriddenFields(final FieldRepr f, final ClassRepr fromClass, final Collection<Pair<FieldRepr, ClassRepr>> container) {
for (int supername : fromClass.getSupers()) {
final ClassRepr superClass = reprByName(supername);
if (superClass != null) {
final FieldRepr ff = superClass.findField(f.name);
if (ff != null && isVisibleIn(superClass, ff, fromClass)) {
container.add(Pair.create(ff, superClass));
}
else{
addOverriddenFields(f, superClass, container);
}
}
}
}
boolean hasOverriddenFields(final FieldRepr f, final ClassRepr fromClass) {
for (int supername : fromClass.getSupers()) {
final ClassRepr superClass = reprByName(supername);
if (superClass != null) {
final FieldRepr ff = superClass.findField(f.name);
if (ff != null && isVisibleIn(superClass, ff, fromClass)) {
return true;
}
final boolean found = hasOverriddenFields(f, superClass);
if (found) {
return true;
}
}
}
return false;
}
@Nullable
ClassRepr reprByName(final int name) {
if (myMappings != null) {
final ClassRepr r = myMappings.getReprByName(null, name);
if (r != null) {
return r;
}
}
return getReprByName(null, name);
}
@Nullable
private Boolean isInheritorOf(final int who, final int whom) {
if (who == whom) {
return Boolean.TRUE;
}
final ClassRepr repr = reprByName(who);
if (repr != null) {
for (int s : repr.getSupers()) {
final Boolean inheritorOf = isInheritorOf(s, whom);
if (inheritorOf != null && inheritorOf) {
return inheritorOf;
}
}
}
return null;
}
@Nullable
Boolean isSubtypeOf(final TypeRepr.AbstractType who, final TypeRepr.AbstractType whom) {
if (who.equals(whom)) {
return Boolean.TRUE;
}
if (who instanceof TypeRepr.PrimitiveType || whom instanceof TypeRepr.PrimitiveType) {
return Boolean.FALSE;
}
if (who instanceof TypeRepr.ArrayType) {
if (whom instanceof TypeRepr.ArrayType) {
return isSubtypeOf(((TypeRepr.ArrayType)who).elementType, ((TypeRepr.ArrayType)whom).elementType);
}
final String descr = whom.getDescr(myContext);
if (descr.equals("Ljava/lang/Cloneable") || descr.equals("Ljava/lang/Object") || descr.equals("Ljava/io/Serializable")) {
return Boolean.TRUE;
}
return Boolean.FALSE;
}
if (whom instanceof TypeRepr.ClassType) {
return isInheritorOf(((TypeRepr.ClassType)who).className, ((TypeRepr.ClassType)whom).className);
}
return Boolean.FALSE;
}
boolean isMethodVisible(final int className, final MethodRepr m) {
final ClassRepr r = reprByName(className);
if (r != null) {
if (r.findMethods(MethodRepr.equalByJavaRules(m)).size() > 0) {
return true;
}
return hasOverriddenMethods(r, MethodRepr.equalByJavaRules(m));
}
return false;
}
boolean isFieldVisible(final int className, final FieldRepr field) {
final ClassRepr r = reprByName(className);
if (r == null || r.getFields().contains(field)) {
return true;
}
return hasOverriddenFields(field, r);
}
void collectSupersRecursively(@NotNull final int className, @NotNull final TIntHashSet container) {
final ClassRepr classRepr = reprByName(className);
if (classRepr != null) {
final int[] supers = classRepr.getSupers();
container.addAll(supers);
for (int aSuper : supers) {
collectSupersRecursively(aSuper, container);
}
}
}
void affectSubclasses(final int className, final Collection<File> affectedFiles, final Collection<UsageRepr.Usage> affectedUsages, final TIntHashSet dependants, final boolean usages, final Collection<File> alreadyCompiledFiles) {
debug("Affecting subclasses of class: ", className);
final Collection<File> allSources = myClassToSourceFile.get(className);
if (allSources == null || allSources.isEmpty()) {
debug("No source file detected for class ", className);
debug("End of affectSubclasses");
return;
}
for (File fName : allSources) {
debug("Source file name: ", fName);
if (!alreadyCompiledFiles.contains(fName)) {
affectedFiles.add(fName);
}
}
if (usages) {
debug("Class usages affection requested");
final ClassRepr classRepr = reprByName(className);
if (classRepr != null) {
debug("Added class usage for ", classRepr.name);
affectedUsages.add(classRepr.createUsage());
}
}
final TIntHashSet depClasses = myClassToClassDependency.get(className);
if (depClasses != null) {
addAll(dependants, depClasses);
}
final TIntHashSet directSubclasses = myClassToSubclasses.get(className);
if (directSubclasses != null) {
directSubclasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int subClass) {
affectSubclasses(subClass, affectedFiles, affectedUsages, dependants, usages, alreadyCompiledFiles);
return true;
}
});
}
}
void affectFieldUsages(final FieldRepr field, final TIntHashSet classes, final UsageRepr.Usage rootUsage, final Set<UsageRepr.Usage> affectedUsages, final TIntHashSet dependents) {
affectedUsages.add(rootUsage);
classes.forEach(new TIntProcedure() {
@Override
public boolean execute(int p) {
final TIntHashSet deps = myClassToClassDependency.get(p);
if (deps != null) {
addAll(dependents, deps);
}
debug("Affect field usage referenced of class ", p);
affectedUsages.add(rootUsage instanceof UsageRepr.FieldAssignUsage ? field.createAssignUsage(myContext, p) : field.createUsage(myContext, p));
return true;
}
});
}
void affectMethodUsages(final MethodRepr method, final TIntHashSet subclasses, final UsageRepr.Usage rootUsage, final Set<UsageRepr.Usage> affectedUsages, final TIntHashSet dependents) {
affectedUsages.add(rootUsage);
if (subclasses != null) {
subclasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int p) {
final TIntHashSet deps = myClassToClassDependency.get(p);
if (deps != null) {
addAll(dependents, deps);
}
debug("Affect method usage referenced of class ", p);
final UsageRepr.Usage usage =
rootUsage instanceof UsageRepr.MetaMethodUsage ? method.createMetaUsage(myContext, p) : method.createUsage(myContext, p);
affectedUsages.add(usage);
return true;
}
});
}
}
public abstract class UsageConstraint {
public abstract boolean checkResidence(final int residence);
}
public class PackageConstraint extends UsageConstraint {
public final String packageName;
public PackageConstraint(final String packageName) {
this.packageName = packageName;
}
@Override
public boolean checkResidence(final int residence) {
return !ClassRepr.getPackageName(myContext.getValue(residence)).equals(packageName);
}
}
public class InheritanceConstraint extends PackageConstraint {
public final int rootClass;
public InheritanceConstraint(final int rootClass) {
super(ClassRepr.getPackageName(myContext.getValue(rootClass)));
this.rootClass = rootClass;
}
@Override
public boolean checkResidence(final int residence) {
final Boolean inheritorOf = isInheritorOf(residence, rootClass);
return inheritorOf == null || !inheritorOf || super.checkResidence(residence);
}
}
public class NegationConstraint extends UsageConstraint {
final UsageConstraint x;
public NegationConstraint(UsageConstraint x) {
this.x = x;
}
@Override
public boolean checkResidence(final int residence) {
return !x.checkResidence(residence);
}
}
public class IntersectionConstraint extends UsageConstraint {
final UsageConstraint x;
final UsageConstraint y;
public IntersectionConstraint(final UsageConstraint x, final UsageConstraint y) {
this.x = x;
this.y = y;
}
@Override
public boolean checkResidence(final int residence) {
return x.checkResidence(residence) && y.checkResidence(residence);
}
}
}
void affectAll(final int className, @NotNull final File sourceFile, final Collection<File> affectedFiles, @Nullable final DependentFilesFilter filter) {
final TIntHashSet dependants = myClassToClassDependency.get(className);
if (dependants != null) {
dependants.forEach(new TIntProcedure() {
@Override
public boolean execute(int depClass) {
final Collection<File> allSources = myClassToSourceFile.get(depClass);
if (allSources != null) {
for (File depFile : allSources) {
if (!FileUtil.filesEqual(depFile, sourceFile)) {
if (filter == null || filter.accept(depFile)) {
affectedFiles.add(depFile);
}
}
}
}
return true;
}
});
}
}
private static boolean isVisibleIn(final ClassRepr c, final ProtoMember m, final ClassRepr scope) {
final boolean privacy = m.isPrivate() && c.name != scope.name;
final boolean packageLocality = m.isPackageLocal() && !c.getPackageName().equals(scope.getPackageName());
return !privacy && !packageLocality;
}
private boolean isEmpty(final int s) {
return s == myEmptyName;
}
@NotNull
private TIntHashSet getAllSubclasses(final int root) {
return addAllSubclasses(root, new TIntHashSet(DEFAULT_SET_CAPACITY, DEFAULT_SET_LOAD_FACTOR));
}
private TIntHashSet addAllSubclasses(final int root, final TIntHashSet acc) {
final TIntHashSet directSubclasses = myClassToSubclasses.get(root);
acc.add(root);
if (directSubclasses != null) {
directSubclasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int s) {
if (!acc.contains(s)) {
addAllSubclasses(s, acc);
}
return true;
}
});
}
return acc;
}
private boolean incrementalDecision(final int owner, final Proto member, final Collection<File> affectedFiles, final Collection<File> currentlyCompiled, @Nullable final DependentFilesFilter filter) {
final boolean isField = member instanceof FieldRepr;
final Util self = new Util();
// Public branch --- hopeless
if (member.isPublic()) {
debug("Public access, switching to a non-incremental mode");
return false;
}
final THashSet<File> toRecompile = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
// Protected branch
if (member.isProtected()) {
debug("Protected access, softening non-incremental decision: adding all relevant subclasses for a recompilation");
debug("Root class: ", owner);
final TIntHashSet propagated = self.propagateFieldAccess(isField ? member.name : myEmptyName, owner);
propagated.forEach(new TIntProcedure() {
@Override
public boolean execute(int className) {
final Collection<File> fileNames = myClassToSourceFile.get(className);
if (fileNames != null) {
for (File fileName : fileNames) {
debug("Adding ", fileName);
}
toRecompile.addAll(fileNames);
}
return true;
}
});
}
final String packageName = ClassRepr.getPackageName(myContext.getValue(isField ? owner : member.name));
debug("Softening non-incremental decision: adding all package classes for a recompilation");
debug("Package name: ", packageName);
// Package-local branch
myClassToSourceFile.forEachEntry(new TIntObjectProcedure<Collection<File>>() {
@Override
public boolean execute(int className, Collection<File> fileNames) {
if (ClassRepr.getPackageName(myContext.getValue(className)).equals(packageName)) {
for (File fileName : fileNames) {
if (filter == null || filter.accept(fileName)) {
debug("Adding: ", fileName);
toRecompile.add(fileName);
}
}
}
return true;
}
});
// filtering already compiled and non-existing paths
toRecompile.removeAll(currentlyCompiled);
for (Iterator<File> it = toRecompile.iterator(); it.hasNext(); ) {
final File file = it.next();
if (!file.exists()) {
it.remove();
}
}
affectedFiles.addAll(toRecompile);
return true;
}
public interface DependentFilesFilter {
DependentFilesFilter ALL_FILES = new DependentFilesFilter() {
@Override
public boolean accept(File file) {
return true;
}
@Override
public boolean belongsToCurrentTargetChunk(File file) {
return true;
}
};
boolean accept(File file);
boolean belongsToCurrentTargetChunk(File file);
}
private class Differential {
private static final int DESPERATE_MASK = Opcodes.ACC_FINAL;
final Mappings myDelta;
final Collection<File> myFilesToCompile;
final Collection<File> myCompiledFiles;
final Collection<File> myAffectedFiles;
@Nullable
final DependentFilesFilter myFilter;
@Nullable final Callbacks.ConstantAffectionResolver myConstantSearch;
final DelayedWorks myDelayedWorks;
final Util myFuture;
final Util myPresent;
final boolean myEasyMode; // true means: no need to search for affected files, only preprocess data for integrate
private class DelayedWorks {
class Triple {
final int owner;
final FieldRepr field;
@Nullable
final Future<Callbacks.ConstantAffection> affection;
private Triple(final int owner, final FieldRepr field, @Nullable final Future<Callbacks.ConstantAffection> affection) {
this.owner = owner;
this.field = field;
this.affection = affection;
}
Callbacks.ConstantAffection getAffection() {
try {
return affection != null ? affection.get() : Callbacks.ConstantAffection.EMPTY;
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
final Collection<Triple> myQueue = new LinkedList<Triple>();
void addConstantWork(final int ownerClass, final FieldRepr changedField, final boolean isRemoved, boolean accessChanged) {
final Future<Callbacks.ConstantAffection> future;
if (myConstantSearch == null) {
future = null;
}
else {
final String className = myContext.getValue(ownerClass);
final String fieldName = myContext.getValue(changedField.name);
future = myConstantSearch.request(className.replace('/', '.'), fieldName, changedField.access, isRemoved, accessChanged);
}
myQueue.add(new Triple(ownerClass, changedField, future));
}
boolean doWork(@NotNull final Collection<File> affectedFiles) {
if (!myQueue.isEmpty()) {
debug("Starting delayed works.");
for (final Triple t : myQueue) {
final Callbacks.ConstantAffection affection = t.getAffection();
debug("Class: ", t.owner);
debug("Field: ", t.field.name);
if (!affection.isKnown()) {
if (myConstantSearch != null) {
debug("No external dependency information available.");
}
else {
debug("Constant search service not available.");
}
debug("Trying to soften non-incremental decision.");
if (!incrementalDecision(t.owner, t.field, affectedFiles, myFilesToCompile, myFilter)) {
debug("No luck.");
debug("End of delayed work, returning false.");
return false;
}
}
else {
debug("External dependency information retrieved.");
final Collection<File> files = affection.getAffectedFiles();
if (myFilter == null) {
affectedFiles.addAll(files);
}
else {
for (File file : files) {
if (myFilter.accept(file)) {
affectedFiles.add(file);
}
}
}
}
}
debug("End of delayed work, returning true.");
}
return true;
}
}
private class FileClasses {
final File myFileName;
final Set<ClassRepr> myFileClasses;
FileClasses(File fileName, Collection<ClassRepr> fileClasses) {
this.myFileName = fileName;
this.myFileClasses = new HashSet<ClassRepr>(fileClasses);
}
}
private class DiffState {
final public TIntHashSet myDependants = new TIntHashSet(DEFAULT_SET_CAPACITY, DEFAULT_SET_LOAD_FACTOR);
final public Set<UsageRepr.Usage> myAffectedUsages = new HashSet<UsageRepr.Usage>();
final public Set<UsageRepr.AnnotationUsage> myAnnotationQuery = new HashSet<UsageRepr.AnnotationUsage>();
final public Map<UsageRepr.Usage, Util.UsageConstraint> myUsageConstraints = new HashMap<UsageRepr.Usage, Util.UsageConstraint>();
final Difference.Specifier<ClassRepr> myClassDiff;
private DiffState(Difference.Specifier<ClassRepr> classDiff) {
this.myClassDiff = classDiff;
}
}
private Differential(final Mappings delta) {
this.myDelta = delta;
this.myFilesToCompile = null;
this.myCompiledFiles = null;
this.myAffectedFiles = null;
this.myFilter = null;
this.myConstantSearch = null;
myDelayedWorks = null;
myFuture = null;
myPresent = null;
myEasyMode = true;
delta.myIsRebuild = true;
}
private Differential(final Mappings delta, final Collection<String> removed, final Collection<File> filesToCompile) {
delta.myRemovedFiles = removed;
this.myDelta = delta;
this.myFilesToCompile = filesToCompile;
this.myCompiledFiles = null;
this.myAffectedFiles = null;
this.myFilter = null;
this.myConstantSearch = null;
myDelayedWorks = null;
myFuture = new Util(delta);
myPresent = new Util();
myEasyMode = true;
}
private Differential(final Mappings delta,
final Collection<String> removed,
final Collection<File> filesToCompile,
final Collection<File> compiledFiles,
final Collection<File> affectedFiles,
@NotNull final DependentFilesFilter filter,
@Nullable final Callbacks.ConstantAffectionResolver constantSearch) {
delta.myRemovedFiles = removed;
this.myDelta = delta;
this.myFilesToCompile = filesToCompile;
this.myCompiledFiles = compiledFiles;
this.myAffectedFiles = affectedFiles;
this.myFilter = filter;
this.myConstantSearch = constantSearch;
myDelayedWorks = new DelayedWorks();
myFuture = new Util(delta);
myPresent = new Util();
myEasyMode = false;
}
private void processDisappearedClasses() {
myDelta.compensateRemovedContent(myFilesToCompile);
if (!myEasyMode) {
final Collection<String> removed = myDelta.myRemovedFiles;
if (removed != null) {
for (final String file : removed) {
final File sourceFile = new File(file);
final Collection<ClassRepr> classes = mySourceFileToClasses.get(sourceFile);
if (classes != null) {
for (ClassRepr c : classes) {
debug("Affecting usages of removed class ", c.name);
affectAll(c.name, sourceFile, myAffectedFiles, myFilter);
}
}
}
}
}
}
private void processAddedMethods(final DiffState state, final ClassRepr.Diff diff, final ClassRepr it) {
final Collection<MethodRepr> added = diff.methods().added();
if (added.isEmpty()) {
return;
}
debug("Processing added methods: ");
if (it.isAnnotation()) {
debug("Class is annotation, skipping method analysis");
return;
}
Ref<ClassRepr> oldItRef = null;
for (final MethodRepr m : added) {
debug("Method: ", m.name);
if (it.isInterface() || it.isAbstract() || m.isAbstract()) {
debug("Class is abstract, or is interface, or added method in abstract => affecting all subclasses");
myFuture.affectSubclasses(it.name, myAffectedFiles, state.myAffectedUsages, state.myDependants, false, myCompiledFiles);
}
TIntHashSet propagated = null;
if (!m.isPrivate() && m.name != myInitName) {
if (oldItRef == null) {
oldItRef = new Ref<ClassRepr>(getReprByName(null, it.name)); // lazy init
}
final ClassRepr oldIt = oldItRef.get();
if (oldIt != null && myPresent.hasOverriddenMethods(oldIt, MethodRepr.equalByJavaRules(m))) {
}
else {
if (m.myArgumentTypes.length > 0) {
propagated = myFuture.propagateMethodAccess(m, it.name);
debug("Conservative case on overriding methods, affecting method usages");
myFuture.affectMethodUsages(m, propagated, m.createMetaUsage(myContext, it.name), state.myAffectedUsages, state.myDependants);
}
}
}
if (!m.isPrivate()) {
final Collection<Pair<MethodRepr, ClassRepr>> affectedMethods = myFuture.findAllMethodsBySpecificity(m, it);
final MethodRepr.Predicate overrides = MethodRepr.equalByJavaRules(m);
if (propagated == null) {
propagated = myFuture.propagateMethodAccess(m, it.name);
}
final Collection<MethodRepr> lessSpecific = it.findMethods(myFuture.lessSpecific(m));
for (final MethodRepr mm : lessSpecific) {
if (!mm.equals(m)) {
debug("Found less specific method, affecting method usages");
myFuture.affectMethodUsages(mm, propagated, mm.createUsage(myContext, it.name), state.myAffectedUsages, state.myDependants);
}
}
debug("Processing affected by specificity methods");
for (final Pair<MethodRepr, ClassRepr> pair : affectedMethods) {
final MethodRepr method = pair.first;
final ClassRepr methodClass = pair.second;
if (methodClass == MOCK_CLASS) {
continue;
}
final Boolean inheritorOf = myPresent.isInheritorOf(methodClass.name, it.name);
final boolean isInheritor = inheritorOf != null && inheritorOf;
debug("Method: ", method.name);
debug("Class : ", methodClass.name);
if (overrides.satisfy(method) && isInheritor) {
debug("Current method overrides that found");
final Collection<File> files = myClassToSourceFile.get(methodClass.name);
if (files != null) {
myAffectedFiles.addAll(files);
for (File file : files) {
debug("Affecting file ", file);
}
}
}
else {
debug("Current method does not override that found");
final TIntHashSet yetPropagated = myPresent.propagateMethodAccess(method, it.name);
if (isInheritor) {
final TIntHashSet deps = myClassToClassDependency.get(methodClass.name);
if (deps != null) {
addAll(state.myDependants, deps);
}
myFuture.affectMethodUsages(method, yetPropagated, method.createUsage(myContext, methodClass.name), state.myAffectedUsages,
state.myDependants);
}
debug("Affecting method usages for that found");
myFuture.affectMethodUsages(method, yetPropagated, method.createUsage(myContext, it.name), state.myAffectedUsages,
state.myDependants);
}
}
final TIntHashSet subClasses = getAllSubclasses(it.name);
subClasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int subClass) {
final ClassRepr r = myFuture.reprByName(subClass);
if (r == null) {
return true;
}
final Collection<File> sourceFileNames = myClassToSourceFile.get(subClass);
if (sourceFileNames != null && !myCompiledFiles.containsAll(sourceFileNames)) {
final int outerClass = r.getOuterClassName();
if (!isEmpty(outerClass) && myFuture.isMethodVisible(outerClass, m)) {
myAffectedFiles.addAll(sourceFileNames);
for (File sourceFileName : sourceFileNames) {
debug("Affecting file due to local overriding: ", sourceFileName);
}
}
}
return true;
}
});
}
}
debug("End of added methods processing");
}
private void processRemovedMethods(final DiffState state, final ClassRepr.Diff diff, final ClassRepr it) {
final Collection<MethodRepr> removed = diff.methods().removed();
if (removed.isEmpty()) {
return;
}
debug("Processing removed methods:");
for (final MethodRepr m : removed) {
debug("Method ", m.name);
final Collection<Pair<MethodRepr, ClassRepr>> overridenMethods = myFuture.findOverriddenMethods(m, it);
final TIntHashSet propagated = myFuture.propagateMethodAccess(m, it.name);
if (overridenMethods.size() == 0) {
debug("No overridden methods found, affecting method usages");
myFuture.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), state.myAffectedUsages, state.myDependants);
}
else {
boolean clear = true;
loop:
for (final Pair<MethodRepr, ClassRepr> overriden : overridenMethods) {
final MethodRepr mm = overriden.first;
if (mm == MOCK_METHOD || !mm.myType.equals(m.myType) || !isEmpty(mm.signature) || !isEmpty(m.signature) || m.isMoreAccessibleThan(mm)) {
clear = false;
break loop;
}
}
if (!clear) {
debug("No clearly overridden methods found, affecting method usages");
myFuture.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), state.myAffectedUsages, state.myDependants);
}
}
final Collection<Pair<MethodRepr, ClassRepr>> overridingMethods = new HashSet<Pair<MethodRepr, ClassRepr>>();
myFuture.addOverridingMethods(m, it, MethodRepr.equalByJavaRules(m), overridingMethods);
for (final Pair<MethodRepr, ClassRepr> p : overridingMethods) {
final Collection<File> fNames = myClassToSourceFile.get(p.second.name);
if (fNames != null) {
myAffectedFiles.addAll(fNames);
for (File fName : fNames) {
debug("Affecting file by overriding: ", fName);
}
}
}
if (!m.isAbstract()) {
propagated.forEach(new TIntProcedure() {
@Override
public boolean execute(int p) {
if (p != it.name) {
final ClassRepr s = myFuture.reprByName(p);
if (s != null) {
final Collection<Pair<MethodRepr, ClassRepr>> overridenInS = myFuture.findOverriddenMethods(m, s);
overridenInS.addAll(overridenMethods);
boolean allAbstract = true;
boolean visited = false;
for (final Pair<MethodRepr, ClassRepr> pp : overridenInS) {
final ClassRepr cc = pp.second;
if (cc == MOCK_CLASS) {
visited = true;
continue;
}
if (cc.name == it.name) {
continue;
}
visited = true;
allAbstract = pp.first.isAbstract() || cc.isInterface();
if (!allAbstract) {
break;
}
}
if (allAbstract && visited) {
final Collection<File> sources = myClassToSourceFile.get(p);
if (sources != null && !myCompiledFiles.containsAll(sources)) {
myAffectedFiles.addAll(sources);
debug("Removed method is not abstract & overrides some abstract method which is not then over-overridden in subclass ", p);
for (File source : sources) {
debug("Affecting subclass source file ", source);
}
}
}
}
}
return true;
}
});
}
}
debug("End of removed methods processing");
}
private void processChangedMethods(final DiffState state, final ClassRepr.Diff diff, final ClassRepr it) {
final Collection<Pair<MethodRepr, Difference>> changed = diff.methods().changed();
if (changed.isEmpty()) {
return;
}
debug("Processing changed methods:");
for (final Pair<MethodRepr, Difference> mr : changed) {
final MethodRepr m = mr.first;
final MethodRepr.Diff d = (MethodRepr.Diff)mr.second;
final boolean throwsChanged = !d.exceptions().unchanged();
debug("Method: ", m.name);
if (it.isAnnotation()) {
if (d.defaultRemoved()) {
debug("Class is annotation, default value is removed => adding annotation query");
final TIntHashSet l = new TIntHashSet(DEFAULT_SET_CAPACITY, DEFAULT_SET_LOAD_FACTOR);
l.add(m.name);
final UsageRepr.AnnotationUsage annotationUsage = (UsageRepr.AnnotationUsage)UsageRepr
.createAnnotationUsage(myContext, TypeRepr.createClassType(myContext, it.name), l, null);
state.myAnnotationQuery.add(annotationUsage);
}
}
else if (d.base() != Difference.NONE || throwsChanged) {
final TIntHashSet propagated = myFuture.propagateMethodAccess(m, it.name);
boolean affected = false;
boolean constrained = false;
final Set<UsageRepr.Usage> usages = new HashSet<UsageRepr.Usage>();
if (d.packageLocalOn()) {
debug("Method became package-local, affecting method usages outside the package");
myFuture.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, state.myDependants);
for (final UsageRepr.Usage usage : usages) {
state.myUsageConstraints.put(usage, myFuture.new InheritanceConstraint(it.name));
}
state.myAffectedUsages.addAll(usages);
affected = true;
constrained = true;
}
if ((d.base() & Difference.TYPE) > 0 || (d.base() & Difference.SIGNATURE) > 0 || throwsChanged) {
if (!affected) {
debug("Return type, throws list or signature changed --- affecting method usages");
myFuture.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, state.myDependants);
final List<Pair<MethodRepr, ClassRepr>> overridingMethods = new LinkedList<Pair<MethodRepr, ClassRepr>>();
myFuture.addOverridingMethods(m, it, MethodRepr.equalByJavaRules(m), overridingMethods);
for(final Pair<MethodRepr, ClassRepr> p : overridingMethods) {
final ClassRepr aClass = p.getSecond();
if (aClass != MOCK_CLASS) {
final Collection<File> fileNames = myClassToSourceFile.get(aClass.name);
if (fileNames != null) {
myAffectedFiles.addAll(fileNames);
}
}
}
state.myAffectedUsages.addAll(usages);
}
}
else if ((d.base() & Difference.ACCESS) > 0) {
if ((d.addedModifiers() & Opcodes.ACC_STATIC) > 0 ||
(d.removedModifiers() & Opcodes.ACC_STATIC) > 0 ||
(d.addedModifiers() & Opcodes.ACC_PRIVATE) > 0) {
if (!affected) {
debug("Added static or private specifier or removed static specifier --- affecting method usages");
myFuture.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, state.myDependants);
state.myAffectedUsages.addAll(usages);
}
if ((d.addedModifiers() & Opcodes.ACC_STATIC) > 0) {
debug("Added static specifier --- affecting subclasses");
myFuture.affectSubclasses(it.name, myAffectedFiles, state.myAffectedUsages, state.myDependants, false, myCompiledFiles);
}
}
else {
if ((d.addedModifiers() & Opcodes.ACC_FINAL) > 0 ||
(d.addedModifiers() & Opcodes.ACC_PUBLIC) > 0 ||
(d.addedModifiers() & Opcodes.ACC_ABSTRACT) > 0) {
debug("Added final, public or abstract specifier --- affecting subclasses");
myFuture.affectSubclasses(it.name, myAffectedFiles, state.myAffectedUsages, state.myDependants, false, myCompiledFiles);
}
if ((d.addedModifiers() & Opcodes.ACC_PROTECTED) > 0 && !((d.removedModifiers() & Opcodes.ACC_PRIVATE) > 0)) {
if (!constrained) {
debug("Added public or package-local method became protected --- affect method usages with protected constraint");
if (!affected) {
myFuture.affectMethodUsages(m, propagated, m.createUsage(myContext, it.name), usages, state.myDependants);
state.myAffectedUsages.addAll(usages);
}
for (final UsageRepr.Usage usage : usages) {
state.myUsageConstraints.put(usage, myFuture.new InheritanceConstraint(it.name));
}
}
}
}
}
}
}
debug("End of changed methods processing");
}
private boolean processAddedFields(final DiffState state, final ClassRepr.Diff diff, final ClassRepr classRepr) {
final Collection<FieldRepr> added = diff.fields().added();
if (added.isEmpty()) {
return true;
}
debug("Processing added fields");
for (final FieldRepr f : added) {
debug("Field: ", f.name);
if (!f.isPrivate()) {
final TIntHashSet subClasses = getAllSubclasses(classRepr.name);
subClasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int subClass) {
final ClassRepr r = myFuture.reprByName(subClass);
if (r != null) {
final Collection<File> sourceFileNames = myClassToSourceFile.get(subClass);
if (sourceFileNames != null && !myCompiledFiles.containsAll(sourceFileNames)) {
if (r.isLocal()) {
for (File sourceFileName : sourceFileNames) {
debug("Affecting local subclass (introduced field can potentially hide surrounding method parameters/local variables): ", sourceFileName);
}
myAffectedFiles.addAll(sourceFileNames);
}
else {
final int outerClass = r.getOuterClassName();
if (!isEmpty(outerClass) && myFuture.isFieldVisible(outerClass, f)) {
for (File sourceFileName : sourceFileNames) {
debug("Affecting inner subclass (introduced field can potentially hide surrounding class fields): ", sourceFileName);
}
myAffectedFiles.addAll(sourceFileNames);
}
}
}
}
debug("Affecting field usages referenced from subclass ", subClass);
final TIntHashSet propagated = myFuture.propagateFieldAccess(f.name, subClass);
myFuture.affectFieldUsages(f, propagated, f.createUsage(myContext, subClass), state.myAffectedUsages, state.myDependants);
final TIntHashSet deps = myClassToClassDependency.get(subClass);
if (deps != null) {
addAll(state.myDependants, deps);
}
return true;
}
});
}
final Collection<Pair<FieldRepr, ClassRepr>> overriddenFields = new HashSet<Pair<FieldRepr, ClassRepr>>();
myFuture.addOverriddenFields(f, classRepr, overriddenFields);
for (final Pair<FieldRepr, ClassRepr> p : overriddenFields) {
final FieldRepr ff = p.first;
final ClassRepr cc = p.second;
if (!ff.isPrivate()) {
final TIntHashSet propagated = myPresent.propagateFieldAccess(ff.name, cc.name);
final Set<UsageRepr.Usage> localUsages = new HashSet<UsageRepr.Usage>();
debug("Affecting usages of overridden field in class ", cc.name);
myFuture.affectFieldUsages(ff, propagated, ff.createUsage(myContext, cc.name), localUsages, state.myDependants);
if (f.isPrivate() || (f.isPublic() && (ff.isPublic() || ff.isPackageLocal())) || (f.isProtected() && ff.isProtected()) || (f.isPackageLocal() && ff.isPackageLocal())) {
// nothing
}
else {
Util.UsageConstraint constaint;
if ((ff.isProtected() && f.isPublic()) || (f.isProtected() && ff.isPublic()) || (ff.isPackageLocal() && f.isProtected())) {
constaint = myFuture.new NegationConstraint(myFuture.new InheritanceConstraint(cc.name));
}
else if (ff.isPublic() && ff.isPackageLocal()) {
constaint = myFuture.new NegationConstraint(myFuture.new PackageConstraint(cc.getPackageName()));
}
else {
constaint =
myFuture.new IntersectionConstraint(myFuture.new NegationConstraint(myFuture.new InheritanceConstraint(cc.name)),
myFuture.new NegationConstraint(
myFuture.new PackageConstraint(cc.getPackageName())));
}
for (final UsageRepr.Usage usage : localUsages) {
state.myUsageConstraints.put(usage, constaint);
}
}
state.myAffectedUsages.addAll(localUsages);
}
}
}
debug("End of added fields processing");
return true;
}
private boolean processRemovedFields(final DiffState state, final ClassRepr.Diff diff, final ClassRepr it) {
final Collection<FieldRepr> removed = diff.fields().removed();
if (removed.isEmpty()) {
return true;
}
debug("Processing removed fields:");
for (final FieldRepr f : removed) {
debug("Field: ", f.name);
if (!f.isPrivate() && (f.access & DESPERATE_MASK) == DESPERATE_MASK && f.hasValue()) {
debug("Field had value and was (non-private) final static => a switch to non-incremental mode requested");
if (myConstantSearch != null) {
myDelayedWorks.addConstantWork(it.name, f, true, false);
}
else {
if (!incrementalDecision(it.name, f, myAffectedFiles, myFilesToCompile, myFilter)) {
debug("End of Differentiate, returning false");
return false;
}
}
}
final TIntHashSet propagated = myFuture.propagateFieldAccess(f.name, it.name);
myFuture.affectFieldUsages(f, propagated, f.createUsage(myContext, it.name), state.myAffectedUsages, state.myDependants);
}
debug("End of removed fields processing");
return true;
}
private boolean processChangedFields(final DiffState state, final ClassRepr.Diff diff, final ClassRepr it) {
final Collection<Pair<FieldRepr, Difference>> changed = diff.fields().changed();
if (changed.isEmpty()) {
return true;
}
debug("Processing changed fields:");
for (final Pair<FieldRepr, Difference> f : changed) {
final Difference d = f.second;
final FieldRepr field = f.first;
debug("Field: ", field.name);
if (!field.isPrivate() && (field.access & DESPERATE_MASK) == DESPERATE_MASK) {
final int changedModifiers = d.addedModifiers() | d.removedModifiers();
final boolean harmful = (changedModifiers & (Opcodes.ACC_STATIC | Opcodes.ACC_FINAL)) > 0;
final boolean accessChanged = (changedModifiers & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) > 0;
final boolean valueChanged = (d.base() & Difference.VALUE) > 0 && d.hadValue();
if (harmful || valueChanged || (accessChanged && !d.weakedAccess())) {
debug("Inline field changed it's access or value => a switch to non-incremental mode requested");
if (myConstantSearch != null) {
myDelayedWorks.addConstantWork(it.name, field, false, accessChanged);
}
else {
if (!incrementalDecision(it.name, field, myAffectedFiles, myFilesToCompile, myFilter)) {
debug("End of Differentiate, returning false");
return false;
}
}
}
}
if (d.base() != Difference.NONE) {
final TIntHashSet propagated = myFuture.propagateFieldAccess(field.name, it.name);
if ((d.base() & Difference.TYPE) > 0 || (d.base() & Difference.SIGNATURE) > 0) {
debug("Type or signature changed --- affecting field usages");
myFuture
.affectFieldUsages(field, propagated, field.createUsage(myContext, it.name), state.myAffectedUsages, state.myDependants);
}
else if ((d.base() & Difference.ACCESS) > 0) {
if ((d.addedModifiers() & Opcodes.ACC_STATIC) > 0 ||
(d.removedModifiers() & Opcodes.ACC_STATIC) > 0 ||
(d.addedModifiers() & Opcodes.ACC_PRIVATE) > 0 ||
(d.addedModifiers() & Opcodes.ACC_VOLATILE) > 0) {
debug("Added/removed static modifier or added private/volatile modifier --- affecting field usages");
myFuture
.affectFieldUsages(field, propagated, field.createUsage(myContext, it.name), state.myAffectedUsages, state.myDependants);
}
else {
boolean affected = false;
final Set<UsageRepr.Usage> usages = new HashSet<UsageRepr.Usage>();
if ((d.addedModifiers() & Opcodes.ACC_FINAL) > 0) {
debug("Added final modifier --- affecting field assign usages");
myFuture.affectFieldUsages(field, propagated, field.createAssignUsage(myContext, it.name), usages, state.myDependants);
state.myAffectedUsages.addAll(usages);
affected = true;
}
if ((d.removedModifiers() & Opcodes.ACC_PUBLIC) > 0) {
debug("Removed public modifier, affecting field usages with appropriate constraint");
if (!affected) {
myFuture.affectFieldUsages(field, propagated, field.createUsage(myContext, it.name), usages, state.myDependants);
state.myAffectedUsages.addAll(usages);
}
for (final UsageRepr.Usage usage : usages) {
if ((d.addedModifiers() & Opcodes.ACC_PROTECTED) > 0) {
state.myUsageConstraints.put(usage, myFuture.new InheritanceConstraint(it.name));
}
else {
state.myUsageConstraints.put(usage, myFuture.new PackageConstraint(it.getPackageName()));
}
}
}
}
}
}
}
debug("End of changed fields processing");
return true;
}
private boolean processChangedClasses(final DiffState state) {
final Collection<Pair<ClassRepr, Difference>> changedClasses = state.myClassDiff.changed();
if (!changedClasses.isEmpty()) {
debug("Processing changed classes:");
for (final Pair<ClassRepr, Difference> changed : changedClasses) {
final ClassRepr changedClass = changed.first;
final ClassRepr.Diff diff = (ClassRepr.Diff)changed.second;
myDelta.addChangedClass(changedClass.name);
debug("Changed: ", changedClass.name);
final int addedModifiers = diff.addedModifiers();
final boolean superClassChanged = (diff.base() & Difference.SUPERCLASS) > 0;
final boolean interfacesChanged = !diff.interfaces().unchanged();
final boolean signatureChanged = (diff.base() & Difference.SIGNATURE) > 0;
if (superClassChanged) {
myDelta.registerRemovedSuperClass(changedClass.name, changedClass.getSuperClass().className);
final ClassRepr newClass = myDelta.getReprByName(null, changedClass.name);
assert (newClass != null);
myDelta.registerAddedSuperClass(changedClass.name, newClass.getSuperClass().className);
}
if (interfacesChanged) {
for (final TypeRepr.AbstractType typ : diff.interfaces().removed()) {
myDelta.registerRemovedSuperClass(changedClass.name, ((TypeRepr.ClassType)typ).className);
}
for (final TypeRepr.AbstractType typ : diff.interfaces().added()) {
myDelta.registerAddedSuperClass(changedClass.name, ((TypeRepr.ClassType)typ).className);
}
}
if (myEasyMode) {
continue;
}
myPresent.appendDependents(changedClass, state.myDependants);
if (superClassChanged || interfacesChanged || signatureChanged) {
debug("Superclass changed: ", superClassChanged);
debug("Interfaces changed: ", interfacesChanged);
debug("Signature changed ", signatureChanged);
final boolean extendsChanged = superClassChanged && !diff.extendsAdded();
final boolean interfacesRemoved = interfacesChanged && !diff.interfaces().removed().isEmpty();
debug("Extends changed: ", extendsChanged);
debug("Interfaces removed: ", interfacesRemoved);
myFuture.affectSubclasses(changedClass.name, myAffectedFiles, state.myAffectedUsages, state.myDependants, extendsChanged || interfacesRemoved || signatureChanged, myCompiledFiles);
if (!changedClass.isAnonymous()) {
final TIntHashSet parents = new TIntHashSet();
myPresent.collectSupersRecursively(changedClass.name, parents);
final TIntHashSet futureParents = new TIntHashSet();
myFuture.collectSupersRecursively(changedClass.name, futureParents);
parents.removeAll(futureParents.toArray());
parents.remove(myObjectClassName);
if (!parents.isEmpty()) {
parents.forEach(new TIntProcedure() {
@Override
public boolean execute(int className) {
debug("Affecting usages in generic type parameter bounds of class: ", className);
state.myAffectedUsages.add(UsageRepr.createClassAsGenericBoundUsage(myContext, className));
final TIntHashSet depClasses = myClassToClassDependency.get(className);
if (depClasses != null) {
addAll(state.myDependants, depClasses);
}
return true;
}
});
}
}
}
if ((diff.addedModifiers() & Opcodes.ACC_INTERFACE) > 0 || (diff.removedModifiers() & Opcodes.ACC_INTERFACE) > 0) {
debug("Class-to-interface or interface-to-class conversion detected, added class usage to affected usages");
state.myAffectedUsages.add(changedClass.createUsage());
}
if (changedClass.isAnnotation() && changedClass.getRetentionPolicy() == RetentionPolicy.SOURCE) {
debug("Annotation, retention policy = SOURCE => a switch to non-incremental mode requested");
if (!incrementalDecision(changedClass.getOuterClassName(), changedClass, myAffectedFiles, myFilesToCompile, myFilter)) {
debug("End of Differentiate, returning false");
return false;
}
}
if ((addedModifiers & Opcodes.ACC_PROTECTED) > 0) {
debug("Introduction of 'protected' modifier detected, adding class usage + inheritance constraint to affected usages");
final UsageRepr.Usage usage = changedClass.createUsage();
state.myAffectedUsages.add(usage);
state.myUsageConstraints.put(usage, myFuture.new InheritanceConstraint(changedClass.name));
}
if (diff.packageLocalOn()) {
debug("Introduction of 'package local' access detected, adding class usage + package constraint to affected usages");
final UsageRepr.Usage usage = changedClass.createUsage();
state.myAffectedUsages.add(usage);
state.myUsageConstraints.put(usage, myFuture.new PackageConstraint(changedClass.getPackageName()));
}
if ((addedModifiers & Opcodes.ACC_FINAL) > 0 || (addedModifiers & Opcodes.ACC_PRIVATE) > 0) {
debug("Introduction of 'private' or 'final' modifier(s) detected, adding class usage to affected usages");
state.myAffectedUsages.add(changedClass.createUsage());
}
if ((addedModifiers & Opcodes.ACC_ABSTRACT) > 0 || (addedModifiers & Opcodes.ACC_STATIC) > 0) {
debug("Introduction of 'abstract' or 'static' modifier(s) detected, adding class new usage to affected usages");
state.myAffectedUsages.add(UsageRepr.createClassNewUsage(myContext, changedClass.name));
}
if (changedClass.isAnnotation()) {
debug("Class is annotation, performing annotation-specific analysis");
if (diff.retentionChanged()) {
debug("Retention policy change detected, adding class usage to affected usages");
state.myAffectedUsages.add(changedClass.createUsage());
}
else {
final Collection<ElemType> removedtargets = diff.targets().removed();
if (removedtargets.contains(ElemType.LOCAL_VARIABLE)) {
debug("Removed target contains LOCAL_VARIABLE => a switch to non-incremental mode requested");
if (!incrementalDecision(changedClass.getOuterClassName(), changedClass, myAffectedFiles, myFilesToCompile, myFilter)) {
debug("End of Differentiate, returning false");
return false;
}
}
if (!removedtargets.isEmpty()) {
debug("Removed some annotation targets, adding annotation query");
final UsageRepr.AnnotationUsage annotationUsage = (UsageRepr.AnnotationUsage)UsageRepr
.createAnnotationUsage(myContext, TypeRepr.createClassType(myContext, changedClass.name), null, EnumSet.copyOf(removedtargets));
state.myAnnotationQuery.add(annotationUsage);
}
for (final MethodRepr m : diff.methods().added()) {
if (!m.hasValue()) {
debug("Added method with no default value: ", m.name);
debug("Adding class usage to affected usages");
state.myAffectedUsages.add(changedClass.createUsage());
}
}
}
debug("End of annotation-specific analysis");
}
processAddedMethods(state, diff, changedClass);
processRemovedMethods(state, diff, changedClass);
processChangedMethods(state, diff, changedClass);
if (!processAddedFields(state, diff, changedClass)) {
return false;
}
if (!processRemovedFields(state, diff, changedClass)) {
return false;
}
if (!processChangedFields(state, diff, changedClass)) {
return false;
}
}
debug("End of changed classes processing");
}
return !myEasyMode;
}
private void processRemovedClases(final DiffState state, @NotNull File fileName) {
final Collection<ClassRepr> removed = state.myClassDiff.removed();
if (removed.isEmpty()) {
return;
}
myDelta.myChangedFiles.add(fileName);
debug("Processing removed classes:");
for (final ClassRepr c : removed) {
myDelta.addDeletedClass(c, fileName);
if (!myEasyMode) {
myPresent.appendDependents(c, state.myDependants);
debug("Adding usages of class ", c.name);
state.myAffectedUsages.add(c.createUsage());
debug("Affecting usages of removed class ", c.name);
affectAll(c.name, fileName, myAffectedFiles, myFilter);
}
}
debug("End of removed classes processing.");
}
private void processAddedClasses(final DiffState state, File srcFile) {
final Collection<ClassRepr> addedClasses = state.myClassDiff.added();
if (addedClasses.isEmpty()) {
return;
}
debug("Processing added classes:");
if (!myEasyMode && myFilter != null) {
// checking if this newly added class duplicates already existing one
for (ClassRepr c : addedClasses) {
if (!c.isLocal() && !c.isAnonymous() && isEmpty(c.getOuterClassName())) {
final Collection<File> currentSources = myClassToSourceFile.get(c.name);
final File currentlyMappedTo = currentSources != null && currentSources.size() == 1? currentSources.iterator().next() : null;
// only check, if exactly one file is mapped
if (currentlyMappedTo != null && !FileUtil.filesEqual(currentlyMappedTo, srcFile) && currentlyMappedTo.exists() && myFilter.belongsToCurrentTargetChunk(currentlyMappedTo)) {
// Same classes from different source files.
// Schedule for recompilation both to make possible 'duplicate sources' error evident
debug("Scheduling for recompilation duplicated sources: ", currentlyMappedTo.getPath() + "; " + srcFile.getPath());
myAffectedFiles.add(currentlyMappedTo);
myAffectedFiles.add(srcFile);
return; // do not process this file because it should not be integrated
}
break;
}
}
}
for (final ClassRepr c : addedClasses) {
debug("Class name: ", c.name);
myDelta.addAddedClass(c);
for (final int sup : c.getSupers()) {
myDelta.registerAddedSuperClass(c.name, sup);
}
if (!myEasyMode && !c.isAnonymous() && !c.isLocal()) {
final TIntHashSet toAffect = new TIntHashSet();
toAffect.add(c.name);
final TIntHashSet classes = myShortClassNameIndex.get(myContext.get(c.getShortName()));
if (classes != null) {
// affecting dependencies on all other classes with the same short name
toAffect.addAll(classes.toArray());
}
toAffect.forEach(new TIntProcedure() {
public boolean execute(int qName) {
final TIntHashSet depClasses = myClassToClassDependency.get(qName);
if (depClasses != null) {
affectCorrespondingSourceFiles(depClasses);
}
return true;
}
});
}
}
debug("End of added classes processing.");
}
private void affectCorrespondingSourceFiles(TIntHashSet toAffect) {
toAffect.forEach(new TIntProcedure() {
@Override
public boolean execute(int depClass) {
final Collection<File> fNames = myClassToSourceFile.get(depClass);
if (fNames != null) {
for (File fName : fNames) {
if (myFilter == null || myFilter.accept(fName)) {
debug("Adding dependent file ", fName);
myAffectedFiles.add(fName);
}
}
}
return true;
}
});
}
private void calculateAffectedFiles(final DiffState state) {
debug("Checking dependent classes:");
state.myDependants.forEach(new TIntProcedure() {
@Override
public boolean execute(final int depClass) {
final Collection<File> depFiles = myClassToSourceFile.get(depClass);
if (depFiles != null) {
for (File depFile : depFiles) {
processDependentFile(depClass, depFile);
}
}
return true;
}
private void processDependentFile(int depClass, @NotNull File depFile) {
if (myAffectedFiles.contains(depFile) || myCompiledFiles.contains(depFile)) {
return;
}
debug("Dependent class: ", depClass);
final ClassRepr classRepr = getReprByName(depFile, depClass);
if (classRepr == null) {
return;
}
final Set<UsageRepr.Usage> depUsages = classRepr.getUsages();
if (depUsages == null || depUsages.isEmpty()) {
return;
}
for (UsageRepr.Usage usage : depUsages) {
if (usage instanceof UsageRepr.AnnotationUsage) {
for (final UsageRepr.AnnotationUsage query : state.myAnnotationQuery) {
if (query.satisfies(usage)) {
debug("Added file due to annotation query");
myAffectedFiles.add(depFile);
return;
}
}
}
else if (state.myAffectedUsages.contains(usage)) {
final Util.UsageConstraint constraint = state.myUsageConstraints.get(usage);
if (constraint == null) {
debug("Added file with no constraints");
myAffectedFiles.add(depFile);
return;
}
if (constraint.checkResidence(depClass)) {
debug("Added file with satisfied constraint");
myAffectedFiles.add(depFile);
return;
}
}
}
}
});
}
boolean differentiate() {
synchronized (myLock) {
myDelta.myIsDifferentiated = true;
if (myDelta.myIsRebuild) {
return true;
}
debug("Begin of Differentiate:");
debug("Easy mode: ", myEasyMode);
processDisappearedClasses();
final List<FileClasses> newClasses = new ArrayList<FileClasses>();
myDelta.mySourceFileToClasses.forEachEntry(new TObjectObjectProcedure<File, Collection<ClassRepr>>() {
@Override
public boolean execute(File fileName, Collection<ClassRepr> classes) {
newClasses.add(new FileClasses(fileName, classes));
return true;
}
});
for (final FileClasses compiledFile : newClasses) {
final File fileName = compiledFile.myFileName;
final Set<ClassRepr> classes = compiledFile.myFileClasses;
final Set<ClassRepr> pastClasses = (Set<ClassRepr>)mySourceFileToClasses.get(fileName);
final DiffState state = new DiffState(Difference.make(pastClasses, classes));
if (!processChangedClasses(state)) {
if (!myEasyMode) {
// turning non-incremental
return false;
}
}
processRemovedClases(state, fileName);
processAddedClasses(state, fileName);
if (!myEasyMode) {
calculateAffectedFiles(state);
}
}
debug("End of Differentiate.");
if (myEasyMode) {
return false;
}
final Collection<String> removed = myDelta.myRemovedFiles;
if (removed != null) {
for (final String r : removed) {
myAffectedFiles.remove(new File(r));
}
}
return myDelayedWorks.doWork(myAffectedFiles);
}
}
}
public void differentiateOnRebuild(final Mappings delta) {
new Differential(delta).differentiate();
}
public void differentiateOnNonIncrementalMake(final Mappings delta,
final Collection<String> removed,
final Collection<File> filesToCompile) {
new Differential(delta, removed, filesToCompile).differentiate();
}
public boolean differentiateOnIncrementalMake
(final Mappings delta,
final Collection<String> removed,
final Collection<File> filesToCompile,
final Collection<File> compiledFiles,
final Collection<File> affectedFiles,
@NotNull final DependentFilesFilter filter,
@Nullable final Callbacks.ConstantAffectionResolver constantSearch) {
return new Differential(delta, removed, filesToCompile, compiledFiles, affectedFiles, filter, constantSearch).differentiate();
}
private void cleanupBackDependency(final int className,
@Nullable Set<UsageRepr.Usage> usages,
final IntIntMultiMaplet buffer) {
if (usages == null) {
final ClassRepr repr = getReprByName(null, className);
if (repr != null) {
usages = repr.getUsages();
}
}
if (usages != null) {
for (final UsageRepr.Usage u : usages) {
buffer.put(u.getOwner(), className);
}
}
}
private void cleanupRemovedClass(final Mappings delta, @NotNull final ClassRepr cr, File sourceFile, final Set<UsageRepr.Usage> usages, final IntIntMultiMaplet dependenciesTrashBin) {
final int className = cr.name;
// it is safe to cleanup class information if it is mapped to non-existing files only
final Collection<File> currentlyMapped = myClassToSourceFile.get(className);
if (currentlyMapped == null || currentlyMapped.isEmpty()) {
return;
}
if (currentlyMapped.size() == 1) {
if (!FileUtil.filesEqual(sourceFile, currentlyMapped.iterator().next())) {
// if classname is already mapped to a different source, the class with such FQ name exists elsewhere, so
// we cannot destroy all these links
return;
}
}
else {
// many files
for (File file : currentlyMapped) {
if (!FileUtil.filesEqual(sourceFile, file) && file.exists()) {
return;
}
}
}
for (final int superSomething : cr.getSupers()) {
delta.registerRemovedSuperClass(className, superSomething);
}
cleanupBackDependency(className, usages, dependenciesTrashBin);
myClassToClassDependency.remove(className);
myClassToSubclasses.remove(className);
myClassToSourceFile.remove(className);
if (!cr.isLocal() && !cr.isAnonymous()) {
myShortClassNameIndex.removeFrom(myContext.get(cr.getShortName()), className);
}
}
public void integrate(final Mappings delta) {
synchronized (myLock) {
try {
assert (delta.isDifferentiated());
final Collection<String> removed = delta.myRemovedFiles;
delta.runPostPasses();
final IntIntMultiMaplet dependenciesTrashBin = new IntIntTransientMultiMaplet();
if (removed != null) {
for (final String file : removed) {
final File deletedFile = new File(file);
final Set<ClassRepr> fileClasses = (Set<ClassRepr>)mySourceFileToClasses.get(deletedFile);
if (fileClasses != null) {
for (final ClassRepr aClass : fileClasses) {
cleanupRemovedClass(delta, aClass, deletedFile, aClass.getUsages(), dependenciesTrashBin);
}
mySourceFileToClasses.remove(deletedFile);
}
}
}
if (!delta.isRebuild()) {
for (final Pair<ClassRepr, File> pair : delta.getDeletedClasses()) {
final ClassRepr deletedClass = pair.first;
cleanupRemovedClass(delta, deletedClass, pair.second, deletedClass.getUsages(), dependenciesTrashBin);
}
for (ClassRepr repr : delta.getAddedClasses()) {
if (!repr.isAnonymous() && !repr.isLocal()) {
myShortClassNameIndex.put(myContext.get(repr.getShortName()), repr.name);
}
}
final TIntHashSet superClasses = new TIntHashSet();
final IntIntTransientMultiMaplet addedSuperClasses = delta.getAddedSuperClasses();
final IntIntTransientMultiMaplet removedSuperClasses = delta.getRemovedSuperClasses();
addAllKeys(superClasses, addedSuperClasses);
addAllKeys(superClasses, removedSuperClasses);
superClasses.forEach(new TIntProcedure() {
@Override
public boolean execute(final int superClass) {
final TIntHashSet added = addedSuperClasses.get(superClass);
TIntHashSet removed = removedSuperClasses.get(superClass);
final TIntHashSet old = myClassToSubclasses.get(superClass);
if (old == null) {
if (added != null && !added.isEmpty()) {
myClassToSubclasses.replace(superClass, added);
}
}
else {
boolean changed = false;
final int[] addedAsArray = added != null && !added.isEmpty()? added.toArray() : null;
if (removed != null && !removed.isEmpty()) {
if (addedAsArray != null) {
// optimization: avoid unnecessary changes in the set
removed = (TIntHashSet)removed.clone();
removed.removeAll(addedAsArray);
}
if (!removed.isEmpty()) {
changed = old.removeAll(removed.toArray());
}
}
if (addedAsArray != null) {
changed |= old.addAll(addedAsArray);
}
if (changed) {
myClassToSubclasses.replace(superClass, old);
}
}
return true;
}
});
delta.getChangedClasses().forEach(new TIntProcedure() {
@Override
public boolean execute(final int className) {
final Collection<File> sourceFiles = delta.myClassToSourceFile.get(className);
myClassToSourceFile.replace(className, sourceFiles);
cleanupBackDependency(className, null, dependenciesTrashBin);
return true;
}
});
delta.getChangedFiles().forEach(new TObjectProcedure<File>() {
@Override
public boolean execute(final File fileName) {
final Collection<ClassRepr> classes = delta.mySourceFileToClasses.get(fileName);
mySourceFileToClasses.replace(fileName, classes);
return true;
}
});
}
else {
myClassToSubclasses.putAll(delta.myClassToSubclasses);
myClassToSourceFile.replaceAll(delta.myClassToSourceFile);
mySourceFileToClasses.replaceAll(delta.mySourceFileToClasses);
delta.mySourceFileToClasses.forEachEntry(new TObjectObjectProcedure<File, Collection<ClassRepr>>() {
public boolean execute(File src, Collection<ClassRepr> classes) {
for (ClassRepr repr : classes) {
if (!repr.isAnonymous() && !repr.isLocal()) {
myShortClassNameIndex.put(myContext.get(repr.getShortName()), repr.name);
}
}
return true;
}
});
}
// updating classToClass dependencies
final TIntHashSet affectedClasses = new TIntHashSet();
addAllKeys(affectedClasses, dependenciesTrashBin);
addAllKeys(affectedClasses, delta.myClassToClassDependency);
affectedClasses.forEach(new TIntProcedure() {
@Override
public boolean execute(int aClass) {
final TIntHashSet now = delta.myClassToClassDependency.get(aClass);
final TIntHashSet toRemove = dependenciesTrashBin.get(aClass);
final boolean hasDataToAdd = now != null && !now.isEmpty();
if (toRemove != null && !toRemove.isEmpty()) {
final TIntHashSet current = myClassToClassDependency.get(aClass);
if (current != null && !current.isEmpty()) {
final TIntHashSet before = new TIntHashSet();
addAll(before, current);
final boolean removed = current.removeAll(toRemove.toArray());
final boolean added = hasDataToAdd && current.addAll(now.toArray());
if ((removed && !added) || (!removed && added) || !before.equals(current)) {
myClassToClassDependency.replace(aClass, current);
}
}
else {
if (hasDataToAdd) {
myClassToClassDependency.put(aClass, now);
}
}
}
else {
// nothing to remove for this class
if (hasDataToAdd) {
myClassToClassDependency.put(aClass, now);
}
}
return true;
}
});
}
finally {
delta.close();
}
}
}
public Callbacks.Backend getCallback() {
return new Callbacks.Backend() {
public void associate(String classFileName, Collection<String> sources, ClassReader cr) {
synchronized (myLock) {
final int classFileNameS = myContext.get(classFileName);
final Pair<ClassRepr, Set<UsageRepr.Usage>> result = new ClassfileAnalyzer(myContext).analyze(classFileNameS, cr);
final ClassRepr repr = result.first;
if (repr != null) {
final Set<UsageRepr.Usage> localUsages = result.second;
final int className = repr.name;
for (String sourceFileName : sources) {
final File sourceFile = new File(sourceFileName);
myClassToSourceFile.put(className, sourceFile);
mySourceFileToClasses.put(sourceFile, repr);
}
for (final int s : repr.getSupers()) {
myClassToSubclasses.put(s, className);
}
for (final UsageRepr.Usage u : localUsages) {
final int owner = u.getOwner();
if (owner != className) {
myClassToClassDependency.put(owner, className);
}
}
}
}
}
public void associate(final String classFileName, final String sourceFileName, final ClassReader cr) {
associate(classFileName, Collections.singleton(sourceFileName), cr);
}
@Override
public void registerImports(final String className, final Collection<String> imports, Collection<String> staticImports) {
final List<String> allImports = new ArrayList<String>();
for (String anImport : imports) {
if (!anImport.endsWith("*")) {
allImports.add(anImport); // filter out wildcard imports
}
}
for (final String s : staticImports) {
int i = s.length() - 1;
for (; s.charAt(i) != '.'; i--) ;
final String anImport = s.substring(0, i);
if (!anImport.endsWith("*")) {
allImports.add(anImport); // filter out wildcard imports
}
}
if (!allImports.isEmpty()) {
myPostPasses.offer(new Runnable() {
public void run() {
final int rootClassName = myContext.get(className.replace(".", "/"));
final Collection<File> fileNames = myClassToSourceFile.get(rootClassName);
final ClassRepr repr = fileNames != null && !fileNames.isEmpty()? getReprByName(fileNames.iterator().next(), rootClassName) : null;
for (final String i : allImports) {
final int iname = myContext.get(i.replace('.', '/'));
myClassToClassDependency.put(iname, rootClassName);
if (repr != null && repr.addUsage(UsageRepr.createClassUsage(myContext, iname))) {
for (File fileName : fileNames) {
mySourceFileToClasses.put(fileName, repr);
}
}
}
}
});
}
}
};
}
@Nullable
public Set<ClassRepr> getClasses(final String sourceFileName) {
synchronized (myLock) {
return (Set<ClassRepr>)mySourceFileToClasses.get(new File(sourceFileName));
}
}
public void close() {
synchronized (myLock) {
myClassToSubclasses.close();
myClassToClassDependency.close();
mySourceFileToClasses.close();
myClassToSourceFile.close();
if (!myIsDelta) {
myShortClassNameIndex.close();
// only close if you own the context
final DependencyContext context = myContext;
if (context != null) {
context.close();
myContext = null;
}
}
else {
if (!myDeltaIsTransient) {
FileUtil.delete(myRootDir);
}
}
}
}
public void flush(final boolean memoryCachesOnly) {
synchronized (myLock) {
myClassToSubclasses.flush(memoryCachesOnly);
myClassToClassDependency.flush(memoryCachesOnly);
mySourceFileToClasses.flush(memoryCachesOnly);
myClassToSourceFile.flush(memoryCachesOnly);
if (!myIsDelta) {
myShortClassNameIndex.flush(memoryCachesOnly);
// flush if you own the context
final DependencyContext context = myContext;
if (context != null) {
context.clearMemoryCaches();
if (!memoryCachesOnly) {
context.flush();
}
}
}
}
}
private static boolean addAll(final TIntHashSet whereToAdd, TIntHashSet whatToAdd) {
if (whatToAdd.isEmpty()) {
return false;
}
final Ref<Boolean> changed = new Ref<Boolean>(Boolean.FALSE);
whatToAdd.forEach(new TIntProcedure() {
@Override
public boolean execute(int value) {
if (whereToAdd.add(value)) {
changed.set(Boolean.TRUE);
}
return true;
}
});
return changed.get();
}
private static void addAllKeys(final TIntHashSet whereToAdd, final IntIntMultiMaplet maplet) {
maplet.forEachEntry(new TIntObjectProcedure<TIntHashSet>() {
@Override
public boolean execute(int key, TIntHashSet b) {
whereToAdd.add(key);
return true;
}
});
}
private void registerAddedSuperClass(final int aClass, final int superClass) {
assert (myAddedSuperClasses != null);
myAddedSuperClasses.put(superClass, aClass);
}
private void registerRemovedSuperClass(final int aClass, final int superClass) {
assert (myRemovedSuperClasses != null);
myRemovedSuperClasses.put(superClass, aClass);
}
private boolean isDifferentiated() {
return myIsDifferentiated;
}
private boolean isRebuild() {
return myIsRebuild;
}
private void addDeletedClass(final ClassRepr cr, File fileName) {
assert (myDeletedClasses != null);
myDeletedClasses.add(Pair.create(cr, fileName));
addChangedClass(cr.name);
}
private void addAddedClass(final ClassRepr cr) {
assert (myAddedClasses != null);
myAddedClasses.add(cr);
addChangedClass(cr.name);
}
private void addChangedClass(final int it) {
assert (myChangedClasses != null && myChangedFiles != null);
myChangedClasses.add(it);
final Collection<File> files = myClassToSourceFile.get(it);
if (files != null) {
myChangedFiles.addAll(files);
}
}
@NotNull
private Set<Pair<ClassRepr, File>> getDeletedClasses() {
return myDeletedClasses == null ? Collections.<Pair<ClassRepr, File>>emptySet() : Collections.unmodifiableSet(myDeletedClasses);
}
@NotNull
private Set<ClassRepr> getAddedClasses() {
return myAddedClasses == null ? Collections.<ClassRepr>emptySet() : Collections.unmodifiableSet(myAddedClasses);
}
private TIntHashSet getChangedClasses() {
return myChangedClasses;
}
private THashSet<File> getChangedFiles() {
return myChangedFiles;
}
private static void debug(final String s) {
LOG.debug(s);
}
private void debug(final String comment, final int s) {
myDebugS.debug(comment, s);
}
private void debug(final String comment, final File f) {
debug(comment, f.getPath());
}
private void debug(final String comment, final String s) {
myDebugS.debug(comment, s);
}
private void debug(final String comment, final boolean s) {
myDebugS.debug(comment, s);
}
public void toStream(final PrintStream stream) {
final Streamable[] data = {
myClassToSubclasses,
myClassToClassDependency,
mySourceFileToClasses,
myClassToSourceFile,
};
final String[] info = {
"ClassToSubclasses",
"ClassToClassDependency",
"SourceFileToClasses",
"ClassToSourceFile",
"SourceFileToAnnotationUsages",
"SourceFileToUsages"
};
for (int i = 0; i < data.length; i++) {
stream.print("Begin Of ");
stream.println(info[i]);
data[i].toStream(myContext, stream);
stream.print("End Of ");
stream.println(info[i]);
}
}
public void toStream(File outputRoot) {
final Streamable[] data = {
myClassToSubclasses,
myClassToClassDependency,
mySourceFileToClasses,
myClassToSourceFile,
myShortClassNameIndex
};
final String[] info = {
"ClassToSubclasses",
"ClassToClassDependency",
"SourceFileToClasses",
"ClassToSourceFile",
"ShortClassNameIndex"
};
for (int i = 0; i < data.length; i++) {
final File file = new File(outputRoot, info[i]);
FileUtil.createIfDoesntExist(file);
try {
final PrintStream stream = new PrintStream(file);
try {
data[i].toStream(myContext, stream);
}
finally {
stream.close();
}
}
catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
}