From 5d3a8e42050064900bd6c08d1785569aa74c5a82 Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Mon, 16 Nov 2009 12:20:07 +0300 Subject: [PATCH] annotation processors: 1. invoke per-module 2. reworked handling of output paths for generated sources (UI coming soon) --- .../compiler/CompilerConfigurationImpl.java | 24 + .../compiler/CompilerManagerImpl.java | 4 +- .../compiler/impl/CompileContextImpl.java | 8 + .../intellij/compiler/impl/CompileDriver.java | 112 +++- .../AnnotationProcessingCompiler.java | 122 ++--- .../AnnotationProcessorsRunner.java | 414 +++++++++++++++ .../impl/javaCompiler/ModuleChunk.java | 2 +- .../javaCompiler/javac/JavacCompiler.java | 3 - .../compiler/make/DependencyCache.java | 4 + .../AnnotationProcessorsConfigurable.java | 14 +- .../options/ProcessedModulesChooser.java | 484 ++++++++++++++++++ .../compiler/CompilerConfiguration.java | 10 + .../openapi/compiler/CompilerPaths.java | 31 ++ .../IdeaProjectSettingsService.java | 11 +- .../ProjectStructureConfigurable.java | 5 + .../configuration/ProjectSettingsService.java | 3 + .../src/messages/CompilerBundle.properties | 3 +- 17 files changed, 1138 insertions(+), 116 deletions(-) create mode 100644 java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessorsRunner.java create mode 100644 java/compiler/impl/src/com/intellij/compiler/options/ProcessedModulesChooser.java diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java index ba85a4e5e0ac..ab4a23c4cd78 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java @@ -80,6 +80,8 @@ public class CompilerConfigurationImpl extends CompilerConfiguration implements private boolean myEnableAnnotationProcessors = false; private final Map myProcessorsMap = new HashMap(); // map: AnnotationProcessorName -> options private boolean myObtainProcessorsFromClasspath = true; + private boolean myStoreGenerateSourcesUnderModuleContent = false; + private String myGeneratedDirName = "generated"; private String myProcessorPath = ""; private final Set myExcludedModules = new HashSet(); private final Set myExcludedModuleNames = new HashSet(); @@ -310,6 +312,24 @@ public class CompilerConfigurationImpl extends CompilerConfiguration implements myObtainProcessorsFromClasspath = obtainProcessorsFromClasspath; } + public boolean isStoreGenerateSourcesUnderModuleContent(Module module) { + // todo: make this per-module setting + return myStoreGenerateSourcesUnderModuleContent; + } + + public void setStoreGenerateSourcesUnderModuleContent(boolean storeGenerateSourcesUnderModuleContent) { + myStoreGenerateSourcesUnderModuleContent = storeGenerateSourcesUnderModuleContent; + } + + @NotNull + public String getGeneratedDirName() { + return myGeneratedDirName; + } + + public void setGeneratedDirName(String generatedDirName) { + myGeneratedDirName = generatedDirName; + } + public String getProcessorPath() { return myProcessorPath; } @@ -522,6 +542,8 @@ public class CompilerConfigurationImpl extends CompilerConfiguration implements if (annotationProcessingSettings != null) { myEnableAnnotationProcessors = Boolean.valueOf(annotationProcessingSettings.getAttributeValue("enabled", "false")); myObtainProcessorsFromClasspath = Boolean.valueOf(annotationProcessingSettings.getAttributeValue("useClasspath", "true")); + myStoreGenerateSourcesUnderModuleContent = Boolean.valueOf(annotationProcessingSettings.getAttributeValue("storeGeneratedUnderContent", "false")); + myGeneratedDirName = annotationProcessingSettings.getAttributeValue("generatedDirName", "generated"); final StringBuilder pathBuilder = new StringBuilder(); for (Element pathElement : ((Collection)annotationProcessingSettings.getChildren("processorPath"))) { @@ -596,6 +618,8 @@ public class CompilerConfigurationImpl extends CompilerConfiguration implements parentNode.addContent(annotationProcessingSettings); annotationProcessingSettings.setAttribute("enabled", String.valueOf(myEnableAnnotationProcessors)); annotationProcessingSettings.setAttribute("useClasspath", String.valueOf(myObtainProcessorsFromClasspath)); + annotationProcessingSettings.setAttribute("storeGeneratedUnderContent", String.valueOf(myStoreGenerateSourcesUnderModuleContent)); + annotationProcessingSettings.setAttribute("generatedDirName", myGeneratedDirName); if (myProcessorPath.length() > 0) { final StringTokenizer tokenizer = new StringTokenizer(myProcessorPath, File.pathSeparator, false); while (tokenizer.hasMoreTokens()) { diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java index aaaebc2a2a2a..d7aa30c8c28f 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java @@ -64,8 +64,8 @@ public class CompilerManagerImpl extends CompilerManager { myEventPublisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS); // predefined compilers - addTranslatingCompiler(new JavaCompiler(myProject), new HashSet(Arrays.asList(StdFileTypes.JAVA)), new HashSet(Arrays.asList(StdFileTypes.CLASS))); - addCompiler(new AnnotationProcessingCompiler(project)); + addTranslatingCompiler(new AnnotationProcessingCompiler(project), new HashSet(Arrays.asList(StdFileTypes.JAVA)), new HashSet(Arrays.asList(StdFileTypes.JAVA, StdFileTypes.CLASS))); + addTranslatingCompiler(new JavaCompiler(project), new HashSet(Arrays.asList(StdFileTypes.JAVA)), new HashSet(Arrays.asList(StdFileTypes.CLASS))); addCompiler(new ResourceCompiler(project, compilerConfiguration)); addCompiler(new RmicCompiler()); addCompiler(new IncrementalArtifactsCompiler()); diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java index c4b7ea5ebe66..cc353207e1a7 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java @@ -51,6 +51,7 @@ import gnu.trove.TObjectHashingStrategy; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.File; import java.io.IOException; import java.util.*; @@ -138,6 +139,13 @@ public class CompileContextImpl extends UserDataHolderBase implements CompileCon return true; } } + final Module module = getModuleByFile(file); + if (module != null) { + final String procGenRoot = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (VfsUtil.isAncestor(new File(procGenRoot), new File(file.getPath()), true)) { + return true; + } + } return false; } diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index 5a062c1a913a..9e054c8bc751 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -53,10 +53,7 @@ import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.project.ProjectBundle; import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.ContentEntry; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.roots.SourceFolder; +import com.intellij.openapi.roots.*; import com.intellij.openapi.roots.ex.ProjectRootManagerEx; import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor; import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; @@ -145,16 +142,24 @@ public class CompileDriver { myGenerationCompilerModuleToOutputDirMap = new HashMap, Pair>(); + final LocalFileSystem lfs = LocalFileSystem.getInstance(); final IntermediateOutputCompiler[] generatingCompilers = CompilerManager.getInstance(myProject).getCompilers(IntermediateOutputCompiler.class, myCompilerFilter); - if (generatingCompilers.length > 0) { - final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); + final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); + final CompilerConfiguration config = CompilerConfiguration.getInstance(project); + for (Module module : allModules) { for (IntermediateOutputCompiler compiler : generatingCompilers) { - for (final Module module : allModules) { - final VirtualFile productionOutput = lookupVFile(compiler, module, false); - final VirtualFile testOutput = lookupVFile(compiler, module, true); - final Pair pair = new Pair(compiler, module); - final Pair outputs = new Pair(productionOutput, testOutput); - myGenerationCompilerModuleToOutputDirMap.put(pair, outputs); + final VirtualFile productionOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, false)); + final VirtualFile testOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, true)); + final Pair pair = new Pair(compiler, module); + final Pair outputs = new Pair(productionOutput, testOutput); + myGenerationCompilerModuleToOutputDirMap.put(pair, outputs); + } + if (config.isAnnotationProcessorsEnabled()) { + if (!config.getExcludedModules().contains(module)) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path != null) { + lookupVFile(lfs, path); // ensure the file is created and added to VFS + } } } } @@ -334,6 +339,27 @@ public class CompileDriver { return scope; } + private void attachAnnotationProcessorsOutputDirectories(CompileContextEx context) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final Set affected = new HashSet(Arrays.asList(context.getCompileScope().getAffectedModules())); + for (Module module : affected) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path == null) { + continue; + } + final VirtualFile vFile = lfs.findFileByPath(path); + if (vFile == null) { + continue; + } + if (ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(vFile)) { + // no need to add, is already marked as source + continue; + } + context.addScope(new FileSetCompileScope(Collections.singletonList(vFile), new Module[]{module})); + context.assignModule(vFile, module, false); + } + } + public static final Key COMPILATION_START_TIMESTAMP = Key.create("COMPILATION_START_TIMESTAMP"); private void startup(final CompileScope scope, @@ -365,7 +391,8 @@ public class CompileDriver { compileContext.assignModule(outputs.getFirst(), module, false); compileContext.assignModule(outputs.getSecond(), module, true); } - + attachAnnotationProcessorsOutputDirectories(compileContext); + compileTask.start(new Runnable() { public void run() { long start = System.currentTimeMillis(); @@ -1147,9 +1174,21 @@ public class CompileDriver { private Set getAllOutputDirectories() { final Set outputDirs = new OrderedSet((TObjectHashingStrategy)TObjectHashingStrategy.CANONICAL); - for (final String path : CompilerPathsEx.getOutputPaths(ModuleManager.getInstance(myProject).getModules())) { + final Module[] modules = ModuleManager.getInstance(myProject).getModules(); + for (final String path : CompilerPathsEx.getOutputPaths(modules)) { outputDirs.add(new File(path)); } + final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); + if (config.isAnnotationProcessorsEnabled()) { + for (Module module : modules) { + if (!config.getExcludedModules().contains(module)) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path != null) { + outputDirs.add(new File(path)); + } + } + } + } return outputDirs; } @@ -1422,6 +1461,7 @@ public class CompileDriver { context.getProgressIndicator().pushState(); final boolean[] wereFilesDeleted = new boolean[]{false}; + boolean traverseRootsProcessed = false; try { ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { @@ -1469,15 +1509,17 @@ public class CompileDriver { context.requestRebuildNextTime(e.getMessage()); } } - - if ((wereFilesDeleted[0] || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + + final boolean hadUnprocessedTraverseRoots = context.getDependencyCache().hasUnprocessedTraverseRoots(); + if ((wereFilesDeleted[0] || hadUnprocessedTraverseRoots || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { compiler.compile(context, moduleChunk, VfsUtil.toVirtualFileArray(toCompile), sink); + traverseRootsProcessed = hadUnprocessedTraverseRoots != context.getDependencyCache().hasUnprocessedTraverseRoots(); } } finally { context.getProgressIndicator().popState(); } - return !toCompile.isEmpty() || wereFilesDeleted[0]; + return !toCompile.isEmpty() || traverseRootsProcessed || wereFilesDeleted[0]; } private static boolean syncOutputDir(final CompileContextEx context, final Collection> toDelete) throws CacheCorruptedException { @@ -1787,8 +1829,10 @@ public class CompileDriver { private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) { final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/; final List modulesWithoutOutputPathSpecified = new ArrayList(); + boolean isProjectCompilePathSpecified = true; final List modulesWithoutJdkAssigned = new ArrayList(); final Set nonExistingOutputPaths = new HashSet(); + final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); for (final Module module : scopeModules) { final boolean hasSources = hasSources(module, false); @@ -1830,6 +1874,23 @@ public class CompileDriver { modulesWithoutOutputPathSpecified.add(module.getName()); } } + if (config.isAnnotationProcessorsEnabled() && !config.getExcludedModules().contains(module)) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + if (path == null) { + if (CompilerProjectExtension.getInstance(module.getProject()).getCompilerOutputUrl() == null) { + isProjectCompilePathSpecified = false; + } + else { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + } + else { + final File file = new File(path); + if (!file.exists()) { + nonExistingOutputPaths.add(file); + } + } + } } } if (!modulesWithoutJdkAssigned.isEmpty()) { @@ -1837,6 +1898,17 @@ public class CompileDriver { return false; } + if (!isProjectCompilePathSpecified) { + final String message = CompilerBundle.message("error.project.output.not.specified"); + if (ApplicationManager.getApplication().isUnitTestMode()) { + LOG.error(message); + } + + Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + ProjectSettingsService.getInstance(myProject).openProjectSettings(); + return false; + } + if (!modulesWithoutOutputPathSpecified.isEmpty()) { showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME); return false; @@ -1880,7 +1952,6 @@ public class CompileDriver { } } final List> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules)); - final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); for (final Chunk chunk : chunks) { final Set chunkModules = chunk.getNodes(); if (chunkModules.size() <= 1) { @@ -2071,9 +2142,8 @@ public class CompileDriver { ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect, false); } - private static VirtualFile lookupVFile(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) { - final File file = new File(CompilerPaths.getGenerationOutputPath(compiler, module, forTestSources)); - final LocalFileSystem lfs = LocalFileSystem.getInstance(); + private static VirtualFile lookupVFile(final LocalFileSystem lfs, final String path) { + final File file = new File(path); VirtualFile vFile = lfs.findFileByIoFile(file); if (vFile != null) { diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessingCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessingCompiler.java index c8fa49fc14cb..5cb1e3e76f1a 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessingCompiler.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessingCompiler.java @@ -24,6 +24,7 @@ package com.intellij.compiler.impl.javaCompiler; import com.intellij.compiler.CompilerConfiguration; import com.intellij.compiler.CompilerConfigurationImpl; import com.intellij.compiler.CompilerException; +import com.intellij.compiler.impl.CompileContextExProxy; import com.intellij.compiler.impl.javaCompiler.javac.JavacCompiler; import com.intellij.compiler.make.CacheCorruptedException; import com.intellij.openapi.application.ApplicationManager; @@ -33,20 +34,24 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Chunk; import org.jetbrains.annotations.NotNull; -import java.io.DataInput; -import java.io.IOException; -import java.util.*; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; -public class AnnotationProcessingCompiler implements SourceProcessingCompiler{ +public class AnnotationProcessingCompiler implements TranslatingCompiler{ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.JavaCompiler"); private final Project myProject; + private CompilerConfiguration myConfig; public AnnotationProcessingCompiler(Project project) { myProject = project; + myConfig = CompilerConfiguration.getInstance(project); } @NotNull @@ -54,74 +59,48 @@ public class AnnotationProcessingCompiler implements SourceProcessingCompiler{ return CompilerBundle.message("annotation.processing.compiler.description"); } - @NotNull - public ProcessingItem[] getProcessingItems(CompileContext context) { - final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); - if (!config.isAnnotationProcessorsEnabled()) { - return ProcessingItem.EMPTY_ARRAY; + public boolean isCompilableFile(VirtualFile file, CompileContext context) { + if (!myConfig.isAnnotationProcessorsEnabled()) { + return false; + } + return file.getFileType() == StdFileTypes.JAVA && !isExcludedFromAnnotationProcessing(file, context); + } + + public void compile(final CompileContext context, final Chunk moduleChunk, final VirtualFile[] files, OutputSink sink) { + if (!myConfig.isAnnotationProcessorsEnabled()) { + return; } - final VirtualFile[] files = context.getCompileScope().getFiles(StdFileTypes.JAVA, true); - final List items = new ArrayList(files.length); - final Set excludedModules = config.getExcludedModules(); - for (final VirtualFile file : files) { - if (excludedModules.size() != 0 && excludedModules.contains(context.getModuleByFile(file))) { - continue; + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final CompileContextEx _context = new CompileContextExProxy((CompileContextEx)context) { + public VirtualFile getModuleOutputDirectory(Module module) { + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + return path != null? lfs.findFileByPath(path) : null; } - if (config.isExcludedFromCompilation(file)) { - continue; - } - items.add(new MyProcessingItem(file)); - } - return items.toArray(new ProcessingItem[items.size()]); - } - - public ProcessingItem[] process(CompileContext context, ProcessingItem[] items) { - final VirtualFile[] files = new VirtualFile[items.length]; - for (int idx = 0; idx < items.length; idx++) { - files[idx] = items[idx].getFile(); - } - compile(context, files); - return context.getMessageCount(CompilerMessageCategory.ERROR) == 0? items : ProcessingItem.EMPTY_ARRAY; - } - - public ValidityState createValidityState(DataInput in) throws IOException { - return null; - } - - private void compile(final CompileContext context, final VirtualFile[] files) { + }; final JavacCompiler javacCompiler = getBackEndCompiler(); final boolean processorMode = javacCompiler.setAnnotationProcessorMode(true); - final Chunk dummyChunk = new Chunk(Collections.emptySet()); // TODO! - final BackendCompilerWrapper wrapper = new BackendCompilerWrapper(dummyChunk, myProject, Arrays.asList(files), (CompileContextEx)context, javacCompiler, DummySink.INSTANCE); + final BackendCompilerWrapper wrapper = new BackendCompilerWrapper(moduleChunk, myProject, Arrays.asList(files), _context, javacCompiler, sink); try { wrapper.compile(); } catch (CompilerException e) { - context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1); + _context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1); } catch (CacheCorruptedException e) { LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); + _context.requestRebuildNextTime(e.getMessage()); } finally { javacCompiler.setAnnotationProcessorMode(processorMode); final Set dirsToRefresh = new HashSet(); ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { - final Set modules = new HashSet(); - for (VirtualFile file : files) { - final Module module = context.getModuleByFile(file); - if (module != null) { - modules.add(module); + for (Module module : moduleChunk.getNodes()) { + final VirtualFile out = _context.getModuleOutputDirectory(module); + if (out != null) { + dirsToRefresh.add(out); } } - for (Module module : modules) { - dirsToRefresh.add(context.getModuleOutputDirectory(module)); - dirsToRefresh.add(context.getModuleOutputDirectoryForTests(module)); - // todo: Some annotation processors put files into the source code. So need to refresh module source roots. - // It is an open question whether we shall support such processors - //dirsToRefresh.addAll(Arrays.asList(ModuleRootManager.getInstance(module).getSourceRoots())); - } } }); for (VirtualFile root : dirsToRefresh) { @@ -130,6 +109,21 @@ public class AnnotationProcessingCompiler implements SourceProcessingCompiler{ } } + private boolean isExcludedFromAnnotationProcessing(VirtualFile file, CompileContext context) { + final Module module = context.getModuleByFile(file); + if (module != null) { + if (myConfig.getExcludedModules().contains(module)) { + return true; + } + final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module); + final VirtualFile generationDir = LocalFileSystem.getInstance().findFileByPath(path); + if (VfsUtil.isAncestor(generationDir, file, false)) { + return true; + } + } + return myConfig.isExcludedFromCompilation(file); + } + public boolean validateConfiguration(CompileScope scope) { final JavacCompiler compiler = getBackEndCompiler(); final boolean previousValue = compiler.setAnnotationProcessorMode(true); @@ -146,26 +140,4 @@ public class AnnotationProcessingCompiler implements SourceProcessingCompiler{ return configuration.getJavacCompiler(); } - private static class MyProcessingItem implements ProcessingItem { - private final VirtualFile myFile; - - public MyProcessingItem(VirtualFile file) { - myFile = file; - } - - @NotNull - public VirtualFile getFile() { - return myFile; - } - - public ValidityState getValidityState() { - return null; - } - } - - private static class DummySink implements TranslatingCompiler.OutputSink { - public static final DummySink INSTANCE = new DummySink(); - public void add(String outputRoot, Collection items, VirtualFile[] filesToRecompile) { - } - } } \ No newline at end of file diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessorsRunner.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessorsRunner.java new file mode 100644 index 000000000000..6c5baa95caa4 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessorsRunner.java @@ -0,0 +1,414 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author: Eugene Zhuravlev + * Date: Jan 24, 2003 + * Time: 4:25:47 PM + */ +package com.intellij.compiler.impl.javaCompiler; + +import com.intellij.compiler.CompilerException; +import com.intellij.compiler.OutputParser; +import com.intellij.compiler.impl.CompilerUtil; +import com.intellij.compiler.make.CacheCorruptedException; +import com.intellij.ide.util.projectWizard.JavaModuleBuilder; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.compiler.CompilerBundle; +import com.intellij.openapi.compiler.CompilerMessageCategory; +import com.intellij.openapi.compiler.TranslatingCompiler; +import com.intellij.openapi.compiler.ex.CompileContextEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.JavaModuleType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Chunk; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class AnnotationProcessorsRunner { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper"); + + private final BackendCompiler myCompiler; + private final Set mySuccesfullyCompiledJavaFiles; // VirtualFile + + private final CompileContextEx myCompileContext; + private final List myFilesToCompile; + private final TranslatingCompiler.OutputSink mySink; + private final Chunk myChunk; + private final Project myProject; + private final Set myFilesToRecompile; + private final ProjectFileIndex myProjectFileIndex; + private long myCompilationDuration = 0L; + + + public AnnotationProcessorsRunner(Chunk chunk, @NotNull final Project project, + @NotNull List filesToCompile, + @NotNull CompileContextEx compileContext, + @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) { + myChunk = chunk; + myProject = project; + myCompiler = compiler; + myCompileContext = compileContext; + myFilesToCompile = filesToCompile; + myFilesToRecompile = new HashSet(filesToCompile); + mySink = sink; + myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); + mySuccesfullyCompiledJavaFiles = new HashSet(filesToCompile.size()); + } + + public void compile() throws CompilerException, CacheCorruptedException { + try { + if (!myFilesToCompile.isEmpty()) { + final Map> moduleToFilesMap = buildModuleToFilesMap(myFilesToCompile); + myProcessedFilesCount = 0; + try { + // todo: need special ModuleChunk to be able to filter sources + final ModuleChunk chunk = new ModuleChunk(myCompileContext, myChunk, moduleToFilesMap); + // TODO: do we really need this for annot. processors? + //runTransformingCompilers(chunk); + + setPresentableNameFor(chunk); + + // assuming output dir pointing to source-generated output dir + final List outs = getOutputDirsToCompileTo(chunk); + + for (final OutputDir outputDir : outs) { + // todo: proper filtering + chunk.setSourcesFilter(outputDir.getKind()); + doCompile(chunk, outputDir.getPath()); + } + } + catch (IOException e) { + throw new CompilerException(e.getMessage(), e); + } + } + } + catch (SecurityException e) { + throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e); + } + catch (IllegalArgumentException e) { + throw new CompilerException(e.getMessage(), e); + } + finally { + CompilerUtil.logDuration(myCompiler.getId() + " running", myCompilationDuration); + } + + // do not update caches if cancelled because there is a chance that they will be incomplete + + myFilesToRecompile.removeAll(mySuccesfullyCompiledJavaFiles); + if (myFilesToRecompile.size() > 0) { + mySink.add(null, Collections.emptyList(), myFilesToRecompile.toArray(new VirtualFile[myFilesToRecompile.size()])); + } + } + + private Map> buildModuleToFilesMap(final List filesToCompile) { + if (myChunk.getNodes().size() == 1) { + return Collections.singletonMap(myChunk.getNodes().iterator().next(), Collections.unmodifiableList(filesToCompile)); + } + return CompilerUtil.buildModuleToFilesMap(myCompileContext, filesToCompile); + } + + + private void setPresentableNameFor(final ModuleChunk chunk) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final Module[] modules = chunk.getModules(); + StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8)); + for (int idx = 0; idx < modules.length; idx++) { + final Module module = modules[idx]; + if (idx > 0) { + moduleName.append(", "); + } + moduleName.append(module.getName()); + if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) { + moduleName.append("..."); + break; + } + } + myModuleName = moduleName.toString(); + } + }); + } + + private List getOutputDirsToCompileTo(ModuleChunk chunk) throws IOException { + // todo + return Collections.emptyList(); + } + + private final Object lock = new Object(); + + private class SynchedCompilerParsing extends CompilerParsingThread { + + private SynchedCompilerParsing(Process process, final CompileContext context, OutputParser outputParser, boolean readErrorStream, + boolean trimLines) { + super(process, outputParser, readErrorStream, trimLines,context); + } + + public void setProgressText(String text) { + synchronized (lock) { + super.setProgressText(text); + } + } + + public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) { + synchronized (lock) { + super.message(category, message, url, lineNum, columnNum); + } + } + + public void fileProcessed(String path) { + synchronized (lock) { + sourceFileProcessed(); + } + } + } + + private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir) throws IOException { + myCompileContext.getProgressIndicator().checkCanceled(); + + if (ApplicationManager.getApplication().runReadAction(new Computable() { + public Boolean compute() { + return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE; + } + }).booleanValue()) { + return; // should not invoke javac with empty sources list + } + + ModuleType moduleType = chunk.getModules()[0].getModuleType(); + if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) && + !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) { + // TODO + // don't try to compile non-java type module + return; + } + + int exitValue = 0; + try { + Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext); + final long compilationStart = System.currentTimeMillis(); + + OutputParser errorParser = myCompiler.createErrorParser(outputDir, process); + CompilerParsingThread errorParsingThread = errorParser == null + ? null + : new SynchedCompilerParsing(process, myCompileContext, errorParser, true, errorParser.isTrimLines()); + Future errorParsingThreadFuture = null; + if (errorParsingThread != null) { + errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread); + } + + OutputParser outputParser = myCompiler.createOutputParser(outputDir); + CompilerParsingThread outputParsingThread = outputParser == null + ? null + : new SynchedCompilerParsing(process, myCompileContext, outputParser, false, outputParser.isTrimLines()); + Future outputParsingThreadFuture = null; + if (outputParsingThread != null) { + outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread); + } + + try { + exitValue = process.waitFor(); + } + catch (InterruptedException e) { + process.destroy(); + exitValue = process.exitValue(); + } + finally { + myCompilationDuration += (System.currentTimeMillis() - compilationStart); + if (errorParsingThread != null) { + errorParsingThread.setProcessTerminated(true); + } + if (outputParsingThread != null) { + outputParsingThread.setProcessTerminated(true); + } + joinThread(errorParsingThreadFuture); + joinThread(outputParsingThreadFuture); + + registerParsingException(outputParsingThread); + registerParsingException(errorParsingThread); + assert outputParsingThread == null || !outputParsingThread.processing; + assert errorParsingThread == null || !errorParsingThread.processing; + } + } + finally { + compileFinished(exitValue, chunk, outputDir); + myModuleName = null; + } + } + + private static void joinThread(final Future threadFuture) { + if (threadFuture != null) { + try { + threadFuture.get(); + } + catch (InterruptedException ignored) { + } + catch(ExecutionException ignored) { + } + } + } + + private void registerParsingException(final CompilerParsingThread outputParsingThread) { + Throwable error = outputParsingThread == null ? null : outputParsingThread.getError(); + if (error != null) { + String message = error.getMessage(); + if (error instanceof CacheCorruptedException) { + myCompileContext.requestRebuildNextTime(message); + } + else { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); + } + } + } + + private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) { + if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() && + myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1); + } + + myCompiler.compileFinished(); + + try { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final String outputDirPath = outputDir.replace(File.separatorChar, '/'); + for (final Module module : chunk.getModules()) { + for (final VirtualFile root : chunk.getSourceRoots(module)) { + final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root); + if (LOG.isDebugEnabled()) { + LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\""); + } + } + } + } + }); + } + finally { + // todo: refresh dirs her + } + } + + private volatile int myProcessedFilesCount = 0; + private volatile int myClassesCount = 0; + private volatile String myModuleName = null; + + private void sourceFileProcessed() { + myProcessedFilesCount++; + updateStatistics(); + } + + private void updateStatistics() { + final String msg; + String moduleName = myModuleName; + if (moduleName != null) { + msg = CompilerBundle.message("statistics.files.classes.module", myProcessedFilesCount, myClassesCount, moduleName); + } + else { + msg = CompilerBundle.message("statistics.files.classes", myProcessedFilesCount, myClassesCount); + } + myCompileContext.getProgressIndicator().setText2(msg); + } + + /* + private void runTransformingCompilers(final ModuleChunk chunk) { + final JavaSourceTransformingCompiler[] transformers = + CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class); + if (transformers.length == 0) { + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("Running transforming compilers..."); + } + final Module[] modules = chunk.getModules(); + for (final JavaSourceTransformingCompiler transformer : transformers) { + final Map originalToCopyFileMap = new HashMap(); + final Application application = ApplicationManager.getApplication(); + application.invokeAndWait(new Runnable() { + public void run() { + for (final Module module : modules) { + List filesToCompile = chunk.getFilesToCompile(module); + for (final VirtualFile file : filesToCompile) { + if (transformer.isTransformable(file)) { + application.runWriteAction(new Runnable() { + public void run() { + try { + VirtualFile fileCopy = createFileCopy(getTempDir(module), file); + originalToCopyFileMap.put(file, fileCopy); + } + catch (IOException e) { + // skip it + } + } + }); + } + } + } + } + }, myCompileContext.getProgressIndicator().getModalityState()); + + // do actual transform + for (final Module module : modules) { + final List filesToCompile = chunk.getFilesToCompile(module); + for (int j = 0; j < filesToCompile.size(); j++) { + final VirtualFile file = filesToCompile.get(j); + VirtualFile fileCopy = originalToCopyFileMap.get(file); + if (fileCopy != null) { + final boolean ok = transformer.transform(myCompileContext, fileCopy, file); + if (ok) { + chunk.substituteWithTransformedVersion(module, j, fileCopy); + } + } + } + } + } + } + + private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException { + final String fileName = file.getName(); + if (tempDir.findChild(fileName) != null) { + int idx = 0; + while (true) { + //noinspection HardCodedStringLiteral + final String dirName = "dir" + idx++; + final VirtualFile dir = tempDir.findChild(dirName); + if (dir == null) { + tempDir = tempDir.createChildDirectory(this, dirName); + break; + } + if (dir.findChild(fileName) == null) { + tempDir = dir; + break; + } + } + } + return VfsUtil.copyFile(this, file, tempDir); + } + */ + +} \ No newline at end of file diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ModuleChunk.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ModuleChunk.java index acc9b1e49f87..1f9703d12eb8 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ModuleChunk.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ModuleChunk.java @@ -237,7 +237,7 @@ public class ModuleChunk extends Chunk { return convertToStringPath(cpFiles); } - private String convertToStringPath(final OrderedSet cpFiles) { + private static String convertToStringPath(final OrderedSet cpFiles) { final StringBuilder classpathBuffer = StringBuilderSpinAllocator.alloc(); try { for (final VirtualFile file : cpFiles) { diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacCompiler.java index 16669f1072f7..f73a41fe8e56 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacCompiler.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacCompiler.java @@ -310,9 +310,6 @@ public class JavacCompiler extends ExternalCompiler { } } } - //additionalOptions.add("-processor"); - //additionalOptions.add("CheckNamesProcessor"); - //additionalOptions.add("org.apache.openjpa.persistence.meta.AnnotationProcessor6"); } while (tokenizer.hasMoreTokens()) { @NonNls String token = tokenizer.nextToken(); diff --git a/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java index 7e9270ba30eb..ad4610fa9ce8 100644 --- a/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java +++ b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java @@ -118,6 +118,10 @@ public class DependencyCache { myTraverseRoots.clear(); } + public boolean hasUnprocessedTraverseRoots() { + return !myTraverseRoots.isEmpty(); + } + public void markSourceRemoved(int qName) { myClassesWithSourceRemoved.add(qName); } diff --git a/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsConfigurable.java index a87e93aae806..cfa2e7c112d4 100644 --- a/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsConfigurable.java +++ b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsConfigurable.java @@ -1,7 +1,6 @@ package com.intellij.compiler.options; import com.intellij.compiler.CompilerConfiguration; -import com.intellij.ide.util.ElementsChooser; import com.intellij.openapi.fileChooser.FileChooser; import com.intellij.openapi.fileChooser.FileChooserDescriptor; import com.intellij.openapi.module.Module; @@ -16,7 +15,6 @@ import com.intellij.ui.TableUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.ui.ItemRemovable; import com.intellij.util.ui.Table; -import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.border.TitledBorder; @@ -38,7 +36,7 @@ import java.util.*; * Date: Oct 5, 2009 */ public class AnnotationProcessorsConfigurable implements Configurable{ - private ElementsChooser myModulesChooser; + private ProcessedModulesChooser myModulesChooser; private final Project myProject; private JRadioButton myRbClasspath; private JRadioButton myRbProcessorsPath; @@ -107,15 +105,7 @@ public class AnnotationProcessorsConfigurable implements Configurable{ processorTablePanel.add(buttons, BorderLayout.EAST); processorTablePanel.setPreferredSize(new Dimension(processorTablePanel.getPreferredSize().width, 50)); - myModulesChooser = new ElementsChooser(true) { - protected String getItemText(@NotNull Module module) { - return module.getName() + " (" + FileUtil.toSystemDependentName(module.getModuleFilePath()) + ")"; - } - - protected Icon getItemIcon(Module module) { - return module.getModuleType().getNodeIcon(false); - } - }; + myModulesChooser = new ProcessedModulesChooser(); myModulesChooser.setBorder(BorderFactory.createTitledBorder("Processed Modules")); mainPanel.add(myCbEnableProcessing, new GridBagConstraints(0, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0)); diff --git a/java/compiler/impl/src/com/intellij/compiler/options/ProcessedModulesChooser.java b/java/compiler/impl/src/com/intellij/compiler/options/ProcessedModulesChooser.java new file mode 100644 index 000000000000..82de94917718 --- /dev/null +++ b/java/compiler/impl/src/com/intellij/compiler/options/ProcessedModulesChooser.java @@ -0,0 +1,484 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.compiler.options; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SpeedSearchBase; +import com.intellij.ui.TableUtil; +import com.intellij.util.ui.Table; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.table.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.*; +import java.util.List; + +public class ProcessedModulesChooser extends JPanel { + private Table myTable = null; + private MyTableModel myTableModel = null; + private boolean myColorUnmarkedElements = true; + private final Map myDisabledMap = new HashMap(); + + public ProcessedModulesChooser() { + super(new BorderLayout()); + + myTableModel = new MyTableModel(true); + myTable = new Table(myTableModel); + myTable.setShowGrid(false); + myTable.setIntercellSpacing(new Dimension(0, 0)); + myTable.setTableHeader(null); + myTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + myTable.setColumnSelectionAllowed(false); + JScrollPane pane = ScrollPaneFactory.createScrollPane(myTable); + pane.setPreferredSize(new Dimension(100, 155)); + int width = new JCheckBox().getPreferredSize().width; + TableColumnModel columnModel = myTable.getColumnModel(); + + TableColumn checkMarkColumn = columnModel.getColumn(myTableModel.CHECK_MARK_COLUM_INDEX); + checkMarkColumn.setPreferredWidth(width); + checkMarkColumn.setMaxWidth(width); + checkMarkColumn.setCellRenderer(new CheckMarkColumnCellRenderer(myTable.getDefaultRenderer(Boolean.class))); + columnModel.getColumn(myTableModel.ELEMENT_COLUMN_INDEX).setCellRenderer(new MyElementColumnCellRenderer()); + + add(pane, BorderLayout.CENTER); + myTable.registerKeyboardAction( + new ActionListener() { + public void actionPerformed(ActionEvent e) { + final int[] selectedRows = myTable.getSelectedRows(); + boolean currentlyMarked = true; + for (int selectedRow : selectedRows) { + currentlyMarked = myTableModel.isElementMarked(selectedRow); + if (!currentlyMarked) { + break; + } + } + myTableModel.setMarked(selectedRows, !currentlyMarked); + } + }, + KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), + JComponent.WHEN_FOCUSED + ); + + final SpeedSearchBase speedSearch = new SpeedSearchBase
(myTable) { + public int getSelectedIndex() { + return myTable.getSelectedRow(); + } + + public Object[] getAllElements() { + final int count = myTableModel.getRowCount(); + Object[] elements = new Object[count]; + for (int idx = 0; idx < count; idx++) { + elements[idx] = myTableModel.getElementAt(idx); + } + return elements; + } + + public String getElementText(Object element) { + return ((Module)element).getName() + " (" + FileUtil.toSystemDependentName(((Module)element).getModuleFilePath()) + ")"; + } + + public void selectElement(Object element, String selectedText) { + final int count = myTableModel.getRowCount(); + for (int row = 0; row < count; row++) { + if (element.equals(myTableModel.getElementAt(row))) { + myTable.getSelectionModel().setSelectionInterval(row, row); + TableUtil.scrollSelectionToVisible(myTable); + break; + } + } + } + }; + speedSearch.setComparator(new SpeedSearchBase.SpeedSearchComparator(false)); + } + + public void refresh() { + myTableModel.fireTableDataChanged(); + } + + public void refresh(Module element) { + final int row = myTableModel.getElementRow(element); + if (row >= 0) { + myTableModel.fireTableRowsUpdated(row, row); + } + } + + private int[] mySavedSelection = null; + public void saveSelection() { + mySavedSelection = myTable.getSelectedRows(); + } + + public void restoreSelection() { + if (mySavedSelection != null) { + TableUtil.selectRows(myTable, mySavedSelection); + mySavedSelection = null; + } + } + + public void addElement(Module element, final boolean isMarked) { + myTableModel.addElement(element, isMarked); + selectRow(myTableModel.getRowCount() - 1); + myTable.requestFocus(); + } + + public boolean isElementMarked(Module element) { + final int elementRow = myTableModel.getElementRow(element); + return myTableModel.isElementMarked(elementRow); + } + + public void setElementMarked(Module element, boolean marked) { + final int elementRow = myTableModel.getElementRow(element); + myTableModel.setMarked(elementRow, marked); + } + + + public void removeElement(Module element) { + final int elementRow = myTableModel.getElementRow(element); + if (elementRow < 0) { + return; // no such element + } + final boolean wasSelected = myTable.getSelectionModel().isSelectedIndex(elementRow); + + myTableModel.removeElement(element); + + if (wasSelected) { + final int rowCount = myTableModel.getRowCount(); + if (rowCount > 0) { + selectRow(elementRow % rowCount); + } + else { + myTable.getSelectionModel().clearSelection(); + } + } + myTable.requestFocus(); + } + + public void removeAllElements() { + myTableModel.removeAllElements(); + myTable.getSelectionModel().clearSelection(); + } + + private void selectRow(final int row) { + myTable.getSelectionModel().setSelectionInterval(row, row); + myTable.scrollRectToVisible(myTable.getCellRect(row, 0, true)); + } + + @Nullable + public Module getSelectedElement() { + final int selectedRow = getSelectedElementRow(); + return selectedRow < 0? null : myTableModel.getElementAt(selectedRow); + } + + public int getSelectedElementRow() { + return myTable.getSelectedRow(); + } + + public List getSelectedElements() { + final List elements = new ArrayList(); + final int[] selectedRows = myTable.getSelectedRows(); + for (int selectedRow : selectedRows) { + if (selectedRow < 0) { + continue; + } + elements.add(myTableModel.getElementAt(selectedRow)); + } + return elements; + } + + public void selectElements(Collection elements) { + if (elements.size() == 0) { + myTable.clearSelection(); + return; + } + final int[] rows = getElementsRows(elements); + TableUtil.selectRows(myTable, rows); + TableUtil.scrollSelectionToVisible(myTable); + myTable.requestFocus(); + } + + private int[] getElementsRows(final Collection elements) { + final int[] rows = new int[elements.size()]; + int index = 0; + for (final Module element : elements) { + rows[index++] = myTableModel.getElementRow(element); + } + return rows; + } + + public void markElements(Collection elements) { + myTableModel.setMarked(getElementsRows(elements), true); + } + + public List getMarkedElements() { + final int count = myTableModel.getRowCount(); + List elements = new ArrayList(); + for (int idx = 0; idx < count; idx++) { + final Module element = myTableModel.getElementAt(idx); + if (myTableModel.isElementMarked(idx)) { + elements.add(element); + } + } + return elements; + } + + public void sort(Comparator comparator) { + myTableModel.sort(comparator); + } + + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + myTable.setRowSelectionAllowed(enabled); + myTableModel.fireTableDataChanged(); + } + + public void stopEditing() { + TableCellEditor editor = myTable.getCellEditor(); + if (editor != null) { + editor.stopCellEditing(); + } + } + + public JComponent getComponent() { + return myTable; + } + + public void clear() { + myTableModel.clear(); + } + + public int getElementCount() { + return myTableModel.getRowCount(); + } + + public Module getElementAt(int row) { + return myTableModel.getElementAt(row); + } + + public void disableElement(Module element) { + myDisabledMap.put(element, Boolean.TRUE); + } + + private final class MyTableModel extends AbstractTableModel { + private final List myElements = new ArrayList(); + private final Map myMarkedMap = new HashMap(); + public final int CHECK_MARK_COLUM_INDEX; + public final int ELEMENT_COLUMN_INDEX; + private final boolean myElementsCanBeMarked; + + public MyTableModel(final boolean elementsCanBeMarked) { + myElementsCanBeMarked = elementsCanBeMarked; + if (elementsCanBeMarked) { + CHECK_MARK_COLUM_INDEX = 0; + ELEMENT_COLUMN_INDEX = 1; + } + else { + CHECK_MARK_COLUM_INDEX = -1; + ELEMENT_COLUMN_INDEX = 0; + } + } + + public void sort(Comparator comparator) { + Collections.sort(myElements, comparator); + fireTableDataChanged(); + } + + public Module getElementAt(int index) { + return myElements.get(index); + } + + public boolean isElementMarked(int index) { + final Module element = myElements.get(index); + final Boolean isMarked = myMarkedMap.get(element); + return isMarked.booleanValue(); + } + + void addElement(Module element, boolean isMarked) { + myElements.add(element); + myMarkedMap.put(element, isMarked? Boolean.TRUE : Boolean.FALSE); + int row = myElements.size() - 1; + fireTableRowsInserted(row, row); + } + + void addElements(List elements, boolean isMarked) { + if (elements == null || elements.size() == 0) { + return; + } + for (final Module element : elements) { + myElements.add(element); + myMarkedMap.put(element, isMarked ? Boolean.TRUE : Boolean.FALSE); + } + fireTableRowsInserted(myElements.size() - elements.size(), myElements.size() - 1); + } + + public void removeElement(Module element) { + final boolean reallyRemoved = myElements.remove(element); + if (reallyRemoved) { + myMarkedMap.remove(element); + fireTableDataChanged(); + } + } + + public void changeElementRow(Module element, int row) { + final boolean reallyRemoved = myElements.remove(element); + if (reallyRemoved) { + myElements.add(row, element); + fireTableDataChanged(); + } + } + + public int getElementRow(Module element) { + return myElements.indexOf(element); + } + + public void removeAllElements() { + myElements.clear(); + fireTableDataChanged(); + } + + public void removeRows(int[] rows) { + final List toRemove = new ArrayList(); + for (int row : rows) { + final Module element = myElements.get(row); + toRemove.add(element); + myMarkedMap.remove(element); + } + myElements.removeAll(toRemove); + fireTableDataChanged(); + } + + public int getRowCount() { + return myElements.size(); + } + + public int getColumnCount() { + return myElementsCanBeMarked? 2 : 1; + } + + @Nullable + public Object getValueAt(int rowIndex, int columnIndex) { + Module element = myElements.get(rowIndex); + if (columnIndex == ELEMENT_COLUMN_INDEX) { + return element; + } + if (columnIndex == CHECK_MARK_COLUM_INDEX) { + return myMarkedMap.get(element); + } + return null; + } + + public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + if (columnIndex == CHECK_MARK_COLUM_INDEX) { + setMarked(rowIndex, ((Boolean)aValue).booleanValue()); + } + } + + private void setMarked(int rowIndex, final boolean marked) { + final Module element = myElements.get(rowIndex); + final Boolean newValue = marked? Boolean.TRUE : Boolean.FALSE; + myMarkedMap.put(element, newValue); + fireTableRowsUpdated(rowIndex, rowIndex); + } + + private void setMarked(int[] rows, final boolean marked) { + if (rows == null || rows.length == 0) { + return; + } + int firstRow = Integer.MAX_VALUE; + int lastRow = Integer.MIN_VALUE; + final Boolean newValue = marked? Boolean.TRUE : Boolean.FALSE; + for (final int row : rows) { + final Module element = myElements.get(row); + myMarkedMap.put(element, newValue); + firstRow = Math.min(firstRow, row); + lastRow = Math.max(lastRow, row); + } + fireTableRowsUpdated(firstRow, lastRow); + } + + public Class getColumnClass(int columnIndex) { + if (columnIndex == CHECK_MARK_COLUM_INDEX) { + return Boolean.class; + } + return super.getColumnClass(columnIndex); + } + + public boolean isCellEditable(int rowIndex, int columnIndex) { + if (!ProcessedModulesChooser.this.isEnabled() || columnIndex != CHECK_MARK_COLUM_INDEX) { + return false; + } + final Module o = (Module)getValueAt(rowIndex, ELEMENT_COLUMN_INDEX); + return myDisabledMap.get(o) == null; + } + + public void clear() { + myElements.clear(); + myMarkedMap.clear(); + fireTableDataChanged(); + } + } + + private class MyElementColumnCellRenderer extends DefaultTableCellRenderer { + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + final Color color = UIUtil.getTableFocusCellBackground(); + Component component; + Module module = (Module)value; + try { + UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, table.getSelectionBackground()); + component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + setText(module != null ? module.getName() + " (" + FileUtil.toSystemDependentName(module.getModuleFilePath()) + ")" : ""); + if (component instanceof JLabel) { + ((JLabel)component).setBorder(noFocusBorder); + } + } + finally { + UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, color); + } + final MyTableModel model = (MyTableModel)table.getModel(); + component.setEnabled(ProcessedModulesChooser.this.isEnabled() && (myColorUnmarkedElements? model.isElementMarked(row) : true)); + if (component instanceof JLabel) { + final Icon icon = module != null ? module.getModuleType().getNodeIcon(false) : null; + JLabel label = (JLabel)component; + label.setIcon(icon); + label.setDisabledIcon(icon); + } + component.setForeground(isSelected ? table.getSelectionForeground() : table.getForeground()); + return component; + } + } + + private class CheckMarkColumnCellRenderer implements TableCellRenderer { + private final TableCellRenderer myDelegate; + + public CheckMarkColumnCellRenderer(TableCellRenderer delegate) { + myDelegate = delegate; + } + + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + Component component = myDelegate.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); + component.setEnabled(ProcessedModulesChooser.this.isEnabled()); + if (component instanceof JComponent) { + ((JComponent)component).setBorder(null); + } + return component; + } + } +} \ No newline at end of file diff --git a/java/compiler/openapi/src/com/intellij/compiler/CompilerConfiguration.java b/java/compiler/openapi/src/com/intellij/compiler/CompilerConfiguration.java index 8862b7fc7c64..277aabe9e05e 100644 --- a/java/compiler/openapi/src/com/intellij/compiler/CompilerConfiguration.java +++ b/java/compiler/openapi/src/com/intellij/compiler/CompilerConfiguration.java @@ -19,6 +19,7 @@ package com.intellij.compiler; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Map; @@ -59,4 +60,13 @@ public abstract class CompilerConfiguration { public abstract Set getExcludedModules(); public abstract void setExcludedModules(Collection modules); + + public abstract boolean isStoreGenerateSourcesUnderModuleContent(Module module); + + public abstract void setStoreGenerateSourcesUnderModuleContent(boolean storeGenerateSourcesUnderModuleContent); + + @NotNull + public abstract String getGeneratedDirName(); + + public abstract void setGeneratedDirName(String generatedDirName); } \ No newline at end of file diff --git a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerPaths.java b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerPaths.java index 11cb60570bae..5516269b5365 100644 --- a/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerPaths.java +++ b/java/compiler/openapi/src/com/intellij/openapi/compiler/CompilerPaths.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.compiler; +import com.intellij.compiler.CompilerConfiguration; import com.intellij.ide.highlighter.ProjectFileType; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; @@ -23,6 +24,8 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.CompilerModuleExtension; +import com.intellij.openapi.roots.CompilerProjectExtension; +import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -33,6 +36,8 @@ import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.util.Arrays; +import java.util.Comparator; import java.util.Locale; /** @@ -41,6 +46,11 @@ import java.util.Locale; public class CompilerPaths { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.compiler.CompilerPaths"); private static volatile String ourSystemPath; + private static final Comparator URLS_COMPARATOR = new Comparator() { + public int compare(String o1, String o2) { + return o1.compareTo(o2); + } + }; /** * Returns a directory @@ -180,6 +190,27 @@ public class CompilerPaths { return outPathUrl != null? VirtualFileManager.extractPath(outPathUrl) : null; } + public static String getAnnotationProcessorsGenerationPath(Module module) { + final CompilerConfiguration config = CompilerConfiguration.getInstance(module.getProject()); + + if (config.isStoreGenerateSourcesUnderModuleContent(module)) { + final String[] roots = ModuleRootManager.getInstance(module).getContentRootUrls(); + if (roots.length == 0) { + return null; + } + if (roots.length > 1) { + Arrays.sort(roots, URLS_COMPARATOR); + } + return VirtualFileManager.extractPath(roots[0]) + "/" + config.getGeneratedDirName(); + } + + final String url = CompilerProjectExtension.getInstance(module.getProject()).getCompilerOutputUrl(); + if (url == null) { + return null; + } + return VirtualFileManager.extractPath(url) + "/generated/" + module.getName().toLowerCase(); + } + @NonNls public static String getGenerationOutputPath(IntermediateOutputCompiler compiler, Module module, final boolean forTestSources) { final String generatedCompilerDirectoryPath = getGeneratedDataDirectory(module.getProject(), compiler).getPath(); diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/IdeaProjectSettingsService.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/IdeaProjectSettingsService.java index 74eb3ab03811..e4cba45840a5 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/IdeaProjectSettingsService.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/IdeaProjectSettingsService.java @@ -21,11 +21,11 @@ import com.intellij.ide.util.projectWizard.JdkChooserPanel; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.JdkOrderEntry; import com.intellij.openapi.roots.LibraryOrderEntry; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.ui.configuration.projectRoot.ModuleStructureConfigurable; -import com.intellij.openapi.projectRoots.Sdk; import org.jetbrains.annotations.Nullable; /** @@ -38,6 +38,15 @@ public class IdeaProjectSettingsService extends ProjectSettingsService { myProject = project; } + public void openProjectSettings() { + final ProjectStructureConfigurable config = ProjectStructureConfigurable.getInstance(myProject); + ShowSettingsUtil.getInstance().editConfigurable(myProject, config, new Runnable() { + public void run() { + config.selectProjectGeneralSettings(true); + } + }); + } + public void openModuleSettings(final Module module) { ModulesConfigurator.showDialog(myProject, module.getName(), null, false); } diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java index 8b0433e65354..48afcd6daf75 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/ProjectStructureConfigurable.java @@ -338,6 +338,11 @@ public class ProjectStructureConfigurable extends BaseConfigurable implements Se Place.queryFurther(mySelectedConfigurable, place); } + public ActionCallback selectProjectGeneralSettings(final boolean requestFocus) { + Place place = new Place().putPath(CATEGORY, myProjectConfig); + return navigateTo(place, requestFocus); + } + public ActionCallback select(@Nullable final String moduleToSelect, String tab, final boolean requestFocus) { Place place = new Place().putPath(CATEGORY, myModulesConfig); if (moduleToSelect != null) { diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ProjectSettingsService.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ProjectSettingsService.java index f50d20f8c2a7..91e6fbe0ef64 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ProjectSettingsService.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ProjectSettingsService.java @@ -32,6 +32,9 @@ public class ProjectSettingsService { return ServiceManager.getService(project, ProjectSettingsService.class); } + public void openProjectSettings() { + } + public void openModuleSettings(final Module module) { } diff --git a/resources-en/src/messages/CompilerBundle.properties b/resources-en/src/messages/CompilerBundle.properties index 19586e5d6c48..bb93c51e970b 100644 --- a/resources-en/src/messages/CompilerBundle.properties +++ b/resources-en/src/messages/CompilerBundle.properties @@ -89,7 +89,8 @@ progress.copying.resources=Copying resources... progress.generating.rmi.stubs=Generating RMI stubs... rmi.compiler.description=RMI Compiler error.jdk.not.specified=Cannot start compiler: the JDK is not specified for {0,choice, 1#module|2#modules\n} {1}.\nSpecify the JDK in Configure Project. -error.output.not.specified=Cannot start compiler: the output path is not specified for {0,choice, 1#module|2#modules\n} {1}.\nSpecify the output path in Configure Project. +error.output.not.specified=Cannot start compilation: the output path is not specified for {0,choice, 1#module|2#modules\n} {1}.\nSpecify the output path in Configure Project. +error.project.output.not.specified=Cannot start compilation: some compilers require that project path is defined.\nSpecify project output path in Configure Project. statistics.files.classes.module=Files: {0} - Classes: {1} - Module: {2} statistics.files.classes=Files: {0} - Classes: {1} compiler.javac.name=Javac