/*
* 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.CompilerMessageImpl;
import com.intellij.compiler.ProblemsView;
import consulo.compiler.make.impl.CompositeDependencyCache;
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.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 consulo.compiler.impl.AdditionalOutputDirectoriesProvider;
import gnu.trove.TIntHashSet;
import consulo.compiler.ModuleCompilerPathsManager;
import consulo.compiler.server.rmi.CompilerClientConnector;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.compiler.roots.CompilerPathsImpl;
import consulo.roots.ContentFolderScopes;
import consulo.roots.ContentFolderTypeProvider;
import consulo.roots.impl.ProductionContentFolderTypeProvider;
import consulo.roots.impl.TestContentFolderTypeProvider;
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 CompileScope myCompileScope;
private final CompositeDependencyCache myDependencyCache;
private final boolean myMake;
private final boolean myIsRebuild;
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 long myStartCompilationStamp;
private final UUID mySessionId = UUID.randomUUID();
public CompileContextImpl(final Project project,
final CompilerTask compilerSession,
CompileScope compileScope,
CompositeDependencyCache 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();
recalculateOutputDirs();
}
@Override
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) {
ModuleCompilerPathsManager moduleCompilerPathsManager = ModuleCompilerPathsManager.getInstance(module);
final VirtualFile output = moduleCompilerPathsManager.getCompilerOutput(ProductionContentFolderTypeProvider.getInstance());
if (output != null && output.isValid()) {
allDirs.add(output);
productionOutputDirs.add(output);
}
final VirtualFile testsOutput = moduleCompilerPathsManager.getCompilerOutput(TestContentFolderTypeProvider.getInstance());
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);
}
@Override
public void markGenerated(Collection<VirtualFile> files) {
for (final VirtualFile file : files) {
myGeneratedSources.add(FileBasedIndex.getFileId(file));
}
}
@Override
public long getStartCompilationStamp() {
return myStartCompilationStamp;
}
@Override
public boolean isGenerated(VirtualFile file) {
if (myGeneratedSources.contains(FileBasedIndex.getFileId(file))) {
return true;
}
if (isUnderRoots(myRootToModuleMap.keySet(), file)) {
return true;
}
final Module module = getModuleByFile(file);
if (module != null) {
for (AdditionalOutputDirectoriesProvider provider : AdditionalOutputDirectoriesProvider.EP_NAME.getExtensions()) {
for (String path : provider.getOutputDirectories(getProject(), module)) {
if (path != null && VfsUtilCore.isAncestor(new File(path), 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;
}
}
*/
@Override
public Project getProject() {
return myProject;
}
@Override
public CompositeDependencyCache getDependencyCache() {
return myDependencyCache;
}
@Override
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()]);
}
@Override
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);
}
@Override
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;
}
@Override
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 (ApplicationManager.getApplication().isCompilerServerMode()) {
VirtualFile virtualFile = msg.getVirtualFile();
CompilerClientConnector.getInstance(myProject)
.addMessage(msg.getCategory(), msg.getMessage(), virtualFile == null ? null : virtualFile.getPath(), msg.getLine(),
msg.getColumn());
}
else {
ProblemsView.getInstance(myProject).addMessage(msg);
}
}
@Override
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;
}
@Override
public CompileScope getCompileScope() {
return myCompileScope;
}
@Override
public void requestRebuildNextTime(String message) {
if (!myRebuildRequested) {
myRebuildRequested = true;
myRebuildReason = message;
addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
}
}
public boolean isRebuildRequested() {
return myRebuildRequested;
}
public String getRebuildReason() {
return myRebuildReason;
}
@NotNull
@Override
public ProgressIndicator getProgressIndicator() {
//if (myProgressIndicatorProxy != null) {
// return myProgressIndicatorProxy;
//}
return myTask.getIndicator();
}
@Override
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);
}
}
@Override
@Nullable
public VirtualFile getSourceFileByOutputFile(VirtualFile outputFile) {
return TranslatingCompilerFilesMonitorImpl.getSourceFileByOutput(outputFile);
}
@Override
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[]>();
@Override
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).getContentFolderFiles(ContentFolderScopes.productionAndTest());
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;
}
@Override
public VirtualFile[] getAllOutputDirectories() {
return myOutputDirectories;
}
@Override
@NotNull
public Set<VirtualFile> getTestOutputDirectories() {
return myTestOutputDirectories;
}
@Override
public VirtualFile getModuleOutputDirectory(Module module) {
return CompilerPathsImpl.getModuleOutputDirectory(module, false);
}
@Override
public VirtualFile getModuleOutputDirectoryForTests(Module module) {
return CompilerPathsImpl.getModuleOutputDirectory(module, true);
}
@Override
public VirtualFile getOutputForFile(Module module, VirtualFile virtualFile) {
ContentFolderTypeProvider contentFolderTypeForFile = myProjectFileIndex.getContentFolderTypeForFile(virtualFile);
if (contentFolderTypeForFile == null) {
contentFolderTypeForFile = ProductionContentFolderTypeProvider.getInstance();
}
return getOutputForFile(module, contentFolderTypeForFile);
}
@Nullable
@Override
public VirtualFile getOutputForFile(Module module, ContentFolderTypeProvider contentFolderType) {
return ModuleCompilerPathsManager.getInstance(module).getCompilerOutput(contentFolderType);
}
@Override
public boolean isMake() {
return myMake;
}
@Override
public boolean isRebuild() {
return myIsRebuild;
}
@Override
public void addScope(final CompileScope additionalScope) {
myCompileScope = new CompositeScope(myCompileScope, additionalScope);
}
@Override
public boolean isInTestSourceContent(@NotNull final VirtualFile fileOrDir) {
if (myProjectFileIndex.isInTestSourceContent(fileOrDir) || myProjectFileIndex.isInTestResource(fileOrDir)) {
return true;
}
if (isUnderRoots(myGeneratedTestRoots, fileOrDir)) {
return true;
}
return false;
}
@Override
public boolean isInSourceContent(@NotNull final VirtualFile fileOrDir) {
if (myProjectFileIndex.isInSourceContent(fileOrDir) || myProjectFileIndex.isInResource(fileOrDir)) {
return true;
}
if (isUnderRoots(myRootToModuleMap.keySet(), fileOrDir)) {
return true;
}
return false;
}
public static boolean isUnderRoots(@NotNull Set<VirtualFile> roots, @NotNull VirtualFile file) {
VirtualFile parent = file;
while (true) {
if (parent == null) {
return false;
}
if (roots.contains(parent)) {
return true;
}
parent = parent.getParent();
}
}
public UUID getSessionId() {
return mySessionId;
}
}