/* * 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<>(); /** * The url explanations */ private final List<UrlExplanation> myUrls = new ArrayList<>(); /** * 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<>(); final OrderRootsEnumerator classes = e.classes(); if (myCompile) { classes.withoutSelfModuleOutput(); } for (String url : classes.getUrls()) { if (!urlExplanations.containsKey(url)) { urlExplanations.put(url, new ArrayList<>()); } } final Map<OrderEntry, List<OrderPath>> orderExplanations = new LinkedHashMap<>(); 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<>(); /** * Visited modules (in order to detect cyclic dependencies) */ private final HashSet<Module> myVisited = new HashSet<>(); /** * 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(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 e1 = CompilerModuleExtension.getInstance(m); final OrderPath p = new OrderPath(myStack); for (String u : e1.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<>(); 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<>(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; } } }