diff --git a/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.properties b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.properties index 22601ac0a1..d5a0fe77c8 100644 --- a/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.properties +++ b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.properties @@ -33,4 +33,5 @@ view.violation = PMD Violations view.outline = Violations Outline view.overview = Violations Overview view.dataflowview = Dataflow View -view.cpd = CPD View \ No newline at end of file +view.cpd = CPD View +view.cpd2 = CPD View2 \ No newline at end of file diff --git a/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.xml b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.xml index a75aafe521..0672414c43 100644 --- a/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.xml +++ b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/plugin.xml @@ -479,12 +479,20 @@ category="net.sourceforge.pmd.eclipse.ui.views" name="%view.dataflowview" id="net.sourceforge.pmd.eclipse.ui.views.dataflowView"/> + + nameWidthsByName; + private TreeColumn messageColumn; // we adjust the width of this one + + private static final int SpanColumnWidth = 50; + private static final int MAX_MATCHES = 100; + private static final int xGap = 6; + public static final int SourceColumnIdx = 1; + + private static List asList(Iterator matchIter) { + + List matches = new ArrayList(MAX_MATCHES); + + for (int count = 0; matchIter.hasNext() && count < MAX_MATCHES; count++) { + matches.add(matchIter.next()); + } + + Collections.sort(matches, Match.MATCHES_COMPARATOR); + Collections.reverse(matches); + + return matches; + } + + public static String[] partsOf(String fullName) { + + int pos = fullName.lastIndexOf('.'); + + return new String[] { + fullName.substring(0, pos+1), + fullName.substring(pos+1) + }; + } + + public static String[] sourceLinesFrom(Match match, boolean trimLeadingWhitespace) { + + final String text = match.getSourceCodeSlice().replaceAll("\t", " "); + final StringTokenizer lines = new StringTokenizer(text, "\n"); + + List sourceLines = new ArrayList(); + + for (int i=0; lines.hasMoreTokens(); i++) { + String line = lines.nextToken(); + sourceLines.add(line); + } + + String[] lineArr = new String[sourceLines.size()]; + lineArr = sourceLines.toArray(lineArr); + + if (trimLeadingWhitespace) { + int trimDepth = StringUtil.maxCommonLeadingWhitespaceForAll(lineArr); + if (trimDepth > 0) { + lineArr = StringUtil.trimStartOn(lineArr, trimDepth); + } + } + return lineArr; + } + + /* + * @see org.eclipse.ui.ViewPart#init(org.eclipse.ui.IViewSite) + */ + @Override + public void init(IViewSite site) throws PartInitException { + super.init(site); + contentProvider = new TreeNodeContentProvider(); + labelProvider = new CPDViewLabelProvider2(); + + measureListener = new Listener() { + public void handleEvent(Event event) { + captureColumnWidths(); + } + }; + + resizeListener = new Listener() { + public void handleEvent(Event event) { + int width = treeViewer.getTree().getBounds().width; + messageColumn.setWidth(width - SpanColumnWidth); + captureColumnWidths(); + treeViewer.refresh(); + } + }; + + nameWidthsByName = new HashMap(); + } + + public int widthOf(int columnIndex) { + if (columnWidths == null) captureColumnWidths(); + return columnWidths[columnIndex]; + } + + private void captureColumnWidths() { + + TreeColumn[] columns = treeViewer.getTree().getColumns(); + columnWidths = new int[columns.length]; + + for (int i=0; i point.x) { + return i; + } + pos += columnWidths[i]; + } + + return -1; + } + + public int[] widthsFor(String name) { + return nameWidthsByName.get(name); + } + + private void paintName(GC gc, int x, int y, String name, int rightEdge, int descent, int cellWidth) { + + String[] parts = partsOf(name); + int packageWidth = 0; + int classWidth = 0; + + int[] widths = nameWidthsByName.get(name); + + if (widths != null) { + packageWidth = widths[0]; + classWidth = widths[1]; + } else { + gc.setFont( treeViewer.getTree().getFont() ); + packageWidth = gc.stringExtent(parts[0]).x; + classWidth = gc.stringExtent(parts[1]).x; + nameWidthsByName.put(name, new int[] {packageWidth, classWidth} ); + } + + int drawX = x + rightEdge - classWidth - xGap; +// Rectangle clipRect = new Rectangle(x, y, cellWidth, 24); + + gc.setForeground(classColor); +// gc.drawRectangle(clipRect); + gc.drawText(parts[1], drawX, y + descent, false); +// gc.setClipping((Rectangle)null); + + drawX = x + rightEdge - classWidth - packageWidth - xGap; +// clipRect.x = drawX; +// clipRect.width = packageWidth; + + gc.setForeground(packageColor); +// gc.drawRectangle(clipRect); + gc.drawText(parts[0], drawX, y + descent, false); +// gc.setClipping((Rectangle)null); + } + + private void addPainters(Tree tree) { + + Listener paintListener = new Listener() { + public void handleEvent(Event event) { + if (event.index != SourceColumnIdx) return; + + Object item = ((TreeNode)event.item.getData()).getValue(); + + String[] names = null; + if (item instanceof Match) { + names = CPDViewLabelProvider2.sourcesFor((Match)item); + } else { + return; + } + + int descent = event.gc.getFontMetrics().getDescent(); + int colWidth = widthOf(SourceColumnIdx); + int cellWidth = colWidth / names.length; + + for (int i=0; i matches) { + + List elements = new ArrayList(); + if (matches != null) { + + for (Match match : asList(matches)) { + + // create a treenode for the match and add to the list + TreeNode matchNode = new TreeNode(match); // NOPMD by Sven on 02.11.06 11:27 + elements.add(matchNode); + + String[] lines = sourceLinesFrom(match, true); + TreeNode[] children = new TreeNode[lines.length]; + + for (int j=0; j) { + Iterator iter = (Iterator) source; + // after setdata(iter) iter.hasNext will always return false + boolean hasResults = iter.hasNext(); + setData(iter); + if (!hasResults) { + // no entries + MessageBox box = new MessageBox(this.treeViewer.getControl().getShell()); + box.setText(getString(StringKeys.DIALOG_CPD_NORESULTS_HEADER)); + box.setMessage(getString(StringKeys.DIALOG_CPD_NORESULTS_BODY)); + box.open(); + } + } + } +} diff --git a/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/src/net/sourceforge/pmd/eclipse/ui/views/cpd2/CPDViewLabelProvider2.java b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/src/net/sourceforge/pmd/eclipse/ui/views/cpd2/CPDViewLabelProvider2.java new file mode 100644 index 0000000000..09d9df992c --- /dev/null +++ b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/src/net/sourceforge/pmd/eclipse/ui/views/cpd2/CPDViewLabelProvider2.java @@ -0,0 +1,181 @@ +/* + * Created on 13.10.2006 + * + * Copyright (c) 2006, PMD for Eclipse Development Team + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed in part by support from + * the Defense Advanced Research Project Agency (DARPA)" + * * Neither the name of "PMD for Eclipse Development Team" nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.sourceforge.pmd.eclipse.ui.views.cpd2; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import net.sourceforge.pmd.cpd.Match; +import net.sourceforge.pmd.cpd.TokenEntry; + +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.viewers.ITableLabelProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TreeNode; +import org.eclipse.swt.graphics.Image; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.ide.IDE.SharedImages; + +/** + * + * + * @author Brian Remedios + */ + +public class CPDViewLabelProvider2 extends LabelProvider implements ITableLabelProvider { + + /* + * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int) + */ + public Image getColumnImage(Object element, int columnIndex) { + Image image = null; + + final TreeNode node = (TreeNode) element; + final Object value = node.getValue(); + + // the second Column gets an Image depending on, + // if the Element is a Match or TokenEntry + switch (columnIndex) { + case 0: + if (value instanceof Match) { + // image = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJS_ERROR_TSK); + } else if (value instanceof TokenEntry) { + image = PlatformUI.getWorkbench().getSharedImages().getImage(SharedImages.IMG_OPEN_MARKER); + } + break; + + default: + // let the image null. + + } + + return image; + } + + private int lineCountFor(TreeNode node) { + + Object source = node.getValue(); + + if (source instanceof Match) { + return node.getChildren().length; + } + + return -1; + } + + public static String pathFor(TokenEntry entry) { + + final IPath path = Path.fromOSString(entry.getTokenSrcID()); + final IResource resource = ResourcesPlugin.getWorkspace().getRoot().getContainerForLocation(path); + if (resource != null) { + return resource.getProjectRelativePath().removeFileExtension().toString().replace(IPath.SEPARATOR, '.'); + } else { + return "?"; + } + } + + public static TokenEntry[] entriesFor(Match match) { + + Set entrySet = match.getMarkSet(); + TokenEntry[] entries = new TokenEntry[entrySet.size()]; + entries = entrySet.toArray(entries); + Arrays.sort(entries); + + return entries; + } + + public static String[] sourcesFor(Match match) { + + TokenEntry[] entries = entriesFor(match); + + String[] classNames = new String[entries.length]; + + int i = 0; + for (TokenEntry entry : entries) { + classNames[i++] = pathFor(entry); + } + + return classNames; + } + + public static Map entriesByClassnameFor(Match match) { + + TokenEntry[] entries = entriesFor(match); + Map entriesByName = new HashMap(entries.length); + + for (TokenEntry entry : entries) { + entriesByName.put( pathFor(entry), entry); + } + + return entriesByName; + } + + /* + * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int) + */ + public String getColumnText(Object element, int columnIndex) { + final TreeNode node = (TreeNode) element; + final Object value = node.getValue(); + String result = ""; + + switch (columnIndex) { + case 0: int count = lineCountFor(node); + if (count > 0) result = Integer.toString(count); + break; + // show the source + case 1: + if (value instanceof String) { + result = String.valueOf(value); + if (result.endsWith("\r")) result = result.substring(0, result.length()-1); + } + if (value instanceof Match) { + // do nothing, let the painter show it + } + break; + default: + // let text empty + } + + return result; + } + +} diff --git a/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/src/net/sourceforge/pmd/eclipse/ui/views/cpd2/CPDViewTooltipListener2.java b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/src/net/sourceforge/pmd/eclipse/ui/views/cpd2/CPDViewTooltipListener2.java new file mode 100644 index 0000000000..4b5bbde39c --- /dev/null +++ b/pmd-eclipse-plugin/plugins/net.sourceforge.pmd.eclipse.plugin/src/net/sourceforge/pmd/eclipse/ui/views/cpd2/CPDViewTooltipListener2.java @@ -0,0 +1,192 @@ +/* + * Created on 14.10.2006 + * + * Copyright (c) 2006, PMD for Eclipse Development Team + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The end-user documentation included with the redistribution, if + * any, must include the following acknowledgement: + * "This product includes software developed in part by support from + * the Defense Advanced Research Project Agency (DARPA)" + * * Neither the name of "PMD for Eclipse Development Team" nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package net.sourceforge.pmd.eclipse.ui.views.cpd2; + +import net.sourceforge.pmd.cpd.Match; +import net.sourceforge.pmd.cpd.TokenEntry; +import net.sourceforge.pmd.eclipse.plugin.PMDPlugin; +import net.sourceforge.pmd.eclipse.ui.nls.StringKeys; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.viewers.TreeNode; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; +import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.texteditor.ITextEditor; + +/** + * + * + */ +public class CPDViewTooltipListener2 implements Listener { + + private final CPDView2 view; + private Cursor normalCursor; + private Cursor handCursor; + + public CPDViewTooltipListener2(CPDView2 view) { + this.view = view; + initialize(); + } + + private void initialize() { + Display disp = Display.getCurrent(); + normalCursor = disp.getSystemCursor(SWT.CURSOR_ARROW); + handCursor = disp.getSystemCursor(SWT.CURSOR_HAND); + } + + // open file and jump to the startline + private void highlight(Match match, TokenEntry entry) { + + IPath path = Path.fromOSString(entry.getTokenSrcID()); + IFile file = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(path); + if (file == null) return; + + try { + // open editor + IWorkbenchPage page = view.getSite().getPage(); + IEditorPart part = IDE.openEditor(page, file); + if (part instanceof ITextEditor) { + // select text + ITextEditor textEditor = (ITextEditor) part; + IDocument document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput()); + int offset = document.getLineOffset(entry.getBeginLine()-1); + int length = document.getLineOffset(entry.getBeginLine()-1 + match.getLineCount()) - offset -1; + textEditor.selectAndReveal(offset, length); + } + } catch (PartInitException pie) { + PMDPlugin.getDefault().logError(getString(StringKeys.ERROR_VIEW_EXCEPTION), pie); + } catch (BadLocationException ble) { + PMDPlugin.getDefault().logError(getString(StringKeys.ERROR_VIEW_EXCEPTION), ble); + } + } + + private static Match matchAt(TreeItem treeItem) { + + Object item = ((TreeNode) treeItem.getData()).getValue(); + return item instanceof Match ? (Match)item : null; + } + + private TokenEntry itemAt(TreeItem treeItem, Point location, GC gc) { + + if (treeItem == null) return null; + + Object item = ((TreeNode) treeItem.getData()).getValue(); + + String[] names = null; + if (item instanceof Match) { + names = CPDViewLabelProvider2.sourcesFor((Match) item); + } else { + return null; + } + + location.x -= view.widthOf(0); // subtract width of preceeding columns + + int colWidth = view.widthOf(CPDView2.SourceColumnIdx); + int cellWidth = colWidth / names.length; + + for (int i=0; i rightEdge-classWidth && // right of the start? + location.x < rightEdge) { // left of the end? + return CPDViewLabelProvider2.entriesFor((Match)item)[i]; + } + } + + return null; + } + + /* + * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) + */ + public void handleEvent(Event event) { + + Tree tree = view.getTreeViewer().getTree(); + Point location = new Point(event.x, event.y); + Shell shell = tree.getShell(); + + if (view.inColumn(location) != CPDView2.SourceColumnIdx) { + shell.setCursor(normalCursor); + return; + } + + TreeItem item = tree.getItem(location); + TokenEntry entry = itemAt(item, location, event.gc); + if (entry == null) { + shell.setCursor(normalCursor); + return; + } + + switch (event.type) { + case SWT.MouseDown: + highlight(matchAt(item), entry); + break; + case SWT.MouseMove: + case SWT.MouseHover: + shell.setCursor(handCursor); + break; + default: + break; + } + + } + + /** + * Helper method to return an NLS string from its key + */ + private String getString(String key) { + return PMDPlugin.getDefault().getStringTable().getString(key); + } +}