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 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 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 path = getStdManager().getLocation(location); + final Iterator 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 location = getStdManager().getLocation(kind); + if (location != null) { + final Iterator it = location.iterator(); + if (it.hasNext()) { + return it.next(); + } + } + return null; + } + + @NotNull + public StandardJavaFileManager getStdManager() { + return fileManager; + } + + public Iterable toJavaFileObjects(Iterable 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); + } + } + +}