diff --git a/jps/.idea/misc.xml b/jps/.idea/misc.xml
index 513d1dec1c76..601befb5a18b 100644
--- a/jps/.idea/misc.xml
+++ b/jps/.idea/misc.xml
@@ -44,7 +44,7 @@
http://www.w3.org/1999/xhtml
-
+
diff --git a/jps/antLayout/antlayout.iml b/jps/antLayout/antlayout.iml
index 11a70982363f..8c643bed5d30 100644
--- a/jps/antLayout/antlayout.iml
+++ b/jps/antLayout/antlayout.iml
@@ -5,7 +5,7 @@
-
+
diff --git a/jps/jps.iml b/jps/jps.iml
index dd8ba7335452..fbf2f3f2a92e 100644
--- a/jps/jps.iml
+++ b/jps/jps.iml
@@ -8,7 +8,7 @@
-
+
diff --git a/jps/plugins/appLauncher/appLauncher.iml b/jps/plugins/appLauncher/appLauncher.iml
index 9e8e44cf1165..9d7005eff21a 100644
--- a/jps/plugins/appLauncher/appLauncher.iml
+++ b/jps/plugins/appLauncher/appLauncher.iml
@@ -6,7 +6,7 @@
-
+
diff --git a/jps/plugins/gwt/gwt.iml b/jps/plugins/gwt/gwt.iml
index 54e5a9f02098..9f566f660385 100644
--- a/jps/plugins/gwt/gwt.iml
+++ b/jps/plugins/gwt/gwt.iml
@@ -6,7 +6,7 @@
-
+
diff --git a/jps/plugins/javaee/javaee.iml b/jps/plugins/javaee/javaee.iml
index b21e687f59bb..f8fe61eb4d9e 100644
--- a/jps/plugins/javaee/javaee.iml
+++ b/jps/plugins/javaee/javaee.iml
@@ -5,7 +5,7 @@
-
+
diff --git a/jps/plugins/jpa/jpa.iml b/jps/plugins/jpa/jpa.iml
index b21e687f59bb..f8fe61eb4d9e 100644
--- a/jps/plugins/jpa/jpa.iml
+++ b/jps/plugins/jpa/jpa.iml
@@ -5,7 +5,7 @@
-
+
diff --git a/jps/plugins/scala/scala.iml b/jps/plugins/scala/scala.iml
index 88c44d22a349..9d8bc05610ac 100644
--- a/jps/plugins/scala/scala.iml
+++ b/jps/plugins/scala/scala.iml
@@ -6,7 +6,7 @@
-
+
diff --git a/jps/serverFacade/serverFacade.iml b/jps/serverFacade/serverFacade.iml
index 1dd3d1f07fb5..8a19ff686956 100644
--- a/jps/serverFacade/serverFacade.iml
+++ b/jps/serverFacade/serverFacade.iml
@@ -5,7 +5,7 @@
-
+
diff --git a/jps/src/org/jetbrains/jps/incremental/BuildTask.java b/jps/src/org/jetbrains/jps/incremental/BuildTask.java
new file mode 100644
index 000000000000..bcabe09e6c96
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/BuildTask.java
@@ -0,0 +1,9 @@
+package org.jetbrains.jps.incremental;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/17/11
+ */
+public abstract class BuildTask {
+ public abstract void build(CompileContext context) throws ProjectBuildException;
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/Builder.java b/jps/src/org/jetbrains/jps/incremental/Builder.java
new file mode 100644
index 000000000000..e6ce5460df49
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/Builder.java
@@ -0,0 +1,17 @@
+package org.jetbrains.jps.incremental;
+
+import org.jetbrains.jps.ModuleChunk;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/17/11
+ */
+public abstract class Builder {
+ public static enum ExitCode {
+ FINISHED, ABORT, ADDITIONAL_PASS_REQUIRED
+ }
+
+ public abstract ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException;
+
+ public abstract String getDescription();
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/BuilderCategory.java b/jps/src/org/jetbrains/jps/incremental/BuilderCategory.java
new file mode 100644
index 000000000000..f1969aa95312
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/BuilderCategory.java
@@ -0,0 +1,16 @@
+package org.jetbrains.jps.incremental;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/17/11
+ */
+public enum BuilderCategory {
+ SOURCE_GENERATOR,
+ SOURCE_INSTRUMENTER,
+ SOURCE_PROCESSOR,
+ TRANSLATOR,
+ CLASS_INSTRUMENTER,
+ CLASS_POST_PROCESSOR,
+ PACKAGER,
+ VALIDATOR
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/BuilderRegistry.java b/jps/src/org/jetbrains/jps/incremental/BuilderRegistry.java
new file mode 100644
index 000000000000..9ba7a50133d8
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/BuilderRegistry.java
@@ -0,0 +1,24 @@
+package org.jetbrains.jps.incremental;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/17/11
+ */
+public class BuilderRegistry {
+
+ public List getBeforeTasks(){
+ return Collections.emptyList(); // todo
+ }
+
+ public List getAfterTasks(){
+ return Collections.emptyList(); // todo
+ }
+
+ public List getBuilders(BuilderCategory category){
+ return Collections.emptyList(); // todo
+ }
+
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/CompileContext.java b/jps/src/org/jetbrains/jps/incremental/CompileContext.java
new file mode 100644
index 000000000000..ae73b758754c
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/CompileContext.java
@@ -0,0 +1,82 @@
+package org.jetbrains.jps.incremental;
+
+import org.jetbrains.jps.Module;
+import org.jetbrains.jps.ModuleChunk;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/17/11
+ */
+public class CompileContext {
+ private final CompileScope myScope;
+
+ public CompileContext(CompileScope scope) {
+ myScope = scope;
+ }
+
+
+ public CompileScope getScope() {
+ return myScope;
+ }
+
+ public boolean isDirty(File file) {
+ return true; // todo
+ }
+
+ public void processFiles(ModuleChunk chunk, FileProcessor processor) {
+ for (Module module : chunk.getModules()) {
+ if (!processModule(module, processor)) {
+ return;
+ }
+ }
+ }
+
+ private boolean processModule(Module module, FileProcessor processor) {
+ final Set excludes = new HashSet();
+ for (String excludePath : (Collection)module.getExcludes()) {
+ excludes.add(new File(excludePath));
+ }
+
+ for (String root : (Collection)module.getSourceRoots()) {
+ if (!processRootRecursively(module, new File(root), processor, excludes)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean processRootRecursively(final Module module, final File file, FileProcessor processor, final Set excluded) {
+ if (file.isDirectory()) {
+ if (isExcluded(excluded, file)) {
+ return true;
+ }
+ final File[] children = file.listFiles();
+ if (children != null) {
+ for (File child : children) {
+ final boolean shouldContinue = processRootRecursively(module, child, processor, excluded);
+ if (!shouldContinue) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return processor.apply(module, file);
+ }
+
+ private static boolean isExcluded(final Set excludedRoots, File file) {
+ while (file != null) {
+ if (excludedRoots.contains(file)) {
+ return true;
+ }
+ file = file.getParentFile();
+ }
+ return false;
+ }
+
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/CompileScope.java b/jps/src/org/jetbrains/jps/incremental/CompileScope.java
new file mode 100644
index 000000000000..50b6df601877
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/CompileScope.java
@@ -0,0 +1,26 @@
+package org.jetbrains.jps.incremental;
+
+import org.jetbrains.jps.Module;
+import org.jetbrains.jps.ModuleChunk;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/17/11
+ */
+public abstract class CompileScope {
+
+ public abstract Collection getAffectedModules();
+
+ public boolean isAffected(ModuleChunk chunk) {
+ final Set modules = chunk.getModules();
+ for (Module module : getAffectedModules()) {
+ if (modules.contains(module)) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/FileProcessor.java b/jps/src/org/jetbrains/jps/incremental/FileProcessor.java
new file mode 100644
index 000000000000..0284799d36a3
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/FileProcessor.java
@@ -0,0 +1,16 @@
+package org.jetbrains.jps.incremental;
+
+import org.jetbrains.jps.Module;
+
+import java.io.File;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 9/21/11
+*/
+public interface FileProcessor {
+ /**
+ * @return true if processing should continue, false if should stop
+ */
+ boolean apply(Module module, File file);
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/FilesCollector.java b/jps/src/org/jetbrains/jps/incremental/FilesCollector.java
new file mode 100644
index 000000000000..8c97d55446af
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/FilesCollector.java
@@ -0,0 +1,46 @@
+package org.jetbrains.jps.incremental;
+
+import org.jetbrains.jps.Module;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/21/11
+ */
+public class FilesCollector implements FileProcessor{
+ private static FileFilter ALL_FILES = new FileFilter() {
+ public boolean accept(File pathname) {
+ return true;
+ }
+ };
+ private final Collection myContainer;
+ private final FileFilter myFilter;
+
+ public FilesCollector() {
+ this(ALL_FILES);
+ }
+
+ public FilesCollector(FileFilter filter) {
+ this(new ArrayList(), filter);
+ }
+
+ public FilesCollector(Collection container, FileFilter filter) {
+ myFilter = filter;
+ myContainer = container;
+ }
+
+ public Collection getFiles() {
+ return myContainer;
+ }
+
+ public boolean apply(Module module, File file) {
+ if (myFilter.accept(file)) {
+ myContainer.add(file);
+ }
+ return true;
+ }
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/IncProjectBuilder.java b/jps/src/org/jetbrains/jps/incremental/IncProjectBuilder.java
new file mode 100644
index 000000000000..ee6de6351c26
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/IncProjectBuilder.java
@@ -0,0 +1,82 @@
+package org.jetbrains.jps.incremental;
+
+import org.jetbrains.jps.ClasspathKind;
+import org.jetbrains.jps.ModuleChunk;
+import org.jetbrains.jps.Project;
+import org.jetbrains.jps.ProjectChunks;
+
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/17/11
+ */
+public class IncProjectBuilder {
+
+ private final Project myProject;
+ private final BuilderRegistry myBuilderRegistry;
+ private ProjectChunks myProductionChunks;
+ private ProjectChunks myTestChunks;
+
+ public IncProjectBuilder(Project project, BuilderRegistry builderRegistry) {
+ myProject = project;
+ myBuilderRegistry = builderRegistry;
+ myProductionChunks = new ProjectChunks(project, ClasspathKind.PRODUCTION_COMPILE);
+ myTestChunks = new ProjectChunks(project, ClasspathKind.TEST_COMPILE);
+ }
+
+ public void build(CompileScope scope) {
+ final CompileContext context = new CompileContext(scope);
+ try {
+ runTasks(context, myBuilderRegistry.getBeforeTasks());
+
+ buildChunks(context, myProductionChunks);
+
+ buildChunks(context, myTestChunks);
+
+ runTasks(context, myBuilderRegistry.getAfterTasks());
+ }
+ catch (ProjectBuildException e) {
+ e.printStackTrace(); // todo
+ }
+ }
+
+ private void runTasks(CompileContext context, final List tasks) throws ProjectBuildException {
+ for (BuildTask task : tasks) {
+ task.build(context);
+ }
+ }
+
+ private void buildChunks(CompileContext context, ProjectChunks chunks) throws ProjectBuildException {
+ final CompileScope scope = context.getScope();
+ for (ModuleChunk chunk : chunks.getChunkList()) {
+ if (scope.isAffected(chunk)) {
+ buildChunk(context, chunk);
+ }
+ }
+ }
+
+ private void buildChunk(CompileContext context, ModuleChunk chunk) throws ProjectBuildException{
+ for (BuilderCategory category : BuilderCategory.values()) {
+ runBuilders(context, chunk, myBuilderRegistry.getBuilders(category));
+ }
+ }
+
+ private void runBuilders(CompileContext context, ModuleChunk chunk, List builders) throws ProjectBuildException {
+ boolean nextPassRequired;
+ do {
+ nextPassRequired = false;
+ for (Builder builder : builders) {
+ final Builder.ExitCode buildResult = builder.build(context, chunk);
+ if (buildResult == Builder.ExitCode.ABORT) {
+ throw new ProjectBuildException("Builder " + builder.getDescription() + " requested build stop");
+ }
+ if (buildResult == Builder.ExitCode.ADDITIONAL_PASS_REQUIRED) {
+ nextPassRequired = true;
+ }
+ }
+ }
+ while (nextPassRequired);
+ }
+
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/ProjectBuildException.java b/jps/src/org/jetbrains/jps/incremental/ProjectBuildException.java
new file mode 100644
index 000000000000..d526ff018ca5
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/ProjectBuildException.java
@@ -0,0 +1,23 @@
+package org.jetbrains.jps.incremental;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/20/11
+ */
+public class ProjectBuildException extends Exception{
+
+ public ProjectBuildException() {
+ }
+
+ public ProjectBuildException(String message) {
+ super(message);
+ }
+
+ public ProjectBuildException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public ProjectBuildException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/impl/EmbeddedJavac.java b/jps/src/org/jetbrains/jps/incremental/impl/EmbeddedJavac.java
new file mode 100644
index 000000000000..ad9e2fcdc723
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/impl/EmbeddedJavac.java
@@ -0,0 +1,144 @@
+package org.jetbrains.jps.incremental.impl;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.tools.*;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.ExecutorService;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/23/11
+ */
+public class EmbeddedJavac {
+ private final JavaCompiler myCompiler;
+ private final ExecutorService myTaskRunner;
+ private final SequentialTaskExecutor mySequentialTaskExecutor;
+ private final List myClassProcessors = new ArrayList();
+
+ public static interface OutputConsumer extends DiagnosticListener {
+ void outputLineAvailable(String line);
+ }
+
+ public static interface ClassPostProcessor {
+ void process(OutputFileObject out);
+ }
+
+ public EmbeddedJavac(ExecutorService taskRunner) {
+ myTaskRunner = taskRunner;
+ mySequentialTaskExecutor = new SequentialTaskExecutor(taskRunner);
+ myCompiler = ToolProvider.getSystemJavaCompiler();
+ }
+
+ public void addClassProcessor(ClassPostProcessor processor) {
+ myClassProcessors.add(processor);
+ }
+
+ public boolean compile(final List sources, final List options, File outputDir, final OutputConsumer outConsumer) {
+
+ final JavacFileManager fileManager = new JavacFileManager(new FileManagerContext(outConsumer));
+ if (!fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(outputDir))) {
+ return false;
+ }
+ //todo setup file manager to support multiple outputs
+
+ final LineOutputWriter out = new LineOutputWriter() {
+ protected void lineAvailable(String line) {
+ outConsumer.outputLineAvailable(line);
+ }
+ };
+ final List _options = new ArrayList();
+ boolean skip = false;
+ for (String option : options) {
+ if ("-d".equals(option)) {
+ skip = true;
+ continue;
+ }
+ if (!skip) {
+ _options.add(option);
+ }
+ skip = false;
+ }
+ final JavaCompiler.CompilationTask task = myCompiler.getTask(
+ out, fileManager, outConsumer, _options, null, fileManager.toJavaFileObjects(sources)
+ );
+ return task.call();
+ }
+
+
+ private class FileManagerContext implements JavacFileManager.Context {
+
+ private final StandardJavaFileManager myStdManager;
+ private final OutputConsumer myOutConsumer;
+
+ public FileManagerContext(OutputConsumer outConsumer) {
+ myOutConsumer = outConsumer;
+ myStdManager = myCompiler.getStandardFileManager(outConsumer, Locale.US, null);
+ }
+
+ public StandardJavaFileManager getStandardFileManager() {
+ return myStdManager;
+ }
+
+ public void reportMessage(final Diagnostic.Kind kind, String message) {
+ myOutConsumer.report(new PlainMessageDiagnostic(kind, message));
+ }
+
+ public void consumeOutputFile(final OutputFileObject cls) {
+ myTaskRunner.submit(new Runnable() {
+ public void run() {
+ try {
+ for (ClassPostProcessor processor : myClassProcessors) {
+ processor.process(cls);
+ }
+ }
+ finally {
+ mySequentialTaskExecutor.submit(new Runnable() {
+ public void run() {
+ try {
+ final File file = cls.getFile();
+ final OutputFileObject.Content content = cls.getContent();
+ if (content != null) {
+ writeToFile(file, content.getBuffer(), content.getOffset(), content.getLength(), false);
+ }
+ else {
+ myOutConsumer.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, "Missing content for file " + file));
+ }
+ }
+ catch (IOException e) {
+ myOutConsumer.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, e.getMessage()));
+ }
+ }
+ });
+ }
+ }
+ });
+ }
+ }
+
+ private static void writeToFile(@NotNull File file, @NotNull byte[] text, final int off, final int len, boolean append) throws IOException {
+ createParentDirs(file);
+ OutputStream stream = new BufferedOutputStream(new FileOutputStream(file, append));
+ try {
+ stream.write(text, off, len);
+ }
+ finally {
+ stream.close();
+ }
+ }
+
+ public static boolean createParentDirs(@NotNull File file) {
+ if (!file.exists()) {
+ String parentDirPath = file.getParent();
+ if (parentDirPath != null) {
+ final File parentFile = new File(parentDirPath);
+ return parentFile.exists() && parentFile.isDirectory() || parentFile.mkdirs();
+ }
+ }
+ return true;
+ }
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/impl/JavaBuilder.java b/jps/src/org/jetbrains/jps/incremental/impl/JavaBuilder.java
new file mode 100644
index 000000000000..f89b9b88649e
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/impl/JavaBuilder.java
@@ -0,0 +1,92 @@
+package org.jetbrains.jps.incremental.impl;
+
+import org.jetbrains.jps.ModuleChunk;
+import org.jetbrains.jps.incremental.Builder;
+import org.jetbrains.jps.incremental.CompileContext;
+import org.jetbrains.jps.incremental.FilesCollector;
+import org.jetbrains.jps.incremental.ProjectBuildException;
+
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import java.io.File;
+import java.io.FileFilter;
+import java.util.*;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/21/11
+ */
+public class JavaBuilder extends Builder{
+ private static final String JAVA_EXTENSION = ".java";
+
+ private static final FileFilter JAVA_SOURCES_FILTER = new FileFilter() {
+ public boolean accept(File file) {
+ return file.getName().endsWith(JAVA_EXTENSION);
+ }
+ };
+
+ public ExitCode build(CompileContext context, ModuleChunk chunk) throws ProjectBuildException {
+ final List files = new ArrayList();
+ context.processFiles(chunk, new FilesCollector(files, JAVA_SOURCES_FILTER));
+ compile(chunk, files);
+ return ExitCode.FINISHED;
+ }
+
+ private void compile(ModuleChunk chunk, List files) {
+ }
+
+ public String getDescription() {
+ return "Java Builder";
+ }
+
+ public static void main(String[] args) {
+
+ final ExecutorService taskRunner = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
+ final EmbeddedJavac javac = new EmbeddedJavac(taskRunner);
+
+ final File srcRoot = new File("C:/tmp/MoveClassProblem/util/src");
+ final List sources = new ArrayList();
+ collectFiles(srcRoot, sources, new FileFilter() {
+ public boolean accept(File pathname) {
+ return pathname.getName().endsWith(".java");
+ }
+ });
+ final File outputDir = new File("C:/tmp/MoveClassProblem/util/_out_");
+ outputDir.mkdirs();
+
+ final List options = Arrays.asList(new String[] {
+ //"-d",
+ //outputDir.getAbsolutePath()
+ });
+ javac.compile(sources, options, outputDir, new EmbeddedJavac.OutputConsumer() {
+ public void outputLineAvailable(String line) {
+ System.out.println("OUTPUT: " + line);
+ }
+
+ public void report(Diagnostic extends JavaFileObject> diagnostic) {
+ System.out.println("DIAGNOSTIC: " + diagnostic.getKind().name() + "/" + diagnostic.getMessage(Locale.US));
+ }
+ });
+
+ taskRunner.shutdownNow();
+ }
+
+ private static void collectFiles(File root, Collection container, FileFilter filter) {
+ final File[] files = root.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ collectFiles(file, container, filter);
+ }
+ else {
+ if (filter.accept(file)) {
+ container.add(file);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/impl/JavacFileManager.java b/jps/src/org/jetbrains/jps/incremental/impl/JavacFileManager.java
new file mode 100644
index 000000000000..e7498b06f32c
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/impl/JavacFileManager.java
@@ -0,0 +1,152 @@
+package org.jetbrains.jps.incremental.impl;
+
+import org.jetbrains.annotations.NotNull;
+
+import javax.tools.*;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.Iterator;
+import java.util.Set;
+
+import static javax.tools.StandardLocation.CLASS_OUTPUT;
+import static javax.tools.StandardLocation.SOURCE_OUTPUT;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/24/11
+ */
+class JavacFileManager extends ForwardingJavaFileManager {
+
+ private final Context myContext;
+
+ public static interface Context {
+ StandardJavaFileManager getStandardFileManager();
+
+ void consumeOutputFile(OutputFileObject obj);
+
+ void reportMessage(final Diagnostic.Kind kind, String message);
+ }
+
+ public JavacFileManager(Context context) {
+ super(context.getStandardFileManager());
+ myContext = context;
+ }
+
+ // todo: check if reading source files can be optimized
+
+ public boolean setLocation(Location location, Iterable extends File> path) {
+ try {
+ getStdManager().setLocation(location, path);
+ }
+ catch (IOException e) {
+ myContext.reportMessage(Diagnostic.Kind.ERROR, e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isSameFile(FileObject a, FileObject b) {
+ if (a instanceof OutputFileObject && b instanceof OutputFileObject) {
+ return a.equals(b);
+ }
+ return super.isSameFile(a, b);
+ }
+
+ public Iterable list(Location location, String packageName, Set kinds, boolean recurse) throws IOException {
+ return super.list(location, packageName, kinds, recurse);
+ }
+
+ public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
+ if (kind != JavaFileObject.Kind.SOURCE && kind != JavaFileObject.Kind.CLASS) {
+ throw new IllegalArgumentException("Invalid kind " + kind);
+ }
+ return getFileForOutput(location, kind, externalizeFileName(className, kind), sibling);
+ }
+
+ public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
+ final StringBuilder name = new StringBuilder();
+ if (packageName.isEmpty()) {
+ name.append(relativeName);
+ }
+ else {
+ name.append(externalizeFileName(packageName)).append(File.separatorChar).append(relativeName);
+ }
+ final String fileName = name.toString();
+ return getFileForOutput(location, getKind(fileName), fileName, sibling);
+ }
+
+ private JavaFileObject getFileForOutput(Location location, JavaFileObject.Kind kind, String fileName, FileObject sibling) throws IOException {
+ File dir = null;
+ if (location == CLASS_OUTPUT) {
+ final File classOutDir = getSingleOutputDirectory(CLASS_OUTPUT);
+ if (classOutDir == null) {
+ throw new IOException("Output directory is not specified");
+ }
+ dir = classOutDir;
+ }
+ else if (location == SOURCE_OUTPUT) {
+ final File sourcesOutput = getSingleOutputDirectory(StandardLocation.SOURCE_OUTPUT);
+ dir = (sourcesOutput != null ? sourcesOutput : getSingleOutputDirectory(CLASS_OUTPUT));
+ }
+ else {
+ Iterable extends File> path = getStdManager().getLocation(location);
+ final Iterator extends File> it = path.iterator();
+ if (it.hasNext()) {
+ dir = it.next();
+ }
+ }
+ final File file = (dir == null ? new File(fileName) : new File(dir, fileName));
+ return new OutputFileObject(myContext, file, kind);
+ }
+
+ private File getSingleOutputDirectory(final Location kind) {
+ final Iterable extends File> location = getStdManager().getLocation(kind);
+ if (location != null) {
+ final Iterator extends File> it = location.iterator();
+ if (it.hasNext()) {
+ return it.next();
+ }
+ }
+ return null;
+ }
+
+ @NotNull
+ public StandardJavaFileManager getStdManager() {
+ return fileManager;
+ }
+
+ public Iterable extends JavaFileObject> toJavaFileObjects(Iterable extends File> files) {
+ return getStdManager().getJavaFileObjectsFromFiles(files);
+ }
+
+ private static URI toURI(String outputDir, String name, JavaFileObject.Kind kind) {
+ return createUri("file:///" + outputDir.replace('\\','/') + "/" + name.replace('.', '/') + kind.extension);
+ }
+
+ private static URI createUri(String url) {
+ return URI.create(url.replaceAll(" ","%20"));
+ }
+
+ private static JavaFileObject.Kind getKind(String name) {
+ if (name.endsWith(JavaFileObject.Kind.CLASS.extension)){
+ return JavaFileObject.Kind.CLASS;
+ }
+ if (name.endsWith(JavaFileObject.Kind.SOURCE.extension)) {
+ return JavaFileObject.Kind.SOURCE;
+ }
+ if (name.endsWith(JavaFileObject.Kind.HTML.extension)) {
+ return JavaFileObject.Kind.HTML;
+ }
+ return JavaFileObject.Kind.OTHER;
+ }
+
+ private static String externalizeFileName(CharSequence cs, JavaFileObject.Kind kind) {
+ return externalizeFileName(cs) + kind.extension;
+ }
+
+ private static String externalizeFileName(CharSequence name) {
+ return name.toString().replace('.', File.separatorChar);
+ }
+
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/impl/LineOutputWriter.java b/jps/src/org/jetbrains/jps/incremental/impl/LineOutputWriter.java
new file mode 100644
index 000000000000..bd7df332e2d1
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/impl/LineOutputWriter.java
@@ -0,0 +1,200 @@
+package org.jetbrains.jps.incremental.impl;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 9/24/11
+*/
+abstract class LineOutputWriter extends Writer {
+ private final LineParser myLineParser = new LineParser();
+
+ public void write(int c) throws IOException {
+ processData(new CharSequenceIterator(c));
+ }
+
+ public void write(char[] cbuf) throws IOException {
+ processData(new CharSequenceIterator(cbuf));
+ }
+
+ public void write(String str) throws IOException {
+ processData(new CharSequenceIterator(str));
+ }
+
+ public void write(String str, int off, int len) throws IOException {
+ processData(new CharSequenceIterator(str.subSequence(off, off + len)));
+ }
+
+ public Writer append(CharSequence csq) throws IOException {
+ processData(new CharSequenceIterator(csq));
+ return this;
+ }
+
+ public Writer append(CharSequence csq, int start, int end) throws IOException {
+ processData(new CharSequenceIterator(csq.subSequence(start, end)));
+ return this;
+ }
+
+ public Writer append(char c) throws IOException {
+ processData(new CharSequenceIterator(c));
+ return this;
+ }
+
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ processData(new CharSequenceIterator(cbuf, off, len));
+ }
+
+ private void processData(CharIterator data) {
+ while (myLineParser.parse(data)) {
+ final String line = myLineParser.getResult();
+ myLineParser.reset();
+ lineAvailable(line);
+ }
+ }
+
+
+ public void flush() throws IOException {
+ }
+
+ public void close() throws IOException {
+ try {
+ if (myLineParser.hasData()) {
+ lineAvailable(myLineParser.getResult());
+ }
+ }
+ finally {
+ myLineParser.reset();
+ }
+ }
+
+ protected abstract void lineAvailable(String line);
+
+ private static interface CharIterator {
+ char nextChar();
+ boolean hasData();
+ }
+
+ private static class LineParser {
+ private final StringBuilder myData = new StringBuilder();
+ private boolean myFoundCR = false;
+
+ public boolean parse(CharIterator it) {
+ while (it.hasData()) {
+ final char ch = it.nextChar();
+ if (ch == '\r') {
+ myFoundCR = true;
+ }
+ else if (ch == '\n') {
+ myFoundCR = false;
+ return true;
+ }
+ else {
+ if (myFoundCR) {
+ myData.append('\r');
+ myFoundCR = false;
+ }
+ myData.append(ch);
+ }
+ }
+ return false;
+ }
+
+ public boolean hasData() {
+ return myData.length() > 0;
+ }
+
+ public String getResult() {
+ return myData.toString();
+ }
+
+ public void reset() {
+ myFoundCR = false;
+ myData.setLength(0);
+ }
+ }
+
+ private static class CharSequenceIterator implements CharIterator {
+ private final CharSequence myChars;
+ private int myCursor = 0;
+
+ CharSequenceIterator(final int ch) {
+ this((char)ch);
+ }
+
+ CharSequenceIterator(final char ch) {
+ this(new SingleCharSequence(ch));
+ }
+
+ CharSequenceIterator(char[] chars) {
+ this(chars, 0, chars.length);
+ }
+
+ CharSequenceIterator(final char[] chars, final int offset, final int length) {
+ this(new ArrayCharSequence(chars, offset, length));
+ }
+
+ CharSequenceIterator(CharSequence sequence) {
+ myChars = sequence;
+ }
+
+ public char nextChar() {
+ return myChars.charAt(myCursor++);
+ }
+
+ public boolean hasData() {
+ return myCursor < myChars.length();
+ }
+ }
+
+ private static class SingleCharSequence implements CharSequence {
+ private final char myCh;
+
+ public SingleCharSequence(char ch) {
+ myCh = ch;
+ }
+
+ public int length() {
+ return 1;
+ }
+
+ public char charAt(int index) {
+ if (index != 0) {
+ throw new IndexOutOfBoundsException("Index out of bounds: " + index);
+ }
+ return myCh;
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ throw new RuntimeException("Method subSequence not implemented");
+ }
+ }
+
+ private static class ArrayCharSequence implements CharSequence {
+ private final char[] myChars;
+ private final int myOffset;
+ private final int myLength;
+
+ public ArrayCharSequence(char[] chars, int offset, int length) {
+ myChars = chars;
+ myOffset = offset;
+ myLength = length;
+ }
+
+ public int length() {
+ return myLength;
+ }
+
+ public char charAt(int index) {
+ return myChars[index];
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ return new ArrayCharSequence(myChars, start, end - start);
+ }
+
+ public String toString() {
+ return new String(myChars, myOffset, myLength);
+ }
+ }
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/impl/OutputFileObject.java b/jps/src/org/jetbrains/jps/incremental/impl/OutputFileObject.java
new file mode 100644
index 000000000000..609e8e554847
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/impl/OutputFileObject.java
@@ -0,0 +1,98 @@
+package org.jetbrains.jps.incremental.impl;
+
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import java.io.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/24/11
+ */
+public class OutputFileObject extends SimpleJavaFileObject {
+
+ private final JavacFileManager.Context myContext;
+ private final File myFile;
+ private volatile Content myContent;
+
+ public OutputFileObject(JavacFileManager.Context context, File file, Kind kind) {
+ super(file.toURI(), kind);
+ myContext = context;
+ myFile = file;
+ }
+
+ public File getFile() {
+ return myFile;
+ }
+
+ @Override
+ public ByteArrayOutputStream openOutputStream() {
+ return new ByteArrayOutputStream() {
+ @Override
+ public void close() throws IOException {
+ try {
+ super.close();
+ }
+ finally {
+ myContent = new Content(buf, 0, size());
+ myContext.consumeOutputFile(OutputFileObject.this);
+ }
+ }
+ };
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ final Content bytes = myContent;
+ if (bytes == null) {
+ throw new FileNotFoundException(toUri().getPath());
+ }
+ return new ByteArrayInputStream(bytes.getBuffer(), bytes.getOffset(), bytes.getLength());
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ final Content content = myContent;
+ if (content == null) {
+ throw null;
+ }
+ return new String(content.getBuffer(), content.getOffset(), content.getLength());
+ }
+
+ public Content getContent() {
+ return myContent;
+ }
+
+ @Override
+ public int hashCode() {
+ return toUri().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof JavaFileObject && toUri().equals(((JavaFileObject)obj).toUri());
+ }
+
+ public static final class Content {
+ private final byte[] myBuffer;
+ private final int myOffset;
+ private final int myLength;
+
+ private Content(byte[] buf, int off, int len) {
+ myBuffer = buf;
+ myOffset = off;
+ myLength = len;
+ }
+
+ public byte[] getBuffer() {
+ return myBuffer;
+ }
+
+ public int getOffset() {
+ return myOffset;
+ }
+
+ public int getLength() {
+ return myLength;
+ }
+ }
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/impl/PlainMessageDiagnostic.java b/jps/src/org/jetbrains/jps/incremental/impl/PlainMessageDiagnostic.java
new file mode 100644
index 000000000000..32037f2c388b
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/impl/PlainMessageDiagnostic.java
@@ -0,0 +1,56 @@
+package org.jetbrains.jps.incremental.impl;
+
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+import java.util.Locale;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/24/11
+ */
+public class PlainMessageDiagnostic implements Diagnostic{
+
+ private final Kind myKind;
+ private final String myMessage;
+
+ public PlainMessageDiagnostic(Kind kind, String message) {
+ myKind = kind;
+ myMessage = message;
+ }
+
+ public Kind getKind() {
+ return myKind;
+ }
+
+ public JavaFileObject getSource() {
+ return null;
+ }
+
+ public long getPosition() {
+ return 0;
+ }
+
+ public long getStartPosition() {
+ return 0;
+ }
+
+ public long getEndPosition() {
+ return 0;
+ }
+
+ public long getLineNumber() {
+ return 0;
+ }
+
+ public long getColumnNumber() {
+ return 0;
+ }
+
+ public String getCode() {
+ return null;
+ }
+
+ public String getMessage(Locale locale) {
+ return myMessage;
+ }
+}
diff --git a/jps/src/org/jetbrains/jps/incremental/impl/SequentialTaskExecutor.java b/jps/src/org/jetbrains/jps/incremental/impl/SequentialTaskExecutor.java
new file mode 100644
index 000000000000..7df863f02af3
--- /dev/null
+++ b/jps/src/org/jetbrains/jps/incremental/impl/SequentialTaskExecutor.java
@@ -0,0 +1,53 @@
+package org.jetbrains.jps.incremental.impl;
+
+import java.util.Queue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/24/11
+ */
+public class SequentialTaskExecutor {
+
+ private final ExecutorService myExecutor;
+ private final Queue myTaskQueue = new LinkedBlockingQueue();
+ private final AtomicBoolean myInProgress = new AtomicBoolean(false);
+ private final Runnable USER_TASK_RUNNER = new Runnable() {
+ public void run() {
+ final Runnable task = myTaskQueue.poll();
+ try {
+ if (task != null) {
+ task.run();
+ }
+ }
+ finally {
+ myInProgress.set(false);
+ if (!myTaskQueue.isEmpty()) {
+ processQueue();
+ }
+ }
+ }
+ };
+
+ public SequentialTaskExecutor(ExecutorService executor) {
+ myExecutor = executor;
+ }
+
+ public void submit(Runnable task) {
+ if (myTaskQueue.offer(task)) {
+ processQueue();
+ }
+ else {
+ throw new RuntimeException("Failed to queue task: " + task);
+ }
+ }
+
+ private void processQueue() {
+ if (!myInProgress.getAndSet(true)) {
+ myExecutor.submit(USER_TASK_RUNNER);
+ }
+ }
+
+}