blob: a90138146a4d95650490745b13e6468cdf8cf2dc [file] [log] [blame]
/*
* 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 21, 2003
* Time: 4:19:03 PM
*/
package com.intellij.compiler.impl;
import com.intellij.compiler.CompilerConfiguration;
import com.intellij.compiler.CompilerMessageImpl;
import com.intellij.compiler.CompilerWorkspaceConfiguration;
import com.intellij.compiler.make.DependencyCache;
import com.intellij.compiler.progress.CompilerTask;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.compiler.Compiler;
import com.intellij.openapi.compiler.ex.CompileContextEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.CompilerModuleExtension;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UserDataHolderBase;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.pom.Navigatable;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.OrderedSet;
import com.intellij.util.indexing.FileBasedIndex;
import gnu.trove.TIntHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
public class CompileContextImpl extends UserDataHolderBase implements CompileContextEx {
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileContextImpl");
private final Project myProject;
private final CompilerTask myTask;
private final Map<CompilerMessageCategory, Collection<CompilerMessage>> myMessages = new EnumMap<CompilerMessageCategory, Collection<CompilerMessage>>(CompilerMessageCategory.class);
private final boolean myShouldUpdateProblemsView;
private CompileScope myCompileScope;
private final DependencyCache myDependencyCache;
private final boolean myMake;
private final boolean myIsRebuild;
private final boolean myIsAnnotationProcessorsEnabled;
private boolean myRebuildRequested = false;
private String myRebuildReason;
private final Map<VirtualFile, Module> myRootToModuleMap = new HashMap<VirtualFile, Module>();
private final Map<Module, Set<VirtualFile>> myModuleToRootsMap = new HashMap<Module, Set<VirtualFile>>();
private final Map<VirtualFile, Pair<SourceGeneratingCompiler, Module>> myOutputRootToSourceGeneratorMap = new HashMap<VirtualFile, Pair<SourceGeneratingCompiler, Module>>();
private final Set<VirtualFile> myGeneratedTestRoots = new java.util.HashSet<VirtualFile>();
private VirtualFile[] myOutputDirectories;
private Set<VirtualFile> myTestOutputDirectories;
private final TIntHashSet myGeneratedSources = new TIntHashSet();
private final ProjectFileIndex myProjectFileIndex; // cached for performance reasons
private final ProjectCompileScope myProjectCompileScope;
private final long myStartCompilationStamp;
private final UUID mySessionId = UUID.randomUUID();
public CompileContextImpl(final Project project,
final CompilerTask compilerSession,
CompileScope compileScope,
DependencyCache dependencyCache, boolean isMake, boolean isRebuild) {
myProject = project;
myTask = compilerSession;
myCompileScope = compileScope;
myDependencyCache = dependencyCache;
myMake = isMake;
myIsRebuild = isRebuild;
myStartCompilationStamp = System.currentTimeMillis();
myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
myProjectCompileScope = new ProjectCompileScope(myProject);
myIsAnnotationProcessorsEnabled = CompilerConfiguration.getInstance(project).isAnnotationProcessorsEnabled();
if (compilerSession != null) {
compilerSession.setContentIdKey(compileScope.getUserData(CompilerManager.CONTENT_ID_KEY));
}
recalculateOutputDirs();
final CompilerWorkspaceConfiguration workspaceConfig = CompilerWorkspaceConfiguration.getInstance(myProject);
myShouldUpdateProblemsView = workspaceConfig.useOutOfProcessBuild() && workspaceConfig.MAKE_PROJECT_ON_SAVE;
}
public boolean shouldUpdateProblemsView() {
return myShouldUpdateProblemsView;
}
public void recalculateOutputDirs() {
final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
final Set<VirtualFile> allDirs = new OrderedSet<VirtualFile>();
final Set<VirtualFile> testOutputDirs = new java.util.HashSet<VirtualFile>();
final Set<VirtualFile> productionOutputDirs = new java.util.HashSet<VirtualFile>();
for (Module module : allModules) {
final CompilerModuleExtension manager = CompilerModuleExtension.getInstance(module);
final VirtualFile output = manager.getCompilerOutputPath();
if (output != null && output.isValid()) {
allDirs.add(output);
productionOutputDirs.add(output);
}
final VirtualFile testsOutput = manager.getCompilerOutputPathForTests();
if (testsOutput != null && testsOutput.isValid()) {
allDirs.add(testsOutput);
testOutputDirs.add(testsOutput);
}
}
myOutputDirectories = VfsUtil.toVirtualFileArray(allDirs);
// need this to ensure that the sent contains only _dedicated_ test output dirs
// Directories that are configured for both test and production classes must not be added in the resulting set
testOutputDirs.removeAll(productionOutputDirs);
myTestOutputDirectories = Collections.unmodifiableSet(testOutputDirs);
}
public void markGenerated(Collection<VirtualFile> files) {
for (final VirtualFile file : files) {
myGeneratedSources.add(FileBasedIndex.getFileId(file));
}
}
public long getStartCompilationStamp() {
return myStartCompilationStamp;
}
public boolean isGenerated(VirtualFile file) {
if (myGeneratedSources.contains(FileBasedIndex.getFileId(file))) {
return true;
}
if (VfsUtilCore.isUnder(file, myRootToModuleMap.keySet())) {
return true;
}
final Module module = getModuleByFile(file);
if (module != null) {
final String procGenRoot = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
if (procGenRoot != null && VfsUtil.isAncestor(new File(procGenRoot), new File(file.getPath()), true)) {
return true;
}
}
return false;
}
/*
private JBZipFile lookupZip(String outputDir) {
synchronized (myOpenZipFiles) {
JBZipFile zip = myOpenZipFiles.get(outputDir);
if (zip == null) {
final File zipFile = CompilerPathsEx.getZippedOutputPath(myProject, outputDir);
try {
try {
zip = new JBZipFile(zipFile);
}
catch (FileNotFoundException e) {
try {
zipFile.createNewFile();
zip = new JBZipFile(zipFile);
}
catch (IOException e1) {
zipFile.getParentFile().mkdirs();
zipFile.createNewFile();
zip = new JBZipFile(zipFile);
}
}
myOpenZipFiles.put(outputDir, zip);
}
catch (IOException e) {
LOG.info(e);
addMessage(CompilerMessageCategory.ERROR, "Cannot create zip file " + zipFile.getPath() + ": " + e.getMessage(), null, -1, -1);
}
}
return zip;
}
}
*/
public Project getProject() {
return myProject;
}
public DependencyCache getDependencyCache() {
return myDependencyCache;
}
public CompilerMessage[] getMessages(CompilerMessageCategory category) {
Collection<CompilerMessage> collection = myMessages.get(category);
if (collection == null) {
return CompilerMessage.EMPTY_ARRAY;
}
return collection.toArray(new CompilerMessage[collection.size()]);
}
public void addMessage(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) {
CompilerMessageImpl msg = new CompilerMessageImpl(myProject, category, message, findPresentableFileForMessage(url), lineNum, columnNum, null);
addMessage(msg);
}
public void addMessage(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum,
Navigatable navigatable) {
CompilerMessageImpl msg = new CompilerMessageImpl(myProject, category, message, findPresentableFileForMessage(url), lineNum, columnNum, navigatable);
addMessage(msg);
}
@Nullable
private VirtualFile findPresentableFileForMessage(@Nullable final String url) {
final VirtualFile file = findFileByUrl(url);
if (file == null) {
return null;
}
return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
@Override
public VirtualFile compute() {
if (file.isValid()) {
for (final Map.Entry<VirtualFile, Pair<SourceGeneratingCompiler, Module>> entry : myOutputRootToSourceGeneratorMap.entrySet()) {
final VirtualFile root = entry.getKey();
if (VfsUtilCore.isAncestor(root, file, false)) {
final Pair<SourceGeneratingCompiler, Module> pair = entry.getValue();
final VirtualFile presentableFile = pair.getFirst().getPresentableFile(CompileContextImpl.this, pair.getSecond(), root, file);
return presentableFile != null ? presentableFile : file;
}
}
}
return file;
}
});
}
@Nullable
private static VirtualFile findFileByUrl(@Nullable String url) {
if (url == null) {
return null;
}
VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
if (file == null) {
// groovy stubs may be placed in completely random directories which aren't refreshed automatically
return VirtualFileManager.getInstance().refreshAndFindFileByUrl(url);
}
return file;
}
public void addMessage(CompilerMessage msg) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
LOG.info("addMessage: " + msg + " this=" + this);
}
Collection<CompilerMessage> messages = myMessages.get(msg.getCategory());
if (messages == null) {
messages = new LinkedHashSet<CompilerMessage>();
myMessages.put(msg.getCategory(), messages);
}
if (messages.add(msg)) {
myTask.addMessage(msg);
}
if (myShouldUpdateProblemsView && msg.getCategory() == CompilerMessageCategory.ERROR) {
ProblemsViewImpl.SERVICE.getInstance(myProject).addMessage(msg, mySessionId);
}
}
public int getMessageCount(CompilerMessageCategory category) {
if (category != null) {
Collection<CompilerMessage> collection = myMessages.get(category);
return collection != null ? collection.size() : 0;
}
int count = 0;
for (Collection<CompilerMessage> collection : myMessages.values()) {
if (collection != null) {
count += collection.size();
}
}
return count;
}
public CompileScope getCompileScope() {
return myCompileScope;
}
public CompileScope getProjectCompileScope() {
return myProjectCompileScope;
}
public void requestRebuildNextTime(String message) {
if (!myRebuildRequested) {
myRebuildRequested = true;
myRebuildReason = message;
final boolean isOutOfProcessBuild = myDependencyCache == null;
if (!isOutOfProcessBuild) {
addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
}
}
}
public boolean isRebuildRequested() {
return myRebuildRequested;
}
@Nullable
public String getRebuildReason() {
return myRebuildReason;
}
public ProgressIndicator getProgressIndicator() {
//if (myProgressIndicatorProxy != null) {
// return myProgressIndicatorProxy;
//}
return myTask.getIndicator();
}
public void assignModule(@NotNull VirtualFile root, @NotNull Module module, final boolean isTestSource, @Nullable Compiler compiler) {
try {
myRootToModuleMap.put(root, module);
Set<VirtualFile> set = myModuleToRootsMap.get(module);
if (set == null) {
set = new HashSet<VirtualFile>();
myModuleToRootsMap.put(module, set);
}
set.add(root);
if (isTestSource) {
myGeneratedTestRoots.add(root);
}
if (compiler instanceof SourceGeneratingCompiler) {
myOutputRootToSourceGeneratorMap.put(root, new Pair<SourceGeneratingCompiler, Module>((SourceGeneratingCompiler)compiler, module));
}
}
finally {
myModuleToRootsCache.remove(module);
}
}
@Nullable
public VirtualFile getSourceFileByOutputFile(VirtualFile outputFile) {
return TranslatingCompilerFilesMonitor.getSourceFileByOutput(outputFile);
}
public Module getModuleByFile(VirtualFile file) {
final Module module = myProjectFileIndex.getModuleForFile(file);
if (module != null) {
LOG.assertTrue(!module.isDisposed());
return module;
}
for (final VirtualFile root : myRootToModuleMap.keySet()) {
if (VfsUtil.isAncestor(root, file, false)) {
final Module mod = myRootToModuleMap.get(root);
if (mod != null) {
LOG.assertTrue(!mod.isDisposed());
}
return mod;
}
}
return null;
}
private final Map<Module, VirtualFile[]> myModuleToRootsCache = new HashMap<Module, VirtualFile[]>();
public VirtualFile[] getSourceRoots(Module module) {
VirtualFile[] cachedRoots = myModuleToRootsCache.get(module);
if (cachedRoots != null) {
if (areFilesValid(cachedRoots)) {
return cachedRoots;
}
else {
myModuleToRootsCache.remove(module); // clear cache for this module and rebuild list of roots
}
}
Set<VirtualFile> additionalRoots = myModuleToRootsMap.get(module);
VirtualFile[] moduleRoots = ModuleRootManager.getInstance(module).getSourceRoots();
if (additionalRoots == null || additionalRoots.isEmpty()) {
myModuleToRootsCache.put(module, moduleRoots);
return moduleRoots;
}
final VirtualFile[] allRoots = new VirtualFile[additionalRoots.size() + moduleRoots.length];
System.arraycopy(moduleRoots, 0, allRoots, 0, moduleRoots.length);
int index = moduleRoots.length;
for (final VirtualFile additionalRoot : additionalRoots) {
allRoots[index++] = additionalRoot;
}
myModuleToRootsCache.put(module, allRoots);
return allRoots;
}
private static boolean areFilesValid(VirtualFile[] files) {
for (VirtualFile file : files) {
if (!file.isValid()) {
return false;
}
}
return true;
}
public VirtualFile[] getAllOutputDirectories() {
return myOutputDirectories;
}
@NotNull
public Set<VirtualFile> getTestOutputDirectories() {
return myTestOutputDirectories;
}
public VirtualFile getModuleOutputDirectory(Module module) {
return CompilerPaths.getModuleOutputDirectory(module, false);
}
public VirtualFile getModuleOutputDirectoryForTests(Module module) {
return CompilerPaths.getModuleOutputDirectory(module, true);
}
public boolean isMake() {
return myMake;
}
public boolean isRebuild() {
return myIsRebuild;
}
public boolean isAnnotationProcessorsEnabled() {
return myIsAnnotationProcessorsEnabled;
}
public void addScope(final CompileScope additionalScope) {
myCompileScope = new CompositeScope(myCompileScope, additionalScope);
}
public boolean isInTestSourceContent(@NotNull final VirtualFile fileOrDir) {
if (myProjectFileIndex.isInTestSourceContent(fileOrDir)) {
return true;
}
if (VfsUtilCore.isUnder(fileOrDir, myGeneratedTestRoots)) {
return true;
}
return false;
}
public boolean isInSourceContent(@NotNull final VirtualFile fileOrDir) {
if (myProjectFileIndex.isInSourceContent(fileOrDir)) {
return true;
}
if (VfsUtilCore.isUnder(fileOrDir, myRootToModuleMap.keySet())) {
return true;
}
return false;
}
public UUID getSessionId() {
return mySessionId;
}
}