blob: 26600290af93d44ebf36f9b5037c86a42119e115 [file] [log] [blame]
/*
* Copyright 2000-2011 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.
*/
package com.intellij.openapi.roots.ui.configuration.dependencyAnalysis;
import com.intellij.icons.AllIcons;
import com.intellij.ide.presentation.VirtualFilePresentation;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.ui.CellAppearanceEx;
import com.intellij.openapi.roots.ui.OrderEntryAppearanceService;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.PathUtil;
import com.intellij.util.Processor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
/**
* Analyzer for module classpath. It uses order enumerator to get classpath details.
*/
public class ModuleDependenciesAnalyzer {
/**
* The current module
*/
private final Module myModule;
/**
* If true production classpath is analyzed. If false, the test classpath.
*/
private final boolean myProduction;
/**
* If true the compilation classpath is analyzed. If false, the test classpath.
*/
private final boolean myCompile;
/**
* If true, SDK classes are included in the classpath.
*/
private final boolean mySdk;
/**
* The order entry explanations
*/
private final List<OrderEntryExplanation> myOrderEntries = new ArrayList<OrderEntryExplanation>();
/**
* The url explanations
*/
private final List<UrlExplanation> myUrls = new ArrayList<UrlExplanation>();
/**
* The constructor (it creates explanations immediately
*
* @param module the context module
* @param production the production/test flag
* @param compile the compile/runtime flag
* @param sdk the include sdk paths
*/
public ModuleDependenciesAnalyzer(Module module, boolean production, boolean compile, boolean sdk) {
myModule = module;
myProduction = production;
myCompile = compile;
mySdk = sdk;
analyze();
}
/**
* @return url explanations
*/
public List<UrlExplanation> getUrls() {
return Collections.unmodifiableList(myUrls);
}
/**
* @return order entry explanations
*/
public List<OrderEntryExplanation> getOrderEntries() {
return Collections.unmodifiableList(myOrderEntries);
}
/**
* Analyze module classpath
*/
private void analyze() {
OrderEnumerator e = ModuleRootManager.getInstance(myModule).orderEntries();
e.recursively();
if (!mySdk) {
e.withoutSdk();
}
if (myCompile) {
e.compileOnly();
}
else {
e.runtimeOnly();
}
if (myProduction) {
e.productionOnly();
}
final Map<String, List<OrderPath>> urlExplanations = new LinkedHashMap<String, List<OrderPath>>();
final OrderRootsEnumerator classes = e.classes();
if (myCompile) {
classes.withoutSelfModuleOutput();
}
for (String url : classes.getUrls()) {
if (!urlExplanations.containsKey(url)) {
urlExplanations.put(url, new ArrayList<OrderPath>());
}
}
final Map<OrderEntry, List<OrderPath>> orderExplanations = new LinkedHashMap<OrderEntry, List<OrderPath>>();
new PathWalker(urlExplanations, orderExplanations).examine(myModule, 0);
for (Map.Entry<OrderEntry, List<OrderPath>> entry : orderExplanations.entrySet()) {
myOrderEntries.add(new OrderEntryExplanation(entry.getKey(), entry.getValue()));
}
for (Map.Entry<String, List<OrderPath>> entry : urlExplanations.entrySet()) {
myUrls.add(new UrlExplanation(entry.getKey(), entry.getValue()));
}
}
/**
* The walker for the class paths. It walks the entire module classpath
*/
private class PathWalker {
/**
* The explanations for urls
*/
private final Map<String, List<OrderPath>> myUrlExplanations;
/**
* The explanations for order entries
*/
private final Map<OrderEntry, List<OrderPath>> myOrderExplanations;
/**
* The current stack
*/
private final ArrayList<OrderPathElement> myStack = new ArrayList<OrderPathElement>();
/**
* Visited modules (in order to detect cyclic dependencies)
*/
private final HashSet<Module> myVisited = new HashSet<Module>();
/**
* The constructor
*
* @param urlExplanations the url explanations to accumulate
* @param orderExplanations the explanations for order entries
*/
public PathWalker(Map<String, List<OrderPath>> urlExplanations,
Map<OrderEntry, List<OrderPath>> orderExplanations) {
myUrlExplanations = urlExplanations;
myOrderExplanations = orderExplanations;
}
/**
* Examine the specified module
*
* @param m the module to examine
* @param level the level of the examination
*/
void examine(final Module m, final int level) {
if (myVisited.contains(m)) {
return;
}
myVisited.add(m);
try {
final OrderEnumerator e = ModuleRootManager.getInstance(m).orderEntries();
if (!mySdk || level != 0) {
e.withoutSdk();
}
if (myCompile && level != 0) {
e.exportedOnly();
}
if (myProduction) {
e.productionOnly();
}
if (myCompile) {
e.compileOnly();
}
else {
e.runtimeOnly();
}
e.forEach(new Processor<OrderEntry>() {
@Override
public boolean process(OrderEntry orderEntry) {
myStack.add(new OrderEntryPathElement(orderEntry));
try {
if (orderEntry instanceof ModuleOrderEntry) {
ModuleOrderEntry o = (ModuleOrderEntry)orderEntry;
examine(o.getModule(), level + 1);
}
else if (orderEntry instanceof ModuleSourceOrderEntry) {
if (!myProduction || !myCompile) {
CompilerModuleExtension e = CompilerModuleExtension.getInstance(m);
final OrderPath p = new OrderPath(myStack);
for (String u : e.getOutputRootUrls(!myCompile ? !myProduction : level > 0 && !myProduction)) {
addUrlPath(p, u);
}
addEntryPath(orderEntry, p);
}
}
else {
final OrderPath p = new OrderPath(myStack);
for (String u : orderEntry.getUrls(OrderRootType.CLASSES)) {
addUrlPath(p, u);
}
addEntryPath(orderEntry, p);
}
}
finally {
myStack.remove(myStack.size() - 1);
}
return true;
}
});
}
finally {
myVisited.remove(m);
}
}
/**
* Add url path
*
* @param p the path to add
* @param u the url to update
*/
private void addUrlPath(OrderPath p, String u) {
final List<OrderPath> orderPaths = myUrlExplanations.get(u);
if (orderPaths != null) {
orderPaths.add(p);
}
}
/**
* Add order entry explanation
*
* @param orderEntry the order entry to explain
* @param p the path that explain order entry
*/
private void addEntryPath(OrderEntry orderEntry, OrderPath p) {
List<OrderPath> paths = myOrderExplanations.get(orderEntry);
if (paths == null) {
paths = new ArrayList<OrderPath>();
myOrderExplanations.put(orderEntry, paths);
}
paths.add(p);
}
}
/**
* The path consisting of order entry path.
*/
public static class OrderPath {
/**
* The immutable list of path elements. The first element in the list is an order entry actually included in the current module.
*/
private final List<OrderPathElement> myEntries;
/**
* The constructor
*
* @param entries the list of entries (will be copied and wrapped)
*/
public OrderPath(List<OrderPathElement> entries) {
this.myEntries = Collections.unmodifiableList(new ArrayList<OrderPathElement>(entries));
}
/**
* @return the immutable list of path elements. The first element in the list is an order entry actually included in the current module.
*/
public List<OrderPathElement> entries() {
return myEntries;
}
@Override
public int hashCode() {
return myEntries.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof OrderPath)) {
return false;
}
return myEntries.equals(((OrderPath)obj).myEntries);
}
}
/**
* The a entry in the path. The implementation should support {@link #equals(Object)} and {@link #hashCode()} methods.
*/
public static abstract class OrderPathElement {
/**
* Get appearance for path element
*
* @param isSelected true if the element is selected
* @return the appearance to use for rendering
*/
@NotNull
public abstract CellAppearanceEx getAppearance(boolean isSelected);
}
/**
* The order entry path element
*/
public static class OrderEntryPathElement extends OrderPathElement {
/**
* The order entry
*/
private final OrderEntry myEntry;
/**
* The constructor
*
* @param entry the order entry
*/
public OrderEntryPathElement(OrderEntry entry) {
this.myEntry = entry;
}
/**
* @return the order entry
*/
public OrderEntry entry() {
return myEntry;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return myEntry.hashCode();
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (!(obj instanceof OrderEntryPathElement)) {
return false;
}
OrderEntryPathElement o = (OrderEntryPathElement)obj;
return o.myEntry == myEntry;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return myEntry.getPresentableName();
}
/**
* {@inheritDoc}
*/
@NotNull
@Override
public CellAppearanceEx getAppearance(boolean isSelected) {
return OrderEntryAppearanceService.getInstance().forOrderEntry(myEntry.getOwnerModule().getProject(), myEntry, isSelected);
}
}
/**
* The base class for explanations
*/
public static class Explanation {
/**
* The paths that refer to the path element
*/
public final List<OrderPath> myPaths;
/**
* The explanation for the path
*
* @param paths the paths to analyze (the list is wrapped)
*/
Explanation(List<OrderPath> paths) {
this.myPaths = Collections.unmodifiableList(paths);
}
/**
* @return the paths that refer to the path element
*/
public List<OrderPath> paths() {
return myPaths;
}
}
/**
* The explanation for
*/
public static class OrderEntryExplanation extends Explanation {
/**
* The URL in the path
*/
private final OrderEntry myEntry;
/**
* The explanation for the path
*
* @param entry the explained order entry
* @param paths the paths to analyze
*/
OrderEntryExplanation(OrderEntry entry, List<OrderPath> paths) {
super(paths);
myEntry = entry;
}
/**
* @return the explained entry
*/
public OrderEntry entry() {
return myEntry;
}
}
/**
* The explanation for url
*/
public static class UrlExplanation extends Explanation {
/**
* The URL in the path
*/
private final String myUrl;
/**
* The explanation for the path
*
* @param url the url for the order entry
* @param paths the paths to analyze
*/
UrlExplanation(String url, List<OrderPath> paths) {
super(paths);
myUrl = url;
}
/**
* @return the explained url
*/
public String url() {
return myUrl;
}
/**
* @return icon for the classpath
*/
@Nullable
public Icon getIcon() {
VirtualFile file = getLocalFile();
return file == null ? AllIcons.General.Error : VirtualFilePresentation.getIcon(file);
}
/**
* @return the local file
*/
@Nullable
public VirtualFile getLocalFile() {
VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(myUrl);
if (file != null) {
file = PathUtil.getLocalFile(file);
}
return file;
}
}
}