Added exception logging to complier wrapper
[idea/community.git] / java / compiler / impl / src / com / intellij / compiler / impl / javaCompiler / BackendCompilerWrapper.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
18  * @author: Eugene Zhuravlev
19  * Date: Jan 24, 2003
20  * Time: 4:25:47 PM
21  */
22 package com.intellij.compiler.impl.javaCompiler;
23
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.vfs.LocalFileSystem;
51 import com.intellij.openapi.vfs.VfsUtil;
52 import com.intellij.openapi.vfs.VirtualFile;
53 import com.intellij.util.Chunk;
54 import com.intellij.util.Function;
55 import com.intellij.util.cls.ClsFormatException;
56 import gnu.trove.THashMap;
57 import gnu.trove.TIntHashSet;
58 import org.jetbrains.annotations.NonNls;
59 import org.jetbrains.annotations.NotNull;
60 import org.jetbrains.annotations.Nullable;
61 import org.objectweb.asm.ClassReader;
62 import org.objectweb.asm.ClassWriter;
63
64 import java.io.File;
65 import java.io.FileNotFoundException;
66 import java.io.IOException;
67 import java.util.*;
68 import java.util.concurrent.ArrayBlockingQueue;
69 import java.util.concurrent.BlockingQueue;
70 import java.util.concurrent.ExecutionException;
71 import java.util.concurrent.Future;
72
73 public class BackendCompilerWrapper {
74   private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper");
75
76   private final BackendCompiler myCompiler;
77   private final Set<VirtualFile> mySuccesfullyCompiledJavaFiles; // VirtualFile
78
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(null,null);
89   private long myCompilationDuration = 0L;
90   public final Map<String, Set<CompiledClass>> myFileNameToSourceMap=  new THashMap<String, Set<CompiledClass>>();
91
92
93   public BackendCompilerWrapper(Chunk<Module> chunk, @NotNull final Project project,
94                                 @NotNull List<VirtualFile> filesToCompile,
95                                 @NotNull CompileContextEx compileContext,
96                                 @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) {
97     myChunk = chunk;
98     myProject = project;
99     myCompiler = compiler;
100     myCompileContext = compileContext;
101     myFilesToCompile = filesToCompile;
102     myFilesToRecompile = new HashSet<VirtualFile>(filesToCompile);
103     mySink = sink;
104     myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
105     mySuccesfullyCompiledJavaFiles = new HashSet<VirtualFile>(filesToCompile.size());
106   }
107
108   public void compile() throws CompilerException, CacheCorruptedException {
109     Application application = ApplicationManager.getApplication();
110     final Set<VirtualFile> allDependent = new HashSet<VirtualFile>();
111     COMPILE:
112     try {
113       if (!myFilesToCompile.isEmpty()) {
114         if (application.isUnitTestMode()) {
115           saveTestData();
116         }
117
118         compileModules(buildModuleToFilesMap(myFilesToCompile));
119       }
120
121       Collection<VirtualFile> dependentFiles;
122       do {
123         dependentFiles = CacheUtils.findDependentFiles(myCompileContext, mySuccesfullyCompiledJavaFiles, myCompiler.getDependencyProcessor(), DEPENDENCY_FILTER);
124
125         if (!dependentFiles.isEmpty()) {
126           myFilesToRecompile.addAll(dependentFiles);
127           allDependent.addAll(dependentFiles);
128           if (myCompileContext.getProgressIndicator().isCanceled() || myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
129             break COMPILE;
130           }
131           final List<VirtualFile> filesInScope = getFilesInScope(dependentFiles);
132           if (filesInScope.isEmpty()) {
133             break;
134           }
135           myCompileContext.getDependencyCache().clearTraverseRoots();
136           compileModules(buildModuleToFilesMap(filesInScope));
137         }
138       }
139       while (!dependentFiles.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0);
140     }
141     catch (SecurityException e) {
142       throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e);
143     }
144     catch (IllegalArgumentException e) {
145       throw new CompilerException(e.getMessage(), e);
146     }
147     finally {
148       CompilerUtil.logDuration(myCompiler.getId() + " running", myCompilationDuration);
149       for (final VirtualFile file : myModuleToTempDirMap.values()) {
150         if (file != null) {
151           final File ioFile = new File(file.getPath());
152           FileUtil.asyncDelete(ioFile);
153         }
154       }
155       myModuleToTempDirMap.clear();
156     }
157
158     // do not update caches if cancelled because there is a chance that they will be incomplete
159     if (CompilerConfiguration.MAKE_ENABLED) {
160       if (!myCompileContext.getProgressIndicator().isCanceled()) {
161         // when cancelled pretend nothing was compiled and next compile will compile everything from the scratch
162         final ProgressIndicator indicator = myCompileContext.getProgressIndicator();
163         final DependencyCache cache = myCompileContext.getDependencyCache();
164
165         indicator.pushState();
166         indicator.setText(CompilerBundle.message("progress.updating.caches"));
167         indicator.setText2("");
168
169         cache.update();
170
171         indicator.setText(CompilerBundle.message("progress.saving.caches"));
172         cache.resetState();
173
174         indicator.popState();
175       }
176     }
177
178     myFilesToRecompile.removeAll(mySuccesfullyCompiledJavaFiles);
179     if (myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) != 0) {
180       myFilesToRecompile.addAll(allDependent);
181     }
182     final List<TranslatingCompiler.OutputItem> outputs = processPackageInfoFiles();
183     if (myFilesToRecompile.size() > 0 || outputs.size() > 0) {
184       mySink.add(null, outputs, VfsUtil.toVirtualFileArray(myFilesToRecompile));
185     }
186   }
187
188   private Map<Module, List<VirtualFile>> buildModuleToFilesMap(final List<VirtualFile> filesToCompile) {
189     if (myChunk.getNodes().size() == 1) {
190       return Collections.singletonMap(myChunk.getNodes().iterator().next(), Collections.unmodifiableList(filesToCompile));
191     }
192     return CompilerUtil.buildModuleToFilesMap(myCompileContext, filesToCompile);
193   }
194
195   // package-info.java hack
196   private List<TranslatingCompiler.OutputItem> processPackageInfoFiles() {
197     if (myFilesToRecompile.isEmpty()) {
198       return Collections.emptyList();
199     }
200     final List<TranslatingCompiler.OutputItem> outputs = new ArrayList<TranslatingCompiler.OutputItem>();
201     ApplicationManager.getApplication().runReadAction(new Runnable() {
202       public void run() {
203         final List<VirtualFile> packageInfoFiles = new ArrayList<VirtualFile>(myFilesToRecompile.size());
204         for (final VirtualFile file : myFilesToRecompile) {
205           if (PACKAGE_ANNOTATION_FILE_NAME.equals(file.getName())) {
206             packageInfoFiles.add(file);
207           }
208         }
209         if (!packageInfoFiles.isEmpty()) {
210           final Set<VirtualFile> badFiles = getFilesCompiledWithErrors();
211           for (final VirtualFile packageInfoFile : packageInfoFiles) {
212             if (!badFiles.contains(packageInfoFile)) {
213               outputs.add(new OutputItemImpl(packageInfoFile));
214               myFilesToRecompile.remove(packageInfoFile);
215             }
216           }
217         }
218       }
219     });
220     return outputs;
221   }
222
223   private List<VirtualFile> getFilesInScope(final Collection<VirtualFile> files) {
224     final List<VirtualFile> filesInScope = new ArrayList<VirtualFile>(files.size());
225     ApplicationManager.getApplication().runReadAction(new Runnable() {
226       public void run() {
227         for (VirtualFile file : files) {
228           if (myCompileContext.getCompileScope().belongs(file.getUrl())) {
229             final Module module = myCompileContext.getModuleByFile(file);
230             if (myChunk.getNodes().contains(module)) {
231               filesInScope.add(file);
232             }
233           }
234         }
235       }
236     });
237     return filesInScope;
238   }
239
240   private void compileModules(final Map<Module, List<VirtualFile>> moduleToFilesMap) throws CompilerException {
241     myProcessedFilesCount = 0;
242     try {
243       compileChunk(new ModuleChunk(myCompileContext, myChunk, moduleToFilesMap));
244     }
245     catch (IOException e) {
246       throw new CompilerException(e.getMessage(), e);
247     }
248   }
249
250   private void compileChunk(ModuleChunk chunk) throws IOException {
251     runTransformingCompilers(chunk);
252
253     setPresentableNameFor(chunk);
254
255     final List<OutputDir> outs = new ArrayList<OutputDir>();
256     File fileToDelete = getOutputDirsToCompileTo(chunk, outs);
257
258     try {
259       for (final OutputDir outputDir : outs) {
260         chunk.setSourcesFilter(outputDir.getKind());
261         doCompile(chunk, outputDir.getPath());
262       }
263     }
264     finally {
265       if (fileToDelete != null) {
266         FileUtil.asyncDelete(fileToDelete);
267       }
268     }
269   }
270
271
272   private void setPresentableNameFor(final ModuleChunk chunk) {
273     ApplicationManager.getApplication().runReadAction(new Runnable() {
274       public void run() {
275         final Module[] modules = chunk.getModules();
276         StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8));
277         for (int idx = 0; idx < modules.length; idx++) {
278           final Module module = modules[idx];
279           if (idx > 0) {
280             moduleName.append(", ");
281           }
282           moduleName.append(module.getName());
283           if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) {
284             moduleName.append("...");
285             break;
286           }
287         }
288         myModuleName = moduleName.toString();
289       }
290     });
291   }
292
293   @Nullable
294   private File getOutputDirsToCompileTo(ModuleChunk chunk, final List<OutputDir> dirs) throws IOException {
295     File fileToDelete = null;
296     if (chunk.getModuleCount() == 1) { // optimization
297       final Module module = chunk.getModules()[0];
298       ApplicationManager.getApplication().runReadAction(new Runnable() {
299         public void run() {
300           final String sourcesOutputDir = getOutputDir(module);
301           if (shouldCompileTestsSeparately(module)) {
302             if (sourcesOutputDir != null) {
303               dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES));
304             }
305             final String testsOutputDir = getTestsOutputDir(module);
306             if (testsOutputDir == null) {
307               LOG.error("Tests output dir is null for module \"" + module.getName() + "\"");
308             }
309             else {
310               dirs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES));
311             }
312           }
313           else { // both sources and test sources go into the same output
314             if (sourcesOutputDir == null) {
315               LOG.error("Sources output dir is null for module \"" + module.getName() + "\"");
316             }
317             else {
318               dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES));
319             }
320           }
321         }
322       });
323     }
324     else { // chunk has several modules
325       final File outputDir = FileUtil.createTempDirectory("compile", "output");
326       fileToDelete = outputDir;
327       dirs.add(new OutputDir(outputDir.getPath(), ModuleChunk.ALL_SOURCES));
328     }
329     return fileToDelete;
330   }
331
332
333   private boolean shouldCompileTestsSeparately(Module module) {
334     final String moduleTestOutputDirectory = getTestsOutputDir(module);
335     if (moduleTestOutputDirectory == null) {
336       return false;
337     }
338     final String moduleOutputDirectory = getOutputDir(module);
339     return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory);
340   }
341
342   private void saveTestData() {
343     ApplicationManager.getApplication().runReadAction(new Runnable() {
344       public void run() {
345         for (VirtualFile file : myFilesToCompile) {
346           CompilerManagerImpl.addCompiledPath(file.getPath());
347         }
348       }
349     });
350   }
351
352   private final TIntHashSet myProcessedNames = new TIntHashSet();
353   private final Set<VirtualFile> myProcessedFiles = new HashSet<VirtualFile>();
354   private final Function<Pair<int[], Set<VirtualFile>>, Pair<int[], Set<VirtualFile>>> DEPENDENCY_FILTER = new Function<Pair<int[], Set<VirtualFile>>, Pair<int[], Set<VirtualFile>>>() {
355     public Pair<int[], Set<VirtualFile>> fun(Pair<int[], Set<VirtualFile>> deps) {
356       final TIntHashSet currentDeps = new TIntHashSet(deps.getFirst());
357       currentDeps.removeAll(myProcessedNames.toArray());
358       myProcessedNames.addAll(deps.getFirst());
359
360       final Set<VirtualFile> depFiles = new HashSet<VirtualFile>(deps.getSecond());
361       depFiles.removeAll(myProcessedFiles);
362       myProcessedFiles.addAll(deps.getSecond());
363       return new Pair<int[], Set<VirtualFile>>(currentDeps.toArray(), depFiles);
364     }
365   };
366
367   private final Object lock = new Object();
368
369   private class SynchedCompilerParsing extends CompilerParsingThread {
370     private final ClassParsingThread myClassParsingThread;
371
372     private SynchedCompilerParsing(Process process,
373                                   final CompileContext context,
374                                   OutputParser outputParser,
375                                   ClassParsingThread classParsingThread,
376                                   boolean readErrorStream,
377                                   boolean trimLines) {
378       super(process, outputParser, readErrorStream, trimLines,context);
379       myClassParsingThread = classParsingThread;
380     }
381
382     public void setProgressText(String text) {
383       synchronized (lock) {
384         super.setProgressText(text);
385       }
386     }
387
388     public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) {
389       synchronized (lock) {
390         super.message(category, message, url, lineNum, columnNum);
391       }
392     }
393
394     public void fileProcessed(String path) {
395       synchronized (lock) {
396         sourceFileProcessed();
397       }
398     }
399
400     protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException {
401       synchronized (lock) {
402         myClassParsingThread.addPath(classFileToProcess);
403       }
404     }
405   }
406
407   private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir) throws IOException {
408     myCompileContext.getProgressIndicator().checkCanceled();
409
410     if (ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
411       public Boolean compute() {
412         return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE;
413       }
414     }).booleanValue()) {
415       return; // should not invoke javac with empty sources list
416     }
417
418     ModuleType moduleType = chunk.getModules()[0].getModuleType();
419     if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) &&
420         !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) {
421       // TODO
422       // don't try to compile non-java type module
423       return;
424     }
425
426     int exitValue = 0;
427     try {
428       Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext);
429       final long compilationStart = System.currentTimeMillis();
430       final ClassParsingThread classParsingThread = new ClassParsingThread(isJdk6(chunk.getJdk()), outputDir);
431       final Future<?> classParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(classParsingThread);
432
433       OutputParser errorParser = myCompiler.createErrorParser(outputDir, process);
434       CompilerParsingThread errorParsingThread = errorParser == null
435                                                  ? null
436                                                  : new SynchedCompilerParsing(process, myCompileContext, errorParser, classParsingThread,
437                                                                               true, errorParser.isTrimLines());
438       Future<?> errorParsingThreadFuture = null;
439       if (errorParsingThread != null) {
440         errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread);
441       }
442
443       OutputParser outputParser = myCompiler.createOutputParser(outputDir);
444       CompilerParsingThread outputParsingThread = outputParser == null
445                                                   ? null
446                                                   : new SynchedCompilerParsing(process, myCompileContext, outputParser, classParsingThread,
447                                                                                false, outputParser.isTrimLines());
448       Future<?> outputParsingThreadFuture = null;
449       if (outputParsingThread != null) {
450         outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread);
451       }
452
453       try {
454         exitValue = process.waitFor();
455       }
456       catch (InterruptedException e) {
457         process.destroy();
458         exitValue = process.exitValue();
459       }
460       finally {
461         myCompilationDuration += (System.currentTimeMillis() - compilationStart);
462         if (errorParsingThread != null) {
463           errorParsingThread.setProcessTerminated(true);
464         }
465         if (outputParsingThread != null) {
466           outputParsingThread.setProcessTerminated(true);
467         }
468         joinThread(errorParsingThreadFuture);
469         joinThread(outputParsingThreadFuture);
470         classParsingThread.stopParsing();
471         joinThread(classParsingThreadFuture);
472
473         registerParsingException(outputParsingThread);
474         registerParsingException(errorParsingThread);
475         assert outputParsingThread == null || !outputParsingThread.processing;
476         assert errorParsingThread == null || !errorParsingThread.processing;
477         assert classParsingThread == null || !classParsingThread.processing;
478       }
479     }
480     finally {
481       compileFinished(exitValue, chunk, outputDir);
482       myModuleName = null;
483     }
484   }
485
486   private static void joinThread(final Future<?> threadFuture) {
487     if (threadFuture != null) {
488       try {
489         threadFuture.get();
490       }
491       catch (InterruptedException ignored) {
492         LOG.info("Thread interrupted", ignored);
493       }
494       catch (ExecutionException ignored) {
495         LOG.info("Thread interrupted", ignored);
496       }
497     }
498   }
499
500   private void registerParsingException(final CompilerParsingThread outputParsingThread) {
501     Throwable error = outputParsingThread == null ? null : outputParsingThread.getError();
502     if (error != null) {
503       String message = error.getMessage();
504       if (error instanceof CacheCorruptedException) {
505         myCompileContext.requestRebuildNextTime(message);
506       }
507       else {
508         myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
509       }
510     }
511   }
512
513   private void runTransformingCompilers(final ModuleChunk chunk) {
514     final JavaSourceTransformingCompiler[] transformers =
515       CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class);
516     if (transformers.length == 0) {
517       return;
518     }
519     if (LOG.isDebugEnabled()) {
520       LOG.debug("Running transforming compilers...");
521     }
522     final Module[] modules = chunk.getModules();
523     for (final JavaSourceTransformingCompiler transformer : transformers) {
524       final Map<VirtualFile, VirtualFile> originalToCopyFileMap = new HashMap<VirtualFile, VirtualFile>();
525       final Application application = ApplicationManager.getApplication();
526       application.invokeAndWait(new Runnable() {
527         public void run() {
528           for (final Module module : modules) {
529             List<VirtualFile> filesToCompile = chunk.getFilesToCompile(module);
530             for (final VirtualFile file : filesToCompile) {
531               if (transformer.isTransformable(file)) {
532                 application.runWriteAction(new Runnable() {
533                   public void run() {
534                     try {
535                       VirtualFile fileCopy = createFileCopy(getTempDir(module), file);
536                       originalToCopyFileMap.put(file, fileCopy);
537                     }
538                     catch (IOException e) {
539                       // skip it
540                     }
541                   }
542                 });
543               }
544             }
545           }
546         }
547       }, myCompileContext.getProgressIndicator().getModalityState());
548
549       // do actual transform
550       for (final Module module : modules) {
551         final List<VirtualFile> filesToCompile = chunk.getFilesToCompile(module);
552         for (int j = 0; j < filesToCompile.size(); j++) {
553           final VirtualFile file = filesToCompile.get(j);
554           VirtualFile fileCopy = originalToCopyFileMap.get(file);
555           if (fileCopy != null) {
556             final boolean ok = transformer.transform(myCompileContext, fileCopy, file);
557             if (ok) {
558               chunk.substituteWithTransformedVersion(module, j, fileCopy);
559             }
560           }
561         }
562       }
563     }
564   }
565
566   private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException {
567     final String fileName = file.getName();
568     if (tempDir.findChild(fileName) != null) {
569       int idx = 0;
570       while (true) {
571         //noinspection HardCodedStringLiteral
572         final String dirName = "dir" + idx++;
573         final VirtualFile dir = tempDir.findChild(dirName);
574         if (dir == null) {
575           tempDir = tempDir.createChildDirectory(this, dirName);
576           break;
577         }
578         if (dir.findChild(fileName) == null) {
579           tempDir = dir;
580           break;
581         }
582       }
583     }
584     return VfsUtil.copyFile(this, file, tempDir);
585   }
586
587   private VirtualFile getTempDir(Module module) throws IOException {
588     VirtualFile tempDir = myModuleToTempDirMap.get(module);
589     if (tempDir == null) {
590       final String projectName = myProject.getName();
591       final String moduleName = module.getName();
592       File tempDirectory = FileUtil.createTempDirectory(projectName, moduleName);
593       tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDirectory);
594       if (tempDir == null) {
595         LOG.error("Cannot locate temp directory " + tempDirectory.getPath());
596       }
597       myModuleToTempDirMap.put(module, tempDir);
598     }
599     return tempDir;
600   }
601
602   private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) {
603     if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() &&
604         myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
605       myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1);
606     }
607
608     myCompiler.compileFinished();
609     final List<File> toRefresh = new ArrayList<File>();
610     final Map<String, Collection<TranslatingCompiler.OutputItem>> results = new HashMap<String, Collection<TranslatingCompiler.OutputItem>>();
611     try {
612       ApplicationManager.getApplication().runReadAction(new Runnable() {
613         public void run() {
614           final Set<VirtualFile> compiledWithErrors = getFilesCompiledWithErrors();
615           final FileTypeManager typeManager = FileTypeManager.getInstance();
616           final String outputDirPath = outputDir.replace(File.separatorChar, '/');
617           try {
618             for (final Module module : chunk.getModules()) {
619               for (final VirtualFile root : chunk.getSourceRoots(module)) {
620                 final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root);
621                 if (LOG.isDebugEnabled()) {
622                   LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\"");
623                 }
624                 buildOutputItemsList(outputDirPath, module, root, typeManager, compiledWithErrors, root, packagePrefix, toRefresh, results);
625               }
626             }
627           }
628           catch (CacheCorruptedException e) {
629             myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted"));
630             if (LOG.isDebugEnabled()) {
631               LOG.debug(e);
632             }
633           }
634         }
635       });
636     }
637     finally {
638       CompilerUtil.refreshIOFiles(toRefresh);
639       for (Iterator<Map.Entry<String, Collection<TranslatingCompiler.OutputItem>>> it = results.entrySet().iterator(); it.hasNext();) {
640         Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry = it.next();
641         mySink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY);
642         it.remove(); // to free memory
643       }
644     }
645     myFileNameToSourceMap.clear(); // clear the map before the next use
646   }
647
648   private Set<VirtualFile> getFilesCompiledWithErrors() {
649     CompilerMessage[] messages = myCompileContext.getMessages(CompilerMessageCategory.ERROR);
650     Set<VirtualFile> compiledWithErrors = Collections.emptySet();
651     if (messages.length > 0) {
652       compiledWithErrors = new HashSet<VirtualFile>(messages.length);
653       for (CompilerMessage message : messages) {
654         final VirtualFile file = message.getVirtualFile();
655         if (file != null) {
656           compiledWithErrors.add(file);
657         }
658       }
659     }
660     return compiledWithErrors;
661   }
662
663   private void buildOutputItemsList(final String outputDir, Module module, VirtualFile from,
664                                     final FileTypeManager typeManager,
665                                     final Set<VirtualFile> compiledWithErrors,
666                                     final VirtualFile sourceRoot,
667                                     final String packagePrefix, final List<File> filesToRefresh, final Map<String, Collection<TranslatingCompiler.OutputItem>> results) throws CacheCorruptedException {
668     final Ref<CacheCorruptedException> exRef = new Ref<CacheCorruptedException>(null);
669     final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex();
670     final ContentIterator contentIterator = new ContentIterator() {
671       public boolean processFile(final VirtualFile child) {
672         try {
673           assert child.isValid();
674           if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(typeManager.getFileTypeByFile(child))) {
675             updateOutputItemsList(outputDir, child, compiledWithErrors, sourceRoot, packagePrefix, filesToRefresh, results);
676           }
677           return true;
678         }
679         catch (CacheCorruptedException e) {
680           exRef.set(e);
681           return false;
682         }
683       }
684     };
685     if (fileIndex.isInContent(from)) {
686       // use file index for iteration to handle 'inner modules' and excludes properly
687       fileIndex.iterateContentUnderDirectory(from, contentIterator);
688     }
689     else {
690       // seems to be a root for generated sources
691       new Object() {
692         void iterateContent(VirtualFile from) {
693           for (VirtualFile child : from.getChildren()) {
694             if (child.isDirectory()) {
695               iterateContent(child);
696             }
697             else {
698               contentIterator.processFile(child);
699             }
700           }
701         }
702       }.iterateContent(from);
703     }
704     final CacheCorruptedException exc = exRef.get();
705     if (exc != null) {
706       throw exc;
707     }
708   }
709
710   private void putName(String sourceFileName, int classQName, String relativePathToSource, String pathToClass) {
711     if (LOG.isDebugEnabled()) {
712       LOG.debug("Registering [sourceFileName, relativePathToSource, pathToClass] = [" + sourceFileName + "; " + relativePathToSource +
713                 "; " + pathToClass + "]");
714     }
715     Set<CompiledClass> paths = myFileNameToSourceMap.get(sourceFileName);
716
717     if (paths == null) {
718       paths = new HashSet<CompiledClass>();
719       myFileNameToSourceMap.put(sourceFileName, paths);
720     }
721     paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass));
722   }
723
724   private void updateOutputItemsList(final String outputDir, VirtualFile srcFile, Set<VirtualFile> compiledWithErrors,
725                                      VirtualFile sourceRoot,
726                                      final String packagePrefix, final List<File> filesToRefresh,
727                                      Map<String, Collection<TranslatingCompiler.OutputItem>> results) throws CacheCorruptedException {
728     final Cache newCache = myCompileContext.getDependencyCache().getNewClassesCache();
729     final Set<CompiledClass> paths = myFileNameToSourceMap.get(srcFile.getName());
730     if (paths == null || paths.isEmpty()) {
731       return;
732     }
733     final String prefix = packagePrefix != null && packagePrefix.length() > 0 ? packagePrefix.replace('.', '/') + "/" : "";
734     final String filePath = "/" + prefix + VfsUtil.getRelativePath(srcFile, sourceRoot, '/');
735     for (final CompiledClass cc : paths) {
736       myCompileContext.getProgressIndicator().checkCanceled();
737       if (LOG.isDebugEnabled()) {
738         LOG.debug("Checking [pathToClass; relPathToSource] = " + cc);
739       }
740       if (FileUtil.pathsEqual(filePath, cc.relativePathToSource)) {
741         final String outputPath = cc.pathToClass.replace(File.separatorChar, '/');
742         final Pair<String, String> realLocation = moveToRealLocation(outputDir, outputPath, srcFile, filesToRefresh);
743         if (realLocation != null) {
744           Collection<TranslatingCompiler.OutputItem> outputs = results.get(realLocation.getFirst());
745           if (outputs == null) {
746             outputs = new ArrayList<TranslatingCompiler.OutputItem>();
747             results.put(realLocation.getFirst(), outputs);
748           }
749           outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile));
750           if (CompilerConfiguration.MAKE_ENABLED) {
751             newCache.setPath(cc.qName, realLocation.getSecond());
752           }
753           if (LOG.isDebugEnabled()) {
754             LOG.debug("Added output item: [outputDir; outputPath; sourceFile]  = [" + realLocation.getFirst() + "; " +
755                       realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]");
756           }
757           if (!compiledWithErrors.contains(srcFile)) {
758             mySuccesfullyCompiledJavaFiles.add(srcFile);
759           }
760         }
761         else {
762           myCompileContext.addMessage(CompilerMessageCategory.ERROR, "Failed to copy from temporary location to output directory: " + outputPath + " (see idea.log for details)", null, -1, -1);
763           if (LOG.isDebugEnabled()) {
764             LOG.debug("Failed to move to real location: " + outputPath + "; from " + outputDir);
765           }
766         }
767       }
768     }
769   }
770
771   @Nullable
772   private Pair<String, String> moveToRealLocation(String tempOutputDir, String pathToClass, VirtualFile sourceFile, final List<File> filesToRefresh) {
773     final Module module = myCompileContext.getModuleByFile(sourceFile);
774     if (module == null) {
775       final String message =
776         "Cannot determine module for source file: " + sourceFile.getPresentableUrl() + ";\nCorresponding output file: " + pathToClass;
777       LOG.info(message);
778       myCompileContext.addMessage(CompilerMessageCategory.WARNING, message, sourceFile.getUrl(), -1, -1);
779       // do not move: looks like source file has been invalidated, need recompilation
780       return new Pair<String, String>(tempOutputDir, pathToClass);
781     }
782     final String realOutputDir;
783     if (myCompileContext.isInTestSourceContent(sourceFile)) {
784       realOutputDir = getTestsOutputDir(module);
785     }
786     else {
787       realOutputDir = getOutputDir(module);
788     }
789
790     if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move
791       filesToRefresh.add(new File(pathToClass));
792       return new Pair<String, String>(realOutputDir, pathToClass);
793     }
794
795     final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length());
796     final File fromFile = new File(pathToClass);
797     final File toFile = new File(realPathToClass);
798
799     boolean success = fromFile.renameTo(toFile);
800     if (!success) {
801       // assuming cause of the fail: intermediate dirs do not exist
802       FileUtil.createParentDirs(toFile);
803       // retry after making non-existent dirs
804       success = fromFile.renameTo(toFile);
805     }
806     if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints.
807       try {
808         FileUtil.copy(fromFile, toFile);
809         FileUtil.delete(fromFile);
810         success = true;
811       }
812       catch (IOException e) {
813         LOG.info(e);
814         success = false;
815       }
816     }
817     if (success) {
818       filesToRefresh.add(toFile);
819       return new Pair<String, String>(realOutputDir, realPathToClass);
820     }
821     return null;
822   }
823
824   private final Map<Module, String> myModuleToTestsOutput = new HashMap<Module, String>();
825
826   private String getTestsOutputDir(final Module module) {
827     if (myModuleToTestsOutput.containsKey(module)) {
828       return myModuleToTestsOutput.get(module);
829     }
830     final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module);
831     final String out = outputDirectory != null? outputDirectory.getPath() : null;
832     myModuleToTestsOutput.put(module, out);
833     return out;
834   }
835
836   private final Map<Module, String> myModuleToOutput = new HashMap<Module, String>();
837
838   private String getOutputDir(final Module module) {
839     if (myModuleToOutput.containsKey(module)) {
840       return myModuleToOutput.get(module);
841     }
842     final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module);
843     final String out = outputDirectory != null? outputDirectory.getPath() : null;
844     myModuleToOutput.put(module, out);
845     return out;
846   }
847
848   private volatile int myProcessedFilesCount = 0;
849   private volatile int myClassesCount = 0;
850   private volatile String myModuleName = null;
851
852   private void sourceFileProcessed() {
853     myProcessedFilesCount++;
854     updateStatistics();
855   }
856
857   private void updateStatistics() {
858     final String msg;
859     String moduleName = myModuleName;
860     if (moduleName != null) {
861       msg = CompilerBundle.message("statistics.files.classes.module", myProcessedFilesCount, myClassesCount, moduleName);
862     }
863     else {
864       msg = CompilerBundle.message("statistics.files.classes", myProcessedFilesCount, myClassesCount);
865     }
866     myCompileContext.getProgressIndicator().setText2(msg);
867     //myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile);
868   }
869
870   private class ClassParsingThread implements Runnable {
871     private final BlockingQueue<FileObject> myPaths = new ArrayBlockingQueue<FileObject>(50000);
872     private CacheCorruptedException myError = null;
873     private final boolean myAddNotNullAssertions;
874     private final boolean myIsJdk16;
875     private final String myOutputDir;
876
877     private ClassParsingThread(final boolean isJdk16, String outputDir) {
878       myIsJdk16 = isJdk16;
879       myOutputDir = FileUtil.toSystemIndependentName(outputDir);
880       myAddNotNullAssertions = CompilerWorkspaceConfiguration.getInstance(myProject).ASSERT_NOT_NULL;
881     }
882
883     volatile boolean processing;
884     public void run() {
885       processing = true;
886       try {
887         while (true) {
888           FileObject path = myPaths.take();
889
890           if (path == myStopThreadToken) break;
891           processPath(path);
892         }
893       }
894       catch (InterruptedException e) {
895         LOG.error(e);
896       }
897       catch (CacheCorruptedException e) {
898         myError = e;
899       }
900       processing = false;
901     }
902
903     public void addPath(FileObject path) throws CacheCorruptedException {
904       if (myError != null) {
905         throw myError;
906       }
907       myPaths.offer(path);
908     }
909
910     public void stopParsing() {
911       myPaths.offer(myStopThreadToken);
912     }
913
914     private void processPath(FileObject fileObject) throws CacheCorruptedException {
915       File file = fileObject.getFile();
916       final String path = file.getPath();
917       try {
918         if (CompilerConfiguration.MAKE_ENABLED) {
919           byte[] fileContent = fileObject.getContent();
920           // the file is assumed to exist!
921           final DependencyCache dependencyCache = myCompileContext.getDependencyCache();
922           final int newClassQName = dependencyCache.reparseClassFile(file, fileContent);
923           final Cache newClassesCache = dependencyCache.getNewClassesCache();
924           final String sourceFileName = newClassesCache.getSourceFileName(newClassQName);
925           final String qName = dependencyCache.resolve(newClassQName);
926           String relativePathToSource = "/" + MakeUtil.createRelativePathToSource(qName, sourceFileName);
927           putName(sourceFileName, newClassQName, relativePathToSource, path);
928           boolean haveToInstrument = myAddNotNullAssertions && hasNotNullAnnotations(newClassesCache, dependencyCache.getSymbolTable(), newClassQName);
929
930           boolean fileContentChanged = false;
931           if (haveToInstrument) {
932             try {
933               ClassReader reader = new ClassReader(fileContent, 0, fileContent.length);
934               ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16);
935
936               final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
937               reader.accept(instrumenter, 0);
938               if (instrumenter.isModification()) {
939                 fileContent = writer.toByteArray();
940                 fileContentChanged = true;
941               }
942             }
943             catch (Exception ignored) {
944               LOG.info(ignored);
945             }
946           }
947
948           if (fileContentChanged || !fileObject.isSaved()) {
949             writeFile(file, fileContent);
950           }
951         }
952         else {
953           final String _path = FileUtil.toSystemIndependentName(path);
954           final int dollarIndex = _path.indexOf('$');
955           final int tailIndex = dollarIndex >=0 ? dollarIndex : _path.length() - ".class".length();
956           final int slashIndex = _path.lastIndexOf('/');
957           final String sourceFileName = _path.substring(slashIndex + 1, tailIndex) + ".java";
958           String relativePathToSource = _path.substring(myOutputDir.length(), tailIndex) + ".java";
959           putName(sourceFileName, 0 /*doesn't matter here*/ , relativePathToSource.startsWith("/")? relativePathToSource : "/" + relativePathToSource, path);
960         }
961       }
962       catch (ClsFormatException e) {
963         String message;
964         final String m = e.getMessage();
965         if (m == null || "".equals(m)) {
966           message = CompilerBundle.message("error.bad.class.file.format", path);
967         }
968         else {
969           message = CompilerBundle.message("error.bad.class.file.format", m + "\n" + path);
970         }
971         myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
972       }
973       catch (IOException e) {
974         myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
975       }
976       finally {
977         myClassesCount++;
978         updateStatistics();
979       }
980     }
981
982     private void writeFile(File file, byte[] fileContent) throws IOException {
983       try {
984         FileUtil.writeToFile(file, fileContent);
985       }
986       catch (FileNotFoundException e) {
987         FileUtil.createParentDirs(file);
988         FileUtil.writeToFile(file, fileContent);
989       }
990     }
991   }
992
993   private static boolean hasNotNullAnnotations(final Cache cache, final SymbolTable symbolTable, final int className) throws CacheCorruptedException {
994     for (MethodInfo methodId : cache.getMethods(className)) {
995       for (AnnotationConstantValue annotation : methodId.getRuntimeInvisibleAnnotations()) {
996         if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) {
997           return true;
998         }
999       }
1000       final AnnotationConstantValue[][] paramAnnotations = methodId.getRuntimeInvisibleParameterAnnotations();
1001       for (AnnotationConstantValue[] _singleParamAnnotations : paramAnnotations) {
1002         for (AnnotationConstantValue annotation : _singleParamAnnotations) {
1003           if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) {
1004             return true;
1005           }
1006         }
1007       }
1008     }
1009     return false;
1010   }
1011
1012   private static boolean isJdk6(final Sdk jdk) {
1013     boolean isJDK16 = false;
1014     if (jdk != null) {
1015       final String versionString = jdk.getVersionString();
1016       if (versionString != null) {
1017         isJDK16 = versionString.contains("1.6") || versionString.contains("6.0");
1018       }
1019     }
1020     return isJDK16;
1021   }
1022 }