blob: ce8378d0b9177d6ca7e4f24f72298b97b8d7b7f6 [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.codeInspection.ui;
import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInspection.*;
import com.intellij.codeInspection.ex.*;
import com.intellij.codeInspection.reference.RefElement;
import com.intellij.codeInspection.reference.RefEntity;
import com.intellij.codeInspection.ui.actions.SuppressActionWrapper;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.profile.codeInspection.InspectionProjectProfileManagerImpl;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.xml.util.XmlStringUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.Document;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLFrameHyperlinkEvent;
import java.awt.*;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
class Browser extends JPanel {
private static final String UNDER_CONSTRUCTION = InspectionsBundle.message("inspection.tool.description.under.construction.text");
private final List<ClickListener> myClickListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private RefEntity myCurrentEntity;
private JEditorPane myHTMLViewer;
private final InspectionResultsView myView;
private final HyperlinkListener myHyperLinkListener;
private CommonProblemDescriptor myCurrentDescriptor;
public static class ClickEvent {
public static final int REF_ELEMENT = 1;
public static final int FILE_OFFSET = 2;
private final VirtualFile myFile;
private final int myStartPosition;
private final int myEndPosition;
private final RefElement refElement;
private final int myEventType;
public ClickEvent(VirtualFile myFile, int myStartPosition, int myEndPosition) {
this.myFile = myFile;
this.myStartPosition = myStartPosition;
this.myEndPosition = myEndPosition;
myEventType = FILE_OFFSET;
refElement = null;
}
public int getEventType() {
return myEventType;
}
public VirtualFile getFile() {
return myFile;
}
public int getStartOffset() {
return myStartPosition;
}
public int getEndOffset() {
return myEndPosition;
}
public RefElement getClickedElement() {
return refElement;
}
}
public void dispose(){
removeAll();
if (myHTMLViewer != null) {
myHTMLViewer.removeHyperlinkListener(myHyperLinkListener);
myHTMLViewer = null;
}
myClickListeners.clear();
}
public interface ClickListener {
void referenceClicked(ClickEvent e);
}
private void showPageFromHistory(@NotNull RefEntity newEntity) {
InspectionToolWrapper toolWrapper = getToolWrapper(newEntity);
try {
String html = generateHTML(newEntity, toolWrapper);
myHTMLViewer.read(new StringReader(html), null);
setupStyle();
myHTMLViewer.setCaretPosition(0);
}
catch (Exception e) {
showEmpty();
}
finally {
myCurrentEntity = newEntity;
myCurrentDescriptor = null;
}
}
public void showPageFor(RefEntity refEntity, CommonProblemDescriptor descriptor) {
try {
String html = generateHTML(refEntity, descriptor);
myHTMLViewer.read(new StringReader(html), null);
setupStyle();
myHTMLViewer.setCaretPosition(0);
}
catch (Exception e) {
showEmpty();
}
finally {
myCurrentEntity = refEntity;
myCurrentDescriptor = descriptor;
}
}
public void showPageFor(RefEntity newEntity) {
if (newEntity == null) {
showEmpty();
return;
}
//multiple problems for one entity -> refresh browser
showPageFromHistory(newEntity.getRefManager().getRefinedElement(newEntity));
}
public Browser(@NotNull InspectionResultsView view) {
super(new BorderLayout());
myView = view;
myCurrentEntity = null;
myCurrentDescriptor = null;
myHTMLViewer = new JEditorPane(UIUtil.HTML_MIME, InspectionsBundle.message("inspection.offline.view.empty.browser.text"));
myHTMLViewer.setEditable(false);
myHyperLinkListener = new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
Browser.this.hyperlinkUpdate(e);
}
};
myHTMLViewer.addHyperlinkListener(myHyperLinkListener);
final JScrollPane pane = ScrollPaneFactory.createScrollPane(myHTMLViewer);
pane.setBorder(null);
add(pane, BorderLayout.CENTER);
setupStyle();
}
private void hyperlinkUpdate(HyperlinkEvent e) {
if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
return;
}
JEditorPane pane = (JEditorPane)e.getSource();
if (e instanceof HTMLFrameHyperlinkEvent) {
HTMLFrameHyperlinkEvent evt = (HTMLFrameHyperlinkEvent)e;
HTMLDocument doc = (HTMLDocument)pane.getDocument();
doc.processHTMLFrameHyperlinkEvent(evt);
return;
}
URL url = null;
try {
url = e.getURL();
@NonNls String ref = url.getRef();
if (ref.startsWith("pos:")) {
int delimeterPos = ref.indexOf(':', "pos:".length() + 1);
String startPosition = ref.substring("pos:".length(), delimeterPos);
String endPosition = ref.substring(delimeterPos + 1);
Integer textStartOffset = new Integer(startPosition);
Integer textEndOffset = new Integer(endPosition);
String fileURL = url.toExternalForm();
fileURL = fileURL.substring(0, fileURL.indexOf('#'));
VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(fileURL);
if (vFile != null) {
fireClickEvent(vFile, textStartOffset.intValue(), textEndOffset.intValue());
}
}
else if (ref.startsWith("descr:")) {
if (myCurrentDescriptor instanceof ProblemDescriptor) {
PsiElement psiElement = ((ProblemDescriptor)myCurrentDescriptor).getPsiElement();
if (psiElement == null) return;
VirtualFile vFile = psiElement.getContainingFile().getVirtualFile();
if (vFile != null) {
TextRange range = ((ProblemDescriptorBase)myCurrentDescriptor).getTextRange();
fireClickEvent(vFile, range.getStartOffset(), range.getEndOffset());
}
}
}
else if (ref.startsWith("invoke:")) {
int actionNumber = Integer.parseInt(ref.substring("invoke:".length()));
InspectionToolWrapper toolWrapper = getToolWrapper();
InspectionToolPresentation presentation = myView.getGlobalInspectionContext().getPresentation(toolWrapper);
QuickFixAction fixAction = presentation.getQuickFixes(new RefElement[]{(RefElement)myCurrentEntity})[actionNumber];
fixAction.doApplyFix(new RefElement[]{(RefElement)myCurrentEntity}, myView);
}
else if (ref.startsWith("invokelocal:")) {
int actionNumber = Integer.parseInt(ref.substring("invokelocal:".length()));
if (actionNumber > -1) {
invokeLocalFix(actionNumber);
}
} else if (ref.startsWith("suppress:")){
final SuppressActionWrapper.SuppressTreeAction[] suppressTreeActions =
new SuppressActionWrapper(myView.getProject(), getToolWrapper(), myView.getTree().getSelectionPaths()).getChildren(null);
final List<AnAction> activeActions = new ArrayList<AnAction>();
for (SuppressActionWrapper.SuppressTreeAction suppressTreeAction : suppressTreeActions) {
if (suppressTreeAction.isAvailable()) activeActions.add(suppressTreeAction);
}
if (!activeActions.isEmpty()) {
int actionNumber = Integer.parseInt(ref.substring("suppress:".length()));
if (actionNumber > -1 && activeActions.size() > actionNumber) {
activeActions.get(actionNumber).actionPerformed(null);
}
}
}
else {
int offset = Integer.parseInt(ref);
String fileURL = url.toExternalForm();
fileURL = fileURL.substring(0, fileURL.indexOf('#'));
VirtualFile vFile = VirtualFileManager.getInstance().findFileByUrl(fileURL);
if (vFile == null) {
vFile = VfsUtil.findFileByURL(url);
}
if (vFile != null) {
fireClickEvent(vFile, offset, offset);
}
}
}
catch (Throwable t) {
if (url != null) {
BrowserUtil.browse(url);
}
}
}
private void setupStyle() {
Document document = myHTMLViewer.getDocument();
if (!(document instanceof StyledDocument)) {
return;
}
StyledDocument styledDocument = (StyledDocument)document;
EditorColorsManager colorsManager = EditorColorsManager.getInstance();
EditorColorsScheme scheme = colorsManager.getGlobalScheme();
Style style = styledDocument.addStyle("active", null);
StyleConstants.setFontFamily(style, scheme.getEditorFontName());
StyleConstants.setFontSize(style, scheme.getEditorFontSize());
styledDocument.setCharacterAttributes(0, document.getLength(), style, false);
}
public void addClickListener(ClickListener listener) {
myClickListeners.add(listener);
}
private void fireClickEvent(VirtualFile file, int startPosition, int endPosition) {
ClickEvent e = new ClickEvent(file, startPosition, endPosition);
for (ClickListener listener : myClickListeners) {
listener.referenceClicked(e);
}
}
private String generateHTML(final RefEntity refEntity, @NotNull final InspectionToolWrapper toolWrapper) {
final StringBuffer buf = new StringBuffer();
final HTMLComposerImpl htmlComposer = getPresentation(toolWrapper).getComposer();
if (refEntity instanceof RefElement) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
htmlComposer.compose(buf, refEntity);
}
});
}
else {
htmlComposer.compose(buf, refEntity);
}
uppercaseFirstLetter(buf);
if (refEntity instanceof RefElement){
appendSuppressSection(buf);
}
insertHeaderFooter(buf);
return buf.toString();
}
private InspectionToolPresentation getPresentation(@NotNull InspectionToolWrapper toolWrapper) {
return myView.getGlobalInspectionContext().getPresentation(toolWrapper);
}
@SuppressWarnings({"HardCodedStringLiteral"})
private static void insertHeaderFooter(final StringBuffer buf) {
buf.insert(0, "<HTML><BODY>");
buf.append("</BODY></HTML>");
}
private String generateHTML(final RefEntity refEntity, final CommonProblemDescriptor descriptor) {
final StringBuffer buf = new StringBuffer();
final Runnable action = new Runnable() {
@Override
public void run() {
InspectionToolWrapper toolWrapper = getToolWrapper(refEntity);
getPresentation(toolWrapper).getComposer().compose(buf, refEntity, descriptor);
}
};
ApplicationManager.getApplication().runReadAction(action);
uppercaseFirstLetter(buf);
if (refEntity instanceof RefElement) {
appendSuppressSection(buf);
}
insertHeaderFooter(buf);
return buf.toString();
}
private InspectionToolWrapper getToolWrapper(final RefEntity refEntity) {
InspectionToolWrapper toolWrapper = getToolWrapper();
assert toolWrapper != null;
final GlobalInspectionContextImpl context = myView.getGlobalInspectionContext();
if (refEntity instanceof RefElement){
PsiElement element = ((RefElement)refEntity).getElement();
if (element == null) return toolWrapper;
InspectionProfileWrapper profileWrapper = InspectionProjectProfileManagerImpl.getInstanceImpl(context.getProject()).getProfileWrapper();
toolWrapper = profileWrapper.getInspectionTool(toolWrapper.getShortName(), element);
}
return toolWrapper;
}
private void appendSuppressSection(final StringBuffer buf) {
final InspectionToolWrapper toolWrapper = getToolWrapper();
if (toolWrapper != null) {
final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName());
if (key != null){//dummy entry points
final SuppressActionWrapper.SuppressTreeAction[] suppressActions = new SuppressActionWrapper(myView.getProject(), toolWrapper, myView.getTree().getSelectionPaths()).getChildren(null);
if (suppressActions.length > 0) {
final List<AnAction> activeSuppressActions = new ArrayList<AnAction>();
for (SuppressActionWrapper.SuppressTreeAction suppressAction : suppressActions) {
if (suppressAction.isAvailable()) {
activeSuppressActions.add(suppressAction);
}
}
if (!activeSuppressActions.isEmpty()) {
int idx = 0;
@NonNls final String br = "<br>";
buf.append(br);
HTMLComposerImpl.appendHeading(buf, InspectionsBundle.message("inspection.export.results.suppress"));
for (AnAction suppressAction : activeSuppressActions) {
buf.append(br);
if (idx == activeSuppressActions.size() - 1) {
buf.append(br);
}
HTMLComposer.appendAfterHeaderIndention(buf);
@NonNls final String href = "<a HREF=\"file://bred.txt#suppress:" + idx + "\">" + suppressAction.getTemplatePresentation().getText() + "</a>";
buf.append(href);
idx++;
}
}
}
}
}
}
private static void uppercaseFirstLetter(final StringBuffer buf) {
if (buf.length() > 1) {
char[] firstLetter = new char[1];
buf.getChars(0, 1, firstLetter, 0);
buf.setCharAt(0, Character.toUpperCase(firstLetter[0]));
}
}
@SuppressWarnings({"HardCodedStringLiteral"})
public void showEmpty() {
myCurrentEntity = null;
try {
myHTMLViewer.read(new StringReader(InspectionsBundle.message("inspection.offline.view.empty.browser.text")), null);
}
catch (IOException e) {
//can't be
}
}
public void showDescription(@NotNull InspectionToolWrapper toolWrapper){
if (toolWrapper.getShortName().isEmpty()){
showEmpty();
return;
}
@NonNls StringBuffer page = new StringBuffer();
page.append("<table border='0' cellspacing='0' cellpadding='0' width='100%'>");
page.append("<tr><td colspan='2'>");
HTMLComposer.appendHeading(page, InspectionsBundle.message("inspection.tool.in.browser.id.title"));
page.append("</td></tr>");
page.append("<tr><td width='37'></td>" +
"<td>");
page.append(toolWrapper.getShortName());
page.append("</td></tr>");
page.append("<tr height='10'></tr>");
page.append("<tr><td colspan='2'>");
HTMLComposer.appendHeading(page, InspectionsBundle.message("inspection.tool.in.browser.description.title"));
page.append("</td></tr>");
page.append("<tr><td width='37'></td>" +
"<td>");
@NonNls final String underConstruction = "<b>" + UNDER_CONSTRUCTION + "</b></html>";
try {
@NonNls String description = toolWrapper.loadDescription();
if (description == null) {
description = underConstruction;
}
page.append(DefaultInspectionToolPresentation.stripUIRefsFromInspectionDescription(UIUtil.getHtmlBody(description)));
page.append("</td></tr></table>");
myHTMLViewer.setText(XmlStringUtil.wrapInHtml(page));
setupStyle();
}
finally {
myCurrentEntity = null;
}
}
@Nullable
private InspectionToolWrapper getToolWrapper() {
return myView.getTree().getSelectedToolWrapper();
}
public void invokeLocalFix(int idx) {
if (myView.getTree().getSelectionCount() != 1) return;
final InspectionTreeNode node = (InspectionTreeNode)myView.getTree().getSelectionPath().getLastPathComponent();
if (node instanceof ProblemDescriptionNode) {
final ProblemDescriptionNode problemNode = (ProblemDescriptionNode)node;
final CommonProblemDescriptor descriptor = problemNode.getDescriptor();
final RefEntity element = problemNode.getElement();
invokeFix(element, descriptor, idx);
}
else if (node instanceof RefElementNode) {
RefElementNode elementNode = (RefElementNode)node;
RefEntity element = elementNode.getElement();
CommonProblemDescriptor descriptor = elementNode.getProblem();
if (descriptor != null) {
invokeFix(element, descriptor, idx);
}
}
}
private void invokeFix(final RefEntity element, final CommonProblemDescriptor descriptor, final int idx) {
final QuickFix[] fixes = descriptor.getFixes();
if (fixes != null && fixes.length > idx && fixes[idx] != null) {
if (element instanceof RefElement) {
PsiElement psiElement = ((RefElement)element).getElement();
if (psiElement != null && psiElement.isValid()) {
if (!FileModificationService.getInstance().preparePsiElementForWrite(psiElement)) return;
performFix(element, descriptor, idx, fixes[idx]);
}
}
else {
performFix(element, descriptor, idx, fixes[idx]);
}
}
}
private void performFix(final RefEntity element, final CommonProblemDescriptor descriptor, final int idx, final QuickFix fix) {
final Runnable command = new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
final PsiModificationTracker tracker = PsiManager.getInstance(myView.getProject()).getModificationTracker();
final long startCount = tracker.getModificationCount();
CommandProcessor.getInstance().markCurrentCommandAsGlobal(myView.getProject());
//CCE here means QuickFix was incorrectly inherited
fix.applyFix(myView.getProject(), descriptor);
if (startCount != tracker.getModificationCount()) {
InspectionToolWrapper toolWrapper = myView.getTree().getSelectedToolWrapper();
if (toolWrapper != null) {
InspectionToolPresentation presentation =
myView.getGlobalInspectionContext().getPresentation(toolWrapper);
presentation.ignoreProblem(element, descriptor, idx);
}
myView.updateView(false);
}
}
});
}
};
CommandProcessor.getInstance().executeCommand(myView.getProject(), command, fix.getName(), null);
}
}