From 09ca0939a4d54f3b7c7884516fb575be18e87e8f Mon Sep 17 00:00:00 2001 From: Dmitry Avdeev Date: Fri, 16 Oct 2009 11:38:51 +0400 Subject: [PATCH] crlfs --- .../com/intellij/compiler/impl/CompileDriver.java | 4396 ++++++++++---------- .../impl/javaCompiler/BackendCompilerWrapper.java | 2198 +++++----- .../intellij/compiler/make/DependencyCache.java | 1342 +++--- 3 files changed, 3968 insertions(+), 3968 deletions(-) diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java index a502bc928e..ff3f5491d8 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java @@ -1,2198 +1,2198 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * @author: Eugene Zhuravlev - * Date: Jan 17, 2003 - * Time: 1:42:26 PM - */ -package com.intellij.compiler.impl; - -import com.intellij.CommonBundle; -import com.intellij.analysis.AnalysisScope; -import com.intellij.compiler.*; -import com.intellij.compiler.make.CacheCorruptedException; -import com.intellij.compiler.make.DependencyCache; -import com.intellij.compiler.progress.CompilerTask; -import com.intellij.diagnostic.IdeErrorsDialog; -import com.intellij.diagnostic.PluginException; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.compiler.*; -import com.intellij.openapi.compiler.Compiler; -import com.intellij.openapi.compiler.ex.CompileContextEx; -import com.intellij.openapi.compiler.ex.CompilerPathsEx; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.extensions.Extensions; -import com.intellij.openapi.extensions.PluginId; -import com.intellij.openapi.fileEditor.FileDocumentManager; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.fileTypes.StdFileTypes; -import com.intellij.openapi.module.LanguageLevelUtil; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.project.ProjectBundle; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.ContentEntry; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.roots.SourceFolder; -import com.intellij.openapi.roots.ex.ProjectRootManagerEx; -import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor; -import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; -import com.intellij.openapi.ui.MessageType; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileManager; -import com.intellij.openapi.vfs.newvfs.RefreshQueue; -import com.intellij.openapi.wm.StatusBar; -import com.intellij.openapi.wm.ToolWindowId; -import com.intellij.openapi.wm.ToolWindowManager; -import com.intellij.openapi.wm.WindowManager; -import com.intellij.packageDependencies.DependenciesBuilder; -import com.intellij.packageDependencies.ForwardDependenciesBuilder; -import com.intellij.pom.java.LanguageLevel; -import com.intellij.psi.PsiCompiledElement; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; -import com.intellij.util.Chunk; -import com.intellij.util.LocalTimeCounter; -import com.intellij.util.StringBuilderSpinAllocator; -import com.intellij.util.ThrowableRunnable; -import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashMap; -import com.intellij.util.containers.OrderedSet; -import gnu.trove.TObjectHashingStrategy; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.io.*; -import java.util.*; -import java.util.concurrent.CountDownLatch; - -public class CompileDriver { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver"); - - private final Project myProject; - private final Map, Pair> myGenerationCompilerModuleToOutputDirMap; // [IntermediateOutputCompiler, Module] -> [ProductionSources, TestSources] - private final String myCachesDirectoryPath; - private boolean myShouldClearOutputDirectory; - - private final Map myModuleOutputPaths = new HashMap(); - private final Map myModuleTestOutputPaths = new HashMap(); - - @NonNls private static final String VERSION_FILE_NAME = "version.dat"; - @NonNls private static final String LOCK_FILE_NAME = "in_progress.dat"; - - @NonNls private static final boolean GENERATE_CLASSPATH_INDEX = "true".equals(System.getProperty("generate.classpath.index")); - - private static final FileProcessingCompilerAdapterFactory FILE_PROCESSING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { - public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { - return new FileProcessingCompilerAdapter(context, compiler); - } - }; - private static final FileProcessingCompilerAdapterFactory FILE_PACKAGING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { - public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { - return new PackagingCompilerAdapter(context, (PackagingCompiler)compiler); - } - }; - private CompilerFilter myCompilerFilter = CompilerFilter.ALL; - private static CompilerFilter SOURCE_PROCESSING_ONLY = new CompilerFilter() { - public boolean acceptCompiler(Compiler compiler) { - return compiler instanceof SourceProcessingCompiler; - } - }; - private static CompilerFilter ALL_EXCEPT_SOURCE_PROCESSING = new CompilerFilter() { - public boolean acceptCompiler(Compiler compiler) { - return !SOURCE_PROCESSING_ONLY.acceptCompiler(compiler); - } - }; - - private OutputPathFinder myOutputFinder; // need this for updating zip archives (experimental feature) - - private Set myAllOutputDirectories; - private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/; - - public CompileDriver(Project project) { - myProject = project; - myCachesDirectoryPath = CompilerPaths.getCacheStoreDirectory(myProject).getPath().replace('/', File.separatorChar); - myShouldClearOutputDirectory = CompilerWorkspaceConfiguration.getInstance(myProject).CLEAR_OUTPUT_DIRECTORY; - - myGenerationCompilerModuleToOutputDirMap = new HashMap, Pair>(); - - final IntermediateOutputCompiler[] generatingCompilers = CompilerManager.getInstance(myProject).getCompilers(IntermediateOutputCompiler.class, myCompilerFilter); - if (generatingCompilers.length > 0) { - final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); - for (IntermediateOutputCompiler compiler : generatingCompilers) { - for (final Module module : allModules) { - final VirtualFile productionOutput = lookupVFile(compiler, module, false); - final VirtualFile testOutput = lookupVFile(compiler, module, true); - final Pair pair = new Pair(compiler, module); - final Pair outputs = new Pair(productionOutput, testOutput); - myGenerationCompilerModuleToOutputDirMap.put(pair, outputs); - } - } - } - } - - public void setCompilerFilter(CompilerFilter compilerFilter) { - myCompilerFilter = compilerFilter == null? CompilerFilter.ALL : compilerFilter; - } - - public void rebuild(CompileStatusNotification callback) { - doRebuild(callback, null, true, addAdditionalRoots(new ProjectCompileScope(myProject), ALL_EXCEPT_SOURCE_PROCESSING)); - } - - public void make(CompileScope scope, CompileStatusNotification callback) { - scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); - if (validateCompilerConfiguration(scope, false)) { - startup(scope, false, false, callback, null, true, false); - } - } - - public boolean isUpToDate(CompileScope scope) { - if (LOG.isDebugEnabled()) { - LOG.debug("isUpToDate operation started"); - } - scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); - - final CompilerTask task = new CompilerTask(myProject, true, "", true); - final CompileContextImpl compileContext = - new CompileContextImpl(myProject, task, scope, createDependencyCache(), true, false); - - checkCachesVersion(compileContext); - if (compileContext.isRebuildRequested()) { - if (LOG.isDebugEnabled()) { - LOG.debug("Rebuild requested, up-to-date=false"); - } - return false; - } - - for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { - final Pair outputs = entry.getValue(); - Module module = entry.getKey().getSecond(); - compileContext.assignModule(outputs.getFirst(), module, false); - compileContext.assignModule(outputs.getSecond(), module, true); - } - - final Ref status = new Ref(); - - task.start(new Runnable() { - public void run() { - try { - myAllOutputDirectories = getAllOutputDirectories(); - // need this for updating zip archives experiment, uncomment if the feature is turned on - //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); - status.set(doCompile(compileContext, false, false, false, true)); - } - finally { - compileContext.commitZipFiles(); // just to be on the safe side; normally should do nothing if called in isUpToDate() - } - } - }, null); - - if (LOG.isDebugEnabled()) { - LOG.debug("isUpToDate operation finished"); - } - - return ExitStatus.UP_TO_DATE.equals(status.get()); - } - - private DependencyCache createDependencyCache() { - return new DependencyCache(myCachesDirectoryPath + File.separator + ".dependency-info"); - } - - public void compile(CompileScope scope, CompileStatusNotification callback, boolean trackDependencies) { - if (trackDependencies) { - scope = new TrackDependenciesScope(scope); - } - if (validateCompilerConfiguration(scope, false)) { - startup(scope, false, true, callback, null, true, trackDependencies); - } - } - - private static class CompileStatus { - final int CACHE_FORMAT_VERSION; - final boolean COMPILATION_IN_PROGRESS; - - private CompileStatus(int cacheVersion, boolean isCompilationInProgress) { - CACHE_FORMAT_VERSION = cacheVersion; - COMPILATION_IN_PROGRESS = isCompilationInProgress; - } - } - - private CompileStatus readStatus() { - final boolean isInProgress = getLockFile().exists(); - int version = -1; - try { - final File versionFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); - DataInputStream in = new DataInputStream(new FileInputStream(versionFile)); - try { - version = in.readInt(); - } - finally { - in.close(); - } - } - catch (FileNotFoundException e) { - // ignore - } - catch (IOException e) { - LOG.info(e); // may happen in case of IDEA crashed and the file is not written properly - return null; - } - return new CompileStatus(version, isInProgress); - } - - private void writeStatus(CompileStatus status, CompileContext context) { - final File statusFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); - - final File lockFile = getLockFile(); - try { - FileUtil.createIfDoesntExist(statusFile); - DataOutputStream out = new DataOutputStream(new FileOutputStream(statusFile)); - try { - out.writeInt(status.CACHE_FORMAT_VERSION); - } - finally { - out.close(); - } - if (status.COMPILATION_IN_PROGRESS) { - FileUtil.createIfDoesntExist(lockFile); - } - else { - deleteFile(lockFile); - } - } - catch (IOException e) { - context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); - } - } - - private File getLockFile() { - return new File(CompilerPaths.getCompilerSystemDirectory(myProject), LOCK_FILE_NAME); - } - - private void doRebuild(CompileStatusNotification callback, - CompilerMessage message, - final boolean checkCachesVersion, - final CompileScope compileScope) { - if (validateCompilerConfiguration(compileScope, true)) { - startup(compileScope, true, false, callback, message, checkCachesVersion, false); - } - } - - private CompileScope addAdditionalRoots(CompileScope originalScope, final CompilerFilter filter) { - CompileScope scope = attachIntermediateOutputDirectories(originalScope, filter); - - final AdditionalCompileScopeProvider[] scopeProviders = Extensions.getExtensions(AdditionalCompileScopeProvider.EXTENSION_POINT_NAME); - CompileScope baseScope = scope; - for (AdditionalCompileScopeProvider scopeProvider : scopeProviders) { - final CompileScope additionalScope = scopeProvider.getAdditionalScope(baseScope); - if (additionalScope != null) { - scope = new CompositeScope(scope, additionalScope); - } - } - return scope; - } - - private CompileScope attachIntermediateOutputDirectories(CompileScope originalScope, CompilerFilter filter) { - CompileScope scope = originalScope; - final Set affected = new HashSet(Arrays.asList(originalScope.getAffectedModules())); - for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { - final Module module = entry.getKey().getSecond(); - if (affected.contains(module) && filter.acceptCompiler(entry.getKey().getFirst())) { - final Pair outputs = entry.getValue(); - scope = new CompositeScope(scope, new FileSetCompileScope(Arrays.asList(outputs.getFirst(), outputs.getSecond()), new Module[]{module})); - } - } - return scope; - } - - public static final Key COMPILATION_START_TIMESTAMP = Key.create("COMPILATION_START_TIMESTAMP"); - - private void startup(final CompileScope scope, - final boolean isRebuild, - final boolean forceCompile, - final CompileStatusNotification callback, - final CompilerMessage message, - final boolean checkCachesVersion, - final boolean trackDependencies) { - final CompilerTask compileTask = new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, - forceCompile - ? CompilerBundle.message("compiler.content.name.compile") - : CompilerBundle.message("compiler.content.name.make"), false); - final WindowManager windowManager = WindowManager.getInstance(); - if (windowManager != null) { - windowManager.getStatusBar(myProject).setInfo(""); - } - - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - FileDocumentManager.getInstance().saveAllDocuments(); - - final DependencyCache dependencyCache = createDependencyCache(); - final CompileContextImpl compileContext = - new CompileContextImpl(myProject, compileTask, scope, dependencyCache, !isRebuild && !forceCompile, isRebuild); - compileContext.putUserData(COMPILATION_START_TIMESTAMP, LocalTimeCounter.currentTime()); - for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { - final Pair outputs = entry.getValue(); - final Module module = entry.getKey().getSecond(); - compileContext.assignModule(outputs.getFirst(), module, false); - compileContext.assignModule(outputs.getSecond(), module, true); - } - - compileTask.start(new Runnable() { - public void run() { - long start = System.currentTimeMillis(); - try { - if (myProject.isDisposed()) { - return; - } - LOG.info("COMPILATION STARTED"); - if (message != null) { - compileContext.addMessage(message); - } - TranslatingCompilerFilesMonitor.getInstance().ensureInitializationCompleted(myProject); - doCompile(compileContext, isRebuild, forceCompile, callback, checkCachesVersion, trackDependencies); - } - finally { - compileContext.commitZipFiles(); - CompilerUtil.logDuration("Refreshing VFS in total", CompilerUtil.ourRefreshTime); - CompilerUtil.ourRefreshTime = 0L; - final long finish = System.currentTimeMillis(); - CompilerUtil.logDuration( - "\tCOMPILATION FINISHED; Errors: " + - compileContext.getMessageCount(CompilerMessageCategory.ERROR) + - "; warnings: " + - compileContext.getMessageCount(CompilerMessageCategory.WARNING), - finish - start - ); - //if (LOG.isDebugEnabled()) { - // LOG.debug("COMPILATION FINISHED"); - //} - } - } - }, new Runnable() { - public void run() { - if (isRebuild) { - final int rv = Messages.showDialog( - myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild", - new String[]{"Make", "Rebuild"}, 0, Messages.getQuestionIcon() - ); - if (rv == 0 /*yes, please, do run make*/) { - startup(scope, false, false, callback, null, checkCachesVersion, trackDependencies); - return; - } - } - startup(scope, isRebuild, forceCompile, callback, message, checkCachesVersion, trackDependencies); - } - }); - } - - private void doCompile(final CompileContextImpl compileContext, - final boolean isRebuild, - final boolean forceCompile, - final CompileStatusNotification callback, - final boolean checkCachesVersion, - final boolean trackDependencies) { - ExitStatus status = ExitStatus.ERRORS; - boolean wereExceptions = false; - try { - if (checkCachesVersion) { - checkCachesVersion(compileContext); - if (compileContext.isRebuildRequested()) { - return; - } - } - writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, true), compileContext); - if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - return; - } - - myAllOutputDirectories = getAllOutputDirectories(); - // need this for updating zip archives experiment, uncomment if the feature is turned on - //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); - status = doCompile(compileContext, isRebuild, forceCompile, trackDependencies, false); - } - catch (Throwable ex) { - wereExceptions = true; - final PluginId pluginId = IdeErrorsDialog.findPluginId(ex); - - final StringBuffer message = new StringBuffer(); - message.append("Internal error"); - if (pluginId != null) { - message.append(" (Plugin: ").append(pluginId).append(")"); - } - message.append(": ").append(ex.getMessage()); - compileContext.addMessage(CompilerMessageCategory.ERROR, message.toString(), null, -1, -1); - - if (pluginId != null) { - throw new PluginException(ex, pluginId); - } - throw new RuntimeException(ex); - } - finally { - dropDependencyCache(compileContext); - final ExitStatus _status = status; - if (compileContext.isRebuildRequested()) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - doRebuild(callback, new CompilerMessageImpl(myProject, CompilerMessageCategory.INFORMATION, compileContext.getRebuildReason(), - null, -1, -1, null), false, compileContext.getCompileScope()); - } - }, ModalityState.NON_MODAL); - } - else { - final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp(); - writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, wereExceptions), compileContext); - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR); - final int warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING); - final String statusMessage = createStatusMessage(_status, warningCount, errorCount); - final StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); - if (statusBar != null) { // because this code is in invoke later, the code may work for already closed project - // in case another project was opened in the frame while the compiler was working (See SCR# 28591) - statusBar.setInfo(statusMessage); - if (duration > ONE_MINUTE_MS) { - ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, MessageType.INFO, statusMessage); - } - } - if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) { - compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1); - } - if (callback != null) { - callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext); - } - } - }, ModalityState.NON_MODAL); - } - } - } - - private void checkCachesVersion(final CompileContextImpl compileContext) { - final CompileStatus compileStatus = readStatus(); - if (compileStatus == null) { - compileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); - } - else if (compileStatus.CACHE_FORMAT_VERSION != -1 && - compileStatus.CACHE_FORMAT_VERSION != CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION) { - compileContext.requestRebuildNextTime(CompilerBundle.message("error.caches.old.format")); - } - else if (compileStatus.COMPILATION_IN_PROGRESS) { - compileContext.requestRebuildNextTime(CompilerBundle.message("error.previous.compilation.failed")); - } - } - - private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount) { - if (status == ExitStatus.CANCELLED) { - return CompilerBundle.message("status.compilation.aborted"); - } - if (status == ExitStatus.UP_TO_DATE) { - return CompilerBundle.message("status.all.up.to.date"); - } - if (status == ExitStatus.SUCCESS) { - return warningCount > 0 - ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount) - : CompilerBundle.message("status.compilation.completed.successfully"); - } - return CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount); - } - - private static class ExitStatus { - private final String myName; - - private ExitStatus(@NonNls String name) { - myName = name; - } - - public String toString() { - return myName; - } - - public static final ExitStatus CANCELLED = new ExitStatus("CANCELLED"); - public static final ExitStatus ERRORS = new ExitStatus("ERRORS"); - public static final ExitStatus SUCCESS = new ExitStatus("SUCCESS"); - public static final ExitStatus UP_TO_DATE = new ExitStatus("UP_TO_DATE"); - } - - private static class ExitException extends Exception { - private final ExitStatus myStatus; - - private ExitException(ExitStatus status) { - myStatus = status; - } - - public ExitStatus getExitStatus() { - return myStatus; - } - } - - private ExitStatus doCompile(final CompileContextEx context, - boolean isRebuild, - final boolean forceCompile, - final boolean trackDependencies, final boolean onlyCheckStatus) { - try { - if (isRebuild) { - deleteAll(context); - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - if (LOG.isDebugEnabled()) { - logErrorMessages(context); - } - return ExitStatus.ERRORS; - } - } - - if (!onlyCheckStatus) { - if (!executeCompileTasks(context, true)) { - if (LOG.isDebugEnabled()) { - LOG.debug("Compilation cancelled"); - } - return ExitStatus.CANCELLED; - } - } - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - if (LOG.isDebugEnabled()) { - logErrorMessages(context); - } - return ExitStatus.ERRORS; - } - - final long refreshStart = System.currentTimeMillis(); - - // need this to make sure the VFS is built - boolean needRecalcOutputDirs = false; - //final List outputsToRefresh = new ArrayList(); - - final VirtualFile[] all = context.getAllOutputDirectories(); - - final ProgressIndicator progressIndicator = context.getProgressIndicator(); - - final int totalCount = all.length + myGenerationCompilerModuleToOutputDirMap.size() * 2; - final CountDownLatch latch = new CountDownLatch(totalCount); - final Runnable decCount = new Runnable() { - public void run() { - latch.countDown(); - progressIndicator.setFraction(((double)(totalCount - latch.getCount())) / totalCount); - } - }; - progressIndicator.pushState(); - progressIndicator.setText("Inspecting output directories..."); - final boolean asyncMode = !ApplicationManager.getApplication().isDispatchThread(); // must not lock awt thread with latch.await() - try { - for (VirtualFile output : all) { - if (output.isValid()) { - walkChildren(output, context); - } - else { - needRecalcOutputDirs = true; - final File file = new File(output.getPath()); - if (!file.exists()) { - final boolean created = file.mkdirs(); - if (!created) { - context.addMessage(CompilerMessageCategory.ERROR, "Failed to create output directory " + file.getPath(), null, 0, 0); - return ExitStatus.ERRORS; - } - } - output = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); - if (output == null) { - context.addMessage(CompilerMessageCategory.ERROR, "Failed to locate output directory " + file.getPath(), null, 0, 0); - return ExitStatus.ERRORS; - } - } - output.refresh(asyncMode, true, decCount); - //outputsToRefresh.add(output); - } - for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { - final Pair generated = myGenerationCompilerModuleToOutputDirMap.get(pair); - walkChildren(generated.getFirst(), context); - //outputsToRefresh.add(generated.getFirst()); - generated.getFirst().refresh(asyncMode, true, decCount); - walkChildren(generated.getSecond(), context); - //outputsToRefresh.add(generated.getSecond()); - generated.getSecond().refresh(asyncMode, true, decCount); - } - - //RefreshQueue.getInstance().refresh(false, true, null, outputsToRefresh.toArray(new VirtualFile[outputsToRefresh.size()])); - try { - latch.await(); // wait until all threads are refreshed - } - catch (InterruptedException e) { - LOG.info(e); - } - } - finally { - progressIndicator.popState(); - } - - final long initialRefreshTime = System.currentTimeMillis() - refreshStart; - CompilerUtil.logDuration("Initial VFS refresh", initialRefreshTime); - CompilerUtil.ourRefreshTime += initialRefreshTime; - - DumbService.getInstance(myProject).waitForSmartMode(); - - if (needRecalcOutputDirs) { - context.recalculateOutputDirs(); - } - - boolean didSomething = false; - - final CompilerManager compilerManager = CompilerManager.getInstance(myProject); - - try { - didSomething |= generateSources(compilerManager, context, forceCompile, onlyCheckStatus); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceInstrumentingCompiler.class, - FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus); - - didSomething |= translate(context, compilerManager, forceCompile, isRebuild, trackDependencies, onlyCheckStatus); - - final boolean sourceProcessed = - invokeFileProcessingCompilers(compilerManager, context, SourceProcessingCompiler.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, - forceCompile, true, onlyCheckStatus); - didSomething |= sourceProcessed; - - if (sourceProcessed) { - final CompileScope intermediateSources = attachIntermediateOutputDirectories(new CompositeScope(CompileScope.EMPTY_ARRAY) { - @NotNull - public Module[] getAffectedModules() { - return context.getCompileScope().getAffectedModules(); - } - }, SOURCE_PROCESSING_ONLY); - context.addScope(intermediateSources); - - // important: override rebuild, forceCompile, and trackDependencies options in order to compile only newly generated stuff - didSomething |= translate(context, compilerManager, false, false, false, onlyCheckStatus); - } - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassInstrumentingCompiler.class, - FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); - - // explicitly passing forceCompile = false because in scopes that is narrower than ProjectScope it is impossible - // to understand whether the class to be processed is in scope or not. Otherwise compiler may process its items even if - // there were changes in completely independent files. - didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassPostProcessingCompiler.class, - FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, PackagingCompiler.class, - FILE_PACKAGING_COMPILER_ADAPTER_FACTORY, - isRebuild, false, onlyCheckStatus); - - didSomething |= invokeFileProcessingCompilers(compilerManager, context, Validator.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, - forceCompile, true, onlyCheckStatus); - } - catch (ExitException e) { - if (LOG.isDebugEnabled()) { - LOG.debug(e); - logErrorMessages(context); - } - return e.getExitStatus(); - } - finally { - // drop in case it has not been dropped yet. - dropDependencyCache(context); - - final VirtualFile[] allOutputDirs = context.getAllOutputDirectories(); - - if (didSomething && GENERATE_CLASSPATH_INDEX) { - CompilerUtil.runInContext(context, "Generating classpath index...", new ThrowableRunnable(){ - public void run() { - int count = 0; - for (VirtualFile file : allOutputDirs) { - context.getProgressIndicator().setFraction((double)++count / allOutputDirs.length); - createClasspathIndex(file); - } - } - }); - } - - if (!context.getProgressIndicator().isCanceled() && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - RefreshQueue.getInstance().refresh(true, true, new Runnable() { - public void run() { - CompilerDirectoryTimestamp.updateTimestamp(Arrays.asList(allOutputDirs)); - } - }, allOutputDirs); - } - } - - if (!onlyCheckStatus) { - if (!executeCompileTasks(context, false)) { - return ExitStatus.CANCELLED; - } - } - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - if (LOG.isDebugEnabled()) { - logErrorMessages(context); - } - return ExitStatus.ERRORS; - } - if (!didSomething) { - return ExitStatus.UP_TO_DATE; - } - return ExitStatus.SUCCESS; - } - catch (ProcessCanceledException e) { - return ExitStatus.CANCELLED; - } - } - - private static void logErrorMessages(final CompileContext context) { - final CompilerMessage[] errors = context.getMessages(CompilerMessageCategory.ERROR); - if (errors.length > 0) { - LOG.debug("Errors reported: "); - for (CompilerMessage error : errors) { - LOG.debug("\t" + error.getMessage()); - } - } - } - - private static void walkChildren(VirtualFile from, final CompileContext context) { - final VirtualFile[] files = from.getChildren(); - if (files != null && files.length > 0) { - context.getProgressIndicator().checkCanceled(); - context.getProgressIndicator().setText2(from.getPresentableUrl()); - for (VirtualFile file : files) { - walkChildren(file, context); - } - } - } - - private static void createClasspathIndex(final VirtualFile file) { - try { - BufferedWriter writer = new BufferedWriter(new FileWriter(new File(VfsUtil.virtualToIoFile(file), "classpath.index"))); - try { - writeIndex(writer, file, file); - } - finally { - writer.close(); - } - } - catch (IOException e) { - // Ignore. Failed to create optional classpath index - } - } - - private static void writeIndex(final BufferedWriter writer, final VirtualFile root, final VirtualFile file) throws IOException { - writer.write(VfsUtil.getRelativePath(file, root, '/')); - writer.write('\n'); - - for (VirtualFile child : file.getChildren()) { - writeIndex(writer, root, child); - } - } - - private static void dropDependencyCache(final CompileContextEx context) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.saving.caches"), new ThrowableRunnable(){ - public void run() { - context.getDependencyCache().resetState(); - } - }); - } - - private boolean generateSources(final CompilerManager compilerManager, - CompileContextEx context, - final boolean forceCompile, - final boolean onlyCheckStatus) throws ExitException { - boolean didSomething = false; - - final SourceGeneratingCompiler[] sourceGenerators = compilerManager.getCompilers(SourceGeneratingCompiler.class, myCompilerFilter); - for (final SourceGeneratingCompiler sourceGenerator : sourceGenerators) { - if (context.getProgressIndicator().isCanceled()) { - throw new ExitException(ExitStatus.CANCELLED); - } - - final boolean generatedSomething = generateOutput(context, sourceGenerator, forceCompile, onlyCheckStatus); - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - throw new ExitException(ExitStatus.ERRORS); - } - didSomething |= generatedSomething; - } - return didSomething; - } - - private boolean translate(final CompileContextEx context, - final CompilerManager compilerManager, - final boolean forceCompile, - boolean isRebuild, - final boolean trackDependencies, final boolean onlyCheckStatus) throws ExitException { - - boolean didSomething = false; - - final TranslatingCompiler[] translators = compilerManager.getCompilers(TranslatingCompiler.class, myCompilerFilter); - - final Set generatedTypes = new HashSet(); - VirtualFile[] snapshot = null; - - - final TranslatorsOutputSink sink = new TranslatorsOutputSink(context, translators); - try { - for (int currentCompiler = 0, translatorsLength = translators.length; currentCompiler < translatorsLength; currentCompiler++) { - sink.setCurrentCompilerIndex(currentCompiler); - final TranslatingCompiler translator = translators[currentCompiler]; - if (context.getProgressIndicator().isCanceled()) { - throw new ExitException(ExitStatus.CANCELLED); - } - - if (snapshot == null || ContainerUtil.intersects(generatedTypes, compilerManager.getRegisteredInputTypes(translator))) { - // rescan snapshot if previously generated files can influence the input of this compiler - snapshot = ApplicationManager.getApplication().runReadAction(new Computable() { - public VirtualFile[] compute() { - return context.getCompileScope().getFiles(null, true); - } - }); - } - - final CompileContextEx _context; - if (translator instanceof IntermediateOutputCompiler) { - // wrap compile context so that output goes into intermediate directories - final IntermediateOutputCompiler _translator = (IntermediateOutputCompiler)translator; - _context = new CompileContextExProxy(context) { - public VirtualFile getModuleOutputDirectory(final Module module) { - return getGenerationOutputDir(_translator, module, false); - } - - public VirtualFile getModuleOutputDirectoryForTests(final Module module) { - return getGenerationOutputDir(_translator, module, true); - } - }; - } - else { - _context = context; - } - final boolean compiledSomething = - compileSources(_context, translators, currentCompiler, snapshot, forceCompile, isRebuild, trackDependencies, onlyCheckStatus, sink); - - if (compiledSomething) { - generatedTypes.addAll(compilerManager.getRegisteredOutputTypes(translator)); - } - - if (_context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - throw new ExitException(ExitStatus.ERRORS); - } - - didSomething |= compiledSomething; - } - } - finally { - if (context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - // perform update only if there were no errors, so it is guaranteed that the file was processd by all neccesary compilers - sink.flushPostponedItems(); - } - dropDependencyCache(context); - } - return didSomething; - } - - private interface FileProcessingCompilerAdapterFactory { - FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler); - } - - private boolean invokeFileProcessingCompilers(final CompilerManager compilerManager, - CompileContextEx context, - Class fileProcessingCompilerClass, - FileProcessingCompilerAdapterFactory factory, - boolean forceCompile, - final boolean checkScope, - final boolean onlyCheckStatus) throws ExitException { - boolean didSomething = false; - final FileProcessingCompiler[] compilers = compilerManager.getCompilers(fileProcessingCompilerClass, myCompilerFilter); - if (compilers.length > 0) { - try { - CacheDeferredUpdater cacheUpdater = new CacheDeferredUpdater(); - try { - for (final FileProcessingCompiler compiler : compilers) { - if (context.getProgressIndicator().isCanceled()) { - throw new ExitException(ExitStatus.CANCELLED); - } - - CompileContextEx _context = context; - if (compiler instanceof IntermediateOutputCompiler) { - final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler; - _context = new CompileContextExProxy(context) { - public VirtualFile getModuleOutputDirectory(final Module module) { - return getGenerationOutputDir(_compiler, module, false); - } - - public VirtualFile getModuleOutputDirectoryForTests(final Module module) { - return getGenerationOutputDir(_compiler, module, true); - } - }; - } - - final boolean processedSomething = processFiles(factory.create(_context, compiler), forceCompile, checkScope, onlyCheckStatus, cacheUpdater); - - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - throw new ExitException(ExitStatus.ERRORS); - } - - didSomething |= processedSomething; - } - } - finally { - cacheUpdater.doUpdate(); - } - } - catch (IOException e) { - LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); - throw new ExitException(ExitStatus.ERRORS); - } - catch (ProcessCanceledException e) { - throw e; - } - catch (ExitException e) { - throw e; - } - catch (Exception e) { - context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); - LOG.error(e); - } - } - - return didSomething; - } - - private static Map> buildModuleToGenerationItemMap(GeneratingCompiler.GenerationItem[] items) { - final Map> map = new HashMap>(); - for (GeneratingCompiler.GenerationItem item : items) { - Module module = item.getModule(); - LOG.assertTrue(module != null); - Set itemSet = map.get(module); - if (itemSet == null) { - itemSet = new HashSet(); - map.put(module, itemSet); - } - itemSet.add(item); - } - return map; - } - - private void deleteAll(final CompileContextEx context) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable() { - public void run() { - final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); - final VirtualFile[] allSources = context.getProjectCompileScope().getFiles(null, true); - if (myShouldClearOutputDirectory) { - clearOutputDirectories(myAllOutputDirectories); - } - else { // refresh is still required - try { - for (final Compiler compiler : CompilerManager.getInstance(myProject).getCompilers(Compiler.class)) { - try { - if (compiler instanceof GeneratingCompiler) { - final StateCache cache = getGeneratingCompilerCache((GeneratingCompiler)compiler); - final Iterator urlIterator = cache.getUrlsIterator(); - while (urlIterator.hasNext()) { - context.getProgressIndicator().checkCanceled(); - deleteFile(new File(VirtualFileManager.extractPath(urlIterator.next()))); - } - } - else if (compiler instanceof TranslatingCompiler) { - final ArrayList> toDelete = new ArrayList>(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - TranslatingCompilerFilesMonitor.getInstance() - .collectFiles(context, (TranslatingCompiler)compiler, Arrays.asList(allSources).iterator(), true - /*pass true to make sure that every source in scope file is processed*/, false - /*important! should pass false to enable collection of files to delete*/, - new ArrayList(), toDelete); - } - }); - for (Trinity trinity : toDelete) { - context.getProgressIndicator().checkCanceled(); - final File file = trinity.getFirst(); - deleteFile(file); - if (isTestMode) { - CompilerManagerImpl.addDeletedPath(file.getPath()); - } - } - } - } - catch (IOException e) { - LOG.info(e); - } - } - pruneEmptyDirectories(context.getProgressIndicator(), myAllOutputDirectories); // to avoid too much files deleted events - } - finally { - CompilerUtil.refreshIODirectories(myAllOutputDirectories); - } - } - dropScopesCaches(); - - clearCompilerSystemDirectory(context); - } - }); - } - - private void dropScopesCaches() { - // hack to be sure the classpath will include the output directories - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - ((ProjectRootManagerEx)ProjectRootManager.getInstance(myProject)).clearScopesCachesForModules(); - } - }); - } - - private static void pruneEmptyDirectories(ProgressIndicator progress, final Set directories) { - for (File directory : directories) { - doPrune(progress, directory, directories); - } - } - - private static boolean doPrune(ProgressIndicator progress, final File directory, final Set outPutDirectories) { - progress.checkCanceled(); - final File[] files = directory.listFiles(); - boolean isEmpty = true; - if (files != null) { - for (File file : files) { - if (!outPutDirectories.contains(file)) { - if (doPrune(progress, file, outPutDirectories)) { - deleteFile(file); - } - else { - isEmpty = false; - } - } - else { - isEmpty = false; - } - } - } - else { - isEmpty = false; - } - - return isEmpty; - } - - private Set getAllOutputDirectories() { - final Set outputDirs = new OrderedSet((TObjectHashingStrategy)TObjectHashingStrategy.CANONICAL); - for (final String path : CompilerPathsEx.getOutputPaths(ModuleManager.getInstance(myProject).getModules())) { - outputDirs.add(new File(path)); - } - return outputDirs; - } - - private void clearOutputDirectories(final Set _outputDirectories) { - final long start = System.currentTimeMillis(); - // do not delete directories themselves, or we'll get rootsChanged() otherwise - final List outputDirectories = new ArrayList(_outputDirectories); - for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { - outputDirectories.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false))); - outputDirectories.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true))); - } - Collection filesToDelete = new ArrayList(outputDirectories.size() * 2); - for (File outputDirectory : outputDirectories) { - File[] files = outputDirectory.listFiles(); - if (files != null) { - filesToDelete.addAll(Arrays.asList(files)); - } - } - FileUtil.asyncDelete(filesToDelete); - - // ensure output directories exist - for (final File file : outputDirectories) { - file.mkdirs(); - } - final long clearStop = System.currentTimeMillis(); - - CompilerUtil.refreshIODirectories(outputDirectories); - - final long refreshStop = System.currentTimeMillis(); - - CompilerUtil.logDuration("Clearing output dirs", clearStop - start); - CompilerUtil.logDuration("Refreshing output directories", refreshStop - clearStop); - } - - private void clearCompilerSystemDirectory(final CompileContextEx context) { - CompilerCacheManager.getInstance(myProject).clearCaches(context); - FileUtil.delete(CompilerPathsEx.getZipStoreDirectory(myProject)); - dropDependencyCache(context); - - for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { - final File[] outputs = { - new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)), - new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true)) - }; - for (File output : outputs) { - final File[] files = output.listFiles(); - if (files != null) { - for (final File file : files) { - final boolean deleteOk = deleteFile(file); - if (!deleteOk) { - context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.failed.to.delete", file.getPath()), - null, -1, -1); - } - } - } - } - } - } - - /** - * @param file a file to delete - * @return true if and only if the file existed and was successfully deleted - * Note: the behaviour is different from FileUtil.delete() which returns true if the file absent on the disk - */ - private static boolean deleteFile(final File file) { - File[] files = file.listFiles(); - if (files != null) { - for (File file1 : files) { - deleteFile(file1); - } - } - - for (int i = 0; i < 10; i++){ - if (file.delete()) { - return true; - } - if (!file.exists()) { - return false; - } - try { - Thread.sleep(50); - } - catch (InterruptedException ignored) { - } - } - return false; - } - - private VirtualFile getGenerationOutputDir(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) { - final Pair outputs = - myGenerationCompilerModuleToOutputDirMap.get(new Pair(compiler, module)); - return forTestSources? outputs.getSecond() : outputs.getFirst(); - } - - private boolean generateOutput(final CompileContextEx context, - final GeneratingCompiler compiler, - final boolean forceGenerate, - final boolean onlyCheckStatus) throws ExitException { - final GeneratingCompiler.GenerationItem[] allItems = compiler.getGenerationItems(context); - final List toGenerate = new ArrayList(); - final List filesToRefresh = new ArrayList(); - final List generatedFiles = new ArrayList(); - final List affectedModules = new ArrayList(); - try { - final StateCache cache = getGeneratingCompilerCache(compiler); - final Set pathsToRemove = new HashSet(cache.getUrls()); - - final Map itemToOutputPathMap = new HashMap(); - final IOException[] ex = {null}; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (final GeneratingCompiler.GenerationItem item : allItems) { - final Module itemModule = item.getModule(); - final String outputDirPath = CompilerPaths.getGenerationOutputPath(compiler, itemModule, item.isTestSource()); - final String outputPath = outputDirPath + "/" + item.getPath(); - itemToOutputPathMap.put(item, outputPath); - - try { - final ValidityState savedState = cache.getState(outputPath); - - if (forceGenerate || savedState == null || !savedState.equalsTo(item.getValidityState())) { - final String outputPathUrl = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, outputPath); - if (context.getCompileScope().belongs(outputPathUrl)) { - toGenerate.add(item); - } - else { - pathsToRemove.remove(outputPath); - } - } - else { - pathsToRemove.remove(outputPath); - } - } - catch (IOException e) { - ex[0] = e; - } - } - } - }); - if (ex[0] != null) { - throw ex[0]; - } - - if (onlyCheckStatus) { - if (toGenerate.isEmpty() && pathsToRemove.isEmpty()) { - return false; - } - if (LOG.isDebugEnabled()) { - if (!toGenerate.isEmpty()) { - LOG.debug("Found items to generate, compiler " + compiler.getDescription()); - } - if (!pathsToRemove.isEmpty()) { - LOG.debug("Found paths to remove, compiler " + compiler.getDescription()); - } - } - throw new ExitException(ExitStatus.CANCELLED); - } - - if (!pathsToRemove.isEmpty()) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ - public void run() throws IOException { - for (final String path : pathsToRemove) { - final File file = new File(path); - final boolean deleted = deleteFile(file); - if (deleted) { - cache.remove(path); - filesToRefresh.add(file); - } - } - } - }); - } - - final Map> moduleToItemMap = - buildModuleToGenerationItemMap(toGenerate.toArray(new GeneratingCompiler.GenerationItem[toGenerate.size()])); - List modules = new ArrayList(moduleToItemMap.size()); - for (final Module module : moduleToItemMap.keySet()) { - modules.add(module); - } - ModuleCompilerUtil.sortModules(myProject, modules); - - for (final Module module : modules) { - CompilerUtil.runInContext(context, "Generating output from "+compiler.getDescription(),new ThrowableRunnable(){ - public void run() throws IOException { - final Set items = moduleToItemMap.get(module); - if (items != null && !items.isEmpty()) { - final GeneratingCompiler.GenerationItem[][] productionAndTestItems = splitGenerationItems(items); - for (GeneratingCompiler.GenerationItem[] _items : productionAndTestItems) { - if (_items.length == 0) continue; - final VirtualFile outputDir = getGenerationOutputDir(compiler, module, _items[0].isTestSource()); - final GeneratingCompiler.GenerationItem[] successfullyGenerated = compiler.generate(context, _items, outputDir); - - CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { - public void run() throws IOException { - if (successfullyGenerated.length > 0) { - affectedModules.add(module); - } - for (final GeneratingCompiler.GenerationItem item : successfullyGenerated) { - final String fullOutputPath = itemToOutputPathMap.get(item); - cache.update(fullOutputPath, item.getValidityState()); - final File file = new File(fullOutputPath); - filesToRefresh.add(file); - generatedFiles.add(file); - context.getProgressIndicator().setText2(file.getPath()); - } - } - }); - } - } - } - }); - } - } - catch (IOException e) { - LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); - throw new ExitException(ExitStatus.ERRORS); - } - finally { - CompilerUtil.refreshIOFiles(filesToRefresh); - if (!generatedFiles.isEmpty()) { - List vFiles = ApplicationManager.getApplication().runReadAction(new Computable>() { - public List compute() { - final ArrayList vFiles = new ArrayList(generatedFiles.size()); - for (File generatedFile : generatedFiles) { - final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(generatedFile); - if (vFile != null) { - vFiles.add(vFile); - } - } - return vFiles; - } - }); - if (forceGenerate) { - context.addScope(new FileSetCompileScope(vFiles, affectedModules.toArray(new Module[affectedModules.size()]))); - } - context.markGenerated(vFiles); - } - } - return !toGenerate.isEmpty() || !filesToRefresh.isEmpty(); - } - - private static GeneratingCompiler.GenerationItem[][] splitGenerationItems(final Set items) { - final List production = new ArrayList(); - final List tests = new ArrayList(); - for (GeneratingCompiler.GenerationItem item : items) { - if (item.isTestSource()) { - tests.add(item); - } - else { - production.add(item); - } - } - return new GeneratingCompiler.GenerationItem[][]{ - production.toArray(new GeneratingCompiler.GenerationItem[production.size()]), - tests.toArray(new GeneratingCompiler.GenerationItem[tests.size()]) - }; - } - - private boolean compileSources(final CompileContextEx context, TranslatingCompiler[] compilers, int currentCompiler, final VirtualFile[] sources, - final boolean forceCompile, - final boolean isRebuild, - final boolean trackDependencies, - final boolean onlyCheckStatus, - TranslatingCompiler.OutputSink sink) throws ExitException { - - final TranslatingCompiler compiler = compilers[currentCompiler]; - - final Set toCompile = new HashSet(); - final List> toDelete = new ArrayList>(); - context.getProgressIndicator().pushState(); - - final boolean[] wereFilesDeleted = new boolean[]{false}; - try { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - - TranslatingCompilerFilesMonitor.getInstance().collectFiles( - context, compiler, Arrays.asList(sources).iterator(), forceCompile, isRebuild, toCompile, toDelete - ); - if (trackDependencies && !toCompile.isEmpty()) { // should add dependent files - final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); - final PsiManager psiManager = PsiManager.getInstance(myProject); - final VirtualFile[] filesToCompile = toCompile.toArray(new VirtualFile[toCompile.size()]); - for (final VirtualFile file : filesToCompile) { - if (fileTypeManager.getFileTypeByFile(file) == StdFileTypes.JAVA) { - final PsiFile psiFile = psiManager.findFile(file); - if (psiFile != null) { - addDependentFiles(psiFile, toCompile, compiler, context); - } - } - } - } - } - }); - - if (onlyCheckStatus) { - if (toDelete.isEmpty() && toCompile.isEmpty()) { - return false; - } - if (LOG.isDebugEnabled()) { - if (!toDelete.isEmpty()) { - LOG.debug("Found items to delete, compiler " + compiler.getDescription()); - } - if (!toCompile.isEmpty()) { - LOG.debug("Found items to compile, compiler " + compiler.getDescription()); - } - } - throw new ExitException(ExitStatus.CANCELLED); - } - - if (!toDelete.isEmpty()) { - try { - wereFilesDeleted[0] = syncOutputDir(context, toDelete); - } - catch (CacheCorruptedException e) { - LOG.info(e); - context.requestRebuildNextTime(e.getMessage()); - } - } - - if ((wereFilesDeleted[0] || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - compiler.compile(context, toCompile.toArray(new VirtualFile[toCompile.size()]), sink); - } - } - finally { - context.getProgressIndicator().popState(); - } - return !toCompile.isEmpty() || wereFilesDeleted[0]; - } - - private static boolean syncOutputDir(final CompileContextEx context, final Collection> toDelete) throws CacheCorruptedException { - final int total = toDelete.size(); - final DependencyCache dependencyCache = context.getDependencyCache(); - final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); - - final List filesToRefresh = new ArrayList(); - final boolean[] wereFilesDeleted = {false}; - CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ - public void run() throws CacheCorruptedException { - final long start = System.currentTimeMillis(); - try { - int current = 0; - for (final Trinity trinity : toDelete) { - final File outputPath = trinity.getFirst(); - context.getProgressIndicator().checkCanceled(); - context.getProgressIndicator().setFraction((double)++current / total); - context.getProgressIndicator().setText2(outputPath.getPath()); - filesToRefresh.add(outputPath); - if (isTestMode) { - LOG.assertTrue(outputPath.exists()); - } - if (!deleteFile(outputPath)) { - if (isTestMode && outputPath.exists()) { - LOG.error("Was not able to delete output file: " + outputPath.getPath()); - } - continue; - } - wereFilesDeleted[0] = true; - - // update zip here - //final String outputDir = myOutputFinder.lookupOutputPath(outputPath); - //if (outputDir != null) { - // try { - // context.updateZippedOuput(outputDir, FileUtil.toSystemIndependentName(outputPath.getPath()).substring(outputDir.length() + 1)); - // } - // catch (IOException e) { - // LOG.info(e); - // } - //} - - final String className = trinity.getSecond(); - if (className != null) { - final int id = dependencyCache.getSymbolTable().getId(className); - dependencyCache.addTraverseRoot(id); - final boolean sourcePresent = trinity.getThird().booleanValue(); - if (!sourcePresent) { - dependencyCache.markSourceRemoved(id); - } - } - if (isTestMode) { - CompilerManagerImpl.addDeletedPath(outputPath.getPath()); - } - } - } - finally { - CompilerUtil.logDuration("Sync output directory", System.currentTimeMillis() - start); - CompilerUtil.refreshIOFiles(filesToRefresh); - } - } - }); - return wereFilesDeleted[0]; - } - - private void addDependentFiles(final PsiFile psiFile, Set toCompile, TranslatingCompiler compiler, CompileContext context) { - final DependenciesBuilder builder = new ForwardDependenciesBuilder(myProject, new AnalysisScope(psiFile)); - builder.analyze(); - final Map> dependencies = builder.getDependencies(); - final Set dependentFiles = dependencies.get(psiFile); - if (dependentFiles != null && !dependentFiles.isEmpty()) { - final TranslatingCompilerFilesMonitor monitor = TranslatingCompilerFilesMonitor.getInstance(); - for (final PsiFile dependentFile : dependentFiles) { - if (dependentFile instanceof PsiCompiledElement) { - continue; - } - final VirtualFile vFile = dependentFile.getVirtualFile(); - if (vFile == null || toCompile.contains(vFile)) { - continue; - } - if (!compiler.isCompilableFile(vFile, context)) { - continue; - } - if (!monitor.isMarkedForCompilation(myProject, vFile)) { - continue; // no need to compile since already compiled - } - toCompile.add(vFile); - addDependentFiles(dependentFile, toCompile, compiler, context); - } - } - } - - // [mike] performance optimization - this method is accessed > 15,000 times in Aurora - private String getModuleOutputPath(final Module module, boolean inTestSourceContent) { - final Map map = inTestSourceContent ? myModuleTestOutputPaths : myModuleOutputPaths; - String path = map.get(module); - if (path == null) { - path = CompilerPaths.getModuleOutputPath(module, inTestSourceContent); - map.put(module, path); - } - - return path; - } - - private boolean processFiles(final FileProcessingCompilerAdapter adapter, - final boolean forceCompile, - final boolean checkScope, - final boolean onlyCheckStatus, final CacheDeferredUpdater cacheUpdater) throws ExitException, IOException { - final CompileContextEx context = (CompileContextEx)adapter.getCompileContext(); - final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache(adapter.getCompiler()); - final FileProcessingCompiler.ProcessingItem[] items = adapter.getProcessingItems(); - if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - return false; - } - final CompileScope scope = context.getCompileScope(); - final List toProcess = new ArrayList(); - final Set allUrls = new HashSet(); - final IOException[] ex = {null}; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - for (FileProcessingCompiler.ProcessingItem item : items) { - final VirtualFile file = item.getFile(); - if (file == null) { - LOG.error("FileProcessingCompiler.ProcessingItem.getFile() must not return null: compiler " + adapter.getCompiler().getDescription()); - continue; - } - final String url = file.getUrl(); - allUrls.add(url); - if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) { - final ValidityState state = cache.getExtState(url); - final ValidityState itemState = item.getValidityState(); - if (state != null ? state.equalsTo(itemState) : itemState == null) { - continue; - } - } - if (LOG.isDebugEnabled()) { - LOG.debug("Adding item to process: " + url + "; saved ts= " + cache.getTimestamp(url) + "; VFS ts=" + file.getTimeStamp()); - } - toProcess.add(item); - } - } - catch (IOException e) { - ex[0] = e; - } - } - }); - - if (ex[0] != null) { - throw ex[0]; - } - - final Collection urls = cache.getUrls(); - final List urlsToRemove = new ArrayList(); - if (!urls.isEmpty()) { - CompilerUtil.runInContext(context, CompilerBundle.message("progress.processing.outdated.files"), new ThrowableRunnable(){ - public void run() throws IOException { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (final String url : urls) { - if (!allUrls.contains(url)) { - if (!checkScope || scope.belongs(url)) { - urlsToRemove.add(url); - } - } - } - } - }); - if (!onlyCheckStatus && !urlsToRemove.isEmpty()) { - for (final String url : urlsToRemove) { - adapter.processOutdatedItem(context, url, cache.getExtState(url)); - cache.remove(url); - } - } - } - }); - } - - if (onlyCheckStatus) { - if (urlsToRemove.isEmpty() && toProcess.isEmpty()) { - return false; - } - if (LOG.isDebugEnabled()) { - if (!urlsToRemove.isEmpty()) { - LOG.debug("Found urls to remove, compiler " + adapter.getCompiler().getDescription()); - for (String url : urlsToRemove) { - LOG.debug("\t" + url); - } - } - if (!toProcess.isEmpty()) { - LOG.debug("Found items to compile, compiler " + adapter.getCompiler().getDescription()); - for (FileProcessingCompiler.ProcessingItem item : toProcess) { - LOG.debug("\t" + item.getFile().getPresentableUrl()); - } - } - } - throw new ExitException(ExitStatus.CANCELLED); - } - - if (toProcess.isEmpty()) { - return false; - } - - final FileProcessingCompiler.ProcessingItem[] processed = - adapter.process(toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()])); - - if (processed.length == 0) { - return true; - } - CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { - public void run() throws IOException { - final List vFiles = new ArrayList(processed.length); - for (FileProcessingCompiler.ProcessingItem aProcessed : processed) { - final VirtualFile file = aProcessed.getFile(); - vFiles.add(file); - if (LOG.isDebugEnabled()) { - LOG.debug("File processed by " + adapter.getCompiler().getDescription()); - LOG.debug("\tFile processed " + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); - } - - //final String path = file.getPath(); - //final String outputDir = myOutputFinder.lookupOutputPath(path); - //if (outputDir != null) { - // context.updateZippedOuput(outputDir, path.substring(outputDir.length() + 1)); - //} - } - LocalFileSystem.getInstance().refreshFiles(vFiles); - if (LOG.isDebugEnabled()) { - LOG.debug("Files after VFS refresh:"); - for (VirtualFile file : vFiles) { - LOG.debug("\t" + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); - } - } - for (FileProcessingCompiler.ProcessingItem item : processed) { - cacheUpdater.addFileForUpdate(item, cache); - } - } - }); - return true; - } - - private FileProcessingCompilerStateCache getFileProcessingCompilerCache(FileProcessingCompiler compiler) throws IOException { - return CompilerCacheManager.getInstance(myProject).getFileProcessingCompilerCache(compiler); - } - - private StateCache getGeneratingCompilerCache(final GeneratingCompiler compiler) throws IOException { - return CompilerCacheManager.getInstance(myProject).getGeneratingCompilerCache(compiler); - } - - public void executeCompileTask(final CompileTask task, final CompileScope scope, final String contentName, final Runnable onTaskFinished) { - final CompilerTask progressManagerTask = - new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, contentName, false); - final CompileContextImpl compileContext = new CompileContextImpl(myProject, progressManagerTask, scope, null, false, false); - - FileDocumentManager.getInstance().saveAllDocuments(); - - progressManagerTask.start(new Runnable() { - public void run() { - try { - task.execute(compileContext); - } - catch (ProcessCanceledException ex) { - // suppressed - } - finally { - compileContext.commitZipFiles(); - if (onTaskFinished != null) { - onTaskFinished.run(); - } - } - } - }, null); - } - - private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) { - final CompilerManager manager = CompilerManager.getInstance(myProject); - final ProgressIndicator progressIndicator = context.getProgressIndicator(); - progressIndicator.pushState(); - try { - CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks(); - if (tasks.length > 0) { - progressIndicator.setText(beforeTasks - ? CompilerBundle.message("progress.executing.precompile.tasks") - : CompilerBundle.message("progress.executing.postcompile.tasks")); - for (CompileTask task : tasks) { - if (!task.execute(context)) { - return false; - } - } - } - } - finally { - progressIndicator.popState(); - WindowManager.getInstance().getStatusBar(myProject).setInfo(""); - if (progressIndicator instanceof CompilerTask) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - public void run() { - ((CompilerTask)progressIndicator).showCompilerContent(); - } - }); - } - } - return true; - } - - // todo: add validation for module chunks: all modules that form a chunk must have the same JDK - private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) { - final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/; - final List modulesWithoutOutputPathSpecified = new ArrayList(); - final List modulesWithoutJdkAssigned = new ArrayList(); - final Set nonExistingOutputPaths = new HashSet(); - - for (final Module module : scopeModules) { - final boolean hasSources = hasSources(module, false); - final boolean hasTestSources = hasSources(module, true); - if (!hasSources && !hasTestSources) { - // If module contains no sources, shouldn't have to select JDK or output directory (SCR #19333) - // todo still there may be problems with this approach if some generated files are attributed by this module - continue; - } - final Sdk jdk = ModuleRootManager.getInstance(module).getSdk(); - if (jdk == null) { - modulesWithoutJdkAssigned.add(module.getName()); - } - final String outputPath = getModuleOutputPath(module, false); - final String testsOutputPath = getModuleOutputPath(module, true); - if (outputPath == null && testsOutputPath == null) { - modulesWithoutOutputPathSpecified.add(module.getName()); - } - else { - if (outputPath != null) { - final File file = new File(outputPath.replace('/', File.separatorChar)); - if (!file.exists()) { - nonExistingOutputPaths.add(file); - } - } - else { - if (hasSources) { - modulesWithoutOutputPathSpecified.add(module.getName()); - } - } - if (testsOutputPath != null) { - final File f = new File(testsOutputPath.replace('/', File.separatorChar)); - if (!f.exists()) { - nonExistingOutputPaths.add(f); - } - } - else { - if (hasTestSources) { - modulesWithoutOutputPathSpecified.add(module.getName()); - } - } - } - } - if (!modulesWithoutJdkAssigned.isEmpty()) { - showNotSpecifiedError("error.jdk.not.specified", modulesWithoutJdkAssigned, ProjectBundle.message("modules.classpath.title")); - return false; - } - - if (!modulesWithoutOutputPathSpecified.isEmpty()) { - showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME); - return false; - } - - if (!nonExistingOutputPaths.isEmpty()) { - for (File file : nonExistingOutputPaths) { - final boolean succeeded = file.mkdirs(); - if (!succeeded) { - if (file.exists()) { - // for overlapping paths, this one might have been created as an intermediate path on a previous iteration - continue; - } - Messages.showMessageDialog(myProject, CompilerBundle.message("error.failed.to.create.directory", file.getPath()), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - return false; - } - } - final Boolean refreshSuccess = ApplicationManager.getApplication().runWriteAction(new Computable() { - public Boolean compute() { - LocalFileSystem.getInstance().refreshIoFiles(nonExistingOutputPaths); - for (File file : nonExistingOutputPaths) { - if (LocalFileSystem.getInstance().findFileByIoFile(file) == null) { - return Boolean.FALSE; - } - } - return Boolean.TRUE; - } - }); - if (!refreshSuccess.booleanValue()) { - return false; - } - dropScopesCaches(); - } - - if (checkOutputAndSourceIntersection) { - if (myShouldClearOutputDirectory) { - if (!validateOutputAndSourcePathsIntersection()) { - return false; - } - } - } - final List> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules)); - final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); - for (final Chunk chunk : chunks) { - final Set chunkModules = chunk.getNodes(); - if (chunkModules.size() <= 1) { - continue; // no need to check one-module chunks - } - if (config.isAnnotationProcessorsEnabled()) { - final Set excluded = config.getExcludedModules(); - for (Module chunkModule : chunkModules) { - if (!excluded.contains(chunkModule)) { - showCyclesNotSupportedForAnnotationProcessors(chunkModules.toArray(new Module[chunkModules.size()])); - return false; - } - } - } - Sdk jdk = null; - LanguageLevel languageLevel = null; - for (final Module module : chunkModules) { - final Sdk moduleJdk = ModuleRootManager.getInstance(module).getSdk(); - if (jdk == null) { - jdk = moduleJdk; - } - else { - if (!jdk.equals(moduleJdk)) { - showCyclicModulesHaveDifferentJdksError(chunkModules.toArray(new Module[chunkModules.size()])); - return false; - } - } - - LanguageLevel moduleLanguageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module); - if (languageLevel == null) { - languageLevel = moduleLanguageLevel; - } - else { - if (!languageLevel.equals(moduleLanguageLevel)) { - showCyclicModulesHaveDifferentLanguageLevel(chunkModules.toArray(new Module[chunkModules.size()])); - return false; - } - } - } - } - final Compiler[] allCompilers = CompilerManager.getInstance(myProject).getCompilers(Compiler.class); - for (Compiler compiler : allCompilers) { - if (!compiler.validateConfiguration(scope)) { - return false; - } - } - return true; - } - - private void showCyclicModulesHaveDifferentLanguageLevel(Module[] modulesInChunk) { - LOG.assertTrue(modulesInChunk.length > 0); - String moduleNameToSelect = modulesInChunk[0].getName(); - final String moduleNames = getModulesString(modulesInChunk); - Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.language.level", moduleNames), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(moduleNameToSelect, null); - } - - private void showCyclicModulesHaveDifferentJdksError(Module[] modulesInChunk) { - LOG.assertTrue(modulesInChunk.length > 0); - String moduleNameToSelect = modulesInChunk[0].getName(); - final String moduleNames = getModulesString(modulesInChunk); - Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.jdk", moduleNames), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(moduleNameToSelect, null); - } - - private void showCyclesNotSupportedForAnnotationProcessors(Module[] modulesInChunk) { - LOG.assertTrue(modulesInChunk.length > 0); - String moduleNameToSelect = modulesInChunk[0].getName(); - final String moduleNames = getModulesString(modulesInChunk); - Messages.showMessageDialog(myProject, CompilerBundle.message("error.annotation.processing.not.supported.for.module.cycles", moduleNames), - CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(moduleNameToSelect, null); - } - - private static String getModulesString(Module[] modulesInChunk) { - final StringBuilder moduleNames = StringBuilderSpinAllocator.alloc(); - try { - for (Module module : modulesInChunk) { - if (moduleNames.length() > 0) { - moduleNames.append("\n"); - } - moduleNames.append("\"").append(module.getName()).append("\""); - } - return moduleNames.toString(); - } - finally { - StringBuilderSpinAllocator.dispose(moduleNames); - } - } - - private static boolean hasSources(Module module, boolean checkTestSources) { - final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); - for (final ContentEntry contentEntry : contentEntries) { - final SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); - for (final SourceFolder sourceFolder : sourceFolders) { - if (sourceFolder.getFile() == null) { - continue; // skip invalid source folders - } - if (checkTestSources) { - if (sourceFolder.isTestSource()) { - return true; - } - } - else { - if (!sourceFolder.isTestSource()) { - return true; - } - } - } - } - return false; - } - - private void showNotSpecifiedError(@NonNls final String resourceId, List modules, String tabNameToSelect) { - String nameToSelect = null; - final StringBuilder names = StringBuilderSpinAllocator.alloc(); - final String message; - try { - final int maxModulesToShow = 10; - for (String name : modules.size() > maxModulesToShow ? modules.subList(0, maxModulesToShow) : modules) { - if (nameToSelect == null) { - nameToSelect = name; - } - if (names.length() > 0) { - names.append(",\n"); - } - names.append("\""); - names.append(name); - names.append("\""); - } - if (modules.size() > maxModulesToShow) { - names.append(",\n..."); - } - message = CompilerBundle.message(resourceId, modules.size(), names.toString()); - } - finally { - StringBuilderSpinAllocator.dispose(names); - } - - if (ApplicationManager.getApplication().isUnitTestMode()) { - LOG.error(message); - } - - Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); - showConfigurationDialog(nameToSelect, tabNameToSelect); - } - - private boolean validateOutputAndSourcePathsIntersection() { - final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); - final VirtualFile[] outputPaths = CompilerPathsEx.getOutputDirectories(allModules); - final Set affectedOutputPaths = new HashSet(); - for (Module allModule : allModules) { - final ModuleRootManager rootManager = ModuleRootManager.getInstance(allModule); - final VirtualFile[] sourceRoots = rootManager.getSourceRoots(); - for (final VirtualFile outputPath : outputPaths) { - for (VirtualFile sourceRoot : sourceRoots) { - if (VfsUtil.isAncestor(outputPath, sourceRoot, true) || VfsUtil.isAncestor(sourceRoot, outputPath, false)) { - affectedOutputPaths.add(outputPath); - } - } - } - } - if (!affectedOutputPaths.isEmpty()) { - final StringBuilder paths = new StringBuilder(); - for (final VirtualFile affectedOutputPath : affectedOutputPaths) { - if (paths.length() < 0) { - paths.append("\n"); - } - paths.append(affectedOutputPath.getPath().replace('/', File.separatorChar)); - } - final int answer = Messages.showOkCancelDialog(myProject, - CompilerBundle.message("warning.sources.under.output.paths", paths.toString()), - CommonBundle.getErrorTitle(), Messages.getWarningIcon()); - if (answer == 0) { // ok - myShouldClearOutputDirectory = false; - return true; - } - else { - return false; - } - } - return true; - } - - private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) { - ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect, false); - } - - private static VirtualFile lookupVFile(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) { - final File file = new File(CompilerPaths.getGenerationOutputPath(compiler, module, forTestSources)); - final LocalFileSystem lfs = LocalFileSystem.getInstance(); - - VirtualFile vFile = lfs.findFileByIoFile(file); - if (vFile != null) { - return vFile; - } - - final boolean justCreated = file.mkdirs(); - vFile = lfs.refreshAndFindFileByIoFile(file); - - assert vFile != null: "Virtual file not found for " + file.getPath() + "; mkdirs() exit code is " + justCreated; - - return vFile; - } - - private static class CacheDeferredUpdater { - private final Map>> myData = new java.util.HashMap>>(); - - public void addFileForUpdate(final FileProcessingCompiler.ProcessingItem item, FileProcessingCompilerStateCache cache) { - final VirtualFile file = item.getFile(); - List> list = myData.get(file); - if (list == null) { - list = new ArrayList>(); - myData.put(file, list); - } - list.add(new Pair(cache, item)); - } - - public void doUpdate() throws IOException{ - final IOException[] ex = {null}; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - for (Map.Entry>> entry : myData.entrySet()) { - for (Pair pair : entry.getValue()) { - final FileProcessingCompiler.ProcessingItem item = pair.getSecond(); - pair.getFirst().update(entry.getKey(), item.getValidityState()); - } - } - } - catch (IOException e) { - ex[0] = e; - } - } - }); - if (ex[0] != null) { - throw ex[0]; - } - } - } - - private static class TranslatorsOutputSink implements TranslatingCompiler.OutputSink { - final Map> myPostponedItems = new HashMap>(); - private final CompileContextEx myContext; - private final TranslatingCompiler[] myCompilers; - private int myCurrentCompilerIdx; - //private LinkedBlockingQueue myFutures = new LinkedBlockingQueue(); - - private TranslatorsOutputSink(CompileContextEx context, TranslatingCompiler[] compilers) { - myContext = context; - this.myCompilers = compilers; - } - - public void setCurrentCompilerIndex(int index) { - myCurrentCompilerIdx = index; - } - - public void add(final String outputRoot, final Collection items, final VirtualFile[] filesToRecompile) { - final TranslatingCompiler compiler = myCompilers[myCurrentCompilerIdx]; - if (compiler instanceof IntermediateOutputCompiler) { - final LocalFileSystem lfs = LocalFileSystem.getInstance(); - final List outputs = new ArrayList(); - for (TranslatingCompiler.OutputItem item : items) { - final VirtualFile vFile = lfs.findFileByPath(item.getOutputPath()); - if (vFile != null) { - outputs.add(vFile); - } - } - myContext.markGenerated(outputs); - } - final int nextCompilerIdx = myCurrentCompilerIdx + 1; - try { - if (nextCompilerIdx < myCompilers.length ) { - final Map> updateNow = new java.util.HashMap>(); - // process postponed - for (Map.Entry> entry : myPostponedItems.entrySet()) { - final String outputDir = entry.getKey(); - final Collection postponed = entry.getValue(); - for (Iterator it = postponed.iterator(); it.hasNext();) { - TranslatingCompiler.OutputItem item = it.next(); - boolean shouldPostpone = false; - for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { - if (shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext)) { - break; - } - } - if (!shouldPostpone) { - // the file is not compilable by the rest of compilers, so it is safe to update it now - it.remove(); - addItemToMap(updateNow, outputDir, item); - } - } - } - // process items from current compilation - for (TranslatingCompiler.OutputItem item : items) { - boolean shouldPostpone = false; - for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { - if (shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext)) { - break; - } - } - if (shouldPostpone) { - // the file is compilable by the next compiler in row, update should be postponed - addItemToMap(myPostponedItems, outputRoot, item); - } - else { - addItemToMap(updateNow, outputRoot, item); - } - } - - if (updateNow.size() == 1) { - final Map.Entry> entry = updateNow.entrySet().iterator().next(); - final String outputDir = entry.getKey(); - final Collection itemsToUpdate = entry.getValue(); - TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, filesToRecompile); - } - else { - for (Map.Entry> entry : updateNow.entrySet()) { - final String outputDir = entry.getKey(); - final Collection itemsToUpdate = entry.getValue(); - TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, VirtualFile.EMPTY_ARRAY); - } - if (filesToRecompile.length > 0) { - TranslatingCompilerFilesMonitor.getInstance().update(myContext, null, Collections.emptyList(), filesToRecompile); - } - } - } - else { - TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputRoot, items, filesToRecompile); - } - } - catch (IOException e) { - LOG.info(e); - myContext.requestRebuildNextTime(e.getMessage()); - } - } - - private void addItemToMap(Map> map, String outputDir, TranslatingCompiler.OutputItem item) { - Collection collection = map.get(outputDir); - if (collection == null) { - collection = new ArrayList(); - map.put(outputDir, collection); - } - collection.add(item); - } - - public void flushPostponedItems() { - final TranslatingCompilerFilesMonitor filesMonitor = TranslatingCompilerFilesMonitor.getInstance(); - try { - for (Map.Entry> entry : myPostponedItems.entrySet()) { - final String outputDir = entry.getKey(); - final Collection items = entry.getValue(); - filesMonitor.update(myContext, outputDir, items, VirtualFile.EMPTY_ARRAY); - } - } - catch (IOException e) { - LOG.info(e); - myContext.requestRebuildNextTime(e.getMessage()); - } - finally { - filesMonitor.updateOutputRootsLayout(myContext.getProject()); - } - } - } -} +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @author: Eugene Zhuravlev + * Date: Jan 17, 2003 + * Time: 1:42:26 PM + */ +package com.intellij.compiler.impl; + +import com.intellij.CommonBundle; +import com.intellij.analysis.AnalysisScope; +import com.intellij.compiler.*; +import com.intellij.compiler.make.CacheCorruptedException; +import com.intellij.compiler.make.DependencyCache; +import com.intellij.compiler.progress.CompilerTask; +import com.intellij.diagnostic.IdeErrorsDialog; +import com.intellij.diagnostic.PluginException; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.compiler.Compiler; +import com.intellij.openapi.compiler.ex.CompileContextEx; +import com.intellij.openapi.compiler.ex.CompilerPathsEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.module.LanguageLevelUtil; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.DumbService; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ProjectBundle; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.ContentEntry; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.roots.SourceFolder; +import com.intellij.openapi.roots.ex.ProjectRootManagerEx; +import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor; +import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService; +import com.intellij.openapi.ui.MessageType; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.*; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.newvfs.RefreshQueue; +import com.intellij.openapi.wm.StatusBar; +import com.intellij.openapi.wm.ToolWindowId; +import com.intellij.openapi.wm.ToolWindowManager; +import com.intellij.openapi.wm.WindowManager; +import com.intellij.packageDependencies.DependenciesBuilder; +import com.intellij.packageDependencies.ForwardDependenciesBuilder; +import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.PsiCompiledElement; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.util.Chunk; +import com.intellij.util.LocalTimeCounter; +import com.intellij.util.StringBuilderSpinAllocator; +import com.intellij.util.ThrowableRunnable; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.HashMap; +import com.intellij.util.containers.OrderedSet; +import gnu.trove.TObjectHashingStrategy; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +import java.io.*; +import java.util.*; +import java.util.concurrent.CountDownLatch; + +public class CompileDriver { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver"); + + private final Project myProject; + private final Map, Pair> myGenerationCompilerModuleToOutputDirMap; // [IntermediateOutputCompiler, Module] -> [ProductionSources, TestSources] + private final String myCachesDirectoryPath; + private boolean myShouldClearOutputDirectory; + + private final Map myModuleOutputPaths = new HashMap(); + private final Map myModuleTestOutputPaths = new HashMap(); + + @NonNls private static final String VERSION_FILE_NAME = "version.dat"; + @NonNls private static final String LOCK_FILE_NAME = "in_progress.dat"; + + @NonNls private static final boolean GENERATE_CLASSPATH_INDEX = "true".equals(System.getProperty("generate.classpath.index")); + + private static final FileProcessingCompilerAdapterFactory FILE_PROCESSING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { + public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { + return new FileProcessingCompilerAdapter(context, compiler); + } + }; + private static final FileProcessingCompilerAdapterFactory FILE_PACKAGING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() { + public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) { + return new PackagingCompilerAdapter(context, (PackagingCompiler)compiler); + } + }; + private CompilerFilter myCompilerFilter = CompilerFilter.ALL; + private static CompilerFilter SOURCE_PROCESSING_ONLY = new CompilerFilter() { + public boolean acceptCompiler(Compiler compiler) { + return compiler instanceof SourceProcessingCompiler; + } + }; + private static CompilerFilter ALL_EXCEPT_SOURCE_PROCESSING = new CompilerFilter() { + public boolean acceptCompiler(Compiler compiler) { + return !SOURCE_PROCESSING_ONLY.acceptCompiler(compiler); + } + }; + + private OutputPathFinder myOutputFinder; // need this for updating zip archives (experimental feature) + + private Set myAllOutputDirectories; + private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/; + + public CompileDriver(Project project) { + myProject = project; + myCachesDirectoryPath = CompilerPaths.getCacheStoreDirectory(myProject).getPath().replace('/', File.separatorChar); + myShouldClearOutputDirectory = CompilerWorkspaceConfiguration.getInstance(myProject).CLEAR_OUTPUT_DIRECTORY; + + myGenerationCompilerModuleToOutputDirMap = new HashMap, Pair>(); + + final IntermediateOutputCompiler[] generatingCompilers = CompilerManager.getInstance(myProject).getCompilers(IntermediateOutputCompiler.class, myCompilerFilter); + if (generatingCompilers.length > 0) { + final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); + for (IntermediateOutputCompiler compiler : generatingCompilers) { + for (final Module module : allModules) { + final VirtualFile productionOutput = lookupVFile(compiler, module, false); + final VirtualFile testOutput = lookupVFile(compiler, module, true); + final Pair pair = new Pair(compiler, module); + final Pair outputs = new Pair(productionOutput, testOutput); + myGenerationCompilerModuleToOutputDirMap.put(pair, outputs); + } + } + } + } + + public void setCompilerFilter(CompilerFilter compilerFilter) { + myCompilerFilter = compilerFilter == null? CompilerFilter.ALL : compilerFilter; + } + + public void rebuild(CompileStatusNotification callback) { + doRebuild(callback, null, true, addAdditionalRoots(new ProjectCompileScope(myProject), ALL_EXCEPT_SOURCE_PROCESSING)); + } + + public void make(CompileScope scope, CompileStatusNotification callback) { + scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); + if (validateCompilerConfiguration(scope, false)) { + startup(scope, false, false, callback, null, true, false); + } + } + + public boolean isUpToDate(CompileScope scope) { + if (LOG.isDebugEnabled()) { + LOG.debug("isUpToDate operation started"); + } + scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING); + + final CompilerTask task = new CompilerTask(myProject, true, "", true); + final CompileContextImpl compileContext = + new CompileContextImpl(myProject, task, scope, createDependencyCache(), true, false); + + checkCachesVersion(compileContext); + if (compileContext.isRebuildRequested()) { + if (LOG.isDebugEnabled()) { + LOG.debug("Rebuild requested, up-to-date=false"); + } + return false; + } + + for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { + final Pair outputs = entry.getValue(); + Module module = entry.getKey().getSecond(); + compileContext.assignModule(outputs.getFirst(), module, false); + compileContext.assignModule(outputs.getSecond(), module, true); + } + + final Ref status = new Ref(); + + task.start(new Runnable() { + public void run() { + try { + myAllOutputDirectories = getAllOutputDirectories(); + // need this for updating zip archives experiment, uncomment if the feature is turned on + //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); + status.set(doCompile(compileContext, false, false, false, true)); + } + finally { + compileContext.commitZipFiles(); // just to be on the safe side; normally should do nothing if called in isUpToDate() + } + } + }, null); + + if (LOG.isDebugEnabled()) { + LOG.debug("isUpToDate operation finished"); + } + + return ExitStatus.UP_TO_DATE.equals(status.get()); + } + + private DependencyCache createDependencyCache() { + return new DependencyCache(myCachesDirectoryPath + File.separator + ".dependency-info"); + } + + public void compile(CompileScope scope, CompileStatusNotification callback, boolean trackDependencies) { + if (trackDependencies) { + scope = new TrackDependenciesScope(scope); + } + if (validateCompilerConfiguration(scope, false)) { + startup(scope, false, true, callback, null, true, trackDependencies); + } + } + + private static class CompileStatus { + final int CACHE_FORMAT_VERSION; + final boolean COMPILATION_IN_PROGRESS; + + private CompileStatus(int cacheVersion, boolean isCompilationInProgress) { + CACHE_FORMAT_VERSION = cacheVersion; + COMPILATION_IN_PROGRESS = isCompilationInProgress; + } + } + + private CompileStatus readStatus() { + final boolean isInProgress = getLockFile().exists(); + int version = -1; + try { + final File versionFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); + DataInputStream in = new DataInputStream(new FileInputStream(versionFile)); + try { + version = in.readInt(); + } + finally { + in.close(); + } + } + catch (FileNotFoundException e) { + // ignore + } + catch (IOException e) { + LOG.info(e); // may happen in case of IDEA crashed and the file is not written properly + return null; + } + return new CompileStatus(version, isInProgress); + } + + private void writeStatus(CompileStatus status, CompileContext context) { + final File statusFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME); + + final File lockFile = getLockFile(); + try { + FileUtil.createIfDoesntExist(statusFile); + DataOutputStream out = new DataOutputStream(new FileOutputStream(statusFile)); + try { + out.writeInt(status.CACHE_FORMAT_VERSION); + } + finally { + out.close(); + } + if (status.COMPILATION_IN_PROGRESS) { + FileUtil.createIfDoesntExist(lockFile); + } + else { + deleteFile(lockFile); + } + } + catch (IOException e) { + context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); + } + } + + private File getLockFile() { + return new File(CompilerPaths.getCompilerSystemDirectory(myProject), LOCK_FILE_NAME); + } + + private void doRebuild(CompileStatusNotification callback, + CompilerMessage message, + final boolean checkCachesVersion, + final CompileScope compileScope) { + if (validateCompilerConfiguration(compileScope, true)) { + startup(compileScope, true, false, callback, message, checkCachesVersion, false); + } + } + + private CompileScope addAdditionalRoots(CompileScope originalScope, final CompilerFilter filter) { + CompileScope scope = attachIntermediateOutputDirectories(originalScope, filter); + + final AdditionalCompileScopeProvider[] scopeProviders = Extensions.getExtensions(AdditionalCompileScopeProvider.EXTENSION_POINT_NAME); + CompileScope baseScope = scope; + for (AdditionalCompileScopeProvider scopeProvider : scopeProviders) { + final CompileScope additionalScope = scopeProvider.getAdditionalScope(baseScope); + if (additionalScope != null) { + scope = new CompositeScope(scope, additionalScope); + } + } + return scope; + } + + private CompileScope attachIntermediateOutputDirectories(CompileScope originalScope, CompilerFilter filter) { + CompileScope scope = originalScope; + final Set affected = new HashSet(Arrays.asList(originalScope.getAffectedModules())); + for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { + final Module module = entry.getKey().getSecond(); + if (affected.contains(module) && filter.acceptCompiler(entry.getKey().getFirst())) { + final Pair outputs = entry.getValue(); + scope = new CompositeScope(scope, new FileSetCompileScope(Arrays.asList(outputs.getFirst(), outputs.getSecond()), new Module[]{module})); + } + } + return scope; + } + + public static final Key COMPILATION_START_TIMESTAMP = Key.create("COMPILATION_START_TIMESTAMP"); + + private void startup(final CompileScope scope, + final boolean isRebuild, + final boolean forceCompile, + final CompileStatusNotification callback, + final CompilerMessage message, + final boolean checkCachesVersion, + final boolean trackDependencies) { + final CompilerTask compileTask = new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, + forceCompile + ? CompilerBundle.message("compiler.content.name.compile") + : CompilerBundle.message("compiler.content.name.make"), false); + final WindowManager windowManager = WindowManager.getInstance(); + if (windowManager != null) { + windowManager.getStatusBar(myProject).setInfo(""); + } + + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + FileDocumentManager.getInstance().saveAllDocuments(); + + final DependencyCache dependencyCache = createDependencyCache(); + final CompileContextImpl compileContext = + new CompileContextImpl(myProject, compileTask, scope, dependencyCache, !isRebuild && !forceCompile, isRebuild); + compileContext.putUserData(COMPILATION_START_TIMESTAMP, LocalTimeCounter.currentTime()); + for (Map.Entry, Pair> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) { + final Pair outputs = entry.getValue(); + final Module module = entry.getKey().getSecond(); + compileContext.assignModule(outputs.getFirst(), module, false); + compileContext.assignModule(outputs.getSecond(), module, true); + } + + compileTask.start(new Runnable() { + public void run() { + long start = System.currentTimeMillis(); + try { + if (myProject.isDisposed()) { + return; + } + LOG.info("COMPILATION STARTED"); + if (message != null) { + compileContext.addMessage(message); + } + TranslatingCompilerFilesMonitor.getInstance().ensureInitializationCompleted(myProject); + doCompile(compileContext, isRebuild, forceCompile, callback, checkCachesVersion, trackDependencies); + } + finally { + compileContext.commitZipFiles(); + CompilerUtil.logDuration("Refreshing VFS in total", CompilerUtil.ourRefreshTime); + CompilerUtil.ourRefreshTime = 0L; + final long finish = System.currentTimeMillis(); + CompilerUtil.logDuration( + "\tCOMPILATION FINISHED; Errors: " + + compileContext.getMessageCount(CompilerMessageCategory.ERROR) + + "; warnings: " + + compileContext.getMessageCount(CompilerMessageCategory.WARNING), + finish - start + ); + //if (LOG.isDebugEnabled()) { + // LOG.debug("COMPILATION FINISHED"); + //} + } + } + }, new Runnable() { + public void run() { + if (isRebuild) { + final int rv = Messages.showDialog( + myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild", + new String[]{"Make", "Rebuild"}, 0, Messages.getQuestionIcon() + ); + if (rv == 0 /*yes, please, do run make*/) { + startup(scope, false, false, callback, null, checkCachesVersion, trackDependencies); + return; + } + } + startup(scope, isRebuild, forceCompile, callback, message, checkCachesVersion, trackDependencies); + } + }); + } + + private void doCompile(final CompileContextImpl compileContext, + final boolean isRebuild, + final boolean forceCompile, + final CompileStatusNotification callback, + final boolean checkCachesVersion, + final boolean trackDependencies) { + ExitStatus status = ExitStatus.ERRORS; + boolean wereExceptions = false; + try { + if (checkCachesVersion) { + checkCachesVersion(compileContext); + if (compileContext.isRebuildRequested()) { + return; + } + } + writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, true), compileContext); + if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + return; + } + + myAllOutputDirectories = getAllOutputDirectories(); + // need this for updating zip archives experiment, uncomment if the feature is turned on + //myOutputFinder = new OutputPathFinder(myAllOutputDirectories); + status = doCompile(compileContext, isRebuild, forceCompile, trackDependencies, false); + } + catch (Throwable ex) { + wereExceptions = true; + final PluginId pluginId = IdeErrorsDialog.findPluginId(ex); + + final StringBuffer message = new StringBuffer(); + message.append("Internal error"); + if (pluginId != null) { + message.append(" (Plugin: ").append(pluginId).append(")"); + } + message.append(": ").append(ex.getMessage()); + compileContext.addMessage(CompilerMessageCategory.ERROR, message.toString(), null, -1, -1); + + if (pluginId != null) { + throw new PluginException(ex, pluginId); + } + throw new RuntimeException(ex); + } + finally { + dropDependencyCache(compileContext); + final ExitStatus _status = status; + if (compileContext.isRebuildRequested()) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + doRebuild(callback, new CompilerMessageImpl(myProject, CompilerMessageCategory.INFORMATION, compileContext.getRebuildReason(), + null, -1, -1, null), false, compileContext.getCompileScope()); + } + }, ModalityState.NON_MODAL); + } + else { + final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp(); + writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, wereExceptions), compileContext); + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + final int errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR); + final int warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING); + final String statusMessage = createStatusMessage(_status, warningCount, errorCount); + final StatusBar statusBar = WindowManager.getInstance().getStatusBar(myProject); + if (statusBar != null) { // because this code is in invoke later, the code may work for already closed project + // in case another project was opened in the frame while the compiler was working (See SCR# 28591) + statusBar.setInfo(statusMessage); + if (duration > ONE_MINUTE_MS) { + ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, MessageType.INFO, statusMessage); + } + } + if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) { + compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1); + } + if (callback != null) { + callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext); + } + } + }, ModalityState.NON_MODAL); + } + } + } + + private void checkCachesVersion(final CompileContextImpl compileContext) { + final CompileStatus compileStatus = readStatus(); + if (compileStatus == null) { + compileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); + } + else if (compileStatus.CACHE_FORMAT_VERSION != -1 && + compileStatus.CACHE_FORMAT_VERSION != CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION) { + compileContext.requestRebuildNextTime(CompilerBundle.message("error.caches.old.format")); + } + else if (compileStatus.COMPILATION_IN_PROGRESS) { + compileContext.requestRebuildNextTime(CompilerBundle.message("error.previous.compilation.failed")); + } + } + + private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount) { + if (status == ExitStatus.CANCELLED) { + return CompilerBundle.message("status.compilation.aborted"); + } + if (status == ExitStatus.UP_TO_DATE) { + return CompilerBundle.message("status.all.up.to.date"); + } + if (status == ExitStatus.SUCCESS) { + return warningCount > 0 + ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount) + : CompilerBundle.message("status.compilation.completed.successfully"); + } + return CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount); + } + + private static class ExitStatus { + private final String myName; + + private ExitStatus(@NonNls String name) { + myName = name; + } + + public String toString() { + return myName; + } + + public static final ExitStatus CANCELLED = new ExitStatus("CANCELLED"); + public static final ExitStatus ERRORS = new ExitStatus("ERRORS"); + public static final ExitStatus SUCCESS = new ExitStatus("SUCCESS"); + public static final ExitStatus UP_TO_DATE = new ExitStatus("UP_TO_DATE"); + } + + private static class ExitException extends Exception { + private final ExitStatus myStatus; + + private ExitException(ExitStatus status) { + myStatus = status; + } + + public ExitStatus getExitStatus() { + return myStatus; + } + } + + private ExitStatus doCompile(final CompileContextEx context, + boolean isRebuild, + final boolean forceCompile, + final boolean trackDependencies, final boolean onlyCheckStatus) { + try { + if (isRebuild) { + deleteAll(context); + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + if (LOG.isDebugEnabled()) { + logErrorMessages(context); + } + return ExitStatus.ERRORS; + } + } + + if (!onlyCheckStatus) { + if (!executeCompileTasks(context, true)) { + if (LOG.isDebugEnabled()) { + LOG.debug("Compilation cancelled"); + } + return ExitStatus.CANCELLED; + } + } + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + if (LOG.isDebugEnabled()) { + logErrorMessages(context); + } + return ExitStatus.ERRORS; + } + + final long refreshStart = System.currentTimeMillis(); + + // need this to make sure the VFS is built + boolean needRecalcOutputDirs = false; + //final List outputsToRefresh = new ArrayList(); + + final VirtualFile[] all = context.getAllOutputDirectories(); + + final ProgressIndicator progressIndicator = context.getProgressIndicator(); + + final int totalCount = all.length + myGenerationCompilerModuleToOutputDirMap.size() * 2; + final CountDownLatch latch = new CountDownLatch(totalCount); + final Runnable decCount = new Runnable() { + public void run() { + latch.countDown(); + progressIndicator.setFraction(((double)(totalCount - latch.getCount())) / totalCount); + } + }; + progressIndicator.pushState(); + progressIndicator.setText("Inspecting output directories..."); + final boolean asyncMode = !ApplicationManager.getApplication().isDispatchThread(); // must not lock awt thread with latch.await() + try { + for (VirtualFile output : all) { + if (output.isValid()) { + walkChildren(output, context); + } + else { + needRecalcOutputDirs = true; + final File file = new File(output.getPath()); + if (!file.exists()) { + final boolean created = file.mkdirs(); + if (!created) { + context.addMessage(CompilerMessageCategory.ERROR, "Failed to create output directory " + file.getPath(), null, 0, 0); + return ExitStatus.ERRORS; + } + } + output = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file); + if (output == null) { + context.addMessage(CompilerMessageCategory.ERROR, "Failed to locate output directory " + file.getPath(), null, 0, 0); + return ExitStatus.ERRORS; + } + } + output.refresh(asyncMode, true, decCount); + //outputsToRefresh.add(output); + } + for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { + final Pair generated = myGenerationCompilerModuleToOutputDirMap.get(pair); + walkChildren(generated.getFirst(), context); + //outputsToRefresh.add(generated.getFirst()); + generated.getFirst().refresh(asyncMode, true, decCount); + walkChildren(generated.getSecond(), context); + //outputsToRefresh.add(generated.getSecond()); + generated.getSecond().refresh(asyncMode, true, decCount); + } + + //RefreshQueue.getInstance().refresh(false, true, null, outputsToRefresh.toArray(new VirtualFile[outputsToRefresh.size()])); + try { + latch.await(); // wait until all threads are refreshed + } + catch (InterruptedException e) { + LOG.info(e); + } + } + finally { + progressIndicator.popState(); + } + + final long initialRefreshTime = System.currentTimeMillis() - refreshStart; + CompilerUtil.logDuration("Initial VFS refresh", initialRefreshTime); + CompilerUtil.ourRefreshTime += initialRefreshTime; + + DumbService.getInstance(myProject).waitForSmartMode(); + + if (needRecalcOutputDirs) { + context.recalculateOutputDirs(); + } + + boolean didSomething = false; + + final CompilerManager compilerManager = CompilerManager.getInstance(myProject); + + try { + didSomething |= generateSources(compilerManager, context, forceCompile, onlyCheckStatus); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceInstrumentingCompiler.class, + FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus); + + didSomething |= translate(context, compilerManager, forceCompile, isRebuild, trackDependencies, onlyCheckStatus); + + final boolean sourceProcessed = + invokeFileProcessingCompilers(compilerManager, context, SourceProcessingCompiler.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, + forceCompile, true, onlyCheckStatus); + didSomething |= sourceProcessed; + + if (sourceProcessed) { + final CompileScope intermediateSources = attachIntermediateOutputDirectories(new CompositeScope(CompileScope.EMPTY_ARRAY) { + @NotNull + public Module[] getAffectedModules() { + return context.getCompileScope().getAffectedModules(); + } + }, SOURCE_PROCESSING_ONLY); + context.addScope(intermediateSources); + + // important: override rebuild, forceCompile, and trackDependencies options in order to compile only newly generated stuff + didSomething |= translate(context, compilerManager, false, false, false, onlyCheckStatus); + } + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassInstrumentingCompiler.class, + FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); + + // explicitly passing forceCompile = false because in scopes that is narrower than ProjectScope it is impossible + // to understand whether the class to be processed is in scope or not. Otherwise compiler may process its items even if + // there were changes in completely independent files. + didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassPostProcessingCompiler.class, + FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, PackagingCompiler.class, + FILE_PACKAGING_COMPILER_ADAPTER_FACTORY, + isRebuild, false, onlyCheckStatus); + + didSomething |= invokeFileProcessingCompilers(compilerManager, context, Validator.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, + forceCompile, true, onlyCheckStatus); + } + catch (ExitException e) { + if (LOG.isDebugEnabled()) { + LOG.debug(e); + logErrorMessages(context); + } + return e.getExitStatus(); + } + finally { + // drop in case it has not been dropped yet. + dropDependencyCache(context); + + final VirtualFile[] allOutputDirs = context.getAllOutputDirectories(); + + if (didSomething && GENERATE_CLASSPATH_INDEX) { + CompilerUtil.runInContext(context, "Generating classpath index...", new ThrowableRunnable(){ + public void run() { + int count = 0; + for (VirtualFile file : allOutputDirs) { + context.getProgressIndicator().setFraction((double)++count / allOutputDirs.length); + createClasspathIndex(file); + } + } + }); + } + + if (!context.getProgressIndicator().isCanceled() && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + RefreshQueue.getInstance().refresh(true, true, new Runnable() { + public void run() { + CompilerDirectoryTimestamp.updateTimestamp(Arrays.asList(allOutputDirs)); + } + }, allOutputDirs); + } + } + + if (!onlyCheckStatus) { + if (!executeCompileTasks(context, false)) { + return ExitStatus.CANCELLED; + } + } + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + if (LOG.isDebugEnabled()) { + logErrorMessages(context); + } + return ExitStatus.ERRORS; + } + if (!didSomething) { + return ExitStatus.UP_TO_DATE; + } + return ExitStatus.SUCCESS; + } + catch (ProcessCanceledException e) { + return ExitStatus.CANCELLED; + } + } + + private static void logErrorMessages(final CompileContext context) { + final CompilerMessage[] errors = context.getMessages(CompilerMessageCategory.ERROR); + if (errors.length > 0) { + LOG.debug("Errors reported: "); + for (CompilerMessage error : errors) { + LOG.debug("\t" + error.getMessage()); + } + } + } + + private static void walkChildren(VirtualFile from, final CompileContext context) { + final VirtualFile[] files = from.getChildren(); + if (files != null && files.length > 0) { + context.getProgressIndicator().checkCanceled(); + context.getProgressIndicator().setText2(from.getPresentableUrl()); + for (VirtualFile file : files) { + walkChildren(file, context); + } + } + } + + private static void createClasspathIndex(final VirtualFile file) { + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(new File(VfsUtil.virtualToIoFile(file), "classpath.index"))); + try { + writeIndex(writer, file, file); + } + finally { + writer.close(); + } + } + catch (IOException e) { + // Ignore. Failed to create optional classpath index + } + } + + private static void writeIndex(final BufferedWriter writer, final VirtualFile root, final VirtualFile file) throws IOException { + writer.write(VfsUtil.getRelativePath(file, root, '/')); + writer.write('\n'); + + for (VirtualFile child : file.getChildren()) { + writeIndex(writer, root, child); + } + } + + private static void dropDependencyCache(final CompileContextEx context) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.saving.caches"), new ThrowableRunnable(){ + public void run() { + context.getDependencyCache().resetState(); + } + }); + } + + private boolean generateSources(final CompilerManager compilerManager, + CompileContextEx context, + final boolean forceCompile, + final boolean onlyCheckStatus) throws ExitException { + boolean didSomething = false; + + final SourceGeneratingCompiler[] sourceGenerators = compilerManager.getCompilers(SourceGeneratingCompiler.class, myCompilerFilter); + for (final SourceGeneratingCompiler sourceGenerator : sourceGenerators) { + if (context.getProgressIndicator().isCanceled()) { + throw new ExitException(ExitStatus.CANCELLED); + } + + final boolean generatedSomething = generateOutput(context, sourceGenerator, forceCompile, onlyCheckStatus); + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + throw new ExitException(ExitStatus.ERRORS); + } + didSomething |= generatedSomething; + } + return didSomething; + } + + private boolean translate(final CompileContextEx context, + final CompilerManager compilerManager, + final boolean forceCompile, + boolean isRebuild, + final boolean trackDependencies, final boolean onlyCheckStatus) throws ExitException { + + boolean didSomething = false; + + final TranslatingCompiler[] translators = compilerManager.getCompilers(TranslatingCompiler.class, myCompilerFilter); + + final Set generatedTypes = new HashSet(); + VirtualFile[] snapshot = null; + + + final TranslatorsOutputSink sink = new TranslatorsOutputSink(context, translators); + try { + for (int currentCompiler = 0, translatorsLength = translators.length; currentCompiler < translatorsLength; currentCompiler++) { + sink.setCurrentCompilerIndex(currentCompiler); + final TranslatingCompiler translator = translators[currentCompiler]; + if (context.getProgressIndicator().isCanceled()) { + throw new ExitException(ExitStatus.CANCELLED); + } + + if (snapshot == null || ContainerUtil.intersects(generatedTypes, compilerManager.getRegisteredInputTypes(translator))) { + // rescan snapshot if previously generated files can influence the input of this compiler + snapshot = ApplicationManager.getApplication().runReadAction(new Computable() { + public VirtualFile[] compute() { + return context.getCompileScope().getFiles(null, true); + } + }); + } + + final CompileContextEx _context; + if (translator instanceof IntermediateOutputCompiler) { + // wrap compile context so that output goes into intermediate directories + final IntermediateOutputCompiler _translator = (IntermediateOutputCompiler)translator; + _context = new CompileContextExProxy(context) { + public VirtualFile getModuleOutputDirectory(final Module module) { + return getGenerationOutputDir(_translator, module, false); + } + + public VirtualFile getModuleOutputDirectoryForTests(final Module module) { + return getGenerationOutputDir(_translator, module, true); + } + }; + } + else { + _context = context; + } + final boolean compiledSomething = + compileSources(_context, translators, currentCompiler, snapshot, forceCompile, isRebuild, trackDependencies, onlyCheckStatus, sink); + + if (compiledSomething) { + generatedTypes.addAll(compilerManager.getRegisteredOutputTypes(translator)); + } + + if (_context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + throw new ExitException(ExitStatus.ERRORS); + } + + didSomething |= compiledSomething; + } + } + finally { + if (context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + // perform update only if there were no errors, so it is guaranteed that the file was processd by all neccesary compilers + sink.flushPostponedItems(); + } + dropDependencyCache(context); + } + return didSomething; + } + + private interface FileProcessingCompilerAdapterFactory { + FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler); + } + + private boolean invokeFileProcessingCompilers(final CompilerManager compilerManager, + CompileContextEx context, + Class fileProcessingCompilerClass, + FileProcessingCompilerAdapterFactory factory, + boolean forceCompile, + final boolean checkScope, + final boolean onlyCheckStatus) throws ExitException { + boolean didSomething = false; + final FileProcessingCompiler[] compilers = compilerManager.getCompilers(fileProcessingCompilerClass, myCompilerFilter); + if (compilers.length > 0) { + try { + CacheDeferredUpdater cacheUpdater = new CacheDeferredUpdater(); + try { + for (final FileProcessingCompiler compiler : compilers) { + if (context.getProgressIndicator().isCanceled()) { + throw new ExitException(ExitStatus.CANCELLED); + } + + CompileContextEx _context = context; + if (compiler instanceof IntermediateOutputCompiler) { + final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler; + _context = new CompileContextExProxy(context) { + public VirtualFile getModuleOutputDirectory(final Module module) { + return getGenerationOutputDir(_compiler, module, false); + } + + public VirtualFile getModuleOutputDirectoryForTests(final Module module) { + return getGenerationOutputDir(_compiler, module, true); + } + }; + } + + final boolean processedSomething = processFiles(factory.create(_context, compiler), forceCompile, checkScope, onlyCheckStatus, cacheUpdater); + + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + throw new ExitException(ExitStatus.ERRORS); + } + + didSomething |= processedSomething; + } + } + finally { + cacheUpdater.doUpdate(); + } + } + catch (IOException e) { + LOG.info(e); + context.requestRebuildNextTime(e.getMessage()); + throw new ExitException(ExitStatus.ERRORS); + } + catch (ProcessCanceledException e) { + throw e; + } + catch (ExitException e) { + throw e; + } + catch (Exception e) { + context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1); + LOG.error(e); + } + } + + return didSomething; + } + + private static Map> buildModuleToGenerationItemMap(GeneratingCompiler.GenerationItem[] items) { + final Map> map = new HashMap>(); + for (GeneratingCompiler.GenerationItem item : items) { + Module module = item.getModule(); + LOG.assertTrue(module != null); + Set itemSet = map.get(module); + if (itemSet == null) { + itemSet = new HashSet(); + map.put(module, itemSet); + } + itemSet.add(item); + } + return map; + } + + private void deleteAll(final CompileContextEx context) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable() { + public void run() { + final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); + final VirtualFile[] allSources = context.getProjectCompileScope().getFiles(null, true); + if (myShouldClearOutputDirectory) { + clearOutputDirectories(myAllOutputDirectories); + } + else { // refresh is still required + try { + for (final Compiler compiler : CompilerManager.getInstance(myProject).getCompilers(Compiler.class)) { + try { + if (compiler instanceof GeneratingCompiler) { + final StateCache cache = getGeneratingCompilerCache((GeneratingCompiler)compiler); + final Iterator urlIterator = cache.getUrlsIterator(); + while (urlIterator.hasNext()) { + context.getProgressIndicator().checkCanceled(); + deleteFile(new File(VirtualFileManager.extractPath(urlIterator.next()))); + } + } + else if (compiler instanceof TranslatingCompiler) { + final ArrayList> toDelete = new ArrayList>(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + TranslatingCompilerFilesMonitor.getInstance() + .collectFiles(context, (TranslatingCompiler)compiler, Arrays.asList(allSources).iterator(), true + /*pass true to make sure that every source in scope file is processed*/, false + /*important! should pass false to enable collection of files to delete*/, + new ArrayList(), toDelete); + } + }); + for (Trinity trinity : toDelete) { + context.getProgressIndicator().checkCanceled(); + final File file = trinity.getFirst(); + deleteFile(file); + if (isTestMode) { + CompilerManagerImpl.addDeletedPath(file.getPath()); + } + } + } + } + catch (IOException e) { + LOG.info(e); + } + } + pruneEmptyDirectories(context.getProgressIndicator(), myAllOutputDirectories); // to avoid too much files deleted events + } + finally { + CompilerUtil.refreshIODirectories(myAllOutputDirectories); + } + } + dropScopesCaches(); + + clearCompilerSystemDirectory(context); + } + }); + } + + private void dropScopesCaches() { + // hack to be sure the classpath will include the output directories + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + ((ProjectRootManagerEx)ProjectRootManager.getInstance(myProject)).clearScopesCachesForModules(); + } + }); + } + + private static void pruneEmptyDirectories(ProgressIndicator progress, final Set directories) { + for (File directory : directories) { + doPrune(progress, directory, directories); + } + } + + private static boolean doPrune(ProgressIndicator progress, final File directory, final Set outPutDirectories) { + progress.checkCanceled(); + final File[] files = directory.listFiles(); + boolean isEmpty = true; + if (files != null) { + for (File file : files) { + if (!outPutDirectories.contains(file)) { + if (doPrune(progress, file, outPutDirectories)) { + deleteFile(file); + } + else { + isEmpty = false; + } + } + else { + isEmpty = false; + } + } + } + else { + isEmpty = false; + } + + return isEmpty; + } + + private Set getAllOutputDirectories() { + final Set outputDirs = new OrderedSet((TObjectHashingStrategy)TObjectHashingStrategy.CANONICAL); + for (final String path : CompilerPathsEx.getOutputPaths(ModuleManager.getInstance(myProject).getModules())) { + outputDirs.add(new File(path)); + } + return outputDirs; + } + + private void clearOutputDirectories(final Set _outputDirectories) { + final long start = System.currentTimeMillis(); + // do not delete directories themselves, or we'll get rootsChanged() otherwise + final List outputDirectories = new ArrayList(_outputDirectories); + for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { + outputDirectories.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false))); + outputDirectories.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true))); + } + Collection filesToDelete = new ArrayList(outputDirectories.size() * 2); + for (File outputDirectory : outputDirectories) { + File[] files = outputDirectory.listFiles(); + if (files != null) { + filesToDelete.addAll(Arrays.asList(files)); + } + } + FileUtil.asyncDelete(filesToDelete); + + // ensure output directories exist + for (final File file : outputDirectories) { + file.mkdirs(); + } + final long clearStop = System.currentTimeMillis(); + + CompilerUtil.refreshIODirectories(outputDirectories); + + final long refreshStop = System.currentTimeMillis(); + + CompilerUtil.logDuration("Clearing output dirs", clearStop - start); + CompilerUtil.logDuration("Refreshing output directories", refreshStop - clearStop); + } + + private void clearCompilerSystemDirectory(final CompileContextEx context) { + CompilerCacheManager.getInstance(myProject).clearCaches(context); + FileUtil.delete(CompilerPathsEx.getZipStoreDirectory(myProject)); + dropDependencyCache(context); + + for (Pair pair : myGenerationCompilerModuleToOutputDirMap.keySet()) { + final File[] outputs = { + new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)), + new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true)) + }; + for (File output : outputs) { + final File[] files = output.listFiles(); + if (files != null) { + for (final File file : files) { + final boolean deleteOk = deleteFile(file); + if (!deleteOk) { + context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.failed.to.delete", file.getPath()), + null, -1, -1); + } + } + } + } + } + } + + /** + * @param file a file to delete + * @return true if and only if the file existed and was successfully deleted + * Note: the behaviour is different from FileUtil.delete() which returns true if the file absent on the disk + */ + private static boolean deleteFile(final File file) { + File[] files = file.listFiles(); + if (files != null) { + for (File file1 : files) { + deleteFile(file1); + } + } + + for (int i = 0; i < 10; i++){ + if (file.delete()) { + return true; + } + if (!file.exists()) { + return false; + } + try { + Thread.sleep(50); + } + catch (InterruptedException ignored) { + } + } + return false; + } + + private VirtualFile getGenerationOutputDir(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) { + final Pair outputs = + myGenerationCompilerModuleToOutputDirMap.get(new Pair(compiler, module)); + return forTestSources? outputs.getSecond() : outputs.getFirst(); + } + + private boolean generateOutput(final CompileContextEx context, + final GeneratingCompiler compiler, + final boolean forceGenerate, + final boolean onlyCheckStatus) throws ExitException { + final GeneratingCompiler.GenerationItem[] allItems = compiler.getGenerationItems(context); + final List toGenerate = new ArrayList(); + final List filesToRefresh = new ArrayList(); + final List generatedFiles = new ArrayList(); + final List affectedModules = new ArrayList(); + try { + final StateCache cache = getGeneratingCompilerCache(compiler); + final Set pathsToRemove = new HashSet(cache.getUrls()); + + final Map itemToOutputPathMap = new HashMap(); + final IOException[] ex = {null}; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (final GeneratingCompiler.GenerationItem item : allItems) { + final Module itemModule = item.getModule(); + final String outputDirPath = CompilerPaths.getGenerationOutputPath(compiler, itemModule, item.isTestSource()); + final String outputPath = outputDirPath + "/" + item.getPath(); + itemToOutputPathMap.put(item, outputPath); + + try { + final ValidityState savedState = cache.getState(outputPath); + + if (forceGenerate || savedState == null || !savedState.equalsTo(item.getValidityState())) { + final String outputPathUrl = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, outputPath); + if (context.getCompileScope().belongs(outputPathUrl)) { + toGenerate.add(item); + } + else { + pathsToRemove.remove(outputPath); + } + } + else { + pathsToRemove.remove(outputPath); + } + } + catch (IOException e) { + ex[0] = e; + } + } + } + }); + if (ex[0] != null) { + throw ex[0]; + } + + if (onlyCheckStatus) { + if (toGenerate.isEmpty() && pathsToRemove.isEmpty()) { + return false; + } + if (LOG.isDebugEnabled()) { + if (!toGenerate.isEmpty()) { + LOG.debug("Found items to generate, compiler " + compiler.getDescription()); + } + if (!pathsToRemove.isEmpty()) { + LOG.debug("Found paths to remove, compiler " + compiler.getDescription()); + } + } + throw new ExitException(ExitStatus.CANCELLED); + } + + if (!pathsToRemove.isEmpty()) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ + public void run() throws IOException { + for (final String path : pathsToRemove) { + final File file = new File(path); + final boolean deleted = deleteFile(file); + if (deleted) { + cache.remove(path); + filesToRefresh.add(file); + } + } + } + }); + } + + final Map> moduleToItemMap = + buildModuleToGenerationItemMap(toGenerate.toArray(new GeneratingCompiler.GenerationItem[toGenerate.size()])); + List modules = new ArrayList(moduleToItemMap.size()); + for (final Module module : moduleToItemMap.keySet()) { + modules.add(module); + } + ModuleCompilerUtil.sortModules(myProject, modules); + + for (final Module module : modules) { + CompilerUtil.runInContext(context, "Generating output from "+compiler.getDescription(),new ThrowableRunnable(){ + public void run() throws IOException { + final Set items = moduleToItemMap.get(module); + if (items != null && !items.isEmpty()) { + final GeneratingCompiler.GenerationItem[][] productionAndTestItems = splitGenerationItems(items); + for (GeneratingCompiler.GenerationItem[] _items : productionAndTestItems) { + if (_items.length == 0) continue; + final VirtualFile outputDir = getGenerationOutputDir(compiler, module, _items[0].isTestSource()); + final GeneratingCompiler.GenerationItem[] successfullyGenerated = compiler.generate(context, _items, outputDir); + + CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { + public void run() throws IOException { + if (successfullyGenerated.length > 0) { + affectedModules.add(module); + } + for (final GeneratingCompiler.GenerationItem item : successfullyGenerated) { + final String fullOutputPath = itemToOutputPathMap.get(item); + cache.update(fullOutputPath, item.getValidityState()); + final File file = new File(fullOutputPath); + filesToRefresh.add(file); + generatedFiles.add(file); + context.getProgressIndicator().setText2(file.getPath()); + } + } + }); + } + } + } + }); + } + } + catch (IOException e) { + LOG.info(e); + context.requestRebuildNextTime(e.getMessage()); + throw new ExitException(ExitStatus.ERRORS); + } + finally { + CompilerUtil.refreshIOFiles(filesToRefresh); + if (!generatedFiles.isEmpty()) { + List vFiles = ApplicationManager.getApplication().runReadAction(new Computable>() { + public List compute() { + final ArrayList vFiles = new ArrayList(generatedFiles.size()); + for (File generatedFile : generatedFiles) { + final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(generatedFile); + if (vFile != null) { + vFiles.add(vFile); + } + } + return vFiles; + } + }); + if (forceGenerate) { + context.addScope(new FileSetCompileScope(vFiles, affectedModules.toArray(new Module[affectedModules.size()]))); + } + context.markGenerated(vFiles); + } + } + return !toGenerate.isEmpty() || !filesToRefresh.isEmpty(); + } + + private static GeneratingCompiler.GenerationItem[][] splitGenerationItems(final Set items) { + final List production = new ArrayList(); + final List tests = new ArrayList(); + for (GeneratingCompiler.GenerationItem item : items) { + if (item.isTestSource()) { + tests.add(item); + } + else { + production.add(item); + } + } + return new GeneratingCompiler.GenerationItem[][]{ + production.toArray(new GeneratingCompiler.GenerationItem[production.size()]), + tests.toArray(new GeneratingCompiler.GenerationItem[tests.size()]) + }; + } + + private boolean compileSources(final CompileContextEx context, TranslatingCompiler[] compilers, int currentCompiler, final VirtualFile[] sources, + final boolean forceCompile, + final boolean isRebuild, + final boolean trackDependencies, + final boolean onlyCheckStatus, + TranslatingCompiler.OutputSink sink) throws ExitException { + + final TranslatingCompiler compiler = compilers[currentCompiler]; + + final Set toCompile = new HashSet(); + final List> toDelete = new ArrayList>(); + context.getProgressIndicator().pushState(); + + final boolean[] wereFilesDeleted = new boolean[]{false}; + try { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + + TranslatingCompilerFilesMonitor.getInstance().collectFiles( + context, compiler, Arrays.asList(sources).iterator(), forceCompile, isRebuild, toCompile, toDelete + ); + if (trackDependencies && !toCompile.isEmpty()) { // should add dependent files + final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); + final PsiManager psiManager = PsiManager.getInstance(myProject); + final VirtualFile[] filesToCompile = toCompile.toArray(new VirtualFile[toCompile.size()]); + for (final VirtualFile file : filesToCompile) { + if (fileTypeManager.getFileTypeByFile(file) == StdFileTypes.JAVA) { + final PsiFile psiFile = psiManager.findFile(file); + if (psiFile != null) { + addDependentFiles(psiFile, toCompile, compiler, context); + } + } + } + } + } + }); + + if (onlyCheckStatus) { + if (toDelete.isEmpty() && toCompile.isEmpty()) { + return false; + } + if (LOG.isDebugEnabled()) { + if (!toDelete.isEmpty()) { + LOG.debug("Found items to delete, compiler " + compiler.getDescription()); + } + if (!toCompile.isEmpty()) { + LOG.debug("Found items to compile, compiler " + compiler.getDescription()); + } + } + throw new ExitException(ExitStatus.CANCELLED); + } + + if (!toDelete.isEmpty()) { + try { + wereFilesDeleted[0] = syncOutputDir(context, toDelete); + } + catch (CacheCorruptedException e) { + LOG.info(e); + context.requestRebuildNextTime(e.getMessage()); + } + } + + if ((wereFilesDeleted[0] || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + compiler.compile(context, toCompile.toArray(new VirtualFile[toCompile.size()]), sink); + } + } + finally { + context.getProgressIndicator().popState(); + } + return !toCompile.isEmpty() || wereFilesDeleted[0]; + } + + private static boolean syncOutputDir(final CompileContextEx context, final Collection> toDelete) throws CacheCorruptedException { + final int total = toDelete.size(); + final DependencyCache dependencyCache = context.getDependencyCache(); + final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode(); + + final List filesToRefresh = new ArrayList(); + final boolean[] wereFilesDeleted = {false}; + CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable(){ + public void run() throws CacheCorruptedException { + final long start = System.currentTimeMillis(); + try { + int current = 0; + for (final Trinity trinity : toDelete) { + final File outputPath = trinity.getFirst(); + context.getProgressIndicator().checkCanceled(); + context.getProgressIndicator().setFraction((double)++current / total); + context.getProgressIndicator().setText2(outputPath.getPath()); + filesToRefresh.add(outputPath); + if (isTestMode) { + LOG.assertTrue(outputPath.exists()); + } + if (!deleteFile(outputPath)) { + if (isTestMode && outputPath.exists()) { + LOG.error("Was not able to delete output file: " + outputPath.getPath()); + } + continue; + } + wereFilesDeleted[0] = true; + + // update zip here + //final String outputDir = myOutputFinder.lookupOutputPath(outputPath); + //if (outputDir != null) { + // try { + // context.updateZippedOuput(outputDir, FileUtil.toSystemIndependentName(outputPath.getPath()).substring(outputDir.length() + 1)); + // } + // catch (IOException e) { + // LOG.info(e); + // } + //} + + final String className = trinity.getSecond(); + if (className != null) { + final int id = dependencyCache.getSymbolTable().getId(className); + dependencyCache.addTraverseRoot(id); + final boolean sourcePresent = trinity.getThird().booleanValue(); + if (!sourcePresent) { + dependencyCache.markSourceRemoved(id); + } + } + if (isTestMode) { + CompilerManagerImpl.addDeletedPath(outputPath.getPath()); + } + } + } + finally { + CompilerUtil.logDuration("Sync output directory", System.currentTimeMillis() - start); + CompilerUtil.refreshIOFiles(filesToRefresh); + } + } + }); + return wereFilesDeleted[0]; + } + + private void addDependentFiles(final PsiFile psiFile, Set toCompile, TranslatingCompiler compiler, CompileContext context) { + final DependenciesBuilder builder = new ForwardDependenciesBuilder(myProject, new AnalysisScope(psiFile)); + builder.analyze(); + final Map> dependencies = builder.getDependencies(); + final Set dependentFiles = dependencies.get(psiFile); + if (dependentFiles != null && !dependentFiles.isEmpty()) { + final TranslatingCompilerFilesMonitor monitor = TranslatingCompilerFilesMonitor.getInstance(); + for (final PsiFile dependentFile : dependentFiles) { + if (dependentFile instanceof PsiCompiledElement) { + continue; + } + final VirtualFile vFile = dependentFile.getVirtualFile(); + if (vFile == null || toCompile.contains(vFile)) { + continue; + } + if (!compiler.isCompilableFile(vFile, context)) { + continue; + } + if (!monitor.isMarkedForCompilation(myProject, vFile)) { + continue; // no need to compile since already compiled + } + toCompile.add(vFile); + addDependentFiles(dependentFile, toCompile, compiler, context); + } + } + } + + // [mike] performance optimization - this method is accessed > 15,000 times in Aurora + private String getModuleOutputPath(final Module module, boolean inTestSourceContent) { + final Map map = inTestSourceContent ? myModuleTestOutputPaths : myModuleOutputPaths; + String path = map.get(module); + if (path == null) { + path = CompilerPaths.getModuleOutputPath(module, inTestSourceContent); + map.put(module, path); + } + + return path; + } + + private boolean processFiles(final FileProcessingCompilerAdapter adapter, + final boolean forceCompile, + final boolean checkScope, + final boolean onlyCheckStatus, final CacheDeferredUpdater cacheUpdater) throws ExitException, IOException { + final CompileContextEx context = (CompileContextEx)adapter.getCompileContext(); + final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache(adapter.getCompiler()); + final FileProcessingCompiler.ProcessingItem[] items = adapter.getProcessingItems(); + if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + return false; + } + final CompileScope scope = context.getCompileScope(); + final List toProcess = new ArrayList(); + final Set allUrls = new HashSet(); + final IOException[] ex = {null}; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + for (FileProcessingCompiler.ProcessingItem item : items) { + final VirtualFile file = item.getFile(); + if (file == null) { + LOG.error("FileProcessingCompiler.ProcessingItem.getFile() must not return null: compiler " + adapter.getCompiler().getDescription()); + continue; + } + final String url = file.getUrl(); + allUrls.add(url); + if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) { + final ValidityState state = cache.getExtState(url); + final ValidityState itemState = item.getValidityState(); + if (state != null ? state.equalsTo(itemState) : itemState == null) { + continue; + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("Adding item to process: " + url + "; saved ts= " + cache.getTimestamp(url) + "; VFS ts=" + file.getTimeStamp()); + } + toProcess.add(item); + } + } + catch (IOException e) { + ex[0] = e; + } + } + }); + + if (ex[0] != null) { + throw ex[0]; + } + + final Collection urls = cache.getUrls(); + final List urlsToRemove = new ArrayList(); + if (!urls.isEmpty()) { + CompilerUtil.runInContext(context, CompilerBundle.message("progress.processing.outdated.files"), new ThrowableRunnable(){ + public void run() throws IOException { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (final String url : urls) { + if (!allUrls.contains(url)) { + if (!checkScope || scope.belongs(url)) { + urlsToRemove.add(url); + } + } + } + } + }); + if (!onlyCheckStatus && !urlsToRemove.isEmpty()) { + for (final String url : urlsToRemove) { + adapter.processOutdatedItem(context, url, cache.getExtState(url)); + cache.remove(url); + } + } + } + }); + } + + if (onlyCheckStatus) { + if (urlsToRemove.isEmpty() && toProcess.isEmpty()) { + return false; + } + if (LOG.isDebugEnabled()) { + if (!urlsToRemove.isEmpty()) { + LOG.debug("Found urls to remove, compiler " + adapter.getCompiler().getDescription()); + for (String url : urlsToRemove) { + LOG.debug("\t" + url); + } + } + if (!toProcess.isEmpty()) { + LOG.debug("Found items to compile, compiler " + adapter.getCompiler().getDescription()); + for (FileProcessingCompiler.ProcessingItem item : toProcess) { + LOG.debug("\t" + item.getFile().getPresentableUrl()); + } + } + } + throw new ExitException(ExitStatus.CANCELLED); + } + + if (toProcess.isEmpty()) { + return false; + } + + final FileProcessingCompiler.ProcessingItem[] processed = + adapter.process(toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()])); + + if (processed.length == 0) { + return true; + } + CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable() { + public void run() throws IOException { + final List vFiles = new ArrayList(processed.length); + for (FileProcessingCompiler.ProcessingItem aProcessed : processed) { + final VirtualFile file = aProcessed.getFile(); + vFiles.add(file); + if (LOG.isDebugEnabled()) { + LOG.debug("File processed by " + adapter.getCompiler().getDescription()); + LOG.debug("\tFile processed " + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); + } + + //final String path = file.getPath(); + //final String outputDir = myOutputFinder.lookupOutputPath(path); + //if (outputDir != null) { + // context.updateZippedOuput(outputDir, path.substring(outputDir.length() + 1)); + //} + } + LocalFileSystem.getInstance().refreshFiles(vFiles); + if (LOG.isDebugEnabled()) { + LOG.debug("Files after VFS refresh:"); + for (VirtualFile file : vFiles) { + LOG.debug("\t" + file.getPresentableUrl() + "; ts=" + file.getTimeStamp()); + } + } + for (FileProcessingCompiler.ProcessingItem item : processed) { + cacheUpdater.addFileForUpdate(item, cache); + } + } + }); + return true; + } + + private FileProcessingCompilerStateCache getFileProcessingCompilerCache(FileProcessingCompiler compiler) throws IOException { + return CompilerCacheManager.getInstance(myProject).getFileProcessingCompilerCache(compiler); + } + + private StateCache getGeneratingCompilerCache(final GeneratingCompiler compiler) throws IOException { + return CompilerCacheManager.getInstance(myProject).getGeneratingCompilerCache(compiler); + } + + public void executeCompileTask(final CompileTask task, final CompileScope scope, final String contentName, final Runnable onTaskFinished) { + final CompilerTask progressManagerTask = + new CompilerTask(myProject, CompilerWorkspaceConfiguration.getInstance(myProject).COMPILE_IN_BACKGROUND, contentName, false); + final CompileContextImpl compileContext = new CompileContextImpl(myProject, progressManagerTask, scope, null, false, false); + + FileDocumentManager.getInstance().saveAllDocuments(); + + progressManagerTask.start(new Runnable() { + public void run() { + try { + task.execute(compileContext); + } + catch (ProcessCanceledException ex) { + // suppressed + } + finally { + compileContext.commitZipFiles(); + if (onTaskFinished != null) { + onTaskFinished.run(); + } + } + } + }, null); + } + + private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) { + final CompilerManager manager = CompilerManager.getInstance(myProject); + final ProgressIndicator progressIndicator = context.getProgressIndicator(); + progressIndicator.pushState(); + try { + CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks(); + if (tasks.length > 0) { + progressIndicator.setText(beforeTasks + ? CompilerBundle.message("progress.executing.precompile.tasks") + : CompilerBundle.message("progress.executing.postcompile.tasks")); + for (CompileTask task : tasks) { + if (!task.execute(context)) { + return false; + } + } + } + } + finally { + progressIndicator.popState(); + WindowManager.getInstance().getStatusBar(myProject).setInfo(""); + if (progressIndicator instanceof CompilerTask) { + ApplicationManager.getApplication().invokeLater(new Runnable() { + public void run() { + ((CompilerTask)progressIndicator).showCompilerContent(); + } + }); + } + } + return true; + } + + // todo: add validation for module chunks: all modules that form a chunk must have the same JDK + private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) { + final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/; + final List modulesWithoutOutputPathSpecified = new ArrayList(); + final List modulesWithoutJdkAssigned = new ArrayList(); + final Set nonExistingOutputPaths = new HashSet(); + + for (final Module module : scopeModules) { + final boolean hasSources = hasSources(module, false); + final boolean hasTestSources = hasSources(module, true); + if (!hasSources && !hasTestSources) { + // If module contains no sources, shouldn't have to select JDK or output directory (SCR #19333) + // todo still there may be problems with this approach if some generated files are attributed by this module + continue; + } + final Sdk jdk = ModuleRootManager.getInstance(module).getSdk(); + if (jdk == null) { + modulesWithoutJdkAssigned.add(module.getName()); + } + final String outputPath = getModuleOutputPath(module, false); + final String testsOutputPath = getModuleOutputPath(module, true); + if (outputPath == null && testsOutputPath == null) { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + else { + if (outputPath != null) { + final File file = new File(outputPath.replace('/', File.separatorChar)); + if (!file.exists()) { + nonExistingOutputPaths.add(file); + } + } + else { + if (hasSources) { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + } + if (testsOutputPath != null) { + final File f = new File(testsOutputPath.replace('/', File.separatorChar)); + if (!f.exists()) { + nonExistingOutputPaths.add(f); + } + } + else { + if (hasTestSources) { + modulesWithoutOutputPathSpecified.add(module.getName()); + } + } + } + } + if (!modulesWithoutJdkAssigned.isEmpty()) { + showNotSpecifiedError("error.jdk.not.specified", modulesWithoutJdkAssigned, ProjectBundle.message("modules.classpath.title")); + return false; + } + + if (!modulesWithoutOutputPathSpecified.isEmpty()) { + showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME); + return false; + } + + if (!nonExistingOutputPaths.isEmpty()) { + for (File file : nonExistingOutputPaths) { + final boolean succeeded = file.mkdirs(); + if (!succeeded) { + if (file.exists()) { + // for overlapping paths, this one might have been created as an intermediate path on a previous iteration + continue; + } + Messages.showMessageDialog(myProject, CompilerBundle.message("error.failed.to.create.directory", file.getPath()), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + return false; + } + } + final Boolean refreshSuccess = ApplicationManager.getApplication().runWriteAction(new Computable() { + public Boolean compute() { + LocalFileSystem.getInstance().refreshIoFiles(nonExistingOutputPaths); + for (File file : nonExistingOutputPaths) { + if (LocalFileSystem.getInstance().findFileByIoFile(file) == null) { + return Boolean.FALSE; + } + } + return Boolean.TRUE; + } + }); + if (!refreshSuccess.booleanValue()) { + return false; + } + dropScopesCaches(); + } + + if (checkOutputAndSourceIntersection) { + if (myShouldClearOutputDirectory) { + if (!validateOutputAndSourcePathsIntersection()) { + return false; + } + } + } + final List> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules)); + final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject); + for (final Chunk chunk : chunks) { + final Set chunkModules = chunk.getNodes(); + if (chunkModules.size() <= 1) { + continue; // no need to check one-module chunks + } + if (config.isAnnotationProcessorsEnabled()) { + final Set excluded = config.getExcludedModules(); + for (Module chunkModule : chunkModules) { + if (!excluded.contains(chunkModule)) { + showCyclesNotSupportedForAnnotationProcessors(chunkModules.toArray(new Module[chunkModules.size()])); + return false; + } + } + } + Sdk jdk = null; + LanguageLevel languageLevel = null; + for (final Module module : chunkModules) { + final Sdk moduleJdk = ModuleRootManager.getInstance(module).getSdk(); + if (jdk == null) { + jdk = moduleJdk; + } + else { + if (!jdk.equals(moduleJdk)) { + showCyclicModulesHaveDifferentJdksError(chunkModules.toArray(new Module[chunkModules.size()])); + return false; + } + } + + LanguageLevel moduleLanguageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module); + if (languageLevel == null) { + languageLevel = moduleLanguageLevel; + } + else { + if (!languageLevel.equals(moduleLanguageLevel)) { + showCyclicModulesHaveDifferentLanguageLevel(chunkModules.toArray(new Module[chunkModules.size()])); + return false; + } + } + } + } + final Compiler[] allCompilers = CompilerManager.getInstance(myProject).getCompilers(Compiler.class); + for (Compiler compiler : allCompilers) { + if (!compiler.validateConfiguration(scope)) { + return false; + } + } + return true; + } + + private void showCyclicModulesHaveDifferentLanguageLevel(Module[] modulesInChunk) { + LOG.assertTrue(modulesInChunk.length > 0); + String moduleNameToSelect = modulesInChunk[0].getName(); + final String moduleNames = getModulesString(modulesInChunk); + Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.language.level", moduleNames), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(moduleNameToSelect, null); + } + + private void showCyclicModulesHaveDifferentJdksError(Module[] modulesInChunk) { + LOG.assertTrue(modulesInChunk.length > 0); + String moduleNameToSelect = modulesInChunk[0].getName(); + final String moduleNames = getModulesString(modulesInChunk); + Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.jdk", moduleNames), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(moduleNameToSelect, null); + } + + private void showCyclesNotSupportedForAnnotationProcessors(Module[] modulesInChunk) { + LOG.assertTrue(modulesInChunk.length > 0); + String moduleNameToSelect = modulesInChunk[0].getName(); + final String moduleNames = getModulesString(modulesInChunk); + Messages.showMessageDialog(myProject, CompilerBundle.message("error.annotation.processing.not.supported.for.module.cycles", moduleNames), + CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(moduleNameToSelect, null); + } + + private static String getModulesString(Module[] modulesInChunk) { + final StringBuilder moduleNames = StringBuilderSpinAllocator.alloc(); + try { + for (Module module : modulesInChunk) { + if (moduleNames.length() > 0) { + moduleNames.append("\n"); + } + moduleNames.append("\"").append(module.getName()).append("\""); + } + return moduleNames.toString(); + } + finally { + StringBuilderSpinAllocator.dispose(moduleNames); + } + } + + private static boolean hasSources(Module module, boolean checkTestSources) { + final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries(); + for (final ContentEntry contentEntry : contentEntries) { + final SourceFolder[] sourceFolders = contentEntry.getSourceFolders(); + for (final SourceFolder sourceFolder : sourceFolders) { + if (sourceFolder.getFile() == null) { + continue; // skip invalid source folders + } + if (checkTestSources) { + if (sourceFolder.isTestSource()) { + return true; + } + } + else { + if (!sourceFolder.isTestSource()) { + return true; + } + } + } + } + return false; + } + + private void showNotSpecifiedError(@NonNls final String resourceId, List modules, String tabNameToSelect) { + String nameToSelect = null; + final StringBuilder names = StringBuilderSpinAllocator.alloc(); + final String message; + try { + final int maxModulesToShow = 10; + for (String name : modules.size() > maxModulesToShow ? modules.subList(0, maxModulesToShow) : modules) { + if (nameToSelect == null) { + nameToSelect = name; + } + if (names.length() > 0) { + names.append(",\n"); + } + names.append("\""); + names.append(name); + names.append("\""); + } + if (modules.size() > maxModulesToShow) { + names.append(",\n..."); + } + message = CompilerBundle.message(resourceId, modules.size(), names.toString()); + } + finally { + StringBuilderSpinAllocator.dispose(names); + } + + if (ApplicationManager.getApplication().isUnitTestMode()) { + LOG.error(message); + } + + Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon()); + showConfigurationDialog(nameToSelect, tabNameToSelect); + } + + private boolean validateOutputAndSourcePathsIntersection() { + final Module[] allModules = ModuleManager.getInstance(myProject).getModules(); + final VirtualFile[] outputPaths = CompilerPathsEx.getOutputDirectories(allModules); + final Set affectedOutputPaths = new HashSet(); + for (Module allModule : allModules) { + final ModuleRootManager rootManager = ModuleRootManager.getInstance(allModule); + final VirtualFile[] sourceRoots = rootManager.getSourceRoots(); + for (final VirtualFile outputPath : outputPaths) { + for (VirtualFile sourceRoot : sourceRoots) { + if (VfsUtil.isAncestor(outputPath, sourceRoot, true) || VfsUtil.isAncestor(sourceRoot, outputPath, false)) { + affectedOutputPaths.add(outputPath); + } + } + } + } + if (!affectedOutputPaths.isEmpty()) { + final StringBuilder paths = new StringBuilder(); + for (final VirtualFile affectedOutputPath : affectedOutputPaths) { + if (paths.length() < 0) { + paths.append("\n"); + } + paths.append(affectedOutputPath.getPath().replace('/', File.separatorChar)); + } + final int answer = Messages.showOkCancelDialog(myProject, + CompilerBundle.message("warning.sources.under.output.paths", paths.toString()), + CommonBundle.getErrorTitle(), Messages.getWarningIcon()); + if (answer == 0) { // ok + myShouldClearOutputDirectory = false; + return true; + } + else { + return false; + } + } + return true; + } + + private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) { + ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect, false); + } + + private static VirtualFile lookupVFile(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) { + final File file = new File(CompilerPaths.getGenerationOutputPath(compiler, module, forTestSources)); + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + + VirtualFile vFile = lfs.findFileByIoFile(file); + if (vFile != null) { + return vFile; + } + + final boolean justCreated = file.mkdirs(); + vFile = lfs.refreshAndFindFileByIoFile(file); + + assert vFile != null: "Virtual file not found for " + file.getPath() + "; mkdirs() exit code is " + justCreated; + + return vFile; + } + + private static class CacheDeferredUpdater { + private final Map>> myData = new java.util.HashMap>>(); + + public void addFileForUpdate(final FileProcessingCompiler.ProcessingItem item, FileProcessingCompilerStateCache cache) { + final VirtualFile file = item.getFile(); + List> list = myData.get(file); + if (list == null) { + list = new ArrayList>(); + myData.put(file, list); + } + list.add(new Pair(cache, item)); + } + + public void doUpdate() throws IOException{ + final IOException[] ex = {null}; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + for (Map.Entry>> entry : myData.entrySet()) { + for (Pair pair : entry.getValue()) { + final FileProcessingCompiler.ProcessingItem item = pair.getSecond(); + pair.getFirst().update(entry.getKey(), item.getValidityState()); + } + } + } + catch (IOException e) { + ex[0] = e; + } + } + }); + if (ex[0] != null) { + throw ex[0]; + } + } + } + + private static class TranslatorsOutputSink implements TranslatingCompiler.OutputSink { + final Map> myPostponedItems = new HashMap>(); + private final CompileContextEx myContext; + private final TranslatingCompiler[] myCompilers; + private int myCurrentCompilerIdx; + //private LinkedBlockingQueue myFutures = new LinkedBlockingQueue(); + + private TranslatorsOutputSink(CompileContextEx context, TranslatingCompiler[] compilers) { + myContext = context; + this.myCompilers = compilers; + } + + public void setCurrentCompilerIndex(int index) { + myCurrentCompilerIdx = index; + } + + public void add(final String outputRoot, final Collection items, final VirtualFile[] filesToRecompile) { + final TranslatingCompiler compiler = myCompilers[myCurrentCompilerIdx]; + if (compiler instanceof IntermediateOutputCompiler) { + final LocalFileSystem lfs = LocalFileSystem.getInstance(); + final List outputs = new ArrayList(); + for (TranslatingCompiler.OutputItem item : items) { + final VirtualFile vFile = lfs.findFileByPath(item.getOutputPath()); + if (vFile != null) { + outputs.add(vFile); + } + } + myContext.markGenerated(outputs); + } + final int nextCompilerIdx = myCurrentCompilerIdx + 1; + try { + if (nextCompilerIdx < myCompilers.length ) { + final Map> updateNow = new java.util.HashMap>(); + // process postponed + for (Map.Entry> entry : myPostponedItems.entrySet()) { + final String outputDir = entry.getKey(); + final Collection postponed = entry.getValue(); + for (Iterator it = postponed.iterator(); it.hasNext();) { + TranslatingCompiler.OutputItem item = it.next(); + boolean shouldPostpone = false; + for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { + if (shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext)) { + break; + } + } + if (!shouldPostpone) { + // the file is not compilable by the rest of compilers, so it is safe to update it now + it.remove(); + addItemToMap(updateNow, outputDir, item); + } + } + } + // process items from current compilation + for (TranslatingCompiler.OutputItem item : items) { + boolean shouldPostpone = false; + for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) { + if (shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext)) { + break; + } + } + if (shouldPostpone) { + // the file is compilable by the next compiler in row, update should be postponed + addItemToMap(myPostponedItems, outputRoot, item); + } + else { + addItemToMap(updateNow, outputRoot, item); + } + } + + if (updateNow.size() == 1) { + final Map.Entry> entry = updateNow.entrySet().iterator().next(); + final String outputDir = entry.getKey(); + final Collection itemsToUpdate = entry.getValue(); + TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, filesToRecompile); + } + else { + for (Map.Entry> entry : updateNow.entrySet()) { + final String outputDir = entry.getKey(); + final Collection itemsToUpdate = entry.getValue(); + TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, VirtualFile.EMPTY_ARRAY); + } + if (filesToRecompile.length > 0) { + TranslatingCompilerFilesMonitor.getInstance().update(myContext, null, Collections.emptyList(), filesToRecompile); + } + } + } + else { + TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputRoot, items, filesToRecompile); + } + } + catch (IOException e) { + LOG.info(e); + myContext.requestRebuildNextTime(e.getMessage()); + } + } + + private void addItemToMap(Map> map, String outputDir, TranslatingCompiler.OutputItem item) { + Collection collection = map.get(outputDir); + if (collection == null) { + collection = new ArrayList(); + map.put(outputDir, collection); + } + collection.add(item); + } + + public void flushPostponedItems() { + final TranslatingCompilerFilesMonitor filesMonitor = TranslatingCompilerFilesMonitor.getInstance(); + try { + for (Map.Entry> entry : myPostponedItems.entrySet()) { + final String outputDir = entry.getKey(); + final Collection items = entry.getValue(); + filesMonitor.update(myContext, outputDir, items, VirtualFile.EMPTY_ARRAY); + } + } + catch (IOException e) { + LOG.info(e); + myContext.requestRebuildNextTime(e.getMessage()); + } + finally { + filesMonitor.updateOutputRootsLayout(myContext.getProject()); + } + } + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java index ec05f385a9..b9586b133b 100644 --- a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java +++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java @@ -1,1099 +1,1099 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * @author: Eugene Zhuravlev - * Date: Jan 24, 2003 - * Time: 4:25:47 PM - */ -package com.intellij.compiler.impl.javaCompiler; - -import com.intellij.codeInsight.AnnotationUtil; -import com.intellij.compiler.*; -import com.intellij.compiler.classParsing.AnnotationConstantValue; -import com.intellij.compiler.classParsing.MethodInfo; -import com.intellij.compiler.impl.CompilerUtil; -import com.intellij.compiler.make.*; -import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter; -import com.intellij.ide.util.projectWizard.JavaModuleBuilder; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.compiler.*; -import com.intellij.openapi.compiler.ex.CompileContextEx; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.module.JavaModuleType; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleType; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.JavaSdkType; -import com.intellij.openapi.projectRoots.Sdk; -import com.intellij.openapi.roots.*; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; -import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.Chunk; -import com.intellij.util.cls.ClsFormatException; -import com.intellij.util.containers.ContainerUtil; -import gnu.trove.THashMap; -import gnu.trove.TIntHashSet; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; - -public class BackendCompilerWrapper { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper"); - - private final BackendCompiler myCompiler; - private final Set mySuccesfullyCompiledJavaFiles; // VirtualFile - - private final CompileContextEx myCompileContext; - private final List myFilesToCompile; - private final TranslatingCompiler.OutputSink mySink; - private final Project myProject; - private final Set myFilesToRecompile; - private final Map myModuleToTempDirMap = new THashMap(); - private final ProjectFileIndex myProjectFileIndex; - @NonNls private static final String PACKAGE_ANNOTATION_FILE_NAME = "package-info.java"; - private static final FileObject myStopThreadToken = new FileObject(null,null); - private long myCompilationDuration = 0L; - public final Map> myFileNameToSourceMap= new THashMap>(); - - - public BackendCompilerWrapper(@NotNull final Project project, - @NotNull List filesToCompile, - @NotNull CompileContextEx compileContext, - @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) { - myProject = project; - myCompiler = compiler; - myCompileContext = compileContext; - myFilesToCompile = filesToCompile; - myFilesToRecompile = new HashSet(filesToCompile); - mySink = sink; - myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); - mySuccesfullyCompiledJavaFiles = new HashSet(filesToCompile.size()); - } - - public List compile() throws CompilerException, CacheCorruptedException { - Application application = ApplicationManager.getApplication(); - final Set allDependent = new HashSet(); - COMPILE: - try { - if (!myFilesToCompile.isEmpty()) { - if (application.isUnitTestMode()) { - saveTestData(); - } - - final Map> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(myCompileContext, myFilesToCompile); - compileModules(moduleToFilesMap); - } - - Collection dependentFiles; - do { - dependentFiles = findDependentFiles(); - - if (!dependentFiles.isEmpty()) { - myFilesToRecompile.addAll(dependentFiles); - allDependent.addAll(dependentFiles); - if (myCompileContext.getProgressIndicator().isCanceled() || myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - break COMPILE; - } - final List filesInScope = getFilesInScope(dependentFiles); - if (filesInScope.isEmpty()) { - break; - } - final Map> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(myCompileContext, filesInScope); - myCompileContext.getDependencyCache().clearTraverseRoots(); - compileModules(moduleToFilesMap); - } - } - while (!dependentFiles.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0); - } - catch (SecurityException e) { - throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e); - } - catch (IllegalArgumentException e) { - throw new CompilerException(e.getMessage(), e); - } - finally { - CompilerUtil.logDuration(myCompiler.getId() + " running", myCompilationDuration); - for (final VirtualFile file : myModuleToTempDirMap.values()) { - if (file != null) { - final File ioFile = new File(file.getPath()); - FileUtil.asyncDelete(ioFile); - } - } - myModuleToTempDirMap.clear(); - } - - if (myCompileContext.getProgressIndicator().isCanceled()) { - myFilesToRecompile.clear(); - // when cancelled pretend nothing was compiled and next compile will compile everything from the scratch - return Collections.emptyList(); - } - - // do not update caches if cancelled because there is a chance that they will be incomplete - if (CompilerConfiguration.MAKE_ENABLED) { - ProgressIndicator indicator = myCompileContext.getProgressIndicator(); - final DependencyCache cache = myCompileContext.getDependencyCache(); - - indicator.setText(CompilerBundle.message("progress.updating.caches")); - indicator.setText2(""); - - cache.update(indicator); - - indicator.setText(CompilerBundle.message("progress.saving.caches")); - cache.resetState(); - - indicator.setText(""); - } - - myFilesToRecompile.removeAll(mySuccesfullyCompiledJavaFiles); - if (myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) != 0) { - myFilesToRecompile.addAll(allDependent); - } - final List outputs = processPackageInfoFiles(); - if (myFilesToRecompile.size() > 0 || outputs.size() > 0) { - mySink.add(null, outputs, myFilesToRecompile.toArray(new VirtualFile[myFilesToRecompile.size()])); - } - return null; - } - - // package-info.java hack - private List processPackageInfoFiles() { - if (myFilesToRecompile.isEmpty()) { - return Collections.EMPTY_LIST; - } - final List outputs = new ArrayList(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final List packageInfoFiles = new ArrayList(myFilesToRecompile.size()); - for (final VirtualFile file : myFilesToRecompile) { - if (PACKAGE_ANNOTATION_FILE_NAME.equals(file.getName())) { - packageInfoFiles.add(file); - } - } - if (!packageInfoFiles.isEmpty()) { - final Set badFiles = getFilesCompiledWithErrors(); - for (final VirtualFile packageInfoFile : packageInfoFiles) { - if (!badFiles.contains(packageInfoFile)) { - outputs.add(new OutputItemImpl(packageInfoFile)); - myFilesToRecompile.remove(packageInfoFile); - } - } - } - } - }); - return outputs; - } - - private List getFilesInScope(final Collection dependentFiles) { - final List filesInScope = new ArrayList(dependentFiles.size()); - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (VirtualFile dependentFile : dependentFiles) { - if (myCompileContext.getCompileScope().belongs(dependentFile.getUrl())) { - filesInScope.add(dependentFile); - } - } - } - }); - return filesInScope; - } - - private void compileModules(final Map> moduleToFilesMap) throws CompilerException { - final List chunks = getModuleChunks(moduleToFilesMap); - List files = ContainerUtil.concat(moduleToFilesMap.values()); - myProcessedFilesCount = 0; - myTotalFilesToCompile = files.size(); - - for (final ModuleChunk chunk : chunks) { - try { - boolean success = compileChunk(chunk); - if (!success) { - return; - } - } - catch (IOException e) { - throw new CompilerException(e.getMessage(), e); - } - } - } - - private boolean compileChunk(ModuleChunk chunk) throws IOException { - runTransformingCompilers(chunk); - - setPresentableNameFor(chunk); - - final List outs = new ArrayList(); - File fileToDelete = getOutputDirsToCompileTo(chunk, outs); - - try { - for (final OutputDir outputDir : outs) { - doCompile(chunk, outputDir.getPath(), outputDir.getKind()); - if (myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { - return false; - } - } - } - finally { - if (fileToDelete != null) { - FileUtil.asyncDelete(fileToDelete); - } - } - - return true; - } - - - private void setPresentableNameFor(final ModuleChunk chunk) { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final Module[] modules = chunk.getModules(); - StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8)); - for (int idx = 0; idx < modules.length; idx++) { - final Module module = modules[idx]; - if (idx > 0) { - moduleName.append(", "); - } - moduleName.append(module.getName()); - if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) { - moduleName.append("..."); - break; - } - } - myModuleName = moduleName.toString(); - } - }); - } - - private File getOutputDirsToCompileTo(ModuleChunk chunk, final List pairs) throws IOException { - File fileToDelete = null; - if (chunk.getModuleCount() == 1) { // optimization - final Module module = chunk.getModules()[0]; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final String sourcesOutputDir = getOutputDir(module); - if (shouldCompileTestsSeparately(module)) { - if (sourcesOutputDir != null) { - pairs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES)); - } - final String testsOutputDir = getTestsOutputDir(module); - if (testsOutputDir == null) { - LOG.error("Tests output dir is null for module \"" + module.getName() + "\""); - } - else { - pairs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES)); - } - } - else { // both sources and test sources go into the same output - if (sourcesOutputDir == null) { - LOG.error("Sources output dir is null for module \"" + module.getName() + "\""); - } - else { - pairs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES)); - } - } - } - }); - } - else { // chunk has several modules - final File outputDir = FileUtil.createTempDirectory("compile", "output"); - fileToDelete = outputDir; - pairs.add(new OutputDir(outputDir.getPath(), ModuleChunk.ALL_SOURCES)); - } - return fileToDelete; - } - - private List getModuleChunks(final Map> moduleToFilesMap) { - final List modules = new ArrayList(moduleToFilesMap.keySet()); - final List> chunks = ApplicationManager.getApplication().runReadAction(new Computable>>() { - public List> compute() { - return ModuleCompilerUtil.getSortedModuleChunks(myProject, modules); - } - }); - final List moduleChunks = new ArrayList(chunks.size()); - for (final Chunk chunk : chunks) { - moduleChunks.add(new ModuleChunk(myCompileContext, chunk, moduleToFilesMap)); - } - return moduleChunks; - } - - private boolean shouldCompileTestsSeparately(Module module) { - final String moduleTestOutputDirectory = getTestsOutputDir(module); - if (moduleTestOutputDirectory == null) { - return false; - } - final String moduleOutputDirectory = getOutputDir(module); - return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory); - } - - private void saveTestData() { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - for (VirtualFile file : myFilesToCompile) { - CompilerManagerImpl.addCompiledPath(file.getPath()); - } - } - }); - } - - private final TIntHashSet myProcessedNames = new TIntHashSet(); - private final Set myProcessedFiles = new HashSet(); - - private Collection findDependentFiles() throws CacheCorruptedException { - if (!CompilerConfiguration.MAKE_ENABLED) { - return Collections.emptyList(); - } - myCompileContext.getProgressIndicator().setText(CompilerBundle.message("progress.checking.dependencies")); - - final DependencyCache dependencyCache = myCompileContext.getDependencyCache(); - - final long start = System.currentTimeMillis(); - - final Pair> deps = - dependencyCache.findDependentClasses(myCompileContext, myProject, mySuccesfullyCompiledJavaFiles, myCompiler.getDependencyProcessor()); - - final TIntHashSet currentDeps = new TIntHashSet(deps.getFirst()); - currentDeps.removeAll(myProcessedNames.toArray()); - final int[] depQNames = currentDeps.toArray(); - myProcessedNames.addAll(deps.getFirst()); - - final Set depFiles = new HashSet(deps.getSecond()); - depFiles.removeAll(myProcessedFiles); - myProcessedFiles.addAll(deps.getSecond()); - - final Set dependentFiles = new HashSet(); - final CacheCorruptedException[] _ex = {null}; - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - try { - CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myProject); - SourceFileFinder sourceFileFinder = new SourceFileFinder(myProject, myCompileContext); - final Cache cache = dependencyCache.getCache(); - for (final int infoQName : depQNames) { - final String qualifiedName = dependencyCache.resolve(infoQName); - final String sourceFileName = cache.getSourceFileName(infoQName); - final VirtualFile file = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName); - if (file != null) { - if (!compilerConfiguration.isExcludedFromCompilation(file)) { - dependentFiles.add(file); - if (ApplicationManager.getApplication().isUnitTestMode()) { - LOG.assertTrue(file.isValid()); - CompilerManagerImpl.addRecompiledPath(file.getPath()); - } - } - } - else { - LOG.info("No source file for " + dependencyCache.resolve(infoQName) + " found; source file name=" + sourceFileName); - } - } - for (final VirtualFile file : depFiles) { - if (!compilerConfiguration.isExcludedFromCompilation(file)) { - dependentFiles.add(file); - if (ApplicationManager.getApplication().isUnitTestMode()) { - LOG.assertTrue(file.isValid()); - CompilerManagerImpl.addRecompiledPath(file.getPath()); - } - } - } - } - catch (CacheCorruptedException e) { - _ex[0] = e; - } - } - }); - if (_ex[0] != null) { - throw _ex[0]; - } - myCompileContext.getProgressIndicator().setText(CompilerBundle.message("progress.found.dependent.files", dependentFiles.size())); - - CompilerUtil.logDuration("Finding dependencies", System.currentTimeMillis() - start); - return dependentFiles; - } - - private final Object lock = new Object(); - - private class SynchedCompilerParsing extends CompilerParsingThread { - private final ClassParsingThread myClassParsingThread; - - private SynchedCompilerParsing(Process process, - final CompileContext context, - OutputParser outputParser, - ClassParsingThread classParsingThread, - boolean readErrorStream, - boolean trimLines) { - super(process, outputParser, readErrorStream, trimLines,context); - myClassParsingThread = classParsingThread; - } - - public void setProgressText(String text) { - synchronized (lock) { - super.setProgressText(text); - } - } - - public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) { - synchronized (lock) { - super.message(category, message, url, lineNum, columnNum); - } - } - - public void fileProcessed(String path) { - synchronized (lock) { - sourceFileProcessed(); - } - } - - protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException { - synchronized (lock) { - myClassParsingThread.addPath(classFileToProcess); - } - } - } - - private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir, int sourcesFilter) throws IOException { - myCompileContext.getProgressIndicator().checkCanceled(); - chunk.setSourcesFilter(sourcesFilter); - - if (ApplicationManager.getApplication().runReadAction(new Computable() { - public Boolean compute() { - return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE; - } - }).booleanValue()) { - return; // should not invoke javac with empty sources list - } - - ModuleType moduleType = chunk.getModules()[0].getModuleType(); - if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) && - !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) { - // TODO - // don't try to compile non-java type module - return; - } - - int exitValue = 0; - try { - Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext); - final long compilationStart = System.currentTimeMillis(); - final ClassParsingThread classParsingThread = new ClassParsingThread(isJdk6(chunk.getJdk()), outputDir); - final Future classParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(classParsingThread); - - OutputParser errorParser = myCompiler.createErrorParser(outputDir, process); - CompilerParsingThread errorParsingThread = errorParser == null - ? null - : new SynchedCompilerParsing(process, myCompileContext, errorParser, classParsingThread, - true, errorParser.isTrimLines()); - Future errorParsingThreadFuture = null; - if (errorParsingThread != null) { - errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread); - } - - OutputParser outputParser = myCompiler.createOutputParser(outputDir); - CompilerParsingThread outputParsingThread = outputParser == null - ? null - : new SynchedCompilerParsing(process, myCompileContext, outputParser, classParsingThread, - false, outputParser.isTrimLines()); - Future outputParsingThreadFuture = null; - if (outputParsingThread != null) { - outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread); - } - - try { - exitValue = process.waitFor(); - } - catch (InterruptedException e) { - process.destroy(); - exitValue = process.exitValue(); - } - finally { - myCompilationDuration += (System.currentTimeMillis() - compilationStart); - if (errorParsingThread != null) { - errorParsingThread.setProcessTerminated(true); - } - if (outputParsingThread != null) { - outputParsingThread.setProcessTerminated(true); - } - joinThread(errorParsingThreadFuture); - joinThread(outputParsingThreadFuture); - classParsingThread.stopParsing(); - joinThread(classParsingThreadFuture); - - registerParsingException(outputParsingThread); - registerParsingException(errorParsingThread); - assert outputParsingThread == null || !outputParsingThread.processing; - assert errorParsingThread == null || !errorParsingThread.processing; - assert classParsingThread == null || !classParsingThread.processing; - } - } - finally { - compileFinished(exitValue, chunk, outputDir); - myModuleName = null; - } - } - - private static void joinThread(final Future threadFuture) { - if (threadFuture != null) { - try { - threadFuture.get(); - } - catch (InterruptedException ignored) { - } - catch(ExecutionException ignored) { - } - } - } - - private void registerParsingException(final CompilerParsingThread outputParsingThread) { - Throwable error = outputParsingThread == null ? null : outputParsingThread.getError(); - if (error != null) { - String message = error.getMessage(); - if (error instanceof CacheCorruptedException) { - myCompileContext.requestRebuildNextTime(message); - } - else { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); - } - } - } - - private void runTransformingCompilers(final ModuleChunk chunk) { - final JavaSourceTransformingCompiler[] transformers = - CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class); - if (transformers.length == 0) { - return; - } - if (LOG.isDebugEnabled()) { - LOG.debug("Running transforming compilers..."); - } - final Module[] modules = chunk.getModules(); - for (final JavaSourceTransformingCompiler transformer : transformers) { - final Map originalToCopyFileMap = new HashMap(); - final Application application = ApplicationManager.getApplication(); - application.invokeAndWait(new Runnable() { - public void run() { - for (final Module module : modules) { - List filesToCompile = chunk.getFilesToCompile(module); - for (final VirtualFile file : filesToCompile) { - if (transformer.isTransformable(file)) { - application.runWriteAction(new Runnable() { - public void run() { - try { - VirtualFile fileCopy = createFileCopy(getTempDir(module), file); - originalToCopyFileMap.put(file, fileCopy); - } - catch (IOException e) { - // skip it - } - } - }); - } - } - } - } - }, myCompileContext.getProgressIndicator().getModalityState()); - - // do actual transform - for (final Module module : modules) { - final List filesToCompile = chunk.getFilesToCompile(module); - for (int j = 0; j < filesToCompile.size(); j++) { - final VirtualFile file = filesToCompile.get(j); - VirtualFile fileCopy = originalToCopyFileMap.get(file); - if (fileCopy != null) { - final boolean ok = transformer.transform(myCompileContext, fileCopy, file); - if (ok) { - chunk.substituteWithTransformedVersion(module, j, fileCopy); - } - } - } - } - } - } - - private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException { - final String fileName = file.getName(); - if (tempDir.findChild(fileName) != null) { - int idx = 0; - while (true) { - //noinspection HardCodedStringLiteral - final String dirName = "dir" + idx++; - final VirtualFile dir = tempDir.findChild(dirName); - if (dir == null) { - tempDir = tempDir.createChildDirectory(this, dirName); - break; - } - if (dir.findChild(fileName) == null) { - tempDir = dir; - break; - } - } - } - return VfsUtil.copyFile(this, file, tempDir); - } - - private VirtualFile getTempDir(Module module) throws IOException { - VirtualFile tempDir = myModuleToTempDirMap.get(module); - if (tempDir == null) { - final String projectName = myProject.getName(); - final String moduleName = module.getName(); - File tempDirectory = FileUtil.createTempDirectory(projectName, moduleName); - tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDirectory); - if (tempDir == null) { - LOG.error("Cannot locate temp directory " + tempDirectory.getPath()); - } - myModuleToTempDirMap.put(module, tempDir); - } - return tempDir; - } - - private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) { - if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() && - myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1); - } - - myCompiler.compileFinished(); - final List toRefresh = new ArrayList(); - final Map> results = new HashMap>(); - try { - ApplicationManager.getApplication().runReadAction(new Runnable() { - public void run() { - final Set compiledWithErrors = getFilesCompiledWithErrors(); - final FileTypeManager typeManager = FileTypeManager.getInstance(); - final String outputDirPath = outputDir.replace(File.separatorChar, '/'); - try { - for (final Module module : chunk.getModules()) { - for (final VirtualFile root : chunk.getSourceRoots(module)) { - final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root); - if (LOG.isDebugEnabled()) { - LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\""); - } - buildOutputItemsList(outputDirPath, module, root, typeManager, compiledWithErrors, root, packagePrefix, toRefresh, results); - } - } - } - catch (CacheCorruptedException e) { - myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); - if (LOG.isDebugEnabled()) { - LOG.debug(e); - } - } - } - }); - } - finally { - CompilerUtil.refreshIOFiles(toRefresh); - for (Iterator>> it = results.entrySet().iterator(); it.hasNext();) { - Map.Entry> entry = it.next(); - mySink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY); - it.remove(); // to free memory - } - } - CompilerUtil.refreshIOFiles(toRefresh); - myFileNameToSourceMap.clear(); // clear the map before the next use - } - - private Set getFilesCompiledWithErrors() { - CompilerMessage[] messages = myCompileContext.getMessages(CompilerMessageCategory.ERROR); - Set compiledWithErrors = Collections.emptySet(); - if (messages.length > 0) { - compiledWithErrors = new HashSet(messages.length); - for (CompilerMessage message : messages) { - final VirtualFile file = message.getVirtualFile(); - if (file != null) { - compiledWithErrors.add(file); - } - } - } - return compiledWithErrors; - } - - private void buildOutputItemsList(final String outputDir, Module module, VirtualFile from, - final FileTypeManager typeManager, - final Set compiledWithErrors, - final VirtualFile sourceRoot, - final String packagePrefix, final List filesToRefresh, final Map> results) throws CacheCorruptedException { - final Ref exRef = new Ref(null); - final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex(); - final ContentIterator contentIterator = new ContentIterator() { - public boolean processFile(final VirtualFile child) { - try { - assert child.isValid(); - if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(typeManager.getFileTypeByFile(child))) { - updateOutputItemsList(outputDir, child, compiledWithErrors, sourceRoot, packagePrefix, filesToRefresh, results); - } - return true; - } - catch (CacheCorruptedException e) { - exRef.set(e); - return false; - } - } - }; - if (fileIndex.isInContent(from)) { - // use file index for iteration to handle 'inner modules' and excludes properly - fileIndex.iterateContentUnderDirectory(from, contentIterator); - } - else { - // seems to be a root for generated sources - new Object() { - void iterateContent(VirtualFile from) { - for (VirtualFile child : from.getChildren()) { - if (child.isDirectory()) { - iterateContent(child); - } - else { - contentIterator.processFile(child); - } - } - } - }.iterateContent(from); - } - if (exRef.get() != null) { - throw exRef.get(); - } - } - - private void putName(String sourceFileName, int classQName, String relativePathToSource, String pathToClass) { - if (LOG.isDebugEnabled()) { - LOG.debug("Registering [sourceFileName, relativePathToSource, pathToClass] = [" + sourceFileName + "; " + relativePathToSource + - "; " + pathToClass + "]"); - } - Set paths = myFileNameToSourceMap.get(sourceFileName); - - if (paths == null) { - paths = new HashSet(); - myFileNameToSourceMap.put(sourceFileName, paths); - } - paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass)); - } - - private void updateOutputItemsList(final String outputDir, VirtualFile srcFile, Set compiledWithErrors, - VirtualFile sourceRoot, - final String packagePrefix, final List filesToRefresh, - Map> results) throws CacheCorruptedException { - final Cache newCache = myCompileContext.getDependencyCache().getNewClassesCache(); - final Set paths = myFileNameToSourceMap.get(srcFile.getName()); - if (paths == null || paths.isEmpty()) { - return; - } - final String prefix = packagePrefix != null && packagePrefix.length() > 0 ? packagePrefix.replace('.', '/') + "/" : ""; - final String filePath = "/" + prefix + VfsUtil.getRelativePath(srcFile, sourceRoot, '/'); - for (final CompiledClass cc : paths) { - myCompileContext.getProgressIndicator().checkCanceled(); - if (LOG.isDebugEnabled()) { - LOG.debug("Checking [pathToClass; relPathToSource] = " + cc); - } - if (FileUtil.pathsEqual(filePath, cc.relativePathToSource)) { - final String outputPath = cc.pathToClass.replace(File.separatorChar, '/'); - final Pair realLocation = moveToRealLocation(outputDir, outputPath, srcFile, filesToRefresh); - if (realLocation != null) { - Collection outputs = results.get(realLocation.getFirst()); - if (outputs == null) { - outputs = new ArrayList(); - results.put(realLocation.getFirst(), outputs); - } - outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile)); - if (CompilerConfiguration.MAKE_ENABLED) { - newCache.setPath(cc.qName, realLocation.getSecond()); - } - if (LOG.isDebugEnabled()) { - LOG.debug("Added output item: [outputDir; outputPath; sourceFile] = [" + realLocation.getFirst() + "; " + - realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]"); - } - if (!compiledWithErrors.contains(srcFile)) { - mySuccesfullyCompiledJavaFiles.add(srcFile); - } - } - else { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, "Failed to copy from temporary location to output directory: " + outputPath + " (see idea.log for details)", null, -1, -1); - if (LOG.isDebugEnabled()) { - LOG.debug("Failed to move to real location: " + outputPath + "; from " + outputDir); - } - } - } - } - } - - private Pair moveToRealLocation(String tempOutputDir, String pathToClass, VirtualFile sourceFile, final List filesToRefresh) { - final Module module = myCompileContext.getModuleByFile(sourceFile); - if (module == null) { - final String message = - "Cannot determine module for source file: " + sourceFile.getPresentableUrl() + ";\nCorresponding output file: " + pathToClass; - LOG.info(message); - myCompileContext.addMessage(CompilerMessageCategory.WARNING, message, sourceFile.getUrl(), -1, -1); - // do not move: looks like source file has been invalidated, need recompilation - return new Pair(tempOutputDir, pathToClass); - } - final String realOutputDir; - if (myCompileContext.isInTestSourceContent(sourceFile)) { - realOutputDir = getTestsOutputDir(module); - } - else { - realOutputDir = getOutputDir(module); - } - - if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move - filesToRefresh.add(new File(pathToClass)); - return new Pair(realOutputDir, pathToClass); - } - - final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length()); - final File fromFile = new File(pathToClass); - final File toFile = new File(realPathToClass); - - boolean success = fromFile.renameTo(toFile); - if (!success) { - // assuming cause of the fail: intermediate dirs do not exist - final File parentFile = toFile.getParentFile(); - if (parentFile != null) { - parentFile.mkdirs(); - success = fromFile.renameTo(toFile); // retry after making non-existent dirs - } - } - if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints. - try { - FileUtil.copy(fromFile, toFile); - FileUtil.delete(fromFile); - success = true; - } - catch (IOException e) { - LOG.info(e); - success = false; - } - } - if (success) { - filesToRefresh.add(toFile); - return new Pair(realOutputDir, realPathToClass); - } - return null; - } - - private final Map myModuleToTestsOutput = new HashMap(); - - private String getTestsOutputDir(final Module module) { - if (myModuleToTestsOutput.containsKey(module)) { - return myModuleToTestsOutput.get(module); - } - final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module); - final String out = outputDirectory != null? outputDirectory.getPath() : null; - myModuleToTestsOutput.put(module, out); - return out; - } - - private final Map myModuleToOutput = new HashMap(); - - private String getOutputDir(final Module module) { - if (myModuleToOutput.containsKey(module)) { - return myModuleToOutput.get(module); - } - final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module); - final String out = outputDirectory != null? outputDirectory.getPath() : null; - myModuleToOutput.put(module, out); - return out; - } - - private int myProcessedFilesCount = 0; - private int myTotalFilesToCompile = 0; - private int myClassesCount = 0; - private volatile String myModuleName = null; - - private void sourceFileProcessed() { - myProcessedFilesCount++; - updateStatistics(); - } - - private void updateStatistics() { - final String msg; - String moduleName = myModuleName; - if (moduleName != null) { - msg = CompilerBundle.message("statistics.files.classes.module", myProcessedFilesCount, myClassesCount, moduleName); - } - else { - msg = CompilerBundle.message("statistics.files.classes", myProcessedFilesCount, myClassesCount); - } - myCompileContext.getProgressIndicator().setText2(msg); - myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile); - } - - private class ClassParsingThread implements Runnable { - private final BlockingQueue myPaths = new ArrayBlockingQueue(50000); - private CacheCorruptedException myError = null; - private final boolean myAddNotNullAssertions; - private final boolean myIsJdk16; - private final String myOutputDir; - - private ClassParsingThread(final boolean isJdk16, String outputDir) { - myIsJdk16 = isJdk16; - myOutputDir = FileUtil.toSystemIndependentName(outputDir); - myAddNotNullAssertions = CompilerWorkspaceConfiguration.getInstance(myProject).ASSERT_NOT_NULL; - } - - volatile boolean processing; - public void run() { - processing = true; - try { - while (true) { - FileObject path = myPaths.take(); - - if (path == myStopThreadToken) break; - processPath(path); - } - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (CacheCorruptedException e) { - myError = e; - } - processing = false; - } - - public void addPath(FileObject path) throws CacheCorruptedException { - if (myError != null) { - throw myError; - } - myPaths.offer(path); - } - - public void stopParsing() { - myPaths.offer(myStopThreadToken); - } - - private void processPath(FileObject fileObject) throws CacheCorruptedException { - File file = fileObject.getFile(); - final String path = file.getPath(); - try { - if (CompilerConfiguration.MAKE_ENABLED) { - byte[] fileContent = fileObject.getContent(); - // the file is assumed to exist! - final DependencyCache dependencyCache = myCompileContext.getDependencyCache(); - final int newClassQName = dependencyCache.reparseClassFile(file, fileContent); - final Cache newClassesCache = dependencyCache.getNewClassesCache(); - final String sourceFileName = newClassesCache.getSourceFileName(newClassQName); - final String qName = dependencyCache.resolve(newClassQName); - String relativePathToSource = "/" + MakeUtil.createRelativePathToSource(qName, sourceFileName); - putName(sourceFileName, newClassQName, relativePathToSource, path); - boolean haveToInstrument = myAddNotNullAssertions && hasNotNullAnnotations(newClassesCache, dependencyCache.getSymbolTable(), newClassQName); - - boolean fileContentChanged = false; - if (haveToInstrument) { - try { - ClassReader reader = new ClassReader(fileContent, 0, fileContent.length); - ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16); - - final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer); - reader.accept(instrumenter, 0); - if (instrumenter.isModification()) { - fileContent = writer.toByteArray(); - fileContentChanged = true; - } - } - catch (Exception ignored) { - LOG.info(ignored); - } - } - - if (fileContentChanged || !fileObject.isSaved()) { - writeFile(file, fileContent); - } - } - else { - final String _path = FileUtil.toSystemIndependentName(path); - final int dollarIndex = _path.indexOf('$'); - final int tailIndex = dollarIndex >=0 ? dollarIndex : _path.length() - ".class".length(); - final int slashIndex = _path.lastIndexOf('/'); - final String sourceFileName = _path.substring(slashIndex + 1, tailIndex) + ".java"; - String relativePathToSource = _path.substring(myOutputDir.length(), tailIndex) + ".java"; - putName(sourceFileName, 0 /*doesn't matter here*/ , relativePathToSource.startsWith("/")? relativePathToSource : "/" + relativePathToSource, path); - } - } - catch (ClsFormatException e) { - String message; - final String m = e.getMessage(); - if (m == null || "".equals(m)) { - message = CompilerBundle.message("error.bad.class.file.format", path); - } - else { - message = CompilerBundle.message("error.bad.class.file.format", m + "\n" + path); - } - myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); - } - catch (IOException e) { - myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1); - } - finally { - myClassesCount++; - updateStatistics(); - } - } - - private void writeFile(File file, byte[] fileContent) throws IOException { - try { - FileUtil.writeToFile(file, fileContent); - } - catch (FileNotFoundException e) { - FileUtil.createParentDirs(file); - FileUtil.writeToFile(file, fileContent); - } - } - } - - private static boolean hasNotNullAnnotations(final Cache cache, final SymbolTable symbolTable, final int className) throws CacheCorruptedException { - for (MethodInfo methodId : cache.getMethods(className)) { - for (AnnotationConstantValue annotation : methodId.getRuntimeInvisibleAnnotations()) { - if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { - return true; - } - } - final AnnotationConstantValue[][] paramAnnotations = methodId.getRuntimeInvisibleParameterAnnotations(); - for (AnnotationConstantValue[] _singleParamAnnotations : paramAnnotations) { - for (AnnotationConstantValue annotation : _singleParamAnnotations) { - if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { - return true; - } - } - } - } - return false; - } - - private static boolean isJdk6(final Sdk jdk) { - boolean isJDK16 = false; - if (jdk != null) { - final String versionString = jdk.getVersionString(); - if (versionString != null) { - isJDK16 = versionString.contains("1.6") || versionString.contains("6.0"); - } - } - return isJDK16; - } -} +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * @author: Eugene Zhuravlev + * Date: Jan 24, 2003 + * Time: 4:25:47 PM + */ +package com.intellij.compiler.impl.javaCompiler; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.compiler.*; +import com.intellij.compiler.classParsing.AnnotationConstantValue; +import com.intellij.compiler.classParsing.MethodInfo; +import com.intellij.compiler.impl.CompilerUtil; +import com.intellij.compiler.make.*; +import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter; +import com.intellij.ide.util.projectWizard.JavaModuleBuilder; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.*; +import com.intellij.openapi.compiler.ex.CompileContextEx; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.JavaModuleType; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleType; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.projectRoots.JavaSdkType; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Chunk; +import com.intellij.util.cls.ClsFormatException; +import com.intellij.util.containers.ContainerUtil; +import gnu.trove.THashMap; +import gnu.trove.TIntHashSet; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class BackendCompilerWrapper { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper"); + + private final BackendCompiler myCompiler; + private final Set mySuccesfullyCompiledJavaFiles; // VirtualFile + + private final CompileContextEx myCompileContext; + private final List myFilesToCompile; + private final TranslatingCompiler.OutputSink mySink; + private final Project myProject; + private final Set myFilesToRecompile; + private final Map myModuleToTempDirMap = new THashMap(); + private final ProjectFileIndex myProjectFileIndex; + @NonNls private static final String PACKAGE_ANNOTATION_FILE_NAME = "package-info.java"; + private static final FileObject myStopThreadToken = new FileObject(null,null); + private long myCompilationDuration = 0L; + public final Map> myFileNameToSourceMap= new THashMap>(); + + + public BackendCompilerWrapper(@NotNull final Project project, + @NotNull List filesToCompile, + @NotNull CompileContextEx compileContext, + @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) { + myProject = project; + myCompiler = compiler; + myCompileContext = compileContext; + myFilesToCompile = filesToCompile; + myFilesToRecompile = new HashSet(filesToCompile); + mySink = sink; + myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); + mySuccesfullyCompiledJavaFiles = new HashSet(filesToCompile.size()); + } + + public List compile() throws CompilerException, CacheCorruptedException { + Application application = ApplicationManager.getApplication(); + final Set allDependent = new HashSet(); + COMPILE: + try { + if (!myFilesToCompile.isEmpty()) { + if (application.isUnitTestMode()) { + saveTestData(); + } + + final Map> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(myCompileContext, myFilesToCompile); + compileModules(moduleToFilesMap); + } + + Collection dependentFiles; + do { + dependentFiles = findDependentFiles(); + + if (!dependentFiles.isEmpty()) { + myFilesToRecompile.addAll(dependentFiles); + allDependent.addAll(dependentFiles); + if (myCompileContext.getProgressIndicator().isCanceled() || myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + break COMPILE; + } + final List filesInScope = getFilesInScope(dependentFiles); + if (filesInScope.isEmpty()) { + break; + } + final Map> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(myCompileContext, filesInScope); + myCompileContext.getDependencyCache().clearTraverseRoots(); + compileModules(moduleToFilesMap); + } + } + while (!dependentFiles.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0); + } + catch (SecurityException e) { + throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e); + } + catch (IllegalArgumentException e) { + throw new CompilerException(e.getMessage(), e); + } + finally { + CompilerUtil.logDuration(myCompiler.getId() + " running", myCompilationDuration); + for (final VirtualFile file : myModuleToTempDirMap.values()) { + if (file != null) { + final File ioFile = new File(file.getPath()); + FileUtil.asyncDelete(ioFile); + } + } + myModuleToTempDirMap.clear(); + } + + if (myCompileContext.getProgressIndicator().isCanceled()) { + myFilesToRecompile.clear(); + // when cancelled pretend nothing was compiled and next compile will compile everything from the scratch + return Collections.emptyList(); + } + + // do not update caches if cancelled because there is a chance that they will be incomplete + if (CompilerConfiguration.MAKE_ENABLED) { + ProgressIndicator indicator = myCompileContext.getProgressIndicator(); + final DependencyCache cache = myCompileContext.getDependencyCache(); + + indicator.setText(CompilerBundle.message("progress.updating.caches")); + indicator.setText2(""); + + cache.update(indicator); + + indicator.setText(CompilerBundle.message("progress.saving.caches")); + cache.resetState(); + + indicator.setText(""); + } + + myFilesToRecompile.removeAll(mySuccesfullyCompiledJavaFiles); + if (myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) != 0) { + myFilesToRecompile.addAll(allDependent); + } + final List outputs = processPackageInfoFiles(); + if (myFilesToRecompile.size() > 0 || outputs.size() > 0) { + mySink.add(null, outputs, myFilesToRecompile.toArray(new VirtualFile[myFilesToRecompile.size()])); + } + return null; + } + + // package-info.java hack + private List processPackageInfoFiles() { + if (myFilesToRecompile.isEmpty()) { + return Collections.EMPTY_LIST; + } + final List outputs = new ArrayList(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final List packageInfoFiles = new ArrayList(myFilesToRecompile.size()); + for (final VirtualFile file : myFilesToRecompile) { + if (PACKAGE_ANNOTATION_FILE_NAME.equals(file.getName())) { + packageInfoFiles.add(file); + } + } + if (!packageInfoFiles.isEmpty()) { + final Set badFiles = getFilesCompiledWithErrors(); + for (final VirtualFile packageInfoFile : packageInfoFiles) { + if (!badFiles.contains(packageInfoFile)) { + outputs.add(new OutputItemImpl(packageInfoFile)); + myFilesToRecompile.remove(packageInfoFile); + } + } + } + } + }); + return outputs; + } + + private List getFilesInScope(final Collection dependentFiles) { + final List filesInScope = new ArrayList(dependentFiles.size()); + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (VirtualFile dependentFile : dependentFiles) { + if (myCompileContext.getCompileScope().belongs(dependentFile.getUrl())) { + filesInScope.add(dependentFile); + } + } + } + }); + return filesInScope; + } + + private void compileModules(final Map> moduleToFilesMap) throws CompilerException { + final List chunks = getModuleChunks(moduleToFilesMap); + List files = ContainerUtil.concat(moduleToFilesMap.values()); + myProcessedFilesCount = 0; + myTotalFilesToCompile = files.size(); + + for (final ModuleChunk chunk : chunks) { + try { + boolean success = compileChunk(chunk); + if (!success) { + return; + } + } + catch (IOException e) { + throw new CompilerException(e.getMessage(), e); + } + } + } + + private boolean compileChunk(ModuleChunk chunk) throws IOException { + runTransformingCompilers(chunk); + + setPresentableNameFor(chunk); + + final List outs = new ArrayList(); + File fileToDelete = getOutputDirsToCompileTo(chunk, outs); + + try { + for (final OutputDir outputDir : outs) { + doCompile(chunk, outputDir.getPath(), outputDir.getKind()); + if (myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) { + return false; + } + } + } + finally { + if (fileToDelete != null) { + FileUtil.asyncDelete(fileToDelete); + } + } + + return true; + } + + + private void setPresentableNameFor(final ModuleChunk chunk) { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final Module[] modules = chunk.getModules(); + StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8)); + for (int idx = 0; idx < modules.length; idx++) { + final Module module = modules[idx]; + if (idx > 0) { + moduleName.append(", "); + } + moduleName.append(module.getName()); + if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) { + moduleName.append("..."); + break; + } + } + myModuleName = moduleName.toString(); + } + }); + } + + private File getOutputDirsToCompileTo(ModuleChunk chunk, final List pairs) throws IOException { + File fileToDelete = null; + if (chunk.getModuleCount() == 1) { // optimization + final Module module = chunk.getModules()[0]; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final String sourcesOutputDir = getOutputDir(module); + if (shouldCompileTestsSeparately(module)) { + if (sourcesOutputDir != null) { + pairs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES)); + } + final String testsOutputDir = getTestsOutputDir(module); + if (testsOutputDir == null) { + LOG.error("Tests output dir is null for module \"" + module.getName() + "\""); + } + else { + pairs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES)); + } + } + else { // both sources and test sources go into the same output + if (sourcesOutputDir == null) { + LOG.error("Sources output dir is null for module \"" + module.getName() + "\""); + } + else { + pairs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES)); + } + } + } + }); + } + else { // chunk has several modules + final File outputDir = FileUtil.createTempDirectory("compile", "output"); + fileToDelete = outputDir; + pairs.add(new OutputDir(outputDir.getPath(), ModuleChunk.ALL_SOURCES)); + } + return fileToDelete; + } + + private List getModuleChunks(final Map> moduleToFilesMap) { + final List modules = new ArrayList(moduleToFilesMap.keySet()); + final List> chunks = ApplicationManager.getApplication().runReadAction(new Computable>>() { + public List> compute() { + return ModuleCompilerUtil.getSortedModuleChunks(myProject, modules); + } + }); + final List moduleChunks = new ArrayList(chunks.size()); + for (final Chunk chunk : chunks) { + moduleChunks.add(new ModuleChunk(myCompileContext, chunk, moduleToFilesMap)); + } + return moduleChunks; + } + + private boolean shouldCompileTestsSeparately(Module module) { + final String moduleTestOutputDirectory = getTestsOutputDir(module); + if (moduleTestOutputDirectory == null) { + return false; + } + final String moduleOutputDirectory = getOutputDir(module); + return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory); + } + + private void saveTestData() { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + for (VirtualFile file : myFilesToCompile) { + CompilerManagerImpl.addCompiledPath(file.getPath()); + } + } + }); + } + + private final TIntHashSet myProcessedNames = new TIntHashSet(); + private final Set myProcessedFiles = new HashSet(); + + private Collection findDependentFiles() throws CacheCorruptedException { + if (!CompilerConfiguration.MAKE_ENABLED) { + return Collections.emptyList(); + } + myCompileContext.getProgressIndicator().setText(CompilerBundle.message("progress.checking.dependencies")); + + final DependencyCache dependencyCache = myCompileContext.getDependencyCache(); + + final long start = System.currentTimeMillis(); + + final Pair> deps = + dependencyCache.findDependentClasses(myCompileContext, myProject, mySuccesfullyCompiledJavaFiles, myCompiler.getDependencyProcessor()); + + final TIntHashSet currentDeps = new TIntHashSet(deps.getFirst()); + currentDeps.removeAll(myProcessedNames.toArray()); + final int[] depQNames = currentDeps.toArray(); + myProcessedNames.addAll(deps.getFirst()); + + final Set depFiles = new HashSet(deps.getSecond()); + depFiles.removeAll(myProcessedFiles); + myProcessedFiles.addAll(deps.getSecond()); + + final Set dependentFiles = new HashSet(); + final CacheCorruptedException[] _ex = {null}; + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + try { + CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(myProject); + SourceFileFinder sourceFileFinder = new SourceFileFinder(myProject, myCompileContext); + final Cache cache = dependencyCache.getCache(); + for (final int infoQName : depQNames) { + final String qualifiedName = dependencyCache.resolve(infoQName); + final String sourceFileName = cache.getSourceFileName(infoQName); + final VirtualFile file = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName); + if (file != null) { + if (!compilerConfiguration.isExcludedFromCompilation(file)) { + dependentFiles.add(file); + if (ApplicationManager.getApplication().isUnitTestMode()) { + LOG.assertTrue(file.isValid()); + CompilerManagerImpl.addRecompiledPath(file.getPath()); + } + } + } + else { + LOG.info("No source file for " + dependencyCache.resolve(infoQName) + " found; source file name=" + sourceFileName); + } + } + for (final VirtualFile file : depFiles) { + if (!compilerConfiguration.isExcludedFromCompilation(file)) { + dependentFiles.add(file); + if (ApplicationManager.getApplication().isUnitTestMode()) { + LOG.assertTrue(file.isValid()); + CompilerManagerImpl.addRecompiledPath(file.getPath()); + } + } + } + } + catch (CacheCorruptedException e) { + _ex[0] = e; + } + } + }); + if (_ex[0] != null) { + throw _ex[0]; + } + myCompileContext.getProgressIndicator().setText(CompilerBundle.message("progress.found.dependent.files", dependentFiles.size())); + + CompilerUtil.logDuration("Finding dependencies", System.currentTimeMillis() - start); + return dependentFiles; + } + + private final Object lock = new Object(); + + private class SynchedCompilerParsing extends CompilerParsingThread { + private final ClassParsingThread myClassParsingThread; + + private SynchedCompilerParsing(Process process, + final CompileContext context, + OutputParser outputParser, + ClassParsingThread classParsingThread, + boolean readErrorStream, + boolean trimLines) { + super(process, outputParser, readErrorStream, trimLines,context); + myClassParsingThread = classParsingThread; + } + + public void setProgressText(String text) { + synchronized (lock) { + super.setProgressText(text); + } + } + + public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) { + synchronized (lock) { + super.message(category, message, url, lineNum, columnNum); + } + } + + public void fileProcessed(String path) { + synchronized (lock) { + sourceFileProcessed(); + } + } + + protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException { + synchronized (lock) { + myClassParsingThread.addPath(classFileToProcess); + } + } + } + + private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir, int sourcesFilter) throws IOException { + myCompileContext.getProgressIndicator().checkCanceled(); + chunk.setSourcesFilter(sourcesFilter); + + if (ApplicationManager.getApplication().runReadAction(new Computable() { + public Boolean compute() { + return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE; + } + }).booleanValue()) { + return; // should not invoke javac with empty sources list + } + + ModuleType moduleType = chunk.getModules()[0].getModuleType(); + if (!(chunk.getJdk().getSdkType() instanceof JavaSdkType) && + !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) { + // TODO + // don't try to compile non-java type module + return; + } + + int exitValue = 0; + try { + Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext); + final long compilationStart = System.currentTimeMillis(); + final ClassParsingThread classParsingThread = new ClassParsingThread(isJdk6(chunk.getJdk()), outputDir); + final Future classParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(classParsingThread); + + OutputParser errorParser = myCompiler.createErrorParser(outputDir, process); + CompilerParsingThread errorParsingThread = errorParser == null + ? null + : new SynchedCompilerParsing(process, myCompileContext, errorParser, classParsingThread, + true, errorParser.isTrimLines()); + Future errorParsingThreadFuture = null; + if (errorParsingThread != null) { + errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread); + } + + OutputParser outputParser = myCompiler.createOutputParser(outputDir); + CompilerParsingThread outputParsingThread = outputParser == null + ? null + : new SynchedCompilerParsing(process, myCompileContext, outputParser, classParsingThread, + false, outputParser.isTrimLines()); + Future outputParsingThreadFuture = null; + if (outputParsingThread != null) { + outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread); + } + + try { + exitValue = process.waitFor(); + } + catch (InterruptedException e) { + process.destroy(); + exitValue = process.exitValue(); + } + finally { + myCompilationDuration += (System.currentTimeMillis() - compilationStart); + if (errorParsingThread != null) { + errorParsingThread.setProcessTerminated(true); + } + if (outputParsingThread != null) { + outputParsingThread.setProcessTerminated(true); + } + joinThread(errorParsingThreadFuture); + joinThread(outputParsingThreadFuture); + classParsingThread.stopParsing(); + joinThread(classParsingThreadFuture); + + registerParsingException(outputParsingThread); + registerParsingException(errorParsingThread); + assert outputParsingThread == null || !outputParsingThread.processing; + assert errorParsingThread == null || !errorParsingThread.processing; + assert classParsingThread == null || !classParsingThread.processing; + } + } + finally { + compileFinished(exitValue, chunk, outputDir); + myModuleName = null; + } + } + + private static void joinThread(final Future threadFuture) { + if (threadFuture != null) { + try { + threadFuture.get(); + } + catch (InterruptedException ignored) { + } + catch(ExecutionException ignored) { + } + } + } + + private void registerParsingException(final CompilerParsingThread outputParsingThread) { + Throwable error = outputParsingThread == null ? null : outputParsingThread.getError(); + if (error != null) { + String message = error.getMessage(); + if (error instanceof CacheCorruptedException) { + myCompileContext.requestRebuildNextTime(message); + } + else { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); + } + } + } + + private void runTransformingCompilers(final ModuleChunk chunk) { + final JavaSourceTransformingCompiler[] transformers = + CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class); + if (transformers.length == 0) { + return; + } + if (LOG.isDebugEnabled()) { + LOG.debug("Running transforming compilers..."); + } + final Module[] modules = chunk.getModules(); + for (final JavaSourceTransformingCompiler transformer : transformers) { + final Map originalToCopyFileMap = new HashMap(); + final Application application = ApplicationManager.getApplication(); + application.invokeAndWait(new Runnable() { + public void run() { + for (final Module module : modules) { + List filesToCompile = chunk.getFilesToCompile(module); + for (final VirtualFile file : filesToCompile) { + if (transformer.isTransformable(file)) { + application.runWriteAction(new Runnable() { + public void run() { + try { + VirtualFile fileCopy = createFileCopy(getTempDir(module), file); + originalToCopyFileMap.put(file, fileCopy); + } + catch (IOException e) { + // skip it + } + } + }); + } + } + } + } + }, myCompileContext.getProgressIndicator().getModalityState()); + + // do actual transform + for (final Module module : modules) { + final List filesToCompile = chunk.getFilesToCompile(module); + for (int j = 0; j < filesToCompile.size(); j++) { + final VirtualFile file = filesToCompile.get(j); + VirtualFile fileCopy = originalToCopyFileMap.get(file); + if (fileCopy != null) { + final boolean ok = transformer.transform(myCompileContext, fileCopy, file); + if (ok) { + chunk.substituteWithTransformedVersion(module, j, fileCopy); + } + } + } + } + } + } + + private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException { + final String fileName = file.getName(); + if (tempDir.findChild(fileName) != null) { + int idx = 0; + while (true) { + //noinspection HardCodedStringLiteral + final String dirName = "dir" + idx++; + final VirtualFile dir = tempDir.findChild(dirName); + if (dir == null) { + tempDir = tempDir.createChildDirectory(this, dirName); + break; + } + if (dir.findChild(fileName) == null) { + tempDir = dir; + break; + } + } + } + return VfsUtil.copyFile(this, file, tempDir); + } + + private VirtualFile getTempDir(Module module) throws IOException { + VirtualFile tempDir = myModuleToTempDirMap.get(module); + if (tempDir == null) { + final String projectName = myProject.getName(); + final String moduleName = module.getName(); + File tempDirectory = FileUtil.createTempDirectory(projectName, moduleName); + tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDirectory); + if (tempDir == null) { + LOG.error("Cannot locate temp directory " + tempDirectory.getPath()); + } + myModuleToTempDirMap.put(module, tempDir); + } + return tempDir; + } + + private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) { + if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() && + myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1); + } + + myCompiler.compileFinished(); + final List toRefresh = new ArrayList(); + final Map> results = new HashMap>(); + try { + ApplicationManager.getApplication().runReadAction(new Runnable() { + public void run() { + final Set compiledWithErrors = getFilesCompiledWithErrors(); + final FileTypeManager typeManager = FileTypeManager.getInstance(); + final String outputDirPath = outputDir.replace(File.separatorChar, '/'); + try { + for (final Module module : chunk.getModules()) { + for (final VirtualFile root : chunk.getSourceRoots(module)) { + final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root); + if (LOG.isDebugEnabled()) { + LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\""); + } + buildOutputItemsList(outputDirPath, module, root, typeManager, compiledWithErrors, root, packagePrefix, toRefresh, results); + } + } + } + catch (CacheCorruptedException e) { + myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted")); + if (LOG.isDebugEnabled()) { + LOG.debug(e); + } + } + } + }); + } + finally { + CompilerUtil.refreshIOFiles(toRefresh); + for (Iterator>> it = results.entrySet().iterator(); it.hasNext();) { + Map.Entry> entry = it.next(); + mySink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY); + it.remove(); // to free memory + } + } + CompilerUtil.refreshIOFiles(toRefresh); + myFileNameToSourceMap.clear(); // clear the map before the next use + } + + private Set getFilesCompiledWithErrors() { + CompilerMessage[] messages = myCompileContext.getMessages(CompilerMessageCategory.ERROR); + Set compiledWithErrors = Collections.emptySet(); + if (messages.length > 0) { + compiledWithErrors = new HashSet(messages.length); + for (CompilerMessage message : messages) { + final VirtualFile file = message.getVirtualFile(); + if (file != null) { + compiledWithErrors.add(file); + } + } + } + return compiledWithErrors; + } + + private void buildOutputItemsList(final String outputDir, Module module, VirtualFile from, + final FileTypeManager typeManager, + final Set compiledWithErrors, + final VirtualFile sourceRoot, + final String packagePrefix, final List filesToRefresh, final Map> results) throws CacheCorruptedException { + final Ref exRef = new Ref(null); + final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex(); + final ContentIterator contentIterator = new ContentIterator() { + public boolean processFile(final VirtualFile child) { + try { + assert child.isValid(); + if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(typeManager.getFileTypeByFile(child))) { + updateOutputItemsList(outputDir, child, compiledWithErrors, sourceRoot, packagePrefix, filesToRefresh, results); + } + return true; + } + catch (CacheCorruptedException e) { + exRef.set(e); + return false; + } + } + }; + if (fileIndex.isInContent(from)) { + // use file index for iteration to handle 'inner modules' and excludes properly + fileIndex.iterateContentUnderDirectory(from, contentIterator); + } + else { + // seems to be a root for generated sources + new Object() { + void iterateContent(VirtualFile from) { + for (VirtualFile child : from.getChildren()) { + if (child.isDirectory()) { + iterateContent(child); + } + else { + contentIterator.processFile(child); + } + } + } + }.iterateContent(from); + } + if (exRef.get() != null) { + throw exRef.get(); + } + } + + private void putName(String sourceFileName, int classQName, String relativePathToSource, String pathToClass) { + if (LOG.isDebugEnabled()) { + LOG.debug("Registering [sourceFileName, relativePathToSource, pathToClass] = [" + sourceFileName + "; " + relativePathToSource + + "; " + pathToClass + "]"); + } + Set paths = myFileNameToSourceMap.get(sourceFileName); + + if (paths == null) { + paths = new HashSet(); + myFileNameToSourceMap.put(sourceFileName, paths); + } + paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass)); + } + + private void updateOutputItemsList(final String outputDir, VirtualFile srcFile, Set compiledWithErrors, + VirtualFile sourceRoot, + final String packagePrefix, final List filesToRefresh, + Map> results) throws CacheCorruptedException { + final Cache newCache = myCompileContext.getDependencyCache().getNewClassesCache(); + final Set paths = myFileNameToSourceMap.get(srcFile.getName()); + if (paths == null || paths.isEmpty()) { + return; + } + final String prefix = packagePrefix != null && packagePrefix.length() > 0 ? packagePrefix.replace('.', '/') + "/" : ""; + final String filePath = "/" + prefix + VfsUtil.getRelativePath(srcFile, sourceRoot, '/'); + for (final CompiledClass cc : paths) { + myCompileContext.getProgressIndicator().checkCanceled(); + if (LOG.isDebugEnabled()) { + LOG.debug("Checking [pathToClass; relPathToSource] = " + cc); + } + if (FileUtil.pathsEqual(filePath, cc.relativePathToSource)) { + final String outputPath = cc.pathToClass.replace(File.separatorChar, '/'); + final Pair realLocation = moveToRealLocation(outputDir, outputPath, srcFile, filesToRefresh); + if (realLocation != null) { + Collection outputs = results.get(realLocation.getFirst()); + if (outputs == null) { + outputs = new ArrayList(); + results.put(realLocation.getFirst(), outputs); + } + outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile)); + if (CompilerConfiguration.MAKE_ENABLED) { + newCache.setPath(cc.qName, realLocation.getSecond()); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Added output item: [outputDir; outputPath; sourceFile] = [" + realLocation.getFirst() + "; " + + realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]"); + } + if (!compiledWithErrors.contains(srcFile)) { + mySuccesfullyCompiledJavaFiles.add(srcFile); + } + } + else { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, "Failed to copy from temporary location to output directory: " + outputPath + " (see idea.log for details)", null, -1, -1); + if (LOG.isDebugEnabled()) { + LOG.debug("Failed to move to real location: " + outputPath + "; from " + outputDir); + } + } + } + } + } + + private Pair moveToRealLocation(String tempOutputDir, String pathToClass, VirtualFile sourceFile, final List filesToRefresh) { + final Module module = myCompileContext.getModuleByFile(sourceFile); + if (module == null) { + final String message = + "Cannot determine module for source file: " + sourceFile.getPresentableUrl() + ";\nCorresponding output file: " + pathToClass; + LOG.info(message); + myCompileContext.addMessage(CompilerMessageCategory.WARNING, message, sourceFile.getUrl(), -1, -1); + // do not move: looks like source file has been invalidated, need recompilation + return new Pair(tempOutputDir, pathToClass); + } + final String realOutputDir; + if (myCompileContext.isInTestSourceContent(sourceFile)) { + realOutputDir = getTestsOutputDir(module); + } + else { + realOutputDir = getOutputDir(module); + } + + if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move + filesToRefresh.add(new File(pathToClass)); + return new Pair(realOutputDir, pathToClass); + } + + final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length()); + final File fromFile = new File(pathToClass); + final File toFile = new File(realPathToClass); + + boolean success = fromFile.renameTo(toFile); + if (!success) { + // assuming cause of the fail: intermediate dirs do not exist + final File parentFile = toFile.getParentFile(); + if (parentFile != null) { + parentFile.mkdirs(); + success = fromFile.renameTo(toFile); // retry after making non-existent dirs + } + } + if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints. + try { + FileUtil.copy(fromFile, toFile); + FileUtil.delete(fromFile); + success = true; + } + catch (IOException e) { + LOG.info(e); + success = false; + } + } + if (success) { + filesToRefresh.add(toFile); + return new Pair(realOutputDir, realPathToClass); + } + return null; + } + + private final Map myModuleToTestsOutput = new HashMap(); + + private String getTestsOutputDir(final Module module) { + if (myModuleToTestsOutput.containsKey(module)) { + return myModuleToTestsOutput.get(module); + } + final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module); + final String out = outputDirectory != null? outputDirectory.getPath() : null; + myModuleToTestsOutput.put(module, out); + return out; + } + + private final Map myModuleToOutput = new HashMap(); + + private String getOutputDir(final Module module) { + if (myModuleToOutput.containsKey(module)) { + return myModuleToOutput.get(module); + } + final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module); + final String out = outputDirectory != null? outputDirectory.getPath() : null; + myModuleToOutput.put(module, out); + return out; + } + + private int myProcessedFilesCount = 0; + private int myTotalFilesToCompile = 0; + private int myClassesCount = 0; + private volatile String myModuleName = null; + + private void sourceFileProcessed() { + myProcessedFilesCount++; + updateStatistics(); + } + + private void updateStatistics() { + final String msg; + String moduleName = myModuleName; + if (moduleName != null) { + msg = CompilerBundle.message("statistics.files.classes.module", myProcessedFilesCount, myClassesCount, moduleName); + } + else { + msg = CompilerBundle.message("statistics.files.classes", myProcessedFilesCount, myClassesCount); + } + myCompileContext.getProgressIndicator().setText2(msg); + myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile); + } + + private class ClassParsingThread implements Runnable { + private final BlockingQueue myPaths = new ArrayBlockingQueue(50000); + private CacheCorruptedException myError = null; + private final boolean myAddNotNullAssertions; + private final boolean myIsJdk16; + private final String myOutputDir; + + private ClassParsingThread(final boolean isJdk16, String outputDir) { + myIsJdk16 = isJdk16; + myOutputDir = FileUtil.toSystemIndependentName(outputDir); + myAddNotNullAssertions = CompilerWorkspaceConfiguration.getInstance(myProject).ASSERT_NOT_NULL; + } + + volatile boolean processing; + public void run() { + processing = true; + try { + while (true) { + FileObject path = myPaths.take(); + + if (path == myStopThreadToken) break; + processPath(path); + } + } + catch (InterruptedException e) { + LOG.error(e); + } + catch (CacheCorruptedException e) { + myError = e; + } + processing = false; + } + + public void addPath(FileObject path) throws CacheCorruptedException { + if (myError != null) { + throw myError; + } + myPaths.offer(path); + } + + public void stopParsing() { + myPaths.offer(myStopThreadToken); + } + + private void processPath(FileObject fileObject) throws CacheCorruptedException { + File file = fileObject.getFile(); + final String path = file.getPath(); + try { + if (CompilerConfiguration.MAKE_ENABLED) { + byte[] fileContent = fileObject.getContent(); + // the file is assumed to exist! + final DependencyCache dependencyCache = myCompileContext.getDependencyCache(); + final int newClassQName = dependencyCache.reparseClassFile(file, fileContent); + final Cache newClassesCache = dependencyCache.getNewClassesCache(); + final String sourceFileName = newClassesCache.getSourceFileName(newClassQName); + final String qName = dependencyCache.resolve(newClassQName); + String relativePathToSource = "/" + MakeUtil.createRelativePathToSource(qName, sourceFileName); + putName(sourceFileName, newClassQName, relativePathToSource, path); + boolean haveToInstrument = myAddNotNullAssertions && hasNotNullAnnotations(newClassesCache, dependencyCache.getSymbolTable(), newClassQName); + + boolean fileContentChanged = false; + if (haveToInstrument) { + try { + ClassReader reader = new ClassReader(fileContent, 0, fileContent.length); + ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16); + + final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer); + reader.accept(instrumenter, 0); + if (instrumenter.isModification()) { + fileContent = writer.toByteArray(); + fileContentChanged = true; + } + } + catch (Exception ignored) { + LOG.info(ignored); + } + } + + if (fileContentChanged || !fileObject.isSaved()) { + writeFile(file, fileContent); + } + } + else { + final String _path = FileUtil.toSystemIndependentName(path); + final int dollarIndex = _path.indexOf('$'); + final int tailIndex = dollarIndex >=0 ? dollarIndex : _path.length() - ".class".length(); + final int slashIndex = _path.lastIndexOf('/'); + final String sourceFileName = _path.substring(slashIndex + 1, tailIndex) + ".java"; + String relativePathToSource = _path.substring(myOutputDir.length(), tailIndex) + ".java"; + putName(sourceFileName, 0 /*doesn't matter here*/ , relativePathToSource.startsWith("/")? relativePathToSource : "/" + relativePathToSource, path); + } + } + catch (ClsFormatException e) { + String message; + final String m = e.getMessage(); + if (m == null || "".equals(m)) { + message = CompilerBundle.message("error.bad.class.file.format", path); + } + else { + message = CompilerBundle.message("error.bad.class.file.format", m + "\n" + path); + } + myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1); + } + catch (IOException e) { + myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1); + } + finally { + myClassesCount++; + updateStatistics(); + } + } + + private void writeFile(File file, byte[] fileContent) throws IOException { + try { + FileUtil.writeToFile(file, fileContent); + } + catch (FileNotFoundException e) { + FileUtil.createParentDirs(file); + FileUtil.writeToFile(file, fileContent); + } + } + } + + private static boolean hasNotNullAnnotations(final Cache cache, final SymbolTable symbolTable, final int className) throws CacheCorruptedException { + for (MethodInfo methodId : cache.getMethods(className)) { + for (AnnotationConstantValue annotation : methodId.getRuntimeInvisibleAnnotations()) { + if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { + return true; + } + } + final AnnotationConstantValue[][] paramAnnotations = methodId.getRuntimeInvisibleParameterAnnotations(); + for (AnnotationConstantValue[] _singleParamAnnotations : paramAnnotations) { + for (AnnotationConstantValue annotation : _singleParamAnnotations) { + if (AnnotationUtil.NOT_NULL.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) { + return true; + } + } + } + } + return false; + } + + private static boolean isJdk6(final Sdk jdk) { + boolean isJDK16 = false; + if (jdk != null) { + final String versionString = jdk.getVersionString(); + if (versionString != null) { + isJDK16 = versionString.contains("1.6") || versionString.contains("6.0"); + } + } + return isJDK16; + } +} diff --git a/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java index c0adcdd986..b1d2ce8a01 100644 --- a/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java +++ b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java @@ -1,671 +1,671 @@ -/* - * Copyright 2000-2009 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * created at Jan 7, 2002 - * @author Jeka - */ -package com.intellij.compiler.make; - -import com.intellij.compiler.SymbolTable; -import com.intellij.compiler.classParsing.*; -import com.intellij.compiler.impl.CompilerUtil; -import com.intellij.compiler.impl.javaCompiler.DependencyProcessor; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.compiler.CompileContext; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ArrayUtil; -import com.intellij.util.cls.ClsFormatException; -import com.intellij.util.cls.ClsUtil; -import gnu.trove.TIntHashSet; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.io.IOException; -import java.rmi.Remote; -import java.util.*; - -public class DependencyCache { - private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.DependencyCache"); - - private Cache myCache; - private Cache myNewClassesCache; - - private static final String REMOTE_INTERFACE_NAME = Remote.class.getName(); - private TIntHashSet myToUpdate = new TIntHashSet(); // qName strings to be updated. - private final TIntHashSet myTraverseRoots = new TIntHashSet(); // Dependencies are calculated from these clasess - private final TIntHashSet myClassesWithSourceRemoved = new TIntHashSet(); - private final TIntHashSet myPreviouslyRemoteClasses = new TIntHashSet(); // classes that were Remote, but became non-Remote for some reason - private final TIntHashSet myMarkedInfos = new TIntHashSet(); // classes to be recompiled - private final Set myMarkedFiles = new HashSet(); - - private DependencyCacheNavigator myCacheNavigator; - private SymbolTable mySymbolTable; - private final String mySymbolTableFilePath; - private final String myStoreDirectoryPath; - @NonNls private static final String SYMBOLTABLE_FILE_NAME = "symboltable.dat"; - - public DependencyCache(@NonNls String storeDirectoryPath) { - myStoreDirectoryPath = storeDirectoryPath; - LOG.assertTrue(myStoreDirectoryPath != null); - - mySymbolTableFilePath = myStoreDirectoryPath + "/" + SYMBOLTABLE_FILE_NAME; - } - - public DependencyCacheNavigator getCacheNavigator() throws CacheCorruptedException { - if (myCacheNavigator == null) { - myCacheNavigator = new DependencyCacheNavigator(getCache()); - } - return myCacheNavigator; - } - - public void wipe() throws CacheCorruptedException { - getCache().wipe(); - getNewClassesCache().wipe(); - } - - public Cache getCache() throws CacheCorruptedException { - try { - if (myCache == null) { - // base number of cached record views of each type - myCache = new Cache(myStoreDirectoryPath, 512); - } - - return myCache; - } - catch (IOException e) { - throw new CacheCorruptedException(e); - } - } - - public Cache getNewClassesCache() throws CacheCorruptedException { - try { - if (myNewClassesCache == null) { - myNewClassesCache = new Cache(myStoreDirectoryPath + "/tmp", 16); - } - return myNewClassesCache; - } - catch (IOException e) { - throw new CacheCorruptedException(e); - } - } - - public void addTraverseRoot(int qName) { - myTraverseRoots.add(qName); - } - - public void clearTraverseRoots() { - myTraverseRoots.clear(); - } - - public void markSourceRemoved(int qName) { - myClassesWithSourceRemoved.add(qName); - } - - public void addClassToUpdate(int qName) { - myToUpdate.add(qName); - } - - public int reparseClassFile(@NotNull File file, final byte[] fileContent) throws ClsFormatException, CacheCorruptedException { - SymbolTable symbolTable = getSymbolTable(); - - final int qName = getNewClassesCache().importClassInfo(new ClassFileReader(file, symbolTable, fileContent), symbolTable); - addClassToUpdate(qName); - addTraverseRoot(qName); - return qName; - } - - // for profiling purposes - /* - private static void pause() { - System.out.println("PAUSED. ENTER A CHAR."); - byte[] buf = new byte[1]; - try { - System.in.read(buf); - } - catch (IOException e) { - e.printStackTrace(); - } - } - */ - - public void update(ProgressIndicator indicator) throws CacheCorruptedException { - if (myToUpdate.isEmpty()) { - return; // optimization - } - - final long updateStart = System.currentTimeMillis(); - //pause(); - - final int[] namesToUpdate = myToUpdate.toArray(); - final Cache cache = getCache(); - final Cache newCache = getNewClassesCache(); - final DependencyCacheNavigator navigator = getCacheNavigator(); - - int i = 0; - // remove unnecesary dependencies - for (final int qName : namesToUpdate) { - indicator.setFraction(i++*1.0/namesToUpdate.length/4); - // process use-dependencies - for (int referencedClassQName : cache.getReferencedClasses(qName)) { - if (!cache.containsClass(referencedClassQName)) { - continue; - } - cache.removeClassReferencer(referencedClassQName, qName); - } - cache.clearReferencedClasses(qName); - // process inheritance dependencies - navigator.walkSuperClasses(qName, new ClassInfoProcessor() { - public boolean process(int classQName) throws CacheCorruptedException { - cache.removeSubclass(classQName, qName); - return true; - } - }); - } - - // do update of classInfos - for (final int qName : namesToUpdate) { - indicator.setFraction(i++*1.0/namesToUpdate.length/4); - cache.importClassInfo(newCache, qName); - } - - // build forward-dependencies for the new infos, all new class infos must be already in the main cache! - - final SymbolTable symbolTable = getSymbolTable(); - - for (final int qName : namesToUpdate) { - indicator.setFraction(i++*1.0/namesToUpdate.length/4); - if (!newCache.containsClass(qName)) { - continue; - } - buildForwardDependencies(qName, newCache.getReferences(qName)); - boolean isRemote = false; - // "remote objects" are classes that _directly_ implement remote interfaces - final int[] superInterfaces = cache.getSuperInterfaces(qName); - if (superInterfaces.length > 0) { - final int remoteInterfaceName = symbolTable.getId(REMOTE_INTERFACE_NAME); - for (int superInterface : superInterfaces) { - if (isRemoteInterface(cache, superInterface, remoteInterfaceName)) { - isRemote = true; - break; - } - } - } - final boolean wasRemote = cache.isRemote(qName); - if (wasRemote && !isRemote) { - synchronized (myPreviouslyRemoteClasses) { - myPreviouslyRemoteClasses.add(qName); - } - } - cache.setRemote(qName, isRemote); - } - - // building subclass dependencies - for (final int qName : namesToUpdate) { - indicator.setFraction(i++*1.0/namesToUpdate.length/4); - final int classId = qName; - buildSubclassDependencies(getCache(), qName, classId); - } - - for (final int qName : myClassesWithSourceRemoved.toArray()) { - cache.removeClass(qName); - } - myToUpdate = new TIntHashSet(); - - CompilerUtil.logDuration("Dependency cache update", System.currentTimeMillis() - updateStart); - //pause(); - } - - private void buildForwardDependencies(final int classQName, final Collection references) throws CacheCorruptedException { - final Cache cache = getCache(); - - final int genericSignature = cache.getGenericSignature(classQName); - if (genericSignature != -1) { - final String genericClassSignature = resolve(genericSignature); - final int[] bounds = findBounds(genericClassSignature); - for (int boundClassQName : bounds) { - cache.addClassReferencer(boundClassQName, classQName); - } - } - - buildAnnotationDependencies(classQName, cache.getRuntimeVisibleAnnotations(classQName)); - buildAnnotationDependencies(classQName, cache.getRuntimeInvisibleAnnotations(classQName)); - - for (final ReferenceInfo refInfo : references) { - final int declaringClassName = getActualDeclaringClassForReference(refInfo); - if (declaringClassName == Cache.UNKNOWN) { - continue; - } - if (refInfo instanceof MemberReferenceInfo) { - final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo(); - if (memberInfo instanceof FieldInfo) { - cache.addFieldReferencer(declaringClassName, memberInfo.getName(), classQName); - } - else if (memberInfo instanceof MethodInfo) { - cache.addMethodReferencer(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor(), classQName); - } - else { - LOG.error("Unknown member info class: " + memberInfo.getClass().getName()); - } - } - else { // reference to class - cache.addClassReferencer(declaringClassName, classQName); - } - } - final SymbolTable symbolTable = getSymbolTable(); - - for (final FieldInfo fieldInfo : cache.getFields(classQName)) { - buildAnnotationDependencies(classQName, fieldInfo.getRuntimeVisibleAnnotations()); - buildAnnotationDependencies(classQName, fieldInfo.getRuntimeInvisibleAnnotations()); - - String className = MakeUtil.parseObjectType(symbolTable.getSymbol(fieldInfo.getDescriptor()), 0); - if (className == null) { - continue; - } - final int cls = symbolTable.getId(className); - cache.addClassReferencer(cls, classQName); - } - - for (final MethodInfo methodInfo : cache.getMethods(classQName)) { - buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleAnnotations()); - buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleAnnotations()); - buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleParameterAnnotations()); - buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleParameterAnnotations()); - - if (methodInfo.isConstructor()) { - continue; - } - - final String returnTypeClassName = MakeUtil.parseObjectType(methodInfo.getReturnTypeDescriptor(symbolTable), 0); - if (returnTypeClassName != null) { - final int returnTypeClassQName = symbolTable.getId(returnTypeClassName); - cache.addClassReferencer(returnTypeClassQName, classQName); - } - - String[] parameterSignatures = CacheUtils.getParameterSignatures(methodInfo, symbolTable); - for (String parameterSignature : parameterSignatures) { - String paramClassName = MakeUtil.parseObjectType(parameterSignature, 0); - if (paramClassName != null) { - final int paramClassId = symbolTable.getId(paramClassName); - cache.addClassReferencer(paramClassId, classQName); - } - } - } - } - - private static boolean isRemoteInterface(Cache cache, int ifaceName, final int remoteInterfaceName) throws CacheCorruptedException { - if (ifaceName == remoteInterfaceName) { - return true; - } - for (int superInterfaceName : cache.getSuperInterfaces(ifaceName)) { - if (isRemoteInterface(cache, superInterfaceName, remoteInterfaceName)) { - return true; - } - } - return false; - } - - - private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[][] annotations) throws CacheCorruptedException { - if (annotations == null || annotations.length == 0) { - return; - } - for (AnnotationConstantValue[] annotation : annotations) { - buildAnnotationDependencies(classQName, annotation); - } - } - - private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[] annotations) throws CacheCorruptedException { - if (annotations == null || annotations.length == 0) { - return; - } - final Cache cache = getCache(); - for (AnnotationConstantValue annotation : annotations) { - final int annotationQName = annotation.getAnnotationQName(); - - cache.addClassReferencer(annotationQName, classQName); - - final AnnotationNameValuePair[] memberValues = annotation.getMemberValues(); - for (final AnnotationNameValuePair nameValuePair : memberValues) { - for (MethodInfo annotationMember : cache.findMethodsByName(annotationQName, nameValuePair.getName())) { - cache.addMethodReferencer(annotationQName, annotationMember.getName(), annotationMember.getDescriptor(), classQName); - } - } - } - } - - private int[] findBounds(final String genericClassSignature) throws CacheCorruptedException{ - try { - final String[] boundInterfaces = BoundsParser.getBounds(genericClassSignature); - int[] ids = ArrayUtil.newIntArray(boundInterfaces.length); - for (int i = 0; i < boundInterfaces.length; i++) { - ids[i] = getSymbolTable().getId(boundInterfaces[i]); - } - return ids; - } - catch (SignatureParsingException e) { - return ArrayUtil.EMPTY_INT_ARRAY; - } - } - - // fixes JDK 1.4 javac bug that generates references in the constant pool - // to the subclass even if the field was declared in a superclass - private int getActualDeclaringClassForReference(final ReferenceInfo refInfo) throws CacheCorruptedException { - if (!(refInfo instanceof MemberReferenceInfo)) { - return refInfo.getClassName(); - } - final int declaringClassName = refInfo.getClassName(); - final Cache cache = getCache(); - final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo(); - if (memberInfo instanceof FieldInfo) { - if (cache.findFieldByName(declaringClassName, memberInfo.getName()) != null) { - return declaringClassName; - } - } - else if (memberInfo instanceof MethodInfo) { - if (cache.findMethod(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor()) != null) { - return declaringClassName; - } - } - final DeclaringClassFinder finder = new DeclaringClassFinder(memberInfo); - getCacheNavigator().walkSuperClasses(declaringClassName, finder); - return finder.getDeclaringClassName(); - } - - /** - * @return qualified names of the classes that should be additionally recompiled - */ - public Pair> findDependentClasses(CompileContext context, Project project, Set successfullyCompiled, @Nullable final DependencyProcessor additionalProcessor) - throws CacheCorruptedException { - - markDependencies(context, project, successfullyCompiled, additionalProcessor); - return new Pair>(myMarkedInfos.toArray(), Collections.unmodifiableSet(myMarkedFiles)); - } - - private void markDependencies(CompileContext context, Project project, final Set successfullyCompiled, - @Nullable final DependencyProcessor additionalProcessor) throws CacheCorruptedException { - try { - if (LOG.isDebugEnabled()) { - LOG.debug("====================Marking dependent files====================="); - } - // myToUpdate can be modified during the mark procedure, so use toArray() to iterate it - int[] qNamesToUpdate = myTraverseRoots.toArray(); - final SourceFileFinder sourceFileFinder = new SourceFileFinder(project, context); - final CachingSearcher searcher = new CachingSearcher(project); - final ChangedRetentionPolicyDependencyProcessor changedRetentionPolicyDependencyProcessor = new ChangedRetentionPolicyDependencyProcessor(project, searcher, this); - for (final int qName : qNamesToUpdate) { - if (!getCache().containsClass(qName)) { - continue; - } - if (getNewClassesCache().containsClass(qName)) { // there is a new class file created - new JavaDependencyProcessor(project, this, qName).run(); - ArrayList changed = - new ArrayList(); - ArrayList removed = - new ArrayList(); - findModifiedConstants(qName, changed, removed); - if (!changed.isEmpty() || !removed.isEmpty()) { - new ChangedConstantsDependencyProcessor( - project, searcher, this, qName, - changed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[changed.size()]), - removed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[removed.size()]) - ).run(); - } - changedRetentionPolicyDependencyProcessor.checkAnnotationRetentionPolicyChanges(qName); - if (additionalProcessor != null) { - additionalProcessor.processDependencies(context, qName); - } - } - else { - boolean isSourceDeleted = false; - if (myClassesWithSourceRemoved.contains(qName)) { // no recompiled class file, check whether the classfile exists - isSourceDeleted = true; - } - else if (!new File(getCache().getPath(qName)).exists()) { - final String qualifiedName = resolve(qName); - final String sourceFileName = getCache().getSourceFileName(qName); - final boolean markAsRemovedSource = ApplicationManager.getApplication().runReadAction(new Computable() { - public Boolean compute() { - VirtualFile sourceFile = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName); - return sourceFile == null || successfullyCompiled.contains(sourceFile) ? Boolean.TRUE : Boolean.FALSE; - } - }).booleanValue(); - if (markAsRemovedSource) { - // for Inner classes: sourceFile may exist, but the inner class declaration inside it may not, - // thus the source for the class info should be considered removed - isSourceDeleted = true; - markSourceRemoved(qName); - myMarkedInfos.remove(qName); // if the info has been marked already, the mark should be removed - } - } - if (isSourceDeleted) { - Dependency[] backDependencies = getCache().getBackDependencies(qName); - for (Dependency backDependency : backDependencies) { - if (markTargetClassInfo(backDependency)) { - if (LOG.isDebugEnabled()) { - LOG.debug( - "Mark dependent class " + backDependency.getClassQualifiedName() + "; reason: no class file found for " + qName); - } - } - } - } - } - } - if (LOG.isDebugEnabled()) { - LOG.debug("================================================================"); - } - } - catch (ProcessCanceledException ignored) { - // deliberately suppressed - } - } - - private void findModifiedConstants( - final int qName, - Collection changedConstants, - Collection removedConstants) throws CacheCorruptedException { - - final Cache cache = getCache(); - for (final FieldInfo field : cache.getFields(qName)) { - final int oldFlags = field.getFlags(); - if (ClsUtil.isStatic(oldFlags) && ClsUtil.isFinal(oldFlags)) { - final Cache newClassesCache = getNewClassesCache(); - FieldInfo newField = newClassesCache.findFieldByName(qName, field.getName()); - if (newField == null) { - if (!ConstantValue.EMPTY_CONSTANT_VALUE.equals(field.getConstantValue())) { - // if the field was really compile time constant - removedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field)); - } - } - else { - final boolean visibilityRestricted = MakeUtil.isMoreAccessible(oldFlags, newField.getFlags()); - if (!field.getConstantValue().equals(newField.getConstantValue()) || visibilityRestricted) { - changedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field, visibilityRestricted)); - } - } - } - } - } - - private static void buildSubclassDependencies(Cache cache, final int qName, int targetClassId) throws CacheCorruptedException { - final int superQName = cache.getSuperQualifiedName(targetClassId); - if (superQName != Cache.UNKNOWN) { - cache.addSubclass(superQName, qName); - buildSubclassDependencies(cache, qName, superQName); - } - - int[] interfaces = cache.getSuperInterfaces(targetClassId); - for (final int interfaceName : interfaces) { - cache.addSubclass(interfaceName, qName); - buildSubclassDependencies(cache, qName, interfaceName); - } - } - - - /** - * Marks ClassInfo targeted by the dependency - * @return true if really added, false otherwise - */ - public boolean markTargetClassInfo(Dependency dependency) throws CacheCorruptedException { - return markClassInfo(dependency.getClassQualifiedName(), false); - } - - /** - * Marks ClassInfo that corresponds to the specified qualified name - * If class info is already recompiled, it is not marked - * @return true if really added, false otherwise - */ - public boolean markClass(int qualifiedName) throws CacheCorruptedException { - return markClass(qualifiedName, false); - } - - /** - * Marks ClassInfo that corresponds to the specified qualified name - * If class info is already recompiled, it is not marked unless force parameter is true - * @return true if really added, false otherwise - */ - public boolean markClass(int qualifiedName, boolean force) throws CacheCorruptedException { - return markClassInfo(qualifiedName, force); - } - - public boolean isTargetClassInfoMarked(Dependency dependency) { - return isClassInfoMarked(dependency.getClassQualifiedName()); - } - - public boolean isClassInfoMarked(int qName) { - return myMarkedInfos.contains(qName); - } - - public void markFile(VirtualFile file) { - myMarkedFiles.add(file); - } - - /** - * @return true if really marked, false otherwise - */ - private boolean markClassInfo(int qName, boolean force) throws CacheCorruptedException { - if (!getCache().containsClass(qName)) { - return false; - } - if (myClassesWithSourceRemoved.contains(qName)) { - return false; // no need to recompile since source has been removed - } - if (!force) { - if (getNewClassesCache().containsClass(qName)) { // already recompiled - return false; - } - } - return myMarkedInfos.add(qName); - } - - public void resetState() { - final long start = System.currentTimeMillis(); - - try { - myClassesWithSourceRemoved.clear(); - myMarkedFiles.clear(); - myMarkedInfos.clear(); - myToUpdate.clear(); - myTraverseRoots.clear(); - if (myNewClassesCache != null) { - myNewClassesCache.wipe(); - myNewClassesCache = null; - } - myCacheNavigator = null; - try { - if (myCache != null) { - myCache.dispose(); - myCache = null; - } - } - catch (CacheCorruptedException e) { - LOG.info(e); - } - try { - if (mySymbolTable != null) { - mySymbolTable.dispose(); - mySymbolTable = null; - } - } - catch (CacheCorruptedException e) { - LOG.info(e); - } - } - finally { - CompilerUtil.logDuration("Dependency cache disposal", System.currentTimeMillis() - start); - } - } - - - public SymbolTable getSymbolTable() throws CacheCorruptedException { - if (mySymbolTable == null) { - mySymbolTable = new SymbolTable(new File(mySymbolTableFilePath)); - } - return mySymbolTable; - } - - public String resolve(int id) throws CacheCorruptedException { - return getSymbolTable().getSymbol(id); - } - - public boolean wasRemote(int qName) { - return myPreviouslyRemoteClasses.contains(qName); - } - - private class DeclaringClassFinder implements ClassInfoProcessor { - private final int myMemberName; - private final int myMemberDescriptor; - private int myDeclaringClass = Cache.UNKNOWN; - private final boolean myIsField; - - private DeclaringClassFinder(MemberInfo memberInfo) { - myMemberName = memberInfo.getName(); - myMemberDescriptor = memberInfo.getDescriptor(); - myIsField = memberInfo instanceof FieldInfo; - } - - public int getDeclaringClassName() { - return myDeclaringClass; - } - - public boolean process(int classQName) throws CacheCorruptedException { - final Cache cache = getCache(); - if (myIsField) { - final FieldInfo fieldId = cache.findField(classQName, myMemberName, myMemberDescriptor); - if (fieldId != null) { - myDeclaringClass = classQName; - return false; - } - } - else { - final MethodInfo methodId = cache.findMethod(classQName, myMemberName, myMemberDescriptor); - if (methodId != null) { - myDeclaringClass = classQName; - return false; - } - } - return true; - } - } -} +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * created at Jan 7, 2002 + * @author Jeka + */ +package com.intellij.compiler.make; + +import com.intellij.compiler.SymbolTable; +import com.intellij.compiler.classParsing.*; +import com.intellij.compiler.impl.CompilerUtil; +import com.intellij.compiler.impl.javaCompiler.DependencyProcessor; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.compiler.CompileContext; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.ArrayUtil; +import com.intellij.util.cls.ClsFormatException; +import com.intellij.util.cls.ClsUtil; +import gnu.trove.TIntHashSet; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.io.IOException; +import java.rmi.Remote; +import java.util.*; + +public class DependencyCache { + private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.DependencyCache"); + + private Cache myCache; + private Cache myNewClassesCache; + + private static final String REMOTE_INTERFACE_NAME = Remote.class.getName(); + private TIntHashSet myToUpdate = new TIntHashSet(); // qName strings to be updated. + private final TIntHashSet myTraverseRoots = new TIntHashSet(); // Dependencies are calculated from these clasess + private final TIntHashSet myClassesWithSourceRemoved = new TIntHashSet(); + private final TIntHashSet myPreviouslyRemoteClasses = new TIntHashSet(); // classes that were Remote, but became non-Remote for some reason + private final TIntHashSet myMarkedInfos = new TIntHashSet(); // classes to be recompiled + private final Set myMarkedFiles = new HashSet(); + + private DependencyCacheNavigator myCacheNavigator; + private SymbolTable mySymbolTable; + private final String mySymbolTableFilePath; + private final String myStoreDirectoryPath; + @NonNls private static final String SYMBOLTABLE_FILE_NAME = "symboltable.dat"; + + public DependencyCache(@NonNls String storeDirectoryPath) { + myStoreDirectoryPath = storeDirectoryPath; + LOG.assertTrue(myStoreDirectoryPath != null); + + mySymbolTableFilePath = myStoreDirectoryPath + "/" + SYMBOLTABLE_FILE_NAME; + } + + public DependencyCacheNavigator getCacheNavigator() throws CacheCorruptedException { + if (myCacheNavigator == null) { + myCacheNavigator = new DependencyCacheNavigator(getCache()); + } + return myCacheNavigator; + } + + public void wipe() throws CacheCorruptedException { + getCache().wipe(); + getNewClassesCache().wipe(); + } + + public Cache getCache() throws CacheCorruptedException { + try { + if (myCache == null) { + // base number of cached record views of each type + myCache = new Cache(myStoreDirectoryPath, 512); + } + + return myCache; + } + catch (IOException e) { + throw new CacheCorruptedException(e); + } + } + + public Cache getNewClassesCache() throws CacheCorruptedException { + try { + if (myNewClassesCache == null) { + myNewClassesCache = new Cache(myStoreDirectoryPath + "/tmp", 16); + } + return myNewClassesCache; + } + catch (IOException e) { + throw new CacheCorruptedException(e); + } + } + + public void addTraverseRoot(int qName) { + myTraverseRoots.add(qName); + } + + public void clearTraverseRoots() { + myTraverseRoots.clear(); + } + + public void markSourceRemoved(int qName) { + myClassesWithSourceRemoved.add(qName); + } + + public void addClassToUpdate(int qName) { + myToUpdate.add(qName); + } + + public int reparseClassFile(@NotNull File file, final byte[] fileContent) throws ClsFormatException, CacheCorruptedException { + SymbolTable symbolTable = getSymbolTable(); + + final int qName = getNewClassesCache().importClassInfo(new ClassFileReader(file, symbolTable, fileContent), symbolTable); + addClassToUpdate(qName); + addTraverseRoot(qName); + return qName; + } + + // for profiling purposes + /* + private static void pause() { + System.out.println("PAUSED. ENTER A CHAR."); + byte[] buf = new byte[1]; + try { + System.in.read(buf); + } + catch (IOException e) { + e.printStackTrace(); + } + } + */ + + public void update(ProgressIndicator indicator) throws CacheCorruptedException { + if (myToUpdate.isEmpty()) { + return; // optimization + } + + final long updateStart = System.currentTimeMillis(); + //pause(); + + final int[] namesToUpdate = myToUpdate.toArray(); + final Cache cache = getCache(); + final Cache newCache = getNewClassesCache(); + final DependencyCacheNavigator navigator = getCacheNavigator(); + + int i = 0; + // remove unnecesary dependencies + for (final int qName : namesToUpdate) { + indicator.setFraction(i++*1.0/namesToUpdate.length/4); + // process use-dependencies + for (int referencedClassQName : cache.getReferencedClasses(qName)) { + if (!cache.containsClass(referencedClassQName)) { + continue; + } + cache.removeClassReferencer(referencedClassQName, qName); + } + cache.clearReferencedClasses(qName); + // process inheritance dependencies + navigator.walkSuperClasses(qName, new ClassInfoProcessor() { + public boolean process(int classQName) throws CacheCorruptedException { + cache.removeSubclass(classQName, qName); + return true; + } + }); + } + + // do update of classInfos + for (final int qName : namesToUpdate) { + indicator.setFraction(i++*1.0/namesToUpdate.length/4); + cache.importClassInfo(newCache, qName); + } + + // build forward-dependencies for the new infos, all new class infos must be already in the main cache! + + final SymbolTable symbolTable = getSymbolTable(); + + for (final int qName : namesToUpdate) { + indicator.setFraction(i++*1.0/namesToUpdate.length/4); + if (!newCache.containsClass(qName)) { + continue; + } + buildForwardDependencies(qName, newCache.getReferences(qName)); + boolean isRemote = false; + // "remote objects" are classes that _directly_ implement remote interfaces + final int[] superInterfaces = cache.getSuperInterfaces(qName); + if (superInterfaces.length > 0) { + final int remoteInterfaceName = symbolTable.getId(REMOTE_INTERFACE_NAME); + for (int superInterface : superInterfaces) { + if (isRemoteInterface(cache, superInterface, remoteInterfaceName)) { + isRemote = true; + break; + } + } + } + final boolean wasRemote = cache.isRemote(qName); + if (wasRemote && !isRemote) { + synchronized (myPreviouslyRemoteClasses) { + myPreviouslyRemoteClasses.add(qName); + } + } + cache.setRemote(qName, isRemote); + } + + // building subclass dependencies + for (final int qName : namesToUpdate) { + indicator.setFraction(i++*1.0/namesToUpdate.length/4); + final int classId = qName; + buildSubclassDependencies(getCache(), qName, classId); + } + + for (final int qName : myClassesWithSourceRemoved.toArray()) { + cache.removeClass(qName); + } + myToUpdate = new TIntHashSet(); + + CompilerUtil.logDuration("Dependency cache update", System.currentTimeMillis() - updateStart); + //pause(); + } + + private void buildForwardDependencies(final int classQName, final Collection references) throws CacheCorruptedException { + final Cache cache = getCache(); + + final int genericSignature = cache.getGenericSignature(classQName); + if (genericSignature != -1) { + final String genericClassSignature = resolve(genericSignature); + final int[] bounds = findBounds(genericClassSignature); + for (int boundClassQName : bounds) { + cache.addClassReferencer(boundClassQName, classQName); + } + } + + buildAnnotationDependencies(classQName, cache.getRuntimeVisibleAnnotations(classQName)); + buildAnnotationDependencies(classQName, cache.getRuntimeInvisibleAnnotations(classQName)); + + for (final ReferenceInfo refInfo : references) { + final int declaringClassName = getActualDeclaringClassForReference(refInfo); + if (declaringClassName == Cache.UNKNOWN) { + continue; + } + if (refInfo instanceof MemberReferenceInfo) { + final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo(); + if (memberInfo instanceof FieldInfo) { + cache.addFieldReferencer(declaringClassName, memberInfo.getName(), classQName); + } + else if (memberInfo instanceof MethodInfo) { + cache.addMethodReferencer(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor(), classQName); + } + else { + LOG.error("Unknown member info class: " + memberInfo.getClass().getName()); + } + } + else { // reference to class + cache.addClassReferencer(declaringClassName, classQName); + } + } + final SymbolTable symbolTable = getSymbolTable(); + + for (final FieldInfo fieldInfo : cache.getFields(classQName)) { + buildAnnotationDependencies(classQName, fieldInfo.getRuntimeVisibleAnnotations()); + buildAnnotationDependencies(classQName, fieldInfo.getRuntimeInvisibleAnnotations()); + + String className = MakeUtil.parseObjectType(symbolTable.getSymbol(fieldInfo.getDescriptor()), 0); + if (className == null) { + continue; + } + final int cls = symbolTable.getId(className); + cache.addClassReferencer(cls, classQName); + } + + for (final MethodInfo methodInfo : cache.getMethods(classQName)) { + buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleAnnotations()); + buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleAnnotations()); + buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleParameterAnnotations()); + buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleParameterAnnotations()); + + if (methodInfo.isConstructor()) { + continue; + } + + final String returnTypeClassName = MakeUtil.parseObjectType(methodInfo.getReturnTypeDescriptor(symbolTable), 0); + if (returnTypeClassName != null) { + final int returnTypeClassQName = symbolTable.getId(returnTypeClassName); + cache.addClassReferencer(returnTypeClassQName, classQName); + } + + String[] parameterSignatures = CacheUtils.getParameterSignatures(methodInfo, symbolTable); + for (String parameterSignature : parameterSignatures) { + String paramClassName = MakeUtil.parseObjectType(parameterSignature, 0); + if (paramClassName != null) { + final int paramClassId = symbolTable.getId(paramClassName); + cache.addClassReferencer(paramClassId, classQName); + } + } + } + } + + private static boolean isRemoteInterface(Cache cache, int ifaceName, final int remoteInterfaceName) throws CacheCorruptedException { + if (ifaceName == remoteInterfaceName) { + return true; + } + for (int superInterfaceName : cache.getSuperInterfaces(ifaceName)) { + if (isRemoteInterface(cache, superInterfaceName, remoteInterfaceName)) { + return true; + } + } + return false; + } + + + private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[][] annotations) throws CacheCorruptedException { + if (annotations == null || annotations.length == 0) { + return; + } + for (AnnotationConstantValue[] annotation : annotations) { + buildAnnotationDependencies(classQName, annotation); + } + } + + private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[] annotations) throws CacheCorruptedException { + if (annotations == null || annotations.length == 0) { + return; + } + final Cache cache = getCache(); + for (AnnotationConstantValue annotation : annotations) { + final int annotationQName = annotation.getAnnotationQName(); + + cache.addClassReferencer(annotationQName, classQName); + + final AnnotationNameValuePair[] memberValues = annotation.getMemberValues(); + for (final AnnotationNameValuePair nameValuePair : memberValues) { + for (MethodInfo annotationMember : cache.findMethodsByName(annotationQName, nameValuePair.getName())) { + cache.addMethodReferencer(annotationQName, annotationMember.getName(), annotationMember.getDescriptor(), classQName); + } + } + } + } + + private int[] findBounds(final String genericClassSignature) throws CacheCorruptedException{ + try { + final String[] boundInterfaces = BoundsParser.getBounds(genericClassSignature); + int[] ids = ArrayUtil.newIntArray(boundInterfaces.length); + for (int i = 0; i < boundInterfaces.length; i++) { + ids[i] = getSymbolTable().getId(boundInterfaces[i]); + } + return ids; + } + catch (SignatureParsingException e) { + return ArrayUtil.EMPTY_INT_ARRAY; + } + } + + // fixes JDK 1.4 javac bug that generates references in the constant pool + // to the subclass even if the field was declared in a superclass + private int getActualDeclaringClassForReference(final ReferenceInfo refInfo) throws CacheCorruptedException { + if (!(refInfo instanceof MemberReferenceInfo)) { + return refInfo.getClassName(); + } + final int declaringClassName = refInfo.getClassName(); + final Cache cache = getCache(); + final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo(); + if (memberInfo instanceof FieldInfo) { + if (cache.findFieldByName(declaringClassName, memberInfo.getName()) != null) { + return declaringClassName; + } + } + else if (memberInfo instanceof MethodInfo) { + if (cache.findMethod(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor()) != null) { + return declaringClassName; + } + } + final DeclaringClassFinder finder = new DeclaringClassFinder(memberInfo); + getCacheNavigator().walkSuperClasses(declaringClassName, finder); + return finder.getDeclaringClassName(); + } + + /** + * @return qualified names of the classes that should be additionally recompiled + */ + public Pair> findDependentClasses(CompileContext context, Project project, Set successfullyCompiled, @Nullable final DependencyProcessor additionalProcessor) + throws CacheCorruptedException { + + markDependencies(context, project, successfullyCompiled, additionalProcessor); + return new Pair>(myMarkedInfos.toArray(), Collections.unmodifiableSet(myMarkedFiles)); + } + + private void markDependencies(CompileContext context, Project project, final Set successfullyCompiled, + @Nullable final DependencyProcessor additionalProcessor) throws CacheCorruptedException { + try { + if (LOG.isDebugEnabled()) { + LOG.debug("====================Marking dependent files====================="); + } + // myToUpdate can be modified during the mark procedure, so use toArray() to iterate it + int[] qNamesToUpdate = myTraverseRoots.toArray(); + final SourceFileFinder sourceFileFinder = new SourceFileFinder(project, context); + final CachingSearcher searcher = new CachingSearcher(project); + final ChangedRetentionPolicyDependencyProcessor changedRetentionPolicyDependencyProcessor = new ChangedRetentionPolicyDependencyProcessor(project, searcher, this); + for (final int qName : qNamesToUpdate) { + if (!getCache().containsClass(qName)) { + continue; + } + if (getNewClassesCache().containsClass(qName)) { // there is a new class file created + new JavaDependencyProcessor(project, this, qName).run(); + ArrayList changed = + new ArrayList(); + ArrayList removed = + new ArrayList(); + findModifiedConstants(qName, changed, removed); + if (!changed.isEmpty() || !removed.isEmpty()) { + new ChangedConstantsDependencyProcessor( + project, searcher, this, qName, + changed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[changed.size()]), + removed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[removed.size()]) + ).run(); + } + changedRetentionPolicyDependencyProcessor.checkAnnotationRetentionPolicyChanges(qName); + if (additionalProcessor != null) { + additionalProcessor.processDependencies(context, qName); + } + } + else { + boolean isSourceDeleted = false; + if (myClassesWithSourceRemoved.contains(qName)) { // no recompiled class file, check whether the classfile exists + isSourceDeleted = true; + } + else if (!new File(getCache().getPath(qName)).exists()) { + final String qualifiedName = resolve(qName); + final String sourceFileName = getCache().getSourceFileName(qName); + final boolean markAsRemovedSource = ApplicationManager.getApplication().runReadAction(new Computable() { + public Boolean compute() { + VirtualFile sourceFile = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName); + return sourceFile == null || successfullyCompiled.contains(sourceFile) ? Boolean.TRUE : Boolean.FALSE; + } + }).booleanValue(); + if (markAsRemovedSource) { + // for Inner classes: sourceFile may exist, but the inner class declaration inside it may not, + // thus the source for the class info should be considered removed + isSourceDeleted = true; + markSourceRemoved(qName); + myMarkedInfos.remove(qName); // if the info has been marked already, the mark should be removed + } + } + if (isSourceDeleted) { + Dependency[] backDependencies = getCache().getBackDependencies(qName); + for (Dependency backDependency : backDependencies) { + if (markTargetClassInfo(backDependency)) { + if (LOG.isDebugEnabled()) { + LOG.debug( + "Mark dependent class " + backDependency.getClassQualifiedName() + "; reason: no class file found for " + qName); + } + } + } + } + } + } + if (LOG.isDebugEnabled()) { + LOG.debug("================================================================"); + } + } + catch (ProcessCanceledException ignored) { + // deliberately suppressed + } + } + + private void findModifiedConstants( + final int qName, + Collection changedConstants, + Collection removedConstants) throws CacheCorruptedException { + + final Cache cache = getCache(); + for (final FieldInfo field : cache.getFields(qName)) { + final int oldFlags = field.getFlags(); + if (ClsUtil.isStatic(oldFlags) && ClsUtil.isFinal(oldFlags)) { + final Cache newClassesCache = getNewClassesCache(); + FieldInfo newField = newClassesCache.findFieldByName(qName, field.getName()); + if (newField == null) { + if (!ConstantValue.EMPTY_CONSTANT_VALUE.equals(field.getConstantValue())) { + // if the field was really compile time constant + removedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field)); + } + } + else { + final boolean visibilityRestricted = MakeUtil.isMoreAccessible(oldFlags, newField.getFlags()); + if (!field.getConstantValue().equals(newField.getConstantValue()) || visibilityRestricted) { + changedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field, visibilityRestricted)); + } + } + } + } + } + + private static void buildSubclassDependencies(Cache cache, final int qName, int targetClassId) throws CacheCorruptedException { + final int superQName = cache.getSuperQualifiedName(targetClassId); + if (superQName != Cache.UNKNOWN) { + cache.addSubclass(superQName, qName); + buildSubclassDependencies(cache, qName, superQName); + } + + int[] interfaces = cache.getSuperInterfaces(targetClassId); + for (final int interfaceName : interfaces) { + cache.addSubclass(interfaceName, qName); + buildSubclassDependencies(cache, qName, interfaceName); + } + } + + + /** + * Marks ClassInfo targeted by the dependency + * @return true if really added, false otherwise + */ + public boolean markTargetClassInfo(Dependency dependency) throws CacheCorruptedException { + return markClassInfo(dependency.getClassQualifiedName(), false); + } + + /** + * Marks ClassInfo that corresponds to the specified qualified name + * If class info is already recompiled, it is not marked + * @return true if really added, false otherwise + */ + public boolean markClass(int qualifiedName) throws CacheCorruptedException { + return markClass(qualifiedName, false); + } + + /** + * Marks ClassInfo that corresponds to the specified qualified name + * If class info is already recompiled, it is not marked unless force parameter is true + * @return true if really added, false otherwise + */ + public boolean markClass(int qualifiedName, boolean force) throws CacheCorruptedException { + return markClassInfo(qualifiedName, force); + } + + public boolean isTargetClassInfoMarked(Dependency dependency) { + return isClassInfoMarked(dependency.getClassQualifiedName()); + } + + public boolean isClassInfoMarked(int qName) { + return myMarkedInfos.contains(qName); + } + + public void markFile(VirtualFile file) { + myMarkedFiles.add(file); + } + + /** + * @return true if really marked, false otherwise + */ + private boolean markClassInfo(int qName, boolean force) throws CacheCorruptedException { + if (!getCache().containsClass(qName)) { + return false; + } + if (myClassesWithSourceRemoved.contains(qName)) { + return false; // no need to recompile since source has been removed + } + if (!force) { + if (getNewClassesCache().containsClass(qName)) { // already recompiled + return false; + } + } + return myMarkedInfos.add(qName); + } + + public void resetState() { + final long start = System.currentTimeMillis(); + + try { + myClassesWithSourceRemoved.clear(); + myMarkedFiles.clear(); + myMarkedInfos.clear(); + myToUpdate.clear(); + myTraverseRoots.clear(); + if (myNewClassesCache != null) { + myNewClassesCache.wipe(); + myNewClassesCache = null; + } + myCacheNavigator = null; + try { + if (myCache != null) { + myCache.dispose(); + myCache = null; + } + } + catch (CacheCorruptedException e) { + LOG.info(e); + } + try { + if (mySymbolTable != null) { + mySymbolTable.dispose(); + mySymbolTable = null; + } + } + catch (CacheCorruptedException e) { + LOG.info(e); + } + } + finally { + CompilerUtil.logDuration("Dependency cache disposal", System.currentTimeMillis() - start); + } + } + + + public SymbolTable getSymbolTable() throws CacheCorruptedException { + if (mySymbolTable == null) { + mySymbolTable = new SymbolTable(new File(mySymbolTableFilePath)); + } + return mySymbolTable; + } + + public String resolve(int id) throws CacheCorruptedException { + return getSymbolTable().getSymbol(id); + } + + public boolean wasRemote(int qName) { + return myPreviouslyRemoteClasses.contains(qName); + } + + private class DeclaringClassFinder implements ClassInfoProcessor { + private final int myMemberName; + private final int myMemberDescriptor; + private int myDeclaringClass = Cache.UNKNOWN; + private final boolean myIsField; + + private DeclaringClassFinder(MemberInfo memberInfo) { + myMemberName = memberInfo.getName(); + myMemberDescriptor = memberInfo.getDescriptor(); + myIsField = memberInfo instanceof FieldInfo; + } + + public int getDeclaringClassName() { + return myDeclaringClass; + } + + public boolean process(int classQName) throws CacheCorruptedException { + final Cache cache = getCache(); + if (myIsField) { + final FieldInfo fieldId = cache.findField(classQName, myMemberName, myMemberDescriptor); + if (fieldId != null) { + myDeclaringClass = classQName; + return false; + } + } + else { + final MethodInfo methodId = cache.findMethod(classQName, myMemberName, myMemberDescriptor); + if (methodId != null) { + myDeclaringClass = classQName; + return false; + } + } + return true; + } + } +} -- 2.11.4.GIT