blob: b964f200fc41eff3e8461f96a748b39c496c3ed1 [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.
*/
package com.intellij.ide.errorTreeView;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration;
import com.intellij.ide.util.treeView.AbstractTreeStructure;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.ui.CustomizeColoredTreeCellRenderer;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.MutableErrorTreeView;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.*;
/**
* @author Eugene Zhuravlev
* Date: Nov 12, 2004
*/
public class ErrorViewStructure extends AbstractTreeStructure {
private final ErrorTreeElement myRoot = new MyRootElement();
private final List<String> myGroupNames = new ArrayList<String>();
private final Map<String, GroupingElement> myGroupNameToElementMap = new HashMap<String, GroupingElement>();
private final Map<String, List<NavigatableMessageElement>> myGroupNameToMessagesMap = new HashMap<String, List<NavigatableMessageElement>>();
private final Map<ErrorTreeElementKind, List<ErrorTreeElement>> mySimpleMessages = new EnumMap<ErrorTreeElementKind, List<ErrorTreeElement>>(ErrorTreeElementKind.class);
private final Object myLock = new Object();
private static final ErrorTreeElementKind[] ourMessagesOrder = {
ErrorTreeElementKind.INFO,
ErrorTreeElementKind.ERROR,
ErrorTreeElementKind.WARNING,
ErrorTreeElementKind.NOTE,
ErrorTreeElementKind.GENERIC
};
private final Project myProject;
private final boolean myCanHideWarnings;
public ErrorViewStructure(Project project, final boolean canHideWarnings) {
myProject = project;
myCanHideWarnings = canHideWarnings;
}
@Override
public Object getRootElement() {
return myRoot;
}
@Override
public ErrorTreeElement[] getChildElements(Object element) {
if (element == myRoot) {
final List<ErrorTreeElement> children = new ArrayList<ErrorTreeElement>();
// simple messages
synchronized (myLock) {
for (final ErrorTreeElementKind kind : ourMessagesOrder) {
if (myCanHideWarnings) {
if (ErrorTreeElementKind.WARNING.equals(kind) || ErrorTreeElementKind.NOTE.equals(kind)) {
if (ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings()) {
continue;
}
}
}
final List<ErrorTreeElement> elems = mySimpleMessages.get(kind);
if (elems != null) {
children.addAll(elems);
}
}
// files
for (final String myGroupName : myGroupNames) {
final GroupingElement groupingElement = myGroupNameToElementMap.get(myGroupName);
if (shouldShowFileElement(groupingElement)) {
children.add(groupingElement);
}
}
}
return ArrayUtil.toObjectArray(children, ErrorTreeElement.class);
}
if (element instanceof GroupingElement) {
synchronized (myLock) {
final List<NavigatableMessageElement> children = myGroupNameToMessagesMap.get(((GroupingElement)element).getName());
if (children != null && !children.isEmpty()) {
if (myCanHideWarnings && ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings()) {
final List<ErrorTreeElement> filtered = new ArrayList<ErrorTreeElement>(children.size());
for (final NavigatableMessageElement navigatableMessageElement : children) {
ErrorTreeElementKind kind = navigatableMessageElement.getKind();
if (ErrorTreeElementKind.WARNING.equals(kind) || ErrorTreeElementKind.NOTE.equals(kind)) {
continue;
}
filtered.add(navigatableMessageElement);
}
return ArrayUtil.toObjectArray(filtered, ErrorTreeElement.class);
}
return ArrayUtil.toObjectArray(children, NavigatableMessageElement.class);
}
}
}
return ErrorTreeElement.EMPTY_ARRAY;
}
private boolean shouldShowFileElement(GroupingElement groupingElement) {
if (!myCanHideWarnings || !ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings()) {
return getChildCount(groupingElement) > 0;
}
synchronized (myLock) {
final List<NavigatableMessageElement> children = myGroupNameToMessagesMap.get(groupingElement.getName());
if (children != null) {
for (final NavigatableMessageElement child : children) {
ErrorTreeElementKind kind = child.getKind();
if (!ErrorTreeElementKind.WARNING.equals(kind) && !ErrorTreeElementKind.NOTE.equals(kind)) {
return true;
}
}
}
}
return false;
}
@Override
public Object getParentElement(Object element) {
if (element instanceof GroupingElement || element instanceof SimpleMessageElement) {
return myRoot;
}
if (element instanceof NavigatableMessageElement) {
GroupingElement result = ((NavigatableMessageElement)element).getParent();
return result == null ? myRoot : result;
}
return null;
}
@Override
@NotNull
public NodeDescriptor createDescriptor(Object element, NodeDescriptor parentDescriptor) {
return new ErrorTreeNodeDescriptor(myProject, parentDescriptor, (ErrorTreeElement)element);
}
@Override
public final void commit() {
}
@Override
public final boolean hasSomethingToCommit() {
return false;
}
public void addMessage(@NotNull ErrorTreeElementKind kind,
@NotNull String[] text,
@Nullable VirtualFile underFileGroup,
@Nullable VirtualFile file,
int line,
int column,
@Nullable Object data) {
if (underFileGroup != null || file != null) {
if (file == null) line = column = -1;
final int guiline = line < 0 ? -1 : line + 1;
final int guicolumn = column < 0 ? -1 : column + 1;
VirtualFile group = underFileGroup != null ? underFileGroup : file;
VirtualFile nav = file != null ? file : underFileGroup;
addNavigatableMessage(
group.getPresentableUrl(),
new OpenFileDescriptor(myProject, nav, line, column),
kind,
text,
data,
NewErrorTreeViewPanel.createExportPrefix(guiline),
NewErrorTreeViewPanel.createRendererPrefix(guiline, guicolumn),
group
);
}
else {
addSimpleMessage(kind, text, data);
}
}
public List<Object> getGroupChildrenData(final String groupName) {
synchronized (myLock) {
final List<NavigatableMessageElement> children = myGroupNameToMessagesMap.get(groupName);
if (children == null || children.isEmpty()) {
return Collections.emptyList();
}
final List<Object> result = new ArrayList<Object>();
for (NavigatableMessageElement child : children) {
final Object data = child.getData();
if (data != null) {
result.add(data);
}
}
return result;
}
}
public void addFixedHotfixGroup(final String text, final List<SimpleErrorData> children) {
final FixedHotfixGroupElement group = new FixedHotfixGroupElement(text, null, null);
addGroupPlusElements(text, group, children);
}
public void addHotfixGroup(final HotfixData hotfixData, final List<SimpleErrorData> children, final MutableErrorTreeView view) {
final String text = hotfixData.getErrorText();
final HotfixGroupElement group = new HotfixGroupElement(text, null, null, hotfixData.getFix(), hotfixData.getFixComment(), view);
addGroupPlusElements(text, group, children);
}
private void addGroupPlusElements(String text, GroupingElement group, List<SimpleErrorData> children) {
final List<NavigatableMessageElement> elements = new ArrayList<NavigatableMessageElement>();
for (SimpleErrorData child : children) {
elements.add(new MyNavigatableWithDataElement(
myProject, child.getKind(), group, child.getMessages(), child.getVf(), NewErrorTreeViewPanel.createExportPrefix(-1), NewErrorTreeViewPanel.createRendererPrefix(-1, -1))
);
}
synchronized (myLock) {
myGroupNames.add(text);
myGroupNameToElementMap.put(text, group);
myGroupNameToMessagesMap.put(text, elements);
}
}
public void addMessage(@NotNull ErrorTreeElementKind kind, String[] text, Object data) {
addSimpleMessage(kind, text, data);
}
public void addNavigatableMessage(@Nullable String groupName,
Navigatable navigatable,
@NotNull ErrorTreeElementKind kind,
final String[] message,
final Object data,
String exportText,
String rendererTextPrefix,
VirtualFile file) {
if (groupName == null) {
addSimpleMessageElement(new NavigatableMessageElement(kind, null, message, navigatable, exportText, rendererTextPrefix));
}
else {
synchronized (myLock) {
List<NavigatableMessageElement> elements = myGroupNameToMessagesMap.get(groupName);
if (elements == null) {
elements = new ArrayList<NavigatableMessageElement>();
myGroupNameToMessagesMap.put(groupName, elements);
}
elements.add(new NavigatableMessageElement(kind, getGroupingElement(groupName, data, file), message, navigatable, exportText, rendererTextPrefix));
}
}
}
public void addNavigatableMessage(@NotNull String groupName,
@NotNull NavigatableMessageElement navigatableMessageElement) {
synchronized (myLock) {
List<NavigatableMessageElement> elements = myGroupNameToMessagesMap.get(groupName);
if (elements == null) {
elements = new ArrayList<NavigatableMessageElement>();
myGroupNameToMessagesMap.put(groupName, elements);
}
if (!myGroupNameToElementMap.containsKey(groupName)) {
myGroupNames.add(groupName);
myGroupNameToElementMap.put(groupName, navigatableMessageElement.getParent());
}
elements.add(navigatableMessageElement);
}
}
private void addSimpleMessage(@NotNull ErrorTreeElementKind kind, final String[] text, final Object data) {
addSimpleMessageElement(new SimpleMessageElement(kind, text, data));
}
private void addSimpleMessageElement(ErrorTreeElement element) {
synchronized (myLock) {
List<ErrorTreeElement> elements = mySimpleMessages.get(element.getKind());
if (elements == null) {
elements = new ArrayList<ErrorTreeElement>();
mySimpleMessages.put(element.getKind(), elements);
}
elements.add(element);
}
}
@Nullable
public GroupingElement lookupGroupingElement(String groupName) {
synchronized (myLock) {
return myGroupNameToElementMap.get(groupName);
}
}
public GroupingElement getGroupingElement(String groupName, Object data, VirtualFile file) {
synchronized (myLock) {
GroupingElement element = myGroupNameToElementMap.get(groupName);
if (element != null) {
return element;
}
element = new GroupingElement(groupName, data, file);
myGroupNames.add(groupName);
myGroupNameToElementMap.put(groupName, element);
return element;
}
}
public int getChildCount(GroupingElement groupingElement) {
synchronized (myLock) {
final List<NavigatableMessageElement> children = myGroupNameToMessagesMap.get(groupingElement.getName());
return children == null ? 0 : children.size();
}
}
public void clear() {
synchronized (myLock) {
myGroupNames.clear();
myGroupNameToElementMap.clear();
myGroupNameToMessagesMap.clear();
mySimpleMessages.clear();
}
}
@Nullable
public ErrorTreeElement getFirstMessage(@NotNull ErrorTreeElementKind kind) {
if (myCanHideWarnings &&
(ErrorTreeElementKind.WARNING.equals(kind) || ErrorTreeElementKind.NOTE.equals(kind)) &&
ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings()) {
return null; // no warnings are available
}
synchronized (myLock) {
final List<ErrorTreeElement> simpleMessages = mySimpleMessages.get(kind);
if (simpleMessages != null && !simpleMessages.isEmpty()) {
return simpleMessages.get(0);
}
for (final String path : myGroupNames) {
final List<NavigatableMessageElement> messages = myGroupNameToMessagesMap.get(path);
if (messages != null) {
for (final NavigatableMessageElement navigatableMessageElement : messages) {
if (kind.equals(navigatableMessageElement.getKind())) {
return navigatableMessageElement;
}
}
}
}
}
return null;
}
private static class MyRootElement extends ErrorTreeElement {
@Override
public String[] getText() {
return null;
}
@Override
public Object getData() {
return null;
}
@Override
public String getExportTextPrefix() {
return "";
}
}
public void removeGroup(final String name) {
synchronized (myLock) {
myGroupNames.remove(name);
myGroupNameToElementMap.remove(name);
myGroupNameToMessagesMap.remove(name);
}
}
public void removeElement(final ErrorTreeElement element) {
if (element == myRoot) {
return;
}
if (element instanceof GroupingElement) {
GroupingElement groupingElement = (GroupingElement)element;
removeGroup(groupingElement.getName());
final VirtualFile virtualFile = groupingElement.getFile();
if (virtualFile != null) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
final PsiFile psiFile = virtualFile.isValid()? PsiManager.getInstance(myProject).findFile(virtualFile) : null;
if (psiFile != null) {
DaemonCodeAnalyzer.getInstance(myProject).restart(psiFile); // urge the daemon to re-highlight the file despite no modification has been made
}
}
});
}
}
else if (element instanceof NavigatableMessageElement){
final NavigatableMessageElement navElement = (NavigatableMessageElement)element;
final GroupingElement parent = navElement.getParent();
if (parent != null) {
synchronized (myLock) {
final List<NavigatableMessageElement> groupMessages = myGroupNameToMessagesMap.get(parent.getName());
if (groupMessages != null) {
groupMessages.remove(navElement);
}
}
}
}
else {
synchronized (myLock) {
final List<ErrorTreeElement> simples = mySimpleMessages.get(element.getKind());
if (simples != null) {
simples.remove(element);
}
}
}
}
private static class MyNavigatableWithDataElement extends NavigatableMessageElement {
private final VirtualFile myVf;
private final CustomizeColoredTreeCellRenderer myCustomizeColoredTreeCellRenderer;
private MyNavigatableWithDataElement(final Project project,
@NotNull ErrorTreeElementKind kind,
GroupingElement parent,
String[] message,
@NotNull final VirtualFile vf,
String exportText,
String rendererTextPrefix) {
super(kind, parent, message, new OpenFileDescriptor(project, vf, -1, -1), exportText, rendererTextPrefix);
myVf = vf;
myCustomizeColoredTreeCellRenderer = new CustomizeColoredTreeCellRenderer() {
@Override
public void customizeCellRenderer(SimpleColoredComponent renderer,
JTree tree,
Object value,
boolean selected,
boolean expanded,
boolean leaf,
int row,
boolean hasFocus) {
final Icon icon = myVf.getFileType().getIcon();
renderer.setIcon(icon);
final String[] messages = getText();
final String text = messages == null || messages.length == 0 ? vf.getPath() : messages[0];
renderer.append(text);
}
};
}
@Override
public Object getData() {
return myVf;
}
@Override
public CustomizeColoredTreeCellRenderer getLeftSelfRenderer() {
return myCustomizeColoredTreeCellRenderer;
}
}
}