/* * 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.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; } try { URL 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) { //??? } } 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("<html><body></body></html>"), 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(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); } }