2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * @author: Eugene Zhuravlev
22 package com.intellij.compiler.impl.javaCompiler;
24 import com.intellij.codeInsight.AnnotationUtil;
25 import com.intellij.compiler.*;
26 import com.intellij.compiler.classParsing.AnnotationConstantValue;
27 import com.intellij.compiler.classParsing.MethodInfo;
28 import com.intellij.compiler.impl.CompilerUtil;
29 import com.intellij.compiler.make.*;
30 import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
31 import com.intellij.ide.util.projectWizard.JavaModuleBuilder;
32 import com.intellij.openapi.application.Application;
33 import com.intellij.openapi.application.ApplicationManager;
34 import com.intellij.openapi.compiler.*;
35 import com.intellij.openapi.compiler.ex.CompileContextEx;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.fileTypes.FileTypeManager;
38 import com.intellij.openapi.module.JavaModuleType;
39 import com.intellij.openapi.module.Module;
40 import com.intellij.openapi.module.ModuleType;
41 import com.intellij.openapi.progress.ProgressIndicator;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.projectRoots.JavaSdkType;
44 import com.intellij.openapi.projectRoots.Sdk;
45 import com.intellij.openapi.roots.*;
46 import com.intellij.openapi.util.Computable;
47 import com.intellij.openapi.util.Pair;
48 import com.intellij.openapi.util.Ref;
49 import com.intellij.openapi.util.io.FileUtil;
50 import com.intellij.openapi.util.text.StringUtil;
51 import com.intellij.openapi.vfs.LocalFileSystem;
52 import com.intellij.openapi.vfs.VfsUtil;
53 import com.intellij.openapi.vfs.VirtualFile;
54 import com.intellij.util.Chunk;
55 import com.intellij.util.Function;
56 import com.intellij.util.cls.ClsFormatException;
57 import gnu.trove.THashMap;
58 import gnu.trove.TIntHashSet;
59 import org.jetbrains.annotations.NonNls;
60 import org.jetbrains.annotations.NotNull;
61 import org.jetbrains.annotations.Nullable;
62 import org.objectweb.asm.ClassReader;
63 import org.objectweb.asm.ClassWriter;
66 import java.io.IOException;
68 import java.util.concurrent.ArrayBlockingQueue;
69 import java.util.concurrent.BlockingQueue;
70 import java.util.concurrent.ExecutionException;
71 import java.util.concurrent.Future;
73 public class BackendCompilerWrapper {
74 private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper");
76 private final BackendCompiler myCompiler;
77 private final Set<VirtualFile> mySuccesfullyCompiledJavaFiles; // VirtualFile
79 private final CompileContextEx myCompileContext;
80 private final List<VirtualFile> myFilesToCompile;
81 private final TranslatingCompiler.OutputSink mySink;
82 private final Chunk<Module> myChunk;
83 private final Project myProject;
84 private final Set<VirtualFile> myFilesToRecompile;
85 private final Map<Module, VirtualFile> myModuleToTempDirMap = new THashMap<Module, VirtualFile>();
86 private final ProjectFileIndex myProjectFileIndex;
87 @NonNls private static final String PACKAGE_ANNOTATION_FILE_NAME = "package-info.java";
88 private static final FileObject myStopThreadToken = new FileObject(new File(""), new byte[0]);
89 public final Map<String, Set<CompiledClass>> myFileNameToSourceMap= new THashMap<String, Set<CompiledClass>>();
92 public BackendCompilerWrapper(Chunk<Module> chunk, @NotNull final Project project,
93 @NotNull List<VirtualFile> filesToCompile,
94 @NotNull CompileContextEx compileContext,
95 @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) {
98 myCompiler = compiler;
99 myCompileContext = compileContext;
100 myFilesToCompile = filesToCompile;
101 myFilesToRecompile = new HashSet<VirtualFile>(filesToCompile);
103 myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
104 mySuccesfullyCompiledJavaFiles = new HashSet<VirtualFile>(filesToCompile.size());
107 public void compile() throws CompilerException, CacheCorruptedException {
108 Application application = ApplicationManager.getApplication();
109 final Set<VirtualFile> allDependent = new HashSet<VirtualFile>();
112 if (!myFilesToCompile.isEmpty()) {
113 if (application.isUnitTestMode()) {
117 compileModules(buildModuleToFilesMap(myFilesToCompile));
120 Collection<VirtualFile> dependentFiles;
122 dependentFiles = CacheUtils.findDependentFiles(myCompileContext, mySuccesfullyCompiledJavaFiles, myCompiler.getDependencyProcessor(), DEPENDENCY_FILTER);
124 if (!dependentFiles.isEmpty()) {
125 myFilesToRecompile.addAll(dependentFiles);
126 allDependent.addAll(dependentFiles);
127 if (myCompileContext.getProgressIndicator().isCanceled() || myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
130 final List<VirtualFile> filesInScope = getFilesInScope(dependentFiles);
131 if (filesInScope.isEmpty()) {
134 myCompileContext.getDependencyCache().clearTraverseRoots();
135 compileModules(buildModuleToFilesMap(filesInScope));
138 while (!dependentFiles.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0);
140 catch (SecurityException e) {
141 throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e);
143 catch (IllegalArgumentException e) {
144 throw new CompilerException(e.getMessage(), e);
147 for (final VirtualFile file : myModuleToTempDirMap.values()) {
149 final File ioFile = new File(file.getPath());
150 FileUtil.asyncDelete(ioFile);
153 myModuleToTempDirMap.clear();
156 // do not update caches if cancelled because there is a chance that they will be incomplete
157 if (CompilerConfiguration.MAKE_ENABLED) {
158 if (!myCompileContext.getProgressIndicator().isCanceled()) {
159 // when cancelled pretend nothing was compiled and next compile will compile everything from the scratch
160 final ProgressIndicator indicator = myCompileContext.getProgressIndicator();
161 final DependencyCache cache = myCompileContext.getDependencyCache();
163 indicator.pushState();
164 indicator.setText(CompilerBundle.message("progress.updating.caches"));
165 indicator.setText2("");
169 indicator.setText(CompilerBundle.message("progress.saving.caches"));
172 indicator.popState();
176 myFilesToRecompile.removeAll(mySuccesfullyCompiledJavaFiles);
177 if (myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) != 0) {
178 myFilesToRecompile.addAll(allDependent);
180 final List<TranslatingCompiler.OutputItem> outputs = processPackageInfoFiles();
181 if (myFilesToRecompile.size() > 0 || outputs.size() > 0) {
182 mySink.add(null, outputs, VfsUtil.toVirtualFileArray(myFilesToRecompile));
186 private Map<Module, List<VirtualFile>> buildModuleToFilesMap(final List<VirtualFile> filesToCompile) {
187 if (myChunk.getNodes().size() == 1) {
188 return Collections.singletonMap(myChunk.getNodes().iterator().next(), Collections.unmodifiableList(filesToCompile));
190 return CompilerUtil.buildModuleToFilesMap(myCompileContext, filesToCompile);
193 // package-info.java hack
194 private List<TranslatingCompiler.OutputItem> processPackageInfoFiles() {
195 if (myFilesToRecompile.isEmpty()) {
196 return Collections.emptyList();
198 final List<TranslatingCompiler.OutputItem> outputs = new ArrayList<TranslatingCompiler.OutputItem>();
199 ApplicationManager.getApplication().runReadAction(new Runnable() {
201 final List<VirtualFile> packageInfoFiles = new ArrayList<VirtualFile>(myFilesToRecompile.size());
202 for (final VirtualFile file : myFilesToRecompile) {
203 if (PACKAGE_ANNOTATION_FILE_NAME.equals(file.getName())) {
204 packageInfoFiles.add(file);
207 if (!packageInfoFiles.isEmpty()) {
208 final Set<VirtualFile> badFiles = getFilesCompiledWithErrors();
209 for (final VirtualFile packageInfoFile : packageInfoFiles) {
210 if (!badFiles.contains(packageInfoFile)) {
211 outputs.add(new OutputItemImpl(packageInfoFile));
212 myFilesToRecompile.remove(packageInfoFile);
221 private List<VirtualFile> getFilesInScope(final Collection<VirtualFile> files) {
222 final List<VirtualFile> filesInScope = new ArrayList<VirtualFile>(files.size());
223 ApplicationManager.getApplication().runReadAction(new Runnable() {
225 for (VirtualFile file : files) {
226 if (myCompileContext.getCompileScope().belongs(file.getUrl())) {
227 final Module module = myCompileContext.getModuleByFile(file);
228 if (myChunk.getNodes().contains(module)) {
229 filesInScope.add(file);
238 private void compileModules(final Map<Module, List<VirtualFile>> moduleToFilesMap) throws CompilerException {
239 myProcessedFilesCount = 0;
241 compileChunk(new ModuleChunk(myCompileContext, myChunk, moduleToFilesMap));
243 catch (IOException e) {
244 throw new CompilerException(e.getMessage(), e);
248 private void compileChunk(ModuleChunk chunk) throws IOException {
249 runTransformingCompilers(chunk);
251 setPresentableNameFor(chunk);
253 final List<OutputDir> outs = new ArrayList<OutputDir>();
254 File fileToDelete = getOutputDirsToCompileTo(chunk, outs);
257 for (final OutputDir outputDir : outs) {
258 chunk.setSourcesFilter(outputDir.getKind());
259 doCompile(chunk, outputDir.getPath());
263 if (fileToDelete != null) {
264 FileUtil.asyncDelete(fileToDelete);
270 private void setPresentableNameFor(final ModuleChunk chunk) {
271 ApplicationManager.getApplication().runReadAction(new Runnable() {
273 final Module[] modules = chunk.getModules();
274 StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8));
275 for (int idx = 0; idx < modules.length; idx++) {
276 final Module module = modules[idx];
278 moduleName.append(", ");
280 moduleName.append(module.getName());
281 if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) {
282 moduleName.append("...");
286 myModuleName = moduleName.toString();
292 private File getOutputDirsToCompileTo(ModuleChunk chunk, final List<OutputDir> dirs) throws IOException {
293 File fileToDelete = null;
294 if (chunk.getModuleCount() == 1) { // optimization
295 final Module module = chunk.getModules()[0];
296 ApplicationManager.getApplication().runReadAction(new Runnable() {
298 final String sourcesOutputDir = getOutputDir(module);
299 if (shouldCompileTestsSeparately(module)) {
300 if (sourcesOutputDir != null) {
301 dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES));
303 final String testsOutputDir = getTestsOutputDir(module);
304 if (testsOutputDir == null) {
305 LOG.error("Tests output dir is null for module \"" + module.getName() + "\"");
308 dirs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES));
311 else { // both sources and test sources go into the same output
312 if (sourcesOutputDir == null) {
313 LOG.error("Sources output dir is null for module \"" + module.getName() + "\"");
316 dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES));
322 else { // chunk has several modules
323 final File outputDir = FileUtil.createTempDirectory("compile", "output");
324 fileToDelete = outputDir;
325 dirs.add(new OutputDir(outputDir.getPath(), ModuleChunk.ALL_SOURCES));
331 private boolean shouldCompileTestsSeparately(Module module) {
332 final String moduleTestOutputDirectory = getTestsOutputDir(module);
333 if (moduleTestOutputDirectory == null) {
336 final String moduleOutputDirectory = getOutputDir(module);
337 return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory);
340 private void saveTestData() {
341 ApplicationManager.getApplication().runReadAction(new Runnable() {
343 for (VirtualFile file : myFilesToCompile) {
344 CompilerManagerImpl.addCompiledPath(file.getPath());
350 private final TIntHashSet myProcessedNames = new TIntHashSet();
351 private final Set<VirtualFile> myProcessedFiles = new HashSet<VirtualFile>();
352 private final Function<Pair<int[], Set<VirtualFile>>, Pair<int[], Set<VirtualFile>>> DEPENDENCY_FILTER = new Function<Pair<int[], Set<VirtualFile>>, Pair<int[], Set<VirtualFile>>>() {
353 public Pair<int[], Set<VirtualFile>> fun(Pair<int[], Set<VirtualFile>> deps) {
354 final TIntHashSet currentDeps = new TIntHashSet(deps.getFirst());
355 currentDeps.removeAll(myProcessedNames.toArray());
356 myProcessedNames.addAll(deps.getFirst());
358 final Set<VirtualFile> depFiles = new HashSet<VirtualFile>(deps.getSecond());
359 depFiles.removeAll(myProcessedFiles);
360 myProcessedFiles.addAll(deps.getSecond());
361 return new Pair<int[], Set<VirtualFile>>(currentDeps.toArray(), depFiles);
365 private final Object lock = new Object();
367 private class SynchedCompilerParsing extends CompilerParsingThread {
368 private final ClassParsingThread myClassParsingThread;
370 private SynchedCompilerParsing(Process process,
371 final CompileContext context,
372 OutputParser outputParser,
373 ClassParsingThread classParsingThread,
374 boolean readErrorStream,
376 super(process, outputParser, readErrorStream, trimLines,context);
377 myClassParsingThread = classParsingThread;
380 public void setProgressText(String text) {
381 synchronized (lock) {
382 super.setProgressText(text);
386 public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) {
387 synchronized (lock) {
388 super.message(category, message, url, lineNum, columnNum);
392 public void fileProcessed(String path) {
393 synchronized (lock) {
394 sourceFileProcessed();
398 protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException {
399 synchronized (lock) {
400 myClassParsingThread.addPath(classFileToProcess);
405 private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir) throws IOException {
406 myCompileContext.getProgressIndicator().checkCanceled();
408 if (ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
409 public Boolean compute() {
410 return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE;
413 return; // should not invoke javac with empty sources list
416 ModuleType moduleType = chunk.getModules()[0].getModuleType();
417 if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) &&
418 !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) {
420 // don't try to compile non-java type module
426 Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext);
427 final long compilationStart = System.currentTimeMillis();
428 final ClassParsingThread classParsingThread = new ClassParsingThread(isJdk6(chunk.getJdk()), outputDir);
429 final Future<?> classParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(classParsingThread);
431 OutputParser errorParser = myCompiler.createErrorParser(outputDir, process);
432 CompilerParsingThread errorParsingThread = errorParser == null
434 : new SynchedCompilerParsing(process, myCompileContext, errorParser, classParsingThread,
435 true, errorParser.isTrimLines());
436 Future<?> errorParsingThreadFuture = null;
437 if (errorParsingThread != null) {
438 errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread);
441 OutputParser outputParser = myCompiler.createOutputParser(outputDir);
442 CompilerParsingThread outputParsingThread = outputParser == null
444 : new SynchedCompilerParsing(process, myCompileContext, outputParser, classParsingThread,
445 false, outputParser.isTrimLines());
446 Future<?> outputParsingThreadFuture = null;
447 if (outputParsingThread != null) {
448 outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread);
452 exitValue = process.waitFor();
454 catch (InterruptedException e) {
456 exitValue = process.exitValue();
459 if (errorParsingThread != null) {
460 errorParsingThread.setProcessTerminated(true);
462 if (outputParsingThread != null) {
463 outputParsingThread.setProcessTerminated(true);
465 joinThread(errorParsingThreadFuture);
466 joinThread(outputParsingThreadFuture);
467 classParsingThread.stopParsing();
468 joinThread(classParsingThreadFuture);
470 registerParsingException(outputParsingThread);
471 registerParsingException(errorParsingThread);
472 assert outputParsingThread == null || !outputParsingThread.processing;
473 assert errorParsingThread == null || !errorParsingThread.processing;
474 assert classParsingThread == null || !classParsingThread.processing;
478 compileFinished(exitValue, chunk, outputDir);
483 private static void joinThread(final Future<?> threadFuture) {
484 if (threadFuture != null) {
488 catch (InterruptedException ignored) {
489 LOG.info("Thread interrupted", ignored);
491 catch (ExecutionException ignored) {
492 LOG.info("Thread interrupted", ignored);
497 private void registerParsingException(final CompilerParsingThread outputParsingThread) {
498 Throwable error = outputParsingThread == null ? null : outputParsingThread.getError();
500 String message = error.getMessage();
501 if (error instanceof CacheCorruptedException) {
502 myCompileContext.requestRebuildNextTime(message);
505 myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
510 private void runTransformingCompilers(final ModuleChunk chunk) {
511 final JavaSourceTransformingCompiler[] transformers =
512 CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class);
513 if (transformers.length == 0) {
516 if (LOG.isDebugEnabled()) {
517 LOG.debug("Running transforming compilers...");
519 final Module[] modules = chunk.getModules();
520 for (final JavaSourceTransformingCompiler transformer : transformers) {
521 final Map<VirtualFile, VirtualFile> originalToCopyFileMap = new HashMap<VirtualFile, VirtualFile>();
522 final Application application = ApplicationManager.getApplication();
523 application.invokeAndWait(new Runnable() {
525 for (final Module module : modules) {
526 for (final VirtualFile file : chunk.getFilesToCompile(module)) {
527 final VirtualFile untransformed = chunk.getOriginalFile(file);
528 if (transformer.isTransformable(untransformed)) {
529 application.runWriteAction(new Runnable() {
532 // if untransformed != file, the file is already a (possibly transformed) copy of the original 'untransformed' file.
533 // If this is the case, just use already created copy and do not copy file content once again
534 final VirtualFile fileCopy = untransformed.equals(file)? createFileCopy(getTempDir(module), file) : file;
535 originalToCopyFileMap.put(file, fileCopy);
537 catch (IOException e) {
546 }, myCompileContext.getProgressIndicator().getModalityState());
548 // do actual transform
549 for (final Module module : modules) {
550 final List<VirtualFile> filesToCompile = chunk.getFilesToCompile(module);
551 for (int j = 0; j < filesToCompile.size(); j++) {
552 final VirtualFile file = filesToCompile.get(j);
553 final VirtualFile fileCopy = originalToCopyFileMap.get(file);
554 if (fileCopy != null) {
555 final boolean ok = transformer.transform(myCompileContext, fileCopy, chunk.getOriginalFile(file));
557 chunk.substituteWithTransformedVersion(module, j, fileCopy);
565 private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException {
566 final String fileName = file.getName();
567 if (tempDir.findChild(fileName) != null) {
570 //noinspection HardCodedStringLiteral
571 final String dirName = "dir" + idx++;
572 final VirtualFile dir = tempDir.findChild(dirName);
574 tempDir = tempDir.createChildDirectory(this, dirName);
577 if (dir.findChild(fileName) == null) {
583 return VfsUtil.copyFile(this, file, tempDir);
586 private VirtualFile getTempDir(Module module) throws IOException {
587 VirtualFile tempDir = myModuleToTempDirMap.get(module);
588 if (tempDir == null) {
589 final String projectName = myProject.getName();
590 final String moduleName = module.getName();
591 File tempDirectory = FileUtil.createTempDirectory(projectName, moduleName);
592 tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDirectory);
593 if (tempDir == null) {
594 LOG.error("Cannot locate temp directory " + tempDirectory.getPath());
596 myModuleToTempDirMap.put(module, tempDir);
601 private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) {
602 if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() &&
603 myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
604 myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1);
607 myCompiler.compileFinished();
608 final List<File> toRefresh = new ArrayList<File>();
609 final Map<String, Collection<TranslatingCompiler.OutputItem>> results = new HashMap<String, Collection<TranslatingCompiler.OutputItem>>();
611 ApplicationManager.getApplication().runReadAction(new Runnable() {
613 final Set<VirtualFile> compiledWithErrors = getFilesCompiledWithErrors();
614 final FileTypeManager typeManager = FileTypeManager.getInstance();
615 final String outputDirPath = outputDir.replace(File.separatorChar, '/');
617 for (final Module module : chunk.getModules()) {
618 for (final VirtualFile root : chunk.getSourceRoots(module)) {
619 final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root);
620 if (LOG.isDebugEnabled()) {
621 LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\"");
623 buildOutputItemsList(outputDirPath, module, root, typeManager, compiledWithErrors, root, packagePrefix, toRefresh, results);
627 catch (CacheCorruptedException e) {
628 myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted"));
629 if (LOG.isDebugEnabled()) {
637 CompilerUtil.refreshIOFiles(toRefresh);
638 for (Iterator<Map.Entry<String, Collection<TranslatingCompiler.OutputItem>>> it = results.entrySet().iterator(); it.hasNext();) {
639 Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry = it.next();
640 mySink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY);
641 it.remove(); // to free memory
644 myFileNameToSourceMap.clear(); // clear the map before the next use
647 private Set<VirtualFile> getFilesCompiledWithErrors() {
648 CompilerMessage[] messages = myCompileContext.getMessages(CompilerMessageCategory.ERROR);
649 Set<VirtualFile> compiledWithErrors = Collections.emptySet();
650 if (messages.length > 0) {
651 compiledWithErrors = new HashSet<VirtualFile>(messages.length);
652 for (CompilerMessage message : messages) {
653 final VirtualFile file = message.getVirtualFile();
655 compiledWithErrors.add(file);
659 return compiledWithErrors;
662 private void buildOutputItemsList(final String outputDir, Module module, VirtualFile from,
663 final FileTypeManager typeManager,
664 final Set<VirtualFile> compiledWithErrors,
665 final VirtualFile sourceRoot,
666 final String packagePrefix, final List<File> filesToRefresh, final Map<String, Collection<TranslatingCompiler.OutputItem>> results) throws CacheCorruptedException {
667 final Ref<CacheCorruptedException> exRef = new Ref<CacheCorruptedException>(null);
668 final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex();
669 final ContentIterator contentIterator = new ContentIterator() {
670 public boolean processFile(final VirtualFile child) {
672 assert child.isValid();
673 if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(typeManager.getFileTypeByFile(child))) {
674 updateOutputItemsList(outputDir, child, compiledWithErrors, sourceRoot, packagePrefix, filesToRefresh, results);
678 catch (CacheCorruptedException e) {
684 if (fileIndex.isInContent(from)) {
685 // use file index for iteration to handle 'inner modules' and excludes properly
686 fileIndex.iterateContentUnderDirectory(from, contentIterator);
689 // seems to be a root for generated sources
691 void iterateContent(VirtualFile from) {
692 for (VirtualFile child : from.getChildren()) {
693 if (child.isDirectory()) {
694 iterateContent(child);
697 contentIterator.processFile(child);
701 }.iterateContent(from);
703 final CacheCorruptedException exc = exRef.get();
709 private void putName(String sourceFileName, int classQName, String relativePathToSource, String pathToClass) {
710 if (LOG.isDebugEnabled()) {
711 LOG.debug("Registering [sourceFileName, relativePathToSource, pathToClass] = [" + sourceFileName + "; " + relativePathToSource +
712 "; " + pathToClass + "]");
714 Set<CompiledClass> paths = myFileNameToSourceMap.get(sourceFileName);
717 paths = new HashSet<CompiledClass>();
718 myFileNameToSourceMap.put(sourceFileName, paths);
720 paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass));
723 private void updateOutputItemsList(final String outputDir, VirtualFile srcFile, Set<VirtualFile> compiledWithErrors,
724 VirtualFile sourceRoot,
725 final String packagePrefix, final List<File> filesToRefresh,
726 Map<String, Collection<TranslatingCompiler.OutputItem>> results) throws CacheCorruptedException {
727 final Cache newCache = myCompileContext.getDependencyCache().getNewClassesCache();
728 final Set<CompiledClass> paths = myFileNameToSourceMap.get(srcFile.getName());
729 if (paths == null || paths.isEmpty()) {
732 final String filePath = "/" + calcPackagePath(srcFile, sourceRoot, packagePrefix);
733 for (final CompiledClass cc : paths) {
734 myCompileContext.getProgressIndicator().checkCanceled();
735 if (LOG.isDebugEnabled()) {
736 LOG.debug("Checking [pathToClass; relPathToSource] = " + cc);
738 if (FileUtil.pathsEqual(filePath, cc.relativePathToSource)) {
739 final String outputPath = cc.pathToClass.replace(File.separatorChar, '/');
740 final Pair<String, String> realLocation = moveToRealLocation(outputDir, outputPath, srcFile, filesToRefresh);
741 if (realLocation != null) {
742 Collection<TranslatingCompiler.OutputItem> outputs = results.get(realLocation.getFirst());
743 if (outputs == null) {
744 outputs = new ArrayList<TranslatingCompiler.OutputItem>();
745 results.put(realLocation.getFirst(), outputs);
747 outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile));
748 if (CompilerConfiguration.MAKE_ENABLED) {
749 newCache.setPath(cc.qName, realLocation.getSecond());
751 if (LOG.isDebugEnabled()) {
752 LOG.debug("Added output item: [outputDir; outputPath; sourceFile] = [" + realLocation.getFirst() + "; " +
753 realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]");
755 if (!compiledWithErrors.contains(srcFile)) {
756 mySuccesfullyCompiledJavaFiles.add(srcFile);
760 myCompileContext.addMessage(CompilerMessageCategory.ERROR, "Failed to copy from temporary location to output directory: " + outputPath + " (see idea.log for details)", null, -1, -1);
761 if (LOG.isDebugEnabled()) {
762 LOG.debug("Failed to move to real location: " + outputPath + "; from " + outputDir);
773 * @param packagePrefix
774 * @return A 'package'-path to a given src file relative to a specified root. "/" slashes must be used
776 protected String calcPackagePath(VirtualFile srcFile, VirtualFile sourceRoot, String packagePrefix) {
777 final String prefix = packagePrefix != null && packagePrefix.length() > 0 ? packagePrefix.replace('.', '/') + "/" : "";
778 return prefix + VfsUtil.getRelativePath(srcFile, sourceRoot, '/');
782 private Pair<String, String> moveToRealLocation(String tempOutputDir, String pathToClass, VirtualFile sourceFile, final List<File> filesToRefresh) {
783 final Module module = myCompileContext.getModuleByFile(sourceFile);
784 if (module == null) {
785 final String message =
786 "Cannot determine module for source file: " + sourceFile.getPresentableUrl() + ";\nCorresponding output file: " + pathToClass;
788 myCompileContext.addMessage(CompilerMessageCategory.WARNING, message, sourceFile.getUrl(), -1, -1);
789 // do not move: looks like source file has been invalidated, need recompilation
790 return new Pair<String, String>(tempOutputDir, pathToClass);
792 final String realOutputDir;
793 if (myCompileContext.isInTestSourceContent(sourceFile)) {
794 realOutputDir = getTestsOutputDir(module);
797 realOutputDir = getOutputDir(module);
800 if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move
801 filesToRefresh.add(new File(pathToClass));
802 return new Pair<String, String>(realOutputDir, pathToClass);
805 final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length());
806 final File fromFile = new File(pathToClass);
807 final File toFile = new File(realPathToClass);
809 boolean success = fromFile.renameTo(toFile);
811 // assuming cause of the fail: intermediate dirs do not exist
812 FileUtil.createParentDirs(toFile);
813 // retry after making non-existent dirs
814 success = fromFile.renameTo(toFile);
816 if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints.
818 FileUtil.copy(fromFile, toFile);
819 FileUtil.delete(fromFile);
822 catch (IOException e) {
828 filesToRefresh.add(toFile);
829 return new Pair<String, String>(realOutputDir, realPathToClass);
834 private final Map<Module, String> myModuleToTestsOutput = new HashMap<Module, String>();
836 private String getTestsOutputDir(final Module module) {
837 if (myModuleToTestsOutput.containsKey(module)) {
838 return myModuleToTestsOutput.get(module);
840 final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module);
841 final String out = outputDirectory != null? outputDirectory.getPath() : null;
842 myModuleToTestsOutput.put(module, out);
846 private final Map<Module, String> myModuleToOutput = new HashMap<Module, String>();
848 private String getOutputDir(final Module module) {
849 if (myModuleToOutput.containsKey(module)) {
850 return myModuleToOutput.get(module);
852 final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module);
853 final String out = outputDirectory != null? outputDirectory.getPath() : null;
854 myModuleToOutput.put(module, out);
858 private volatile int myProcessedFilesCount = 0;
859 private volatile int myClassesCount = 0;
860 private volatile String myModuleName = null;
862 private void sourceFileProcessed() {
863 myProcessedFilesCount++;
867 private void updateStatistics() {
869 String moduleName = myModuleName;
870 if (moduleName != null) {
871 msg = CompilerBundle.message("statistics.files.classes.module", myProcessedFilesCount, myClassesCount, moduleName);
874 msg = CompilerBundle.message("statistics.files.classes", myProcessedFilesCount, myClassesCount);
876 myCompileContext.getProgressIndicator().setText2(msg);
877 //myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile);
880 private class ClassParsingThread implements Runnable {
881 private final BlockingQueue<FileObject> myPaths = new ArrayBlockingQueue<FileObject>(50000);
882 private CacheCorruptedException myError = null;
883 private final boolean myAddNotNullAssertions;
884 private final boolean myIsJdk16;
885 private final String myOutputDir;
887 private ClassParsingThread(final boolean isJdk16, String outputDir) {
889 myOutputDir = FileUtil.toSystemIndependentName(outputDir);
890 myAddNotNullAssertions = CompilerWorkspaceConfiguration.getInstance(myProject).ASSERT_NOT_NULL;
893 private volatile boolean processing;
898 FileObject path = myPaths.take();
900 if (path == myStopThreadToken) break;
904 catch (InterruptedException e) {
907 catch (CacheCorruptedException e) {
915 public void addPath(FileObject path) throws CacheCorruptedException {
916 if (myError != null) {
922 public void stopParsing() {
923 myPaths.offer(myStopThreadToken);
926 private void processPath(FileObject fileObject) throws CacheCorruptedException {
927 File file = fileObject.getFile();
928 final String path = file.getPath();
930 if (CompilerConfiguration.MAKE_ENABLED) {
931 byte[] fileContent = fileObject.getContent();
932 // the file is assumed to exist!
933 final DependencyCache dependencyCache = myCompileContext.getDependencyCache();
934 final int newClassQName = dependencyCache.reparseClassFile(file, fileContent);
935 final Cache newClassesCache = dependencyCache.getNewClassesCache();
936 final String sourceFileName = newClassesCache.getSourceFileName(newClassQName);
937 final String qName = dependencyCache.resolve(newClassQName);
938 String relativePathToSource = "/" + MakeUtil.createRelativePathToSource(qName, sourceFileName);
939 putName(sourceFileName, newClassQName, relativePathToSource, path);
940 boolean haveToInstrument = myAddNotNullAssertions && hasNotNullAnnotations(newClassesCache, dependencyCache.getSymbolTable(), newClassQName);
942 if (haveToInstrument) {
944 ClassReader reader = new ClassReader(fileContent, 0, fileContent.length);
945 ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16);
947 final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
948 reader.accept(instrumenter, 0);
949 if (instrumenter.isModification()) {
950 fileObject = new FileObject(file, writer.toByteArray());
953 catch (Exception ignored) {
961 final String _path = FileUtil.toSystemIndependentName(path);
962 final int dollarIndex = _path.indexOf('$');
963 final int tailIndex = dollarIndex >=0 ? dollarIndex : _path.length() - ".class".length();
964 final int slashIndex = _path.lastIndexOf('/');
965 final String sourceFileName = _path.substring(slashIndex + 1, tailIndex) + ".java";
966 String relativePathToSource = _path.substring(myOutputDir.length(), tailIndex) + ".java";
967 putName(sourceFileName, 0 /*doesn't matter here*/ , relativePathToSource.startsWith("/")? relativePathToSource : "/" + relativePathToSource, path);
970 catch (ClsFormatException e) {
971 final String m = e.getMessage();
972 String message = CompilerBundle.message("error.bad.class.file.format", StringUtil.isEmpty(m) ? path : m + "\n" + path);
973 myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
976 catch (IOException e) {
977 myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
987 private static boolean hasNotNullAnnotations(final Cache cache, final SymbolTable symbolTable, final int className) throws CacheCorruptedException {
988 for (MethodInfo methodId : cache.getMethods(className)) {
989 for (AnnotationConstantValue annotation : methodId.getRuntimeInvisibleAnnotations()) {
990 if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) {
994 final AnnotationConstantValue[][] paramAnnotations = methodId.getRuntimeInvisibleParameterAnnotations();
995 for (AnnotationConstantValue[] _singleParamAnnotations : paramAnnotations) {
996 for (AnnotationConstantValue annotation : _singleParamAnnotations) {
997 if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) {
1006 private static boolean isJdk6(final Sdk jdk) {
1007 boolean isJDK16 = false;
1009 final String versionString = jdk.getVersionString();
1010 if (versionString != null) {
1011 isJDK16 = versionString.contains("1.6") || versionString.contains("6.0");