supporting for multiple source files association with a single class name (needed for Kotlin)

This commit is contained in:
Eugene Zhuravlev
2014-08-05 13:25:36 +02:00
parent d2909231f3
commit caa43aea98
6 changed files with 173 additions and 117 deletions
@@ -29,6 +29,7 @@ public class Callbacks {
public interface Backend {
void associate(String classFileName, String sourceFileName, ClassReader cr);
void associate(String classFileName, Collection<String> sources, ClassReader cr);
void registerImports(String className, Collection<String> imports, Collection<String> staticImports);
}
@@ -28,7 +28,7 @@ import java.util.List;
* @author: db
* Date: 03.11.11
*/
abstract class IntObjectMultiMaplet<V extends Streamable> implements Streamable {
abstract class IntObjectMultiMaplet<V> implements Streamable {
abstract boolean containsKey(final int key);
abstract Collection<V> get(final int key);
@@ -78,12 +78,17 @@ abstract class IntObjectMultiMaplet<V extends Streamable> implements Streamable
final List<String> list = new LinkedList<String>();
for (final V value : b) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream s = new PrintStream(baos);
if (value instanceof Streamable) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream s = new PrintStream(baos);
value.toStream(context, s);
((Streamable)value).toStream(context, s);
list.add(baos.toString());
list.add(baos.toString());
}
else {
list.add(value.toString());
}
}
Collections.sort(list);
@@ -32,7 +32,7 @@ import java.util.Collections;
* @author: db
* Date: 08.03.11
*/
class IntObjectPersistentMultiMaplet<V extends Streamable> extends IntObjectMultiMaplet<V> {
class IntObjectPersistentMultiMaplet<V> extends IntObjectMultiMaplet<V> {
private static final Collection NULL_COLLECTION = Collections.emptySet();
private static final int CACHE_SIZE = 128;
private final PersistentHashMap<Integer, Collection<V>> myMap;
@@ -24,7 +24,7 @@ import java.util.Collection;
* @author: db
* Date: 08.03.11
*/
class IntObjectTransientMultiMaplet<V extends Streamable> extends IntObjectMultiMaplet<V> {
class IntObjectTransientMultiMaplet<V> extends IntObjectMultiMaplet<V> {
private final TIntObjectHashMap<Collection<V>> myMap = new TIntObjectHashMap<Collection<V>>();
private final CollectionFactory<V> myCollectionFactory;
@@ -85,7 +85,7 @@ public class Mappings {
*/
private IntIntMultiMaplet myClassToClassDependency;
private ObjectObjectMultiMaplet<File, ClassRepr> mySourceFileToClasses;
private IntObjectMaplet<File> myClassToSourceFile;
private IntObjectMultiMaplet<File> myClassToSourceFile;
/**
* [short className] -> list of FQ names
*/
@@ -138,12 +138,17 @@ public class Mappings {
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 IntObjectTransientMaplet<File>();
myClassToSourceFile = new IntObjectTransientMultiMaplet<File>(fileCollectionFactory);
}
else {
if (myIsDelta) {
@@ -156,7 +161,7 @@ public class Mappings {
DependencyContext.getTableFile(myRootDir, SOURCE_TO_CLASS), new FileKeyDescriptor(), ClassRepr.externalizer(myContext),
ourClassSetConstructor
);
myClassToSourceFile = new IntObjectPersistentMaplet<File>(DependencyContext.getTableFile(myRootDir, CLASS_TO_SOURCE), new FileKeyDescriptor());
myClassToSourceFile = new IntObjectPersistentMultiMaplet<File>(DependencyContext.getTableFile(myRootDir, CLASS_TO_SOURCE), INT_KEY_DESCRIPTOR, new FileKeyDescriptor(), fileCollectionFactory);
}
}
@@ -190,17 +195,17 @@ public class Mappings {
}
@Nullable
private ClassRepr getReprByName(@Nullable File source, final int name) {
if (source == null) {
source = myClassToSourceFile.get(name);
}
if (source != null) {
final Collection<ClassRepr> reprs = mySourceFileToClasses.get(source);
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 == name) {
return repr;
if (reprs != null) {
for (ClassRepr repr : reprs) {
if (repr.name == qName) {
return repr;
}
}
}
}
@@ -556,14 +561,19 @@ public class Mappings {
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 File fileName = myClassToSourceFile.get(className);
if (fileName == null) {
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;
}
debug("Source file name: ", fileName);
for (File fName : allSources) {
debug("Source file name: ", fName);
if (!alreadyCompiledFiles.contains(fName)) {
affectedFiles.add(fName);
}
}
if (usages) {
debug("Class usages affection requested");
@@ -579,9 +589,6 @@ public class Mappings {
if (depClasses != null) {
addAll(dependants, depClasses);
}
if (!alreadyCompiledFiles.contains(fileName)) {
affectedFiles.add(fileName);
}
final TIntHashSet directSubclasses = myClassToSubclasses.get(className);
if (directSubclasses != null) {
@@ -701,10 +708,14 @@ public class Mappings {
dependants.forEach(new TIntProcedure() {
@Override
public boolean execute(int depClass) {
final File depFile = myClassToSourceFile.get(depClass);
if (depFile != null && !FileUtil.filesEqual(depFile, sourceFile)) {
if (filter == null || filter.accept(depFile)) {
affectedFiles.add(depFile);
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;
@@ -768,10 +779,12 @@ public class Mappings {
propagated.forEach(new TIntProcedure() {
@Override
public boolean execute(int className) {
final File fileName = myClassToSourceFile.get(className);
if (fileName != null) {
debug("Adding ", fileName);
toRecompile.add(fileName);
final Collection<File> fileNames = myClassToSourceFile.get(className);
if (fileNames != null) {
for (File fileName : fileNames) {
debug("Adding ", fileName);
}
toRecompile.addAll(fileNames);
}
return true;
}
@@ -784,13 +797,15 @@ public class Mappings {
debug("Package name: ", packageName);
// Package-local branch
myClassToSourceFile.forEachEntry(new TIntObjectProcedure<File>() {
myClassToSourceFile.forEachEntry(new TIntObjectProcedure<Collection<File>>() {
@Override
public boolean execute(int className, File fileName) {
public boolean execute(int className, Collection<File> fileNames) {
if (ClassRepr.getPackageName(myContext.getValue(className)).equals(packageName)) {
if (filter == null || filter.accept(fileName)) {
debug("Adding: ", fileName);
toRecompile.add(fileName);
for (File fileName : fileNames) {
if (filter == null || filter.accept(fileName)) {
debug("Adding: ", fileName);
toRecompile.add(fileName);
}
}
}
return true;
@@ -1107,12 +1122,14 @@ public class Mappings {
if (overrides.satisfy(method) && isInheritor) {
debug("Current method overrides that found");
final File file = myClassToSourceFile.get(methodClass.name);
if (file != null) {
myAffectedFiles.add(file);
debug("Affecting file ", file);
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");
@@ -1141,12 +1158,15 @@ public class Mappings {
@Override
public boolean execute(int subClass) {
final ClassRepr r = myFuture.reprByName(subClass);
if (r != null) {
final File sourceFileName = myClassToSourceFile.get(subClass);
if (sourceFileName != null && !myCompiledFiles.contains(sourceFileName)) {
final int outerClass = r.getOuterClassName();
if (!isEmpty(outerClass) && myFuture.isMethodVisible(outerClass, m)) {
myAffectedFiles.add(sourceFileName);
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);
}
}
@@ -1199,10 +1219,12 @@ public class Mappings {
myFuture.addOverridingMethods(m, it, MethodRepr.equalByJavaRules(m), overridingMethods);
for (final Pair<MethodRepr, ClassRepr> p : overridingMethods) {
final File fName = myClassToSourceFile.get(p.second.name);
if (fName != null) {
myAffectedFiles.add(fName);
debug("Affecting file by overriding: ", fName);
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);
}
}
}
@@ -1242,12 +1264,14 @@ public class Mappings {
}
if (allAbstract && visited) {
final File source = myClassToSourceFile.get(p);
final Collection<File> sources = myClassToSourceFile.get(p);
if (source != null && !myCompiledFiles.contains(source)) {
myAffectedFiles.add(source);
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);
debug("Affecting subclass source file ", source);
for (File source : sources) {
debug("Affecting subclass source file ", source);
}
}
}
}
@@ -1317,9 +1341,9 @@ public class Mappings {
final ClassRepr aClass = p.getSecond();
if (aClass != MOCK_CLASS) {
final File fileName = myClassToSourceFile.get(aClass.name);
if (fileName != null) {
myAffectedFiles.add(fileName);
final Collection<File> fileNames = myClassToSourceFile.get(aClass.name);
if (fileNames != null) {
myAffectedFiles.addAll(fileNames);
}
}
}
@@ -1387,17 +1411,21 @@ public class Mappings {
public boolean execute(int subClass) {
final ClassRepr r = myFuture.reprByName(subClass);
if (r != null) {
final File sourceFileName = myClassToSourceFile.get(subClass);
if (sourceFileName != null && !myCompiledFiles.contains(sourceFileName)) {
final Collection<File> sourceFileNames = myClassToSourceFile.get(subClass);
if (sourceFileNames != null && !myCompiledFiles.containsAll(sourceFileNames)) {
if (r.isLocal()) {
debug("Affecting local subclass (introduced field can potentially hide surrounding method parameters/local variables): ", sourceFileName);
myAffectedFiles.add(sourceFileName);
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)) {
debug("Affecting inner subclass (introduced field can potentially hide surrounding class fields): ", sourceFileName);
myAffectedFiles.add(sourceFileName);
for (File sourceFileName : sourceFileNames) {
debug("Affecting inner subclass (introduced field can potentially hide surrounding class fields): ", sourceFileName);
}
myAffectedFiles.addAll(sourceFileNames);
}
}
}
@@ -1796,7 +1824,9 @@ public class Mappings {
// checking if this newly added class duplicates already existing one
for (ClassRepr c : addedClasses) {
if (!c.isLocal() && !c.isAnonymous() && isEmpty(c.getOuterClassName())) {
final File currentlyMappedTo = myClassToSourceFile.get(c.name);
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
@@ -1845,11 +1875,13 @@ public class Mappings {
toAffect.forEach(new TIntProcedure() {
@Override
public boolean execute(int depClass) {
final File fName = myClassToSourceFile.get(depClass);
if (fName != null) {
if (myFilter == null || myFilter.accept(fName)) {
debug("Adding dependent file ", fName);
myAffectedFiles.add(fName);
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;
@@ -1863,24 +1895,30 @@ public class Mappings {
state.myDependants.forEach(new TIntProcedure() {
@Override
public boolean execute(final int depClass) {
final File depFile = myClassToSourceFile.get(depClass);
final Collection<File> depFiles = myClassToSourceFile.get(depClass);
if (depFiles != null) {
for (File depFile : depFiles) {
processDependentFile(depClass, depFile);
}
}
return true;
}
if (depFile == null || myAffectedFiles.contains(depFile) || myCompiledFiles.contains(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 true;
return;
}
final Set<UsageRepr.Usage> depUsages = classRepr.getUsages();
if (depUsages == null || depUsages.isEmpty()) {
return true;
return;
}
for (UsageRepr.Usage usage : depUsages) {
@@ -1889,32 +1927,24 @@ public class Mappings {
if (query.satisfies(usage)) {
debug("Added file due to annotation query");
myAffectedFiles.add(depFile);
return true;
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 true;
return;
}
else {
if (constraint.checkResidence(depClass)) {
debug("Added file with satisfied constraint");
myAffectedFiles.add(depFile);
return true;
}
if (constraint.checkResidence(depClass)) {
debug("Added file with satisfied constraint");
myAffectedFiles.add(depFile);
return;
}
}
}
return true;
}
});
}
@@ -2020,11 +2050,27 @@ public class Mappings {
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;
if (!FileUtil.filesEqual(sourceFile, myClassToSourceFile.get(className))) {
// if classname is already mapped to a different source, the class with such FQ name exists elsewhere, so
// we cannot destroy all these links
// 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);
@@ -2126,13 +2172,8 @@ public class Mappings {
delta.getChangedClasses().forEach(new TIntProcedure() {
@Override
public boolean execute(final int className) {
final File sourceFile = delta.myClassToSourceFile.get(className);
if (sourceFile != null) {
myClassToSourceFile.put(className, sourceFile);
}
else {
myClassToSourceFile.remove(className);
}
final Collection<File> sourceFiles = delta.myClassToSourceFile.get(className);
myClassToSourceFile.replace(className, sourceFiles);
cleanupBackDependency(className, null, dependenciesTrashBin);
@@ -2151,7 +2192,7 @@ public class Mappings {
}
else {
myClassToSubclasses.putAll(delta.myClassToSubclasses);
myClassToSourceFile.putAll(delta.myClassToSourceFile);
myClassToSourceFile.replaceAll(delta.myClassToSourceFile);
mySourceFileToClasses.replaceAll(delta.mySourceFileToClasses);
delta.mySourceFileToClasses.forEachEntry(new TObjectObjectProcedure<File, Collection<ClassRepr>>() {
public boolean execute(File src, Collection<ClassRepr> classes) {
@@ -2216,18 +2257,21 @@ public class Mappings {
public Callbacks.Backend getCallback() {
return new Callbacks.Backend() {
public void associate(final String classFileName, final String sourceFileName, final ClassReader cr) {
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 File sourceFile = new File(sourceFileName);
final int className = repr.name;
myClassToSourceFile.put(className, sourceFile);
mySourceFileToClasses.put(sourceFile, repr);
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);
@@ -2244,6 +2288,10 @@ public class Mappings {
}
}
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>();
@@ -2265,14 +2313,16 @@ public class Mappings {
myPostPasses.offer(new Runnable() {
public void run() {
final int rootClassName = myContext.get(className.replace(".", "/"));
final File fileName = myClassToSourceFile.get(rootClassName);
final ClassRepr repr = fileName != null? getReprByName(fileName, rootClassName) : null;
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(".", "/"));
final int iname = myContext.get(i.replace('.', '/'));
myClassToClassDependency.put(iname, rootClassName);
if (repr != null && repr.addUsage(UsageRepr.createClassUsage(myContext, iname))) {
mySourceFileToClasses.put(fileName, repr);
for (File fileName : fileNames) {
mySourceFileToClasses.put(fileName, repr);
}
}
}
}
@@ -2399,10 +2449,10 @@ public class Mappings {
assert (myChangedClasses != null && myChangedFiles != null);
myChangedClasses.add(it);
final File file = myClassToSourceFile.get(it);
final Collection<File> files = myClassToSourceFile.get(it);
if (file != null) {
myChangedFiles.add(file);
if (files != null) {
myChangedFiles.addAll(files);
}
}
@@ -42,7 +42,7 @@ import java.util.concurrent.ConcurrentMap;
* Date: 10/7/11
*/
public class BuildDataManager implements StorageOwner {
private static final int VERSION = 24;
private static final int VERSION = 25;
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.incremental.storage.BuildDataManager");
private static final String SRC_TO_FORM_STORAGE = "src-form";
private static final String OUT_TARGET_STORAGE = "out-target";