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);
+ }
+}