/*
* Copyright 2013 the original author or authors.
*
* 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.gradle.api.internal.tasks.compile.incremental.jar;
import com.google.common.collect.Lists;
import org.gradle.api.Action;
import org.gradle.api.internal.tasks.compile.incremental.deps.AffectedClasses;
import org.gradle.api.internal.tasks.compile.incremental.deps.ClassSetAnalysisData;
import org.gradle.api.internal.tasks.compile.incremental.deps.DefaultDependentsSet;
import org.gradle.api.internal.tasks.compile.incremental.deps.DependencyToAll;
import org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet;
import org.gradle.api.tasks.incremental.InputFileDetails;
import java.util.Deque;
import java.util.Set;
public class JarChangeDependentsFinder {
private final JarClasspathSnapshot jarClasspathSnapshot;
private final PreviousCompilation previousCompilation;
public JarChangeDependentsFinder(JarClasspathSnapshot jarClasspathSnapshot, PreviousCompilation previousCompilation) {
this.jarClasspathSnapshot = jarClasspathSnapshot;
this.previousCompilation = previousCompilation;
}
public DependentsSet getActualDependents(InputFileDetails jarChangeDetails, JarArchive jarArchive) {
if (jarChangeDetails.isAdded()) {
if (jarClasspathSnapshot.isAnyClassDuplicated(jarArchive)) {
//at least one of the classes from the new jar is already present in jar classpath
//to avoid calculation which class gets on the classpath first, rebuild all
return new DependencyToAll("at least one of the classes of '" + jarArchive.file.getName() + "' is already present in classpath");
} else {
//none of the new classes in the jar are duplicated on classpath, don't rebuild
return DefaultDependentsSet.EMPTY;
}
}
final JarSnapshot previous = previousCompilation.getJarSnapshot(jarChangeDetails.getFile());
if (previous == null) {
//we don't know what classes were dependents of the jar in the previous build
//for example, a class (in jar) with a constant might have changed into a class without a constant - we need to rebuild everything
return new DependencyToAll("missing jar snapshot of '" + jarArchive.file.getName() + "' from previous build");
}
if (jarChangeDetails.isRemoved()) {
DependentsSet allClasses = previous.getAllClasses();
if (allClasses.isDependencyToAll()) {
return new DependencyToAll("at least one of the classes of removed jar '" + jarArchive.file.getName() + "' requires it");
}
//recompile all dependents of all the classes from jar
return previousCompilation.getDependents(allClasses.getDependentClasses(), previous.getAllConstants(allClasses));
}
if (jarChangeDetails.isModified()) {
final JarSnapshot currentSnapshot = jarClasspathSnapshot.getSnapshot(jarArchive);
AffectedClasses affected = currentSnapshot.getAffectedClassesSince(previous);
DependentsSet altered = affected.getAltered();
if (altered.isDependencyToAll()) {
//at least one of the classes changed in the jar is a 'dependency-to-all'
return altered;
}
if (jarClasspathSnapshot.isAnyClassDuplicated(affected.getAdded())) {
//A new duplicate class on classpath. As we don't fancy-handle classpath order right now, we don't know which class is on classpath first.
//For safe measure rebuild everything
return new DependencyToAll("at least one of the classes of modified jar '" + jarArchive.file.getName() + "' is already present in the classpath");
}
//recompile all dependents of the classes changed in the jar
final Set<String> dependentClasses = altered.getDependentClasses();
final Deque<String> queue = Lists.newLinkedList(dependentClasses);
while (!queue.isEmpty()) {
final String dependentClass = queue.poll();
jarClasspathSnapshot.forEachSnapshot(new Action<JarSnapshot>() {
@Override
public void execute(JarSnapshot jarSnapshot) {
if (jarSnapshot != previous) {
// we need to find in the other jars classes that would potentially extend classes changed
// in the current snapshot (they are intermediates)
ClassSetAnalysisData data = jarSnapshot.getData().data;
Set<String> children = data.getChildren(dependentClass);
for (String child : children) {
if (dependentClasses.add(child)) {
queue.add(child);
}
}
}
}
});
}
return previousCompilation.getDependents(dependentClasses, currentSnapshot.getRelevantConstants(previous, dependentClasses));
}
throw new IllegalArgumentException("Unknown input file details provided: " + jarChangeDetails);
}
}