/* * Copyright 2000-2015 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.codeInspection.ex; import com.intellij.codeInsight.daemon.impl.HighlightInfoType; import com.intellij.codeInspection.CommonProblemDescriptor; import com.intellij.codeInspection.QuickFix; import com.intellij.codeInspection.reference.RefDirectory; import com.intellij.codeInspection.reference.RefElement; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.reference.RefModule; import com.intellij.codeInspection.ui.*; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Ref; import com.intellij.util.Function; import com.intellij.util.containers.MultiMap; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.util.*; import java.util.function.UnaryOperator; public abstract class InspectionRVContentProvider { private static final Logger LOG = Logger.getInstance(InspectionRVContentProvider.class); private final Project myProject; public InspectionRVContentProvider(@NotNull Project project) { myProject = project; } protected static class RefEntityContainer<Descriptor> { private final Descriptor[] myDescriptors; @Nullable private final RefEntity myEntity; public RefEntityContainer(@Nullable RefEntity entity, Descriptor[] descriptors) { myEntity = entity; myDescriptors = descriptors; } @Nullable public RefEntityContainer<Descriptor> getOwner() { if (myEntity == null) return null; final RefEntity entity = myEntity.getOwner(); return entity instanceof RefElement && !(entity instanceof RefDirectory) ? new RefEntityContainer<>(entity, myDescriptors) : null; } @NotNull public RefElementNode createNode(@NotNull InspectionToolPresentation presentation) { return ReadAction.compute(() -> presentation.createRefNode(myEntity)); } @Nullable public RefEntity getRefEntity() { return myEntity; } @Nullable public String getModule() { final RefModule refModule = myEntity instanceof RefElement ? ((RefElement)myEntity).getModule() : myEntity instanceof RefModule ? (RefModule)myEntity : null; return refModule != null ? refModule.getName() : null; } boolean areEqual(final @NotNull RefEntity o1, final @NotNull RefEntity o2) { return Comparing.equal(o1, o2); } boolean supportStructure() { return myEntity == null || myEntity instanceof RefElement && !(myEntity instanceof RefDirectory); //do not show structure for refModule and refPackage } public Descriptor[] getDescriptors() { return myDescriptors; } } public abstract boolean checkReportedProblems(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionToolWrapper toolWrapper); public Iterable<? extends ScopeToolState> getTools(Tools tools) { return tools.getTools(); } public boolean hasQuickFixes(InspectionTree tree) { final TreePath[] treePaths = tree.getSelectionPaths(); if (treePaths == null) return false; for (TreePath selectionPath : treePaths) { if (!TreeUtil.traverseDepth((TreeNode)selectionPath.getLastPathComponent(), node -> { if (!((InspectionTreeNode) node).isValid()) return true; if (node instanceof ProblemDescriptionNode) { ProblemDescriptionNode problemDescriptionNode = (ProblemDescriptionNode)node; if (!problemDescriptionNode.isQuickFixAppliedFromView()) { final CommonProblemDescriptor descriptor = problemDescriptionNode.getDescriptor(); final QuickFix[] fixes = descriptor != null ? descriptor.getFixes() : null; return fixes == null || fixes.length == 0; } } return true; })) { return true; } } return false; } @Nullable public abstract QuickFixAction[] getQuickFixes(@NotNull InspectionToolWrapper toolWrapper, @NotNull InspectionTree tree); public InspectionNode appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionNode toolNode, @NotNull InspectionTreeNode parentNode, boolean showStructure, boolean groupBySeverity) { InspectionToolWrapper wrapper = toolNode.getToolWrapper(); InspectionToolPresentation presentation = context.getPresentation(wrapper); Map<String, Set<RefEntity>> content = presentation.getContent(); Map<RefEntity, CommonProblemDescriptor[]> problems = presentation.getProblemElements(); return appendToolNodeContent(context, toolNode, parentNode, showStructure, groupBySeverity, content, problems); } public abstract InspectionNode appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionNode toolNode, @NotNull InspectionTreeNode parentNode, final boolean showStructure, boolean groupBySeverity, @NotNull Map<String, Set<RefEntity>> contents, @NotNull Map<RefEntity, CommonProblemDescriptor[]> problems); protected abstract void appendDescriptor(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionToolWrapper toolWrapper, @NotNull RefEntityContainer container, @NotNull InspectionTreeNode pNode, final boolean canPackageRepeat); public boolean isContentLoaded() { return true; } protected <T> void buildTree(@NotNull GlobalInspectionContextImpl context, @NotNull Map<String, Set<T>> packageContents, final boolean canPackageRepeat, @NotNull InspectionToolWrapper toolWrapper, @NotNull Function<T, RefEntityContainer<?>> computeContainer, final boolean showStructure, final UnaryOperator<InspectionTreeNode> createdNodesConsumer) { final Map<String, Map<String, InspectionPackageNode>> module2PackageMap = new HashMap<>(); boolean supportStructure = showStructure; final MultiMap<InspectionPackageNode, RefEntityContainer<?>> packageDescriptors = new MultiMap<>(); for (String packageName : packageContents.keySet()) { final Set<T> elements = packageContents.get(packageName); for (T userObject : elements) { final RefEntityContainer container = computeContainer.fun(userObject); supportStructure &= container.supportStructure(); final String moduleName = showStructure ? container.getModule() : null; Map<String, InspectionPackageNode> packageNodes = module2PackageMap.get(moduleName); if (packageNodes == null) { packageNodes = new HashMap<>(); module2PackageMap.put(moduleName, packageNodes); } InspectionPackageNode pNode = packageNodes.get(packageName); if (pNode == null) { pNode = new InspectionPackageNode(packageName); packageNodes.put(packageName, pNode); } packageDescriptors.putValue(pNode, container); } } if (supportStructure) { final HashMap<String, InspectionModuleNode> moduleNodes = new HashMap<>(); for (final String moduleName : module2PackageMap.keySet()) { final Map<String, InspectionPackageNode> packageNodes = module2PackageMap.get(moduleName); InspectionModuleNode moduleNode = moduleNodes.get(moduleName); if (moduleNode == null) { if (moduleName != null) { final Module module = ReadAction.compute(() -> ModuleManager.getInstance(myProject).findModuleByName(moduleName)); if (module != null) { moduleNode = new InspectionModuleNode(module); moduleNodes.put(moduleName, moduleNode); moduleNode = (InspectionModuleNode)createdNodesConsumer.apply(moduleNode); } else { //module content was removed ? continue; } } else { for (InspectionPackageNode packageNode : packageNodes.values()) { createdNodesConsumer.apply(packageNode); for (RefEntityContainer<?> container : packageDescriptors.get(packageNode)) { appendDescriptor(context, toolWrapper, container, packageNode, canPackageRepeat); } } continue; } } else { moduleNode = (InspectionModuleNode)createdNodesConsumer.apply(moduleNode); } for (InspectionPackageNode packageNode : packageNodes.values()) { if (packageNode.getPackageName() != null) { Collection<RefEntityContainer<?>> objectContainers = packageDescriptors.get(packageNode); packageNode = (InspectionPackageNode)merge(packageNode, moduleNode, true); for (RefEntityContainer<?> container : objectContainers) { appendDescriptor(context, toolWrapper, container, packageNode, canPackageRepeat); } } else { for (RefEntityContainer<?> container : packageDescriptors.get(packageNode)) { appendDescriptor(context, toolWrapper, container, moduleNode, canPackageRepeat); } } } } } else { for (Map<String, InspectionPackageNode> packageNodes : module2PackageMap.values()) { for (InspectionPackageNode pNode : packageNodes.values()) { for (RefEntityContainer<?> container : packageDescriptors.get(pNode)) { appendDescriptor(context, toolWrapper, container, pNode, canPackageRepeat); } final int count = pNode.getChildCount(); final ArrayList<TreeNode> childNodes = new ArrayList<>(count); for (int i = 0; i < count; i++) { childNodes.add(pNode.getChildAt(i)); } for (TreeNode childNode: childNodes) { if (childNode instanceof ProblemDescriptionNode) { createdNodesConsumer.apply(pNode); break; } LOG.assertTrue(childNode instanceof RefElementNode, childNode.getClass().getName()); final RefElementNode elementNode = (RefElementNode)childNode; final Set<RefElementNode> parentNodes = new LinkedHashSet<>(); if (pNode.getPackageName() != null) { parentNodes.add(elementNode); } else { boolean hasElementNodeUnder = true; for(int e = 0; e < elementNode.getChildCount(); e++) { final TreeNode grandChildNode = elementNode.getChildAt(e); if (grandChildNode instanceof ProblemDescriptionNode) { hasElementNodeUnder = false; break; } LOG.assertTrue(grandChildNode instanceof RefElementNode); parentNodes.add((RefElementNode)grandChildNode); } if (!hasElementNodeUnder) { createdNodesConsumer.apply(elementNode); continue; } } //allow unused declaration to have structure at file level even when there are unused parameters if (!HighlightInfoType.UNUSED_SYMBOL_SHORT_NAME.equals(toolWrapper.getShortName())) { for (RefElementNode parentNode : parentNodes) { final List<ProblemDescriptionNode> nodes = new ArrayList<>(); TreeUtil.traverse(parentNode, node -> { if (node instanceof ProblemDescriptionNode) { nodes.add((ProblemDescriptionNode)node); } return true; }); if (nodes.isEmpty()) continue; parentNode.removeAllChildren(); for (ProblemDescriptionNode node : nodes) { parentNode.add(node); } } } for (RefElementNode node : parentNodes) { createdNodesConsumer.apply(node); } } } } } } @NotNull protected static RefElementNode addNodeToParent(@NotNull RefEntityContainer container, @NotNull InspectionToolPresentation presentation, final InspectionTreeNode parentNode) { final RefElementNode nodeToBeAdded = container.createNode(presentation); final Ref<Boolean> firstLevel = new Ref<>(true); RefElementNode prevNode = null; final Ref<RefElementNode> result = new Ref<>(); while (true) { final RefElementNode currentNode = firstLevel.get() ? nodeToBeAdded : container.createNode(presentation); final RefEntityContainer finalContainer = container; final RefElementNode finalPrevNode = prevNode; TreeUtil.traverseDepth(parentNode, node -> { if (node instanceof RefElementNode) { final RefElementNode refElementNode = (RefElementNode)node; final RefEntity userObject = finalContainer.getRefEntity(); final RefEntity object = refElementNode.getElement(); if (userObject != null && object != null && (userObject.getClass().equals(object.getClass())) && finalContainer.areEqual(object, userObject)) { if (firstLevel.get()) { result.set(refElementNode); return false; } else { refElementNode.insertByOrder(finalPrevNode, false); result.set(nodeToBeAdded); return false; } } } return true; }); if(!result.isNull()) return result.get(); if (!firstLevel.get()) { currentNode.insertByOrder(prevNode, false); } final RefEntityContainer owner = container.getOwner(); if (owner == null) { parentNode.insertByOrder(currentNode, false); return nodeToBeAdded; } container = owner; prevNode = currentNode; firstLevel.set(false); } } @SuppressWarnings({"ConstantConditions"}) //class cast suppression public static InspectionTreeNode merge(InspectionTreeNode child, InspectionTreeNode parent, boolean merge) { return ReadAction.compute(() -> { if (merge) { for (int i = 0; i < parent.getChildCount(); i++) { InspectionTreeNode current = (InspectionTreeNode)parent.getChildAt(i); if (child.getClass() != current.getClass()) { continue; } if (current instanceof InspectionPackageNode) { if (((InspectionPackageNode)current).getPackageName().compareTo(((InspectionPackageNode)child).getPackageName()) == 0) { processDepth(child, current); return current; } } else if (current instanceof RefElementNode) { if (InspectionResultsViewComparator.getInstance().compare(current, child) == 0) { processDepth(child, current); return current; } } else if (current instanceof InspectionNode) { if (((InspectionNode)current).getToolWrapper().getShortName().compareTo(((InspectionNode)child).getToolWrapper().getShortName()) == 0) { processDepth(child, current); return current; } } else if (current instanceof InspectionModuleNode) { if (((InspectionModuleNode)current).getName().compareTo(((InspectionModuleNode)child).getName()) == 0) { processDepth(child, current); return current; } } } } return parent.insertByOrder(child, false); }); } private static void processDepth(final InspectionTreeNode child, final InspectionTreeNode current) { InspectionTreeNode[] children = new InspectionTreeNode[child.getChildCount()]; for (int i = 0; i < children.length; i++) { children[i] = (InspectionTreeNode)child.getChildAt(i); } for (InspectionTreeNode node : children) { merge(node, current, true); } } }