TreeUi: lost nodes fixed when tree gets collapsed on expansion callback
[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.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;
64
65 import java.io.File;
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(new File(""), new byte[0]);
89   public final Map<String, Set<CompiledClass>> myFileNameToSourceMap=  new THashMap<String, Set<CompiledClass>>();
90
91
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) {
96     myChunk = chunk;
97     myProject = project;
98     myCompiler = compiler;
99     myCompileContext = compileContext;
100     myFilesToCompile = filesToCompile;
101     myFilesToRecompile = new HashSet<VirtualFile>(filesToCompile);
102     mySink = sink;
103     myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
104     mySuccesfullyCompiledJavaFiles = new HashSet<VirtualFile>(filesToCompile.size());
105   }
106
107   public void compile() throws CompilerException, CacheCorruptedException {
108     Application application = ApplicationManager.getApplication();
109     final Set<VirtualFile> allDependent = new HashSet<VirtualFile>();
110     COMPILE:
111     try {
112       if (!myFilesToCompile.isEmpty()) {
113         if (application.isUnitTestMode()) {
114           saveTestData();
115         }
116
117         compileModules(buildModuleToFilesMap(myFilesToCompile));
118       }
119
120       Collection<VirtualFile> dependentFiles;
121       do {
122         dependentFiles = CacheUtils.findDependentFiles(myCompileContext, mySuccesfullyCompiledJavaFiles, myCompiler.getDependencyProcessor(), DEPENDENCY_FILTER);
123
124         if (!dependentFiles.isEmpty()) {
125           myFilesToRecompile.addAll(dependentFiles);
126           allDependent.addAll(dependentFiles);
127           if (myCompileContext.getProgressIndicator().isCanceled() || myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
128             break COMPILE;
129           }
130           final List<VirtualFile> filesInScope = getFilesInScope(dependentFiles);
131           if (filesInScope.isEmpty()) {
132             break;
133           }
134           myCompileContext.getDependencyCache().clearTraverseRoots();
135           compileModules(buildModuleToFilesMap(filesInScope));
136         }
137       }
138       while (!dependentFiles.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0);
139     }
140     catch (SecurityException e) {
141       throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e);
142     }
143     catch (IllegalArgumentException e) {
144       throw new CompilerException(e.getMessage(), e);
145     }
146     finally {
147       for (final VirtualFile file : myModuleToTempDirMap.values()) {
148         if (file != null) {
149           final File ioFile = new File(file.getPath());
150           FileUtil.asyncDelete(ioFile);
151         }
152       }
153       myModuleToTempDirMap.clear();
154     }
155
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();
162
163         indicator.pushState();
164         indicator.setText(CompilerBundle.message("progress.updating.caches"));
165         indicator.setText2("");
166
167         cache.update();
168
169         indicator.setText(CompilerBundle.message("progress.saving.caches"));
170         cache.resetState();
171
172         indicator.popState();
173       }
174     }
175
176     myFilesToRecompile.removeAll(mySuccesfullyCompiledJavaFiles);
177     if (myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) != 0) {
178       myFilesToRecompile.addAll(allDependent);
179     }
180     final List<TranslatingCompiler.OutputItem> outputs = processPackageInfoFiles();
181     if (myFilesToRecompile.size() > 0 || outputs.size() > 0) {
182       mySink.add(null, outputs, VfsUtil.toVirtualFileArray(myFilesToRecompile));
183     }
184   }
185
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));
189     }
190     return CompilerUtil.buildModuleToFilesMap(myCompileContext, filesToCompile);
191   }
192
193   // package-info.java hack
194   private List<TranslatingCompiler.OutputItem> processPackageInfoFiles() {
195     if (myFilesToRecompile.isEmpty()) {
196       return Collections.emptyList();
197     }
198     final List<TranslatingCompiler.OutputItem> outputs = new ArrayList<TranslatingCompiler.OutputItem>();
199     ApplicationManager.getApplication().runReadAction(new Runnable() {
200       public void run() {
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);
205           }
206         }
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);
213             }
214           }
215         }
216       }
217     });
218     return outputs;
219   }
220
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() {
224       public void run() {
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);
230             }
231           }
232         }
233       }
234     });
235     return filesInScope;
236   }
237
238   private void compileModules(final Map<Module, List<VirtualFile>> moduleToFilesMap) throws CompilerException {
239     myProcessedFilesCount = 0;
240     try {
241       compileChunk(new ModuleChunk(myCompileContext, myChunk, moduleToFilesMap));
242     }
243     catch (IOException e) {
244       throw new CompilerException(e.getMessage(), e);
245     }
246   }
247
248   private void compileChunk(ModuleChunk chunk) throws IOException {
249     runTransformingCompilers(chunk);
250
251     setPresentableNameFor(chunk);
252
253     final List<OutputDir> outs = new ArrayList<OutputDir>();
254     File fileToDelete = getOutputDirsToCompileTo(chunk, outs);
255
256     try {
257       for (final OutputDir outputDir : outs) {
258         chunk.setSourcesFilter(outputDir.getKind());
259         doCompile(chunk, outputDir.getPath());
260       }
261     }
262     finally {
263       if (fileToDelete != null) {
264         FileUtil.asyncDelete(fileToDelete);
265       }
266     }
267   }
268
269
270   private void setPresentableNameFor(final ModuleChunk chunk) {
271     ApplicationManager.getApplication().runReadAction(new Runnable() {
272       public void run() {
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];
277           if (idx > 0) {
278             moduleName.append(", ");
279           }
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("...");
283             break;
284           }
285         }
286         myModuleName = moduleName.toString();
287       }
288     });
289   }
290
291   @Nullable
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() {
297         public void run() {
298           final String sourcesOutputDir = getOutputDir(module);
299           if (shouldCompileTestsSeparately(module)) {
300             if (sourcesOutputDir != null) {
301               dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES));
302             }
303             final String testsOutputDir = getTestsOutputDir(module);
304             if (testsOutputDir == null) {
305               LOG.error("Tests output dir is null for module \"" + module.getName() + "\"");
306             }
307             else {
308               dirs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES));
309             }
310           }
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() + "\"");
314             }
315             else {
316               dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES));
317             }
318           }
319         }
320       });
321     }
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));
326     }
327     return fileToDelete;
328   }
329
330
331   private boolean shouldCompileTestsSeparately(Module module) {
332     final String moduleTestOutputDirectory = getTestsOutputDir(module);
333     if (moduleTestOutputDirectory == null) {
334       return false;
335     }
336     final String moduleOutputDirectory = getOutputDir(module);
337     return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory);
338   }
339
340   private void saveTestData() {
341     ApplicationManager.getApplication().runReadAction(new Runnable() {
342       public void run() {
343         for (VirtualFile file : myFilesToCompile) {
344           CompilerManagerImpl.addCompiledPath(file.getPath());
345         }
346       }
347     });
348   }
349
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());
357
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);
362     }
363   };
364
365   private final Object lock = new Object();
366
367   private class SynchedCompilerParsing extends CompilerParsingThread {
368     private final ClassParsingThread myClassParsingThread;
369
370     private SynchedCompilerParsing(Process process,
371                                   final CompileContext context,
372                                   OutputParser outputParser,
373                                   ClassParsingThread classParsingThread,
374                                   boolean readErrorStream,
375                                   boolean trimLines) {
376       super(process, outputParser, readErrorStream, trimLines,context);
377       myClassParsingThread = classParsingThread;
378     }
379
380     public void setProgressText(String text) {
381       synchronized (lock) {
382         super.setProgressText(text);
383       }
384     }
385
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);
389       }
390     }
391
392     public void fileProcessed(String path) {
393       synchronized (lock) {
394         sourceFileProcessed();
395       }
396     }
397
398     protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException {
399       synchronized (lock) {
400         myClassParsingThread.addPath(classFileToProcess);
401       }
402     }
403   }
404
405   private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir) throws IOException {
406     myCompileContext.getProgressIndicator().checkCanceled();
407
408     if (ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
409       public Boolean compute() {
410         return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE;
411       }
412     }).booleanValue()) {
413       return; // should not invoke javac with empty sources list
414     }
415
416     ModuleType moduleType = chunk.getModules()[0].getModuleType();
417     if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) &&
418         !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) {
419       // TODO
420       // don't try to compile non-java type module
421       return;
422     }
423
424     int exitValue = 0;
425     try {
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);
430
431       OutputParser errorParser = myCompiler.createErrorParser(outputDir, process);
432       CompilerParsingThread errorParsingThread = errorParser == null
433                                                  ? 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);
439       }
440
441       OutputParser outputParser = myCompiler.createOutputParser(outputDir);
442       CompilerParsingThread outputParsingThread = outputParser == null
443                                                   ? 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);
449       }
450
451       try {
452         exitValue = process.waitFor();
453       }
454       catch (InterruptedException e) {
455         process.destroy();
456         exitValue = process.exitValue();
457       }
458       finally {
459         if (errorParsingThread != null) {
460           errorParsingThread.setProcessTerminated(true);
461         }
462         if (outputParsingThread != null) {
463           outputParsingThread.setProcessTerminated(true);
464         }
465         joinThread(errorParsingThreadFuture);
466         joinThread(outputParsingThreadFuture);
467         classParsingThread.stopParsing();
468         joinThread(classParsingThreadFuture);
469
470         registerParsingException(outputParsingThread);
471         registerParsingException(errorParsingThread);
472         assert outputParsingThread == null || !outputParsingThread.processing;
473         assert errorParsingThread == null || !errorParsingThread.processing;
474         assert classParsingThread == null || !classParsingThread.processing;
475       }
476     }
477     finally {
478       compileFinished(exitValue, chunk, outputDir);
479       myModuleName = null;
480     }
481   }
482
483   private static void joinThread(final Future<?> threadFuture) {
484     if (threadFuture != null) {
485       try {
486         threadFuture.get();
487       }
488       catch (InterruptedException ignored) {
489         LOG.info("Thread interrupted", ignored);
490       }
491       catch (ExecutionException ignored) {
492         LOG.info("Thread interrupted", ignored);
493       }
494     }
495   }
496
497   private void registerParsingException(final CompilerParsingThread outputParsingThread) {
498     Throwable error = outputParsingThread == null ? null : outputParsingThread.getError();
499     if (error != null) {
500       String message = error.getMessage();
501       if (error instanceof CacheCorruptedException) {
502         myCompileContext.requestRebuildNextTime(message);
503       }
504       else {
505         myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
506       }
507     }
508   }
509
510   private void runTransformingCompilers(final ModuleChunk chunk) {
511     final JavaSourceTransformingCompiler[] transformers =
512       CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class);
513     if (transformers.length == 0) {
514       return;
515     }
516     if (LOG.isDebugEnabled()) {
517       LOG.debug("Running transforming compilers...");
518     }
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() {
524         public void run() {
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() {
530                   public void run() {
531                     try {
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);
536                     }
537                     catch (IOException e) {
538                       // skip it
539                     }
540                   }
541                 });
542               }
543             }
544           }
545         }
546       }, myCompileContext.getProgressIndicator().getModalityState());
547
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));
556             if (ok) {
557               chunk.substituteWithTransformedVersion(module, j, fileCopy);
558             }
559           }
560         }
561       }
562     }
563   }
564
565   private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException {
566     final String fileName = file.getName();
567     if (tempDir.findChild(fileName) != null) {
568       int idx = 0;
569       while (true) {
570         //noinspection HardCodedStringLiteral
571         final String dirName = "dir" + idx++;
572         final VirtualFile dir = tempDir.findChild(dirName);
573         if (dir == null) {
574           tempDir = tempDir.createChildDirectory(this, dirName);
575           break;
576         }
577         if (dir.findChild(fileName) == null) {
578           tempDir = dir;
579           break;
580         }
581       }
582     }
583     return VfsUtil.copyFile(this, file, tempDir);
584   }
585
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());
595       }
596       myModuleToTempDirMap.put(module, tempDir);
597     }
598     return tempDir;
599   }
600
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);
605     }
606
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>>();
610     try {
611       ApplicationManager.getApplication().runReadAction(new Runnable() {
612         public void run() {
613           final Set<VirtualFile> compiledWithErrors = getFilesCompiledWithErrors();
614           final FileTypeManager typeManager = FileTypeManager.getInstance();
615           final String outputDirPath = outputDir.replace(File.separatorChar, '/');
616           try {
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 + "\"");
622                 }
623                 buildOutputItemsList(outputDirPath, module, root, typeManager, compiledWithErrors, root, packagePrefix, toRefresh, results);
624               }
625             }
626           }
627           catch (CacheCorruptedException e) {
628             myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted"));
629             if (LOG.isDebugEnabled()) {
630               LOG.debug(e);
631             }
632           }
633         }
634       });
635     }
636     finally {
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
642       }
643     }
644     myFileNameToSourceMap.clear(); // clear the map before the next use
645   }
646
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();
654         if (file != null) {
655           compiledWithErrors.add(file);
656         }
657       }
658     }
659     return compiledWithErrors;
660   }
661
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) {
671         try {
672           assert child.isValid();
673           if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(typeManager.getFileTypeByFile(child))) {
674             updateOutputItemsList(outputDir, child, compiledWithErrors, sourceRoot, packagePrefix, filesToRefresh, results);
675           }
676           return true;
677         }
678         catch (CacheCorruptedException e) {
679           exRef.set(e);
680           return false;
681         }
682       }
683     };
684     if (fileIndex.isInContent(from)) {
685       // use file index for iteration to handle 'inner modules' and excludes properly
686       fileIndex.iterateContentUnderDirectory(from, contentIterator);
687     }
688     else {
689       // seems to be a root for generated sources
690       new Object() {
691         void iterateContent(VirtualFile from) {
692           for (VirtualFile child : from.getChildren()) {
693             if (child.isDirectory()) {
694               iterateContent(child);
695             }
696             else {
697               contentIterator.processFile(child);
698             }
699           }
700         }
701       }.iterateContent(from);
702     }
703     final CacheCorruptedException exc = exRef.get();
704     if (exc != null) {
705       throw exc;
706     }
707   }
708
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 + "]");
713     }
714     Set<CompiledClass> paths = myFileNameToSourceMap.get(sourceFileName);
715
716     if (paths == null) {
717       paths = new HashSet<CompiledClass>();
718       myFileNameToSourceMap.put(sourceFileName, paths);
719     }
720     paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass));
721   }
722
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()) {
730       return;
731     }
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);
737       }
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);
746           }
747           outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile));
748           if (CompilerConfiguration.MAKE_ENABLED) {
749             newCache.setPath(cc.qName, realLocation.getSecond());
750           }
751           if (LOG.isDebugEnabled()) {
752             LOG.debug("Added output item: [outputDir; outputPath; sourceFile]  = [" + realLocation.getFirst() + "; " +
753                       realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]");
754           }
755           if (!compiledWithErrors.contains(srcFile)) {
756             mySuccesfullyCompiledJavaFiles.add(srcFile);
757           }
758         }
759         else {
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);
763           }
764         }
765       }
766     }
767   }
768
769   /**
770    *
771    * @param srcFile
772    * @param sourceRoot
773    * @param packagePrefix
774    * @return A 'package'-path to a given src file relative to a specified root. "/" slashes must be used
775    */
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, '/');
779   }
780
781   @Nullable
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;
787       LOG.info(message);
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);
791     }
792     final String realOutputDir;
793     if (myCompileContext.isInTestSourceContent(sourceFile)) {
794       realOutputDir = getTestsOutputDir(module);
795     }
796     else {
797       realOutputDir = getOutputDir(module);
798     }
799
800     if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move
801       filesToRefresh.add(new File(pathToClass));
802       return new Pair<String, String>(realOutputDir, pathToClass);
803     }
804
805     final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length());
806     final File fromFile = new File(pathToClass);
807     final File toFile = new File(realPathToClass);
808
809     boolean success = fromFile.renameTo(toFile);
810     if (!success) {
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);
815     }
816     if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints.
817       try {
818         FileUtil.copy(fromFile, toFile);
819         FileUtil.delete(fromFile);
820         success = true;
821       }
822       catch (IOException e) {
823         LOG.info(e);
824         success = false;
825       }
826     }
827     if (success) {
828       filesToRefresh.add(toFile);
829       return new Pair<String, String>(realOutputDir, realPathToClass);
830     }
831     return null;
832   }
833
834   private final Map<Module, String> myModuleToTestsOutput = new HashMap<Module, String>();
835
836   private String getTestsOutputDir(final Module module) {
837     if (myModuleToTestsOutput.containsKey(module)) {
838       return myModuleToTestsOutput.get(module);
839     }
840     final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module);
841     final String out = outputDirectory != null? outputDirectory.getPath() : null;
842     myModuleToTestsOutput.put(module, out);
843     return out;
844   }
845
846   private final Map<Module, String> myModuleToOutput = new HashMap<Module, String>();
847
848   private String getOutputDir(final Module module) {
849     if (myModuleToOutput.containsKey(module)) {
850       return myModuleToOutput.get(module);
851     }
852     final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module);
853     final String out = outputDirectory != null? outputDirectory.getPath() : null;
854     myModuleToOutput.put(module, out);
855     return out;
856   }
857
858   private volatile int myProcessedFilesCount = 0;
859   private volatile int myClassesCount = 0;
860   private volatile String myModuleName = null;
861
862   private void sourceFileProcessed() {
863     myProcessedFilesCount++;
864     updateStatistics();
865   }
866
867   private void updateStatistics() {
868     final String msg;
869     String moduleName = myModuleName;
870     if (moduleName != null) {
871       msg = CompilerBundle.message("statistics.files.classes.module", myProcessedFilesCount, myClassesCount, moduleName);
872     }
873     else {
874       msg = CompilerBundle.message("statistics.files.classes", myProcessedFilesCount, myClassesCount);
875     }
876     myCompileContext.getProgressIndicator().setText2(msg);
877     //myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile);
878   }
879
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;
886
887     private ClassParsingThread(final boolean isJdk16, String outputDir) {
888       myIsJdk16 = isJdk16;
889       myOutputDir = FileUtil.toSystemIndependentName(outputDir);
890       myAddNotNullAssertions = CompilerWorkspaceConfiguration.getInstance(myProject).ASSERT_NOT_NULL;
891     }
892
893     private volatile boolean processing;
894     public void run() {
895       processing = true;
896       try {
897         while (true) {
898           FileObject path = myPaths.take();
899
900           if (path == myStopThreadToken) break;
901           processPath(path);
902         }
903       }
904       catch (InterruptedException e) {
905         LOG.error(e);
906       }
907       catch (CacheCorruptedException e) {
908         myError = e;
909       }
910       finally {
911         processing = false;
912       }
913     }
914
915     public void addPath(FileObject path) throws CacheCorruptedException {
916       if (myError != null) {
917         throw myError;
918       }
919       myPaths.offer(path);
920     }
921
922     public void stopParsing() {
923       myPaths.offer(myStopThreadToken);
924     }
925
926     private void processPath(FileObject fileObject) throws CacheCorruptedException {
927       File file = fileObject.getFile();
928       final String path = file.getPath();
929       try {
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);
941
942           if (haveToInstrument) {
943             try {
944               ClassReader reader = new ClassReader(fileContent, 0, fileContent.length);
945               ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16);
946
947               final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
948               reader.accept(instrumenter, 0);
949               if (instrumenter.isModification()) {
950                 fileObject = new FileObject(file, writer.toByteArray());
951               }
952             }
953             catch (Exception ignored) {
954               LOG.info(ignored);
955             }
956           }
957
958           fileObject.save();
959         }
960         else {
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);
968         }
969       }
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);
974         LOG.info(e);
975       }
976       catch (IOException e) {
977         myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
978         LOG.info(e);
979       }
980       finally {
981         myClassesCount++;
982         updateStatistics();
983       }
984     }
985   }
986
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()))) {
991           return true;
992         }
993       }
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()))) {
998             return true;
999           }
1000         }
1001       }
1002     }
1003     return false;
1004   }
1005
1006   private static boolean isJdk6(final Sdk jdk) {
1007     boolean isJDK16 = false;
1008     if (jdk != null) {
1009       final String versionString = jdk.getVersionString();
1010       if (versionString != null) {
1011         isJDK16 = versionString.contains("1.6") || versionString.contains("6.0");
1012       }
1013     }
1014     return isJDK16;
1015   }
1016 }