package org.jetbrains.ether;
import com.intellij.openapi.util.io.FileUtil;
import org.codehaus.gant.GantBinding;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.jetbrains.ether.dependencyView.*;
import org.jetbrains.jps.*;
import org.jetbrains.jps.idea.IdeaProjectLoader;
import org.jetbrains.jps.resolvers.PathEntry;
import java.io.*;
import java.util.*;
/**
* Created by IntelliJ IDEA.
* User: db
* Date: 19.11.10
* Time: 2:58
* To change this template use File | Settings | File Templates.
*/
public class ProjectWrapper {
public interface Flags {
boolean tests();
boolean incremental();
boolean force();
PrintStream logStream();
}
private final static Flags defaultFlags = new Flags() {
public boolean tests() {
return true;
}
public boolean incremental() {
return false;
}
public boolean force() {
return true;
}
public PrintStream logStream() {
return null;
}
};
private abstract class Logger {
private final PrintStream stream;
public Logger(final Flags flags) {
this.stream = flags.logStream();
}
public void logFilePaths(PrintStream stream, Collection<StringCache.S> paths) {
List<String> strings = new ArrayList<String>(paths.size());
for (StringCache.S path : paths) {
strings.add(FileUtil.toSystemIndependentName(path.toString()));
}
logMany(stream, strings);
}
public <T> void logMany(final PrintStream stream, final Collection<T> list) {
final String[] a = new String[list.size()];
int i = 0;
for (T e : list) {
a[i++] = e.toString();
}
Arrays.sort(a);
for (String o : a) {
stream.println(o);
}
}
public abstract void log(final PrintStream stream);
public void log() {
if (stream != null)
log(stream);
}
}
// Home directory
private static final String myHomeDir = System.getProperty("user.home");
// JPS directory
private static final String myJPSDir = ".jps";
// IDEA project structure directory name
private static final String myIDEADir = ".idea";
// JPS directory initialization
private static void initJPSDirectory() {
final File f = new File(myHomeDir + File.separator + myJPSDir);
if (!f.exists()) {
if (!f.mkdir()) {
throw new RuntimeException("unable to create JPS snapshot directory " + f.getPath());
}
}
}
// File separator replacement
private static final char myFileSeparatorReplacement = '.';
// Original JPS Project
private final GantBasedProject myProject;
private final ProjectBuilder myProjectBuilder;
// Project directory
private final String myRoot;
// Project snapshot file name
private final String myProjectSnapshot;
public interface ClasspathItemWrapper extends RW.Writable {
public List<String> getClassPath(ClasspathKind kind);
}
private final RW.Reader<LibraryWrapper> myLibraryWrapperReader =
new RW.Reader<LibraryWrapper>() {
public LibraryWrapper read(final BufferedReader r) {
return new LibraryWrapper(r);
}
};
public class LibraryWrapper implements ClasspathItemWrapper {
final String myName;
final List<String> myClassPath;
public void write(final BufferedWriter w) {
RW.writeln(w, "Library:" + myName);
RW.writeln(w, "Classpath:");
RW.writeln(w, myClassPath, RW.fromString);
}
public LibraryWrapper(final BufferedReader r) {
myName = RW.readStringAttribute(r, "Library:");
RW.readTag(r, "Classpath:");
myClassPath = (List<String>) RW.readMany(r, RW.myStringReader, new ArrayList<String>());
}
public LibraryWrapper(final Library lib) {
lib.forceInit();
myName = lib.getName();
myClassPath = (List<String>) getRelativePaths(lib.getClasspath(), new ArrayList<String>());
}
public String getName() {
return myName;
}
public List<String> getClassPath(final ClasspathKind kind) {
return myClassPath;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LibraryWrapper that = (LibraryWrapper) o;
if (myName != null ? !myName.equals(that.myName) : that.myName != null) return false;
return true;
}
@Override
public int hashCode() {
return myName != null ? myName.hashCode() : 0;
}
}
private final RW.Reader<ClasspathItemWrapper> myWeakClasspathItemWrapperReader =
new RW.Reader<ClasspathItemWrapper>() {
public ClasspathItemWrapper read(final BufferedReader r) {
final String s = RW.lookString(r);
if (s.startsWith("Library:")) {
return new WeakClasspathItemWrapper(RW.readStringAttribute(r, "Library:"), "Library");
}
if (s.startsWith("Module:")) {
return new WeakClasspathItemWrapper(RW.readStringAttribute(r, "Module:"), "Module");
} else {
return new GenericClasspathItemWrapper(r);
}
}
};
public class WeakClasspathItemWrapper implements ClasspathItemWrapper {
final String myName;
final String myType;
public WeakClasspathItemWrapper(final String name, final String type) {
myName = name;
myType = type;
}
public WeakClasspathItemWrapper(final ModuleWrapper m) {
myType = "Module";
myName = m.getName();
}
public WeakClasspathItemWrapper(final LibraryWrapper l) {
myType = "Library";
myName = l.getName();
}
public boolean isModule() {
return myType.equals("Module");
}
public String getName() {
return myName;
}
public List<String> getClassPath(ClasspathKind kind) {
return null;
}
public void write(final BufferedWriter w) {
RW.writeln(w, myType + ":" + getName());
}
}
public class GenericClasspathItemWrapper implements ClasspathItemWrapper {
final List<String> myClassPath;
final String myType;
public GenericClasspathItemWrapper(final ClasspathItem item) {
if (item instanceof PathEntry)
myType = "PathEntry";
else if (item instanceof JavaSdk)
myType = "JavaSdk";
else if (item instanceof Sdk)
myType = "Sdk";
else
myType = null;
myClassPath = (List<String>) getRelativePaths(item.getClasspathRoots(null), new ArrayList<String>());
}
public GenericClasspathItemWrapper(final BufferedReader r) {
myType = RW.readString(r);
RW.readTag(r, "Classpath:");
myClassPath = (List<String>) RW.readMany(r, RW.myStringReader, new ArrayList<String>());
}
public String getType() {
return myType;
}
public List<String> getClassPath(final ClasspathKind kind) {
return myClassPath;
}
public void write(final BufferedWriter w) {
RW.writeln(w, myType);
RW.writeln(w, "Classpath:");
RW.writeln(w, myClassPath, RW.fromString);
}
}
private final RW.Reader<FileWrapper> myFileWrapperReader =
new RW.Reader<FileWrapper>() {
public FileWrapper read(final BufferedReader r) {
return new FileWrapper(r);
}
};
public class FileWrapper implements RW.Writable {
final StringCache.S myName;
final long myModificationTime;
FileWrapper(final File f) {
myName = StringCache.get(getRelativePath(f.getAbsolutePath()));
myModificationTime = f.lastModified();
}
FileWrapper(final BufferedReader r) {
myName = StringCache.get(RW.readString(r));
myModificationTime = RW.readLong(r);
final Set<ClassRepr> classes = (Set<ClassRepr>) RW.readMany(r, ClassRepr.reader, new HashSet<ClassRepr>());
final Set<Pair<ClassRepr, Set<StringCache.S>>> classesWithSubclasses = new HashSet<Pair<ClassRepr, Set<StringCache.S>>>();
for (ClassRepr c : classes) {
final Set<StringCache.S> subClasses = (Set<StringCache.S>) RW.readMany(r, StringCache.reader, new HashSet<StringCache.S>());
classesWithSubclasses.add(new Pair<ClassRepr, Set<StringCache.S>>(c, subClasses));
}
final UsageRepr.Cluster usages = new UsageRepr.Cluster(r);
final Set<UsageRepr.Usage> annotationUsages = (Set<UsageRepr.Usage>) RW.readMany(r, UsageRepr.reader, new HashSet<UsageRepr.Usage>());
final Set<StringCache.S> formClasses = (Set<StringCache.S>) RW.readMany(r, StringCache.S.reader, new HashSet<StringCache.S>());
backendCallback.associate(classesWithSubclasses, new Pair<UsageRepr.Cluster, Set<UsageRepr.Usage>>(usages, annotationUsages), myName.value);
for (StringCache.S classFileName : formClasses) {
backendCallback.associateForm(myName, classFileName);
}
}
public StringCache.S getName() {
return myName;
}
public long getStamp() {
return myModificationTime;
}
public void write(final BufferedWriter w) {
final StringCache.S name = getName();
RW.writeln(w, name.value);
RW.writeln(w, Long.toString(getStamp()));
final Set<ClassRepr> classes = dependencyMapping.getClasses(name);
RW.writeln(w, classes);
if (classes != null) {
for (ClassRepr c : classes) {
RW.writeln(w, dependencyMapping.getSubClasses(c.name));
}
}
dependencyMapping.getUsages(name).write(w);
RW.writeln(w, dependencyMapping.getAnnotationUsages(name));
RW.writeln(w, dependencyMapping.getFormClass(name));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FileWrapper that = (FileWrapper) o;
if (myName != null ? !myName.equals(that.myName) : that.myName != null) return false;
return true;
}
@Override
public int hashCode() {
return myName != null ? myName.hashCode() : 0;
}
}
private final RW.Reader<ModuleWrapper> myModuleWrapperReader =
new RW.Reader<ModuleWrapper>() {
public ModuleWrapper read(final BufferedReader r) {
return new ModuleWrapper(r);
}
};
public class ModuleWrapper implements ClasspathItemWrapper {
private class Properties implements RW.Writable {
final Set<String> myRoots;
final Map<FileWrapper, FileWrapper> mySources;
final String myOutput;
String myOutputStatus;
public Set<StringCache.S> getFiles() {
final Set<StringCache.S> result = new HashSet<StringCache.S>();
for (FileWrapper f : mySources.keySet()) {
result.add(f.getName());
}
return result;
}
public Set<StringCache.S> getOutdatedFiles(final Properties past) {
final Set<StringCache.S> result = new HashSet<StringCache.S>();
for (FileWrapper now : mySources.keySet()) {
final FileWrapper than = past == null ? null : past.mySources.get(now);
if (than == null || than.getStamp() < now.getStamp() || affectedFiles.contains(now.getName())) {
result.add(now.getName());
}
}
return result;
}
public Set<StringCache.S> getRemovedFiles(final Properties past) {
final Set<StringCache.S> result = new HashSet<StringCache.S>();
if (past != null) {
for (FileWrapper was : past.mySources.keySet()) {
final FileWrapper now = mySources.get(was);
if (now == null) {
result.add(was.getName());
}
}
}
return result;
}
public void write(final BufferedWriter w) {
RW.writeln(w, "Roots:");
RW.writeln(w, myRoots, RW.fromString);
RW.writeln(w, "Sources:");
RW.writeln(w, mySources.keySet());
RW.writeln(w, "Output:");
RW.writeln(w, myOutput == null ? "" : myOutput);
RW.writeln(w, "OutputStatus:" + myOutputStatus);
}
public Properties(final BufferedReader r) {
RW.readTag(r, "Roots:");
myRoots = (Set<String>) RW.readMany(r, RW.myStringReader, new HashSet<String>());
RW.readTag(r, "Sources:");
mySources = new HashMap<FileWrapper, FileWrapper>();
for (FileWrapper fw : (Set<FileWrapper>) RW.readMany(r, myFileWrapperReader, new HashSet<FileWrapper>())) {
mySources.put(fw, fw);
}
RW.readTag(r, "Output:");
final String s = RW.readString(r);
myOutput = s.equals("") ? null : s;
myOutputStatus = RW.readStringAttribute(r, "OutputStatus:");
}
public Properties(final List<String> sources, final String output, final Set<String> excludes) {
myRoots = (Set<String>) getRelativePaths(sources, new HashSet<String>());
final DirectoryScanner.Result result = DirectoryScanner.getFiles(myRoots, excludes, ProjectWrapper.this);
mySources = new HashMap<FileWrapper, FileWrapper>();
for (FileWrapper fw : result.getFiles()) {
mySources.put(fw, fw);
}
myOutput = getRelativePath(output);
updateOutputStatus();
}
public void updateOutputStatus() {
final String path = getAbsolutePath(myOutput);
final File ok = new File(path + File.separator + Reporter.myOkFlag);
final File fail = new File(path + File.separator + Reporter.myFailFlag);
if (ok.exists()) {
myOutputStatus = "ok";
return;
}
if (fail.exists()) {
myOutputStatus = "fail";
return;
}
myOutputStatus = "empty";
}
public Set<String> getRoots() {
return myRoots;
}
public Set<FileWrapper> getSources() {
return mySources.keySet();
}
public String getOutputPath() {
return myOutput;
}
public boolean emptySource() {
return mySources.isEmpty();
}
public boolean outputOk() {
return myOutputStatus.equals("ok");
}
public boolean outputEmpty() {
return myOutputStatus.equals("empty");
}
public boolean isOutdated() {
return (!emptySource() && !outputOk());
}
}
final String myName;
final Properties mySource;
final Properties myTest;
List<ClasspathItemWrapper> myDependsOn;
List<ClasspathItemWrapper> myTestDependsOn;
final Set<String> myExcludes;
final Module myModule;
final Set<LibraryWrapper> myLibraries;
public Set<StringCache.S> getOutdatedSources() {
return mySource.getOutdatedFiles(myHistory == null ? null : myHistory.getModule(myName).mySource);
}
public Set<StringCache.S> getOutdatedTests() {
return myTest.getOutdatedFiles(myHistory == null ? null : myHistory.getModule(myName).myTest);
}
public Set<StringCache.S> getRemovedSources() {
return mySource.getRemovedFiles(myHistory == null ? null : myHistory.getModule(myName).mySource);
}
public Set<StringCache.S> getRemovedTests() {
return myTest.getRemovedFiles(myHistory == null ? null : myHistory.getModule(myName).myTest);
}
public void updateOutputStatus() {
mySource.updateOutputStatus();
myTest.updateOutputStatus();
}
private ClasspathItemWrapper weaken(final ClasspathItemWrapper x) {
if (x instanceof ModuleWrapper) {
return new WeakClasspathItemWrapper((ModuleWrapper) x);
} else if (x instanceof LibraryWrapper) {
return new WeakClasspathItemWrapper((LibraryWrapper) x);
} else
return x;
}
public void write(final BufferedWriter w) {
RW.writeln(w, "Module:" + myName);
RW.writeln(w, "SourceProperties:");
mySource.write(w);
RW.writeln(w, "TestProperties:");
myTest.write(w);
RW.writeln(w, "Excludes:");
RW.writeln(w, myExcludes, RW.fromString);
RW.writeln(w, "Libraries:");
RW.writeln(w, myLibraries);
RW.writeln(w, "Dependencies:");
final List<ClasspathItemWrapper> weakened = new ArrayList<ClasspathItemWrapper>();
for (ClasspathItemWrapper cpiw : dependsOn(false)) {
weakened.add(weaken(cpiw));
}
RW.writeln(w, weakened);
weakened.clear();
for (ClasspathItemWrapper cpiw : dependsOn(true)) {
weakened.add(weaken(cpiw));
}
RW.writeln(w, weakened);
}
public ModuleWrapper(final BufferedReader r) {
myModule = null;
myName = RW.readStringAttribute(r, "Module:");
RW.readTag(r, "SourceProperties:");
mySource = new Properties(r);
RW.readTag(r, "TestProperties:");
myTest = new Properties(r);
RW.readTag(r, "Excludes:");
myExcludes = (Set<String>) RW.readMany(r, RW.myStringReader, new HashSet<String>());
RW.readTag(r, "Libraries:");
myLibraries = (Set<LibraryWrapper>) RW.readMany(r, myLibraryWrapperReader, new HashSet<LibraryWrapper>());
RW.readTag(r, "Dependencies:");
myDependsOn = (List<ClasspathItemWrapper>) RW.readMany(r, myWeakClasspathItemWrapperReader, new ArrayList<ClasspathItemWrapper>());
myTestDependsOn = (List<ClasspathItemWrapper>) RW.readMany(r, myWeakClasspathItemWrapperReader, new ArrayList<ClasspathItemWrapper>());
}
public ModuleWrapper(final Module m) {
m.forceInit();
myModule = m;
myDependsOn = null;
myTestDependsOn = null;
myName = m.getName();
myExcludes = (Set<String>) getRelativePaths(m.getExcludes(), new HashSet<String>());
mySource = new Properties(m.getSourceRoots(), m.getOutputPath(), myExcludes);
myTest = new Properties(m.getTestRoots(), m.getTestOutputPath(), myExcludes);
myLibraries = new HashSet<LibraryWrapper>();
for (Library lib : m.getLibraries().values()) {
myLibraries.add(new LibraryWrapper(lib));
}
}
public String getName() {
return myName;
}
public Set<StringCache.S> getOutdatedFiles(final boolean tests) {
if (tests) {
return myTest.outputEmpty() ? getTests() : getOutdatedTests();
}
return mySource.outputEmpty() ? getSources() : getOutdatedSources();
}
public Set<StringCache.S> getRemovedFiles(final boolean tests) {
if (tests) {
return getRemovedTests();
}
return getRemovedSources();
}
public Set<StringCache.S> getSources(final boolean tests) {
if (tests) {
return myTest.getFiles();
}
return mySource.getFiles();
}
public Set<String> getSourceRoots() {
return mySource.getRoots();
}
public Set<FileWrapper> getSourceFiles() {
return mySource.getSources();
}
public Set<FileWrapper> getTestFiles() {
return myTest.getSources();
}
public Set<StringCache.S> getSources() {
return mySource.getFiles();
}
public Set<StringCache.S> getTests() {
return myTest.getFiles();
}
public String getOutputPath() {
return mySource.getOutputPath();
}
public Set<String> getTestSourceRoots() {
return myTest.getRoots();
}
public Set<FileWrapper> getTestSourceFiles() {
return myTest.getSources();
}
public String getTestOutputPath() {
return myTest.getOutputPath();
}
public List<ClasspathItemWrapper> dependsOn(final boolean tests) {
if (tests) {
if (myTestDependsOn != null) {
return myTestDependsOn;
}
} else if (myDependsOn != null) {
return myDependsOn;
}
final List<ClasspathItemWrapper> result = new ArrayList<ClasspathItemWrapper>();
for (ClasspathItem cpi : myModule.getClasspath(ClasspathKind.compile(tests))) {
if (cpi instanceof Module) {
result.add(getModule(((Module) cpi).getName()));
} else if (cpi instanceof Library) {
result.add(new LibraryWrapper((Library) cpi));
} else {
result.add(new GenericClasspathItemWrapper(cpi));
}
}
if (tests) {
myTestDependsOn = result;
} else {
myDependsOn = result;
}
return result;
}
public List<String> getClassPath(final ClasspathKind kind) {
final List<String> result = new ArrayList<String>();
result.add(getOutputPath());
if (kind.isTestsIncluded()) {
result.add(getTestOutputPath());
}
return result;
}
private boolean safeEquals(final String a, final String b) {
if (a == null || b == null)
return a == b;
return a.equals(b);
}
private boolean safeEquals(final ClasspathItemWrapper a, final ClasspathItemWrapper b) {
try {
final StringWriter as = new StringWriter();
final StringWriter bs = new StringWriter();
final BufferedWriter bas = new BufferedWriter(as);
final BufferedWriter bbs = new BufferedWriter(bs);
weaken(a).write(bas);
weaken(b).write(bbs);
bas.flush();
bbs.flush();
as.close();
bs.close();
final String x = as.getBuffer().toString();
final String y = bs.getBuffer().toString();
return x.equals(y);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public boolean isOutdated(final boolean tests, final ProjectWrapper history) {
if (history == null) {
return true;
}
final ModuleWrapper past = history.getModule(myName);
if (past == null) {
return true;
}
final boolean outputChanged = !safeEquals(past.getOutputPath(), getOutputPath());
final boolean testOutputChanged = tests && !safeEquals(past.getTestOutputPath(), getTestOutputPath());
final boolean sourceChanged = !past.getSourceFiles().equals(getSourceFiles());
final boolean testSourceChanged = tests && !past.getTestSourceFiles().equals(getTestSourceFiles());
final boolean sourceOutdated = mySource.isOutdated() || !mySource.getOutdatedFiles(past.mySource).isEmpty();
final boolean testSourceOutdated = tests && (myTest.isOutdated() || !myTest.getOutdatedFiles(past.myTest).isEmpty());
final boolean unsafeDependencyChange = (
new Object() {
public boolean run(final List<ClasspathItemWrapper> today, final List<ClasspathItemWrapper> yesterday) {
final Iterator<ClasspathItemWrapper> t = today.iterator();
final Iterator<ClasspathItemWrapper> y = yesterday.iterator();
while (true) {
if (!y.hasNext())
return false;
if (!t.hasNext())
return true;
if (!safeEquals(t.next(), y.next()))
return true;
}
}
}.run(dependsOn(tests), past.dependsOn(tests))
);
return sourceOutdated ||
testSourceOutdated ||
sourceChanged ||
testSourceChanged ||
outputChanged ||
testOutputChanged ||
unsafeDependencyChange;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ModuleWrapper that = (ModuleWrapper) o;
if (myName != null ? !myName.equals(that.myName) : that.myName != null) return false;
return true;
}
@Override
public int hashCode() {
return myName != null ? myName.hashCode() : 0;
}
}
final Map<String, ModuleWrapper> myModules = new HashMap<String, ModuleWrapper>();
final Map<String, LibraryWrapper> myLibraries = new HashMap<String, LibraryWrapper>();
public ModuleWrapper getModule(final String name) {
return myModules.get(name);
}
public LibraryWrapper getLibrary(final String name) {
return myLibraries.get(name);
}
public Collection<LibraryWrapper> getLibraries() {
return myLibraries.values();
}
public Collection<ModuleWrapper> getModules() {
return myModules.values();
}
final Mappings dependencyMapping;
final Callbacks.Backend backendCallback;
final Set<StringCache.S> affectedFiles;
final ProjectWrapper myHistory;
private static String getCanonicalPath(final String path) {
try {
return new File(path).getCanonicalPath();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private ProjectWrapper(final GantBinding binding, final String prjDir, final String setupScript, final Map<String, String> pathVariables, final boolean loadHistory) {
dependencyMapping = new Mappings(this);
backendCallback = dependencyMapping.getCallback();
affectedFiles = new HashSet<StringCache.S>();
myProject = new GantBasedProject(binding == null ? new GantBinding() : binding);
myProjectBuilder = myProject.getBuilder();
final File prjFile = new File(prjDir);
final boolean dirBased = !(prjFile.isFile() && prjDir.endsWith(".ipr"));
myRoot = dirBased ? getCanonicalPath(prjDir) : getCanonicalPath(prjFile.getParent());
final String loadPath = dirBased ? getAbsolutePath(myIDEADir) : prjDir;
myProjectSnapshot = myHomeDir + File.separator + myJPSDir + File.separator + myRoot.replace(File.separatorChar, myFileSeparatorReplacement);
IdeaProjectLoader.loadFromPath(myProject, loadPath, pathVariables != null ? pathVariables : Collections.<String, String>emptyMap(), setupScript);
for (Module m : myProject.getModules().values()) {
myModules.put(m.getName(), new ModuleWrapper(m));
}
for (Library l : myProject.getLibraries().values()) {
myLibraries.put(l.getName(), new LibraryWrapper(l));
}
myHistory = loadHistory ? loadSnapshot(dependencyMapping, affectedFiles) : null;
}
private ProjectWrapper(final BufferedReader r, final Mappings mappings, final Set<StringCache.S> affected) {
dependencyMapping = mappings;
affectedFiles = affected;
backendCallback = dependencyMapping.getCallback();
myProject = null;
myProjectBuilder = null;
myHistory = null;
myRoot = RW.readStringAttribute(r, "Root:");
myProjectSnapshot = myHomeDir + File.separator + myJPSDir + File.separator + myRoot.replace(File.separatorChar, myFileSeparatorReplacement);
RW.readTag(r, "Libraries:");
final Set<LibraryWrapper> libs = (Set<LibraryWrapper>) RW.readMany(r, myLibraryWrapperReader, new HashSet<LibraryWrapper>());
for (LibraryWrapper l : libs) {
myLibraries.put(l.getName(), l);
}
RW.readTag(r, "Modules:");
final Set<ModuleWrapper> mods = (Set<ModuleWrapper>) RW.readMany(r, myModuleWrapperReader, new HashSet<ModuleWrapper>());
for (ModuleWrapper m : mods) {
myModules.put(m.getName(), m);
}
RW.readMany(r, StringCache.reader, affectedFiles);
}
public String getAbsolutePath(final String relative) {
if (relative == null)
return relative;
if (new File(relative).isAbsolute())
return relative;
return myRoot + File.separator + relative;
}
public String getRelativePath(final String absolute) {
if (absolute == null)
return absolute;
if (absolute.startsWith(myRoot)) {
return absolute.substring(myRoot.length() + 1);
}
return absolute;
}
public Collection<String> getAbsolutePaths(final Collection<String> paths, final Collection<String> result) {
for (String path : paths) {
if (path != null)
result.add(getAbsolutePath(path));
}
return result;
}
public Collection<String> getRelativePaths(final Collection<String> paths, final Collection<String> result) {
for (String path : paths) {
if (path != null)
result.add(getRelativePath(path));
}
return result;
}
private boolean isHistory() {
return myProject == null;
}
public void write(final BufferedWriter w) {
RW.writeln(w, "Root:" + myRoot);
RW.writeln(w, "Libraries:");
RW.writeln(w, getLibraries());
RW.writeln(w, "Modules:");
RW.writeln(w, getModules());
RW.writeln(w, affectedFiles, StringCache.fromS);
}
private String getProjectSnapshotFileName() {
return myProjectSnapshot;
}
private ProjectWrapper loadSnapshot(final Mappings mappings, final Set<StringCache.S> affectedFiles) {
initJPSDirectory();
try {
final BufferedReader r = new BufferedReader(new FileReader(getProjectSnapshotFileName()));
final ProjectWrapper w = new ProjectWrapper(r, mappings, affectedFiles);
r.close();
return w;
} catch (FileNotFoundException e) {
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private void saveSnapshot() {
initJPSDirectory();
try {
BufferedWriter bw = new BufferedWriter(new FileWriter(getProjectSnapshotFileName()));
write(bw);
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static ProjectWrapper load(final String path, final String setupScript, final boolean loadHistory) {
return new ProjectWrapper(null, path, setupScript, null, loadHistory);
}
public static ProjectWrapper load(final GantBinding binding, final String path, final String setupScript, final Map<String, String> pathVariables, final boolean loadHistory) {
return new ProjectWrapper(binding, path, setupScript, pathVariables, loadHistory);
}
public void report(final String module) {
final ModuleWrapper m = getModule(module);
if (m == null) {
System.out.println("No module \"" + module + "\" found in project \"");
} else {
System.out.println("Module " + m.myName + " " + (m.isOutdated(false, myHistory) ? "is outdated" : "is up-to-date"));
System.out.println("Module " + m.myName + " tests " + (m.isOutdated(true, myHistory) ? "are outdated" : "are up-to-date"));
}
}
public void report() {
boolean moduleReport = true;
System.out.println("Project \"" + myRoot + "\" report:");
if (myHistory == null) {
System.out.println(" no project history found");
}
if (moduleReport) {
for (ModuleWrapper m : myModules.values()) {
System.out.println(" module " + m.getName() + " " + (m.isOutdated(false, myHistory) ? "is outdated" : "is up-to-date"));
System.out.println(" module " + m.getName() + " tests " + (m.isOutdated(true, myHistory) ? "are outdated" : "are up-to-date"));
}
}
}
public void save() {
saveSnapshot();
}
public void clean() {
myProjectBuilder.clean();
for (ModuleWrapper m : myModules.values()) {
m.updateOutputStatus();
}
new File(myProjectSnapshot).delete();
}
public void rebuild() {
makeModules(myProject.getModules().values(), defaultFlags);
}
public Project getProject() {
return myProject;
}
enum BuildStatus {FAILURE, INCREMENTAL, CONSERVATIVE}
class BusyBeaver {
final ProjectBuilder builder;
final Set<StringCache.S> compiledFiles = new HashSet<StringCache.S>();
final Set<Module> cleared = new HashSet<Module>();
BusyBeaver(ProjectBuilder builder) {
this.builder = builder;
}
BuildStatus iterativeCompile(final ModuleChunk chunk, final Set<StringCache.S> sources, final Set<StringCache.S> outdated, final Set<StringCache.S> removed, final Flags flags) {
final Collection<StringCache.S> filesToCompile = DefaultGroovyMethods.intersect(affectedFiles, sources);
final Set<StringCache.S> safeFiles = new HashSet<StringCache.S>();
if (outdated != null) {
for (StringCache.S s : outdated) {
assert (s != null);
}
filesToCompile.addAll(outdated);
for (StringCache.S f : outdated) {
if (f.value.endsWith(".form")) {
final StringCache.S sourceFileName = dependencyMapping.getJavaByForm(f);
if (sourceFileName != null && !filesToCompile.contains(sourceFileName)) {
safeFiles.add(sourceFileName);
filesToCompile.add(sourceFileName);
}
} else if (f.value.endsWith(".java")) {
final StringCache.S formFileName = dependencyMapping.getFormByJava(f);
if (formFileName != null) {
filesToCompile.add(formFileName);
}
}
}
}
filesToCompile.removeAll(compiledFiles);
if (!filesToCompile.isEmpty() || removed != null) {
final Set<StringCache.S> outputFiles = new HashSet<StringCache.S>();
for (StringCache.S f : filesToCompile) {
final Set<ClassRepr> classes = dependencyMapping.getClasses(f);
if (classes != null)
for (ClassRepr cr : classes) {
outputFiles.add(cr.fileName);
}
}
if (removed != null) {
for (StringCache.S f : removed) {
final Set<ClassRepr> classes = dependencyMapping.getClasses(f);
if (classes != null) {
for (ClassRepr cr : classes) {
outputFiles.add(cr.fileName);
}
}
}
}
if (!outputFiles.isEmpty()) {
new Logger(flags) {
@Override
public void log(PrintStream stream) {
stream.println("Cleaning output files:");
logFilePaths(stream, outputFiles);
stream.println("End of files");
}
}.log();
builder.clearChunk(chunk, outputFiles, ProjectWrapper.this);
}
final Mappings delta = new Mappings(ProjectWrapper.this);
final Callbacks.Backend deltaBackend = delta.getCallback();
new Logger(flags) {
@Override
public void log(PrintStream stream) {
stream.println("Compiling files:");
logFilePaths(stream, filesToCompile);
stream.println("End of files");
}
}.log();
boolean buildException = false;
try {
builder.buildChunk(chunk, flags.tests(), filesToCompile, deltaBackend, ProjectWrapper.this);
} catch (Exception e) {
e.printStackTrace();
buildException = true;
}
if (!buildException) {
compiledFiles.addAll(filesToCompile);
affectedFiles.removeAll(filesToCompile);
delta.compensateRemovedContent(filesToCompile);
final boolean incremental = dependencyMapping.differentiate(delta, removed, compiledFiles, affectedFiles, safeFiles);
dependencyMapping.integrate(delta, removed);
if (!incremental) {
affectedFiles.addAll(sources);
affectedFiles.removeAll(compiledFiles);
final BuildStatus result = iterativeCompile(chunk, sources, null, null, flags);
if (result == BuildStatus.FAILURE) {
return result;
}
return BuildStatus.CONSERVATIVE;
}
return iterativeCompile(chunk, sources, null, null, flags);
} else {
return BuildStatus.FAILURE;
}
} else {
for (Module m : chunk.getElements()) {
Reporter.reportBuildSuccess(m, flags.tests());
}
}
return BuildStatus.INCREMENTAL;
}
public BuildStatus build(final Collection<Module> modules, final Flags flags) {
boolean incremental = flags.incremental();
final List<ModuleChunk> chunks = myProjectBuilder.getChunks(flags.tests()).getChunkList();
for (final ModuleChunk c : chunks) {
final Set<Module> chunkModules = c.getElements();
if (!DefaultGroovyMethods.intersect(modules, chunkModules).isEmpty()) {
final Set<StringCache.S> removedSources = new HashSet<StringCache.S>();
if (incremental) {
final Set<StringCache.S> chunkSources = new HashSet<StringCache.S>();
final Set<StringCache.S> outdatedSources = new HashSet<StringCache.S>();
for (Module m : chunkModules) {
final ModuleWrapper mw = getModule(m.getName());
outdatedSources.addAll(mw.getOutdatedFiles(flags.tests()));
chunkSources.addAll(mw.getSources(flags.tests()));
removedSources.addAll(mw.getRemovedFiles(flags.tests()));
}
final BuildStatus result = iterativeCompile(c, chunkSources, outdatedSources, removedSources, flags);
incremental = result == BuildStatus.INCREMENTAL;
if (result == BuildStatus.FAILURE) {
return result;
}
} else {
new Logger(flags) {
@Override
public void log(PrintStream stream) {
stream.println("Compiling chunk " + c.getName() + " non-incrementally.");
}
}.log();
for (Module m : chunkModules) {
final ModuleWrapper mw = getModule(m.getName());
removedSources.addAll(flags.tests() ? mw.getRemovedTests() : mw.getRemovedSources());
}
final Set<Module> toClean = new HashSet<Module>();
for (Module m : chunkModules) {
if (!cleared.contains(m)) {
toClean.add(m);
}
}
if (!toClean.isEmpty() && !flags.tests()) {
builder.clearChunk(new ModuleChunk(toClean), null, ProjectWrapper.this);
cleared.addAll(toClean);
}
final Mappings delta = new Mappings(ProjectWrapper.this);
final Callbacks.Backend deltaCallback = delta.getCallback();
try {
builder.buildChunk(c, flags.tests(), null, deltaCallback, ProjectWrapper.this);
} catch (Exception e) {
e.printStackTrace();
return BuildStatus.FAILURE;
}
for (Module m : c.getElements()) {
final ModuleWrapper module = getModule(m.getName());
affectedFiles.removeAll(module.getSources(flags.tests()));
}
dependencyMapping.integrate(delta, removedSources);
for (Module m : chunkModules) {
Reporter.reportBuildSuccess(m, flags.tests());
}
}
}
}
return BuildStatus.INCREMENTAL;
}
}
public void makeModules(final Collection<Module> initial, final Flags flags) {
if (myHistory == null && !flags.tests()) {
clean();
}
new Logger(flags) {
@Override
public void log(final PrintStream stream) {
stream.println("Request to make modules:");
logMany(stream, initial);
stream.println("End of request");
}
}.log();
final ClasspathKind kind = ClasspathKind.compile(flags.tests());
final Set<Module> modules = new HashSet<Module>();
final Set<String> marked = new HashSet<String>();
final Map<String, Boolean> visited = new HashMap<String, Boolean>();
final Set<String> frontier = new HashSet<String>();
final Map<String, Set<String>> reversedDependencies = new HashMap<String, Set<String>>();
final DotPrinter printer = new DotPrinter(flags.logStream());
printer.header();
for (Module m : myProject.getModules().values()) {
final String mName = m.getName();
printer.node(mName);
for (ClasspathItem cpi : m.getClasspath(kind)) {
if (cpi instanceof Module) {
final String name = ((Module) cpi).getName();
printer.edge(name, mName);
Set<String> sm = reversedDependencies.get(name);
if (sm == null) {
sm = new HashSet<String>();
reversedDependencies.put(name, sm);
}
sm.add(mName);
}
}
}
printer.footer();
// Building "upper" subgraph
printer.header();
new Object() {
public void run(final Collection<Module> initial) {
if (initial == null)
return;
for (Module module : initial) {
final String mName = module.getName();
if (marked.contains(mName))
continue;
printer.node(mName);
final List<Module> dep = new ArrayList<Module>();
for (ClasspathItem cpi : module.getClasspath(kind)) {
if (cpi instanceof Module && !marked.contains(((Module) cpi).getName())) {
printer.edge(((Module) cpi).getName(), mName);
dep.add((Module) cpi);
}
}
if (dep.size() == 0) {
frontier.add(mName);
}
marked.add(mName);
run(dep);
}
}
}.run(initial);
printer.footer();
// Traversing "upper" subgraph and collecting outdated modules and their descendants
new Object() {
public void run(final Collection<String> initial, final boolean force) {
if (initial == null)
return;
for (String moduleName : initial) {
if (!marked.contains(moduleName))
continue;
final Boolean property = visited.get(moduleName);
if (property == null || !property && force) {
final boolean outdated = getModule(moduleName).isOutdated(flags.tests(), myHistory);
if (force || outdated) {
visited.put(moduleName, true);
modules.add(myProject.getModules().get(moduleName));
run(reversedDependencies.get(moduleName), true);
} else {
if (property == null) {
visited.put(moduleName, false);
}
run(reversedDependencies.get(moduleName), false);
}
}
}
}
}.run(frontier, flags.force());
new Logger(flags) {
@Override
public void log(PrintStream stream) {
stream.println("Propagated modules:");
logMany(stream, modules);
stream.println("End of propagated");
}
}.log();
if (modules.size() == 0 && !flags.force()) {
System.out.println("All requested modules are up-to-date.");
return;
}
final BusyBeaver beaver = new BusyBeaver(myProjectBuilder);
myProjectBuilder.buildStart();
if (flags.tests()) {
beaver.build(modules, new Flags() {
public boolean tests() {
return false;
}
public boolean incremental() {
return flags.incremental();
}
public boolean force() {
return flags.force();
}
public PrintStream logStream() {
return flags.logStream();
}
});
}
beaver.build(modules, flags);
myProjectBuilder.buildStop();
for (Module mod : modules) {
getModule(mod.getName()).updateOutputStatus();
}
}
public void makeModule(final String modName, final Flags flags) {
if (modName == null) {
makeModules(myProject.getModules().values(), flags);
} else {
final Module module = myProject.getModules().get(modName);
final List<Module> list = new ArrayList<Module>();
if (module == null) {
System.err.println("Module \"" + modName + "\" not found in project \"" + myRoot + "\"");
return;
}
list.add(module);
makeModules(list, flags);
}
}
}