From 9fc8a56e316719999586a5f5f0845c9d1671d30b Mon Sep 17 00:00:00 2001 From: Maikel Steneker Date: Wed, 29 Jun 2022 11:35:36 +0200 Subject: [PATCH 01/13] Add total number of tokens to XML reports --- .../pmd/userdocs/cpd/cpd_report_formats.md | 2 + .../java/net/sourceforge/pmd/cpd/CPD.java | 16 ++++++- .../sourceforge/pmd/cpd/CPDConfiguration.java | 44 ++++++++++++++++++- .../net/sourceforge/pmd/cpd/CPDReport.java | 26 +++++++++++ .../java/net/sourceforge/pmd/cpd/CPDTask.java | 14 +++--- .../java/net/sourceforge/pmd/cpd/GUI.java | 41 +++++++++-------- .../net/sourceforge/pmd/cpd/XMLRenderer.java | 33 ++++++++++++-- .../pmd/cpd/renderer/CPDRenderer.java | 4 ++ .../pmd/cpd/renderer/CPDRendererAdapter.java | 23 ++++++++++ .../pmd/cpd/renderer/CPDReportRenderer.java | 14 ++++++ .../pmd/cpd/CPDCommandLineInterfaceTest.java | 42 +++++++++++++++--- .../sourceforge/pmd/cpd/XMLRendererTest.java | 35 +++++++++++++++ 12 files changed, 257 insertions(+), 37 deletions(-) create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java create mode 100644 pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDReportRenderer.java diff --git a/docs/pages/pmd/userdocs/cpd/cpd_report_formats.md b/docs/pages/pmd/userdocs/cpd/cpd_report_formats.md index db4ac9846c..4e1a23a0d0 100644 --- a/docs/pages/pmd/userdocs/cpd/cpd_report_formats.md +++ b/docs/pages/pmd/userdocs/cpd/cpd_report_formats.md @@ -101,6 +101,8 @@ Example: ```xml + + diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index 47bc1c021c..b0bb3a1dd7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -10,6 +10,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStreamWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -21,6 +22,7 @@ import java.util.logging.Logger; import net.sourceforge.pmd.annotation.Experimental; import net.sourceforge.pmd.cli.internal.CliMessages; +import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.util.FileFinder; import net.sourceforge.pmd.util.IOUtil; @@ -38,6 +40,8 @@ public class CPD { private Tokens tokens = new Tokens(); private MatchAlgorithm matchAlgorithm; private Set current = new HashSet<>(); + private final Map numberOfTokensPerFile = new HashMap<>(); + private int lastTokenSize = 0; public CPD(CPDConfiguration theConfiguration) { configuration = theConfiguration; @@ -59,6 +63,10 @@ public class CPD { return matchAlgorithm.matches(); } + public Map getNumberOfTokensPerFile() { + return numberOfTokensPerFile; + } + public void addAllInDirectory(File dir) throws IOException { addDirectory(dir, false); } @@ -145,6 +153,8 @@ public class CPD { configuration.tokenizer().tokenize(sourceCode, tokens); listener.addedFile(1, new File(sourceCode.getFileName())); source.put(sourceCode.getFileName(), sourceCode); + numberOfTokensPerFile.put(sourceCode.getFileName(), tokens.size() - lastTokenSize - 1 /*EOF*/); + lastTokenSize = tokens.size(); } private void addAndSkipLexicalErrors(SourceCode sourceCode) throws IOException { @@ -207,11 +217,13 @@ public class CPD { CPDCommandLineInterface.addSourceFilesToCPD(cpd, arguments); cpd.go(); - if (arguments.getCPDRenderer() == null) { + final CPDReportRenderer renderer = arguments.getCPDReportRenderer(); + if (renderer == null) { // legacy writer System.out.println(arguments.getRenderer().render(cpd.getMatches())); } else { - arguments.getCPDRenderer().render(cpd.getMatches(), new BufferedWriter(new OutputStreamWriter(System.out))); + final CPDReport report = new CPDReport(cpd.getMatches(), cpd.numberOfTokensPerFile); + renderer.render(report, new BufferedWriter(new OutputStreamWriter(System.out))); } if (cpd.getMatches().hasNext()) { if (arguments.isFailOnViolation()) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java index 6a56ec7ff0..0ce6fc4908 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -22,6 +22,8 @@ import java.util.Set; import net.sourceforge.pmd.AbstractConfiguration; import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.cpd.renderer.CPDRendererAdapter; +import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; import net.sourceforge.pmd.util.FileFinder; import net.sourceforge.pmd.util.FileUtil; @@ -72,8 +74,11 @@ public class CPDConfiguration extends AbstractConfiguration { @Deprecated private Renderer renderer; + @Deprecated private CPDRenderer cpdRenderer; + private CPDReportRenderer cpdReportRenderer; + private String encoding; @Parameter(names = "--ignore-literals", @@ -170,7 +175,12 @@ public class CPDConfiguration extends AbstractConfiguration { } if (getRenderer() == null && getCPDRenderer() == null) { try { - setCPDRenderer(getCPDRendererFromString(getRendererName(), getEncoding())); + try { + setCPDReportRenderer(getCPDReportRendererFromString(getRendererName(), getEncoding())); + } catch (ClassCastException e) { + // The renderer class configured is not using the new CPDReportRenderer interface... + setCPDRenderer(getCPDRendererFromString(getRendererName(), getEncoding())); + } } catch (ClassCastException e) { // The renderer class configured is not using the new CPDRenderer interface... setRenderer(getRendererFromString(getRendererName(), getEncoding())); @@ -207,6 +217,10 @@ public class CPDConfiguration extends AbstractConfiguration { } } + /** + * @deprecated use {@link #getCPDReportRendererFromString(String, String)} + */ + @Deprecated public static CPDRenderer getCPDRendererFromString(String name, String encoding) { String clazzname = name; if (clazzname == null || "".equals(clazzname)) { @@ -231,6 +245,15 @@ public class CPDConfiguration extends AbstractConfiguration { } } + public static CPDReportRenderer getCPDReportRendererFromString(String name, String encoding) { + final CPDRenderer renderer = getCPDRendererFromString(name, encoding); + if (renderer instanceof CPDReportRenderer) { + return (CPDReportRenderer) renderer; + } else { + return new CPDRendererAdapter(renderer); + } + } + private static void setRendererEncoding(Object renderer, String encoding) throws IllegalAccessException, InvocationTargetException { try { @@ -326,10 +349,18 @@ public class CPDConfiguration extends AbstractConfiguration { return renderer; } + /** + * @deprecated Use {@link #getCPDReportRenderer()} instead + */ + @Deprecated public CPDRenderer getCPDRenderer() { return cpdRenderer; } + public CPDReportRenderer getCPDReportRenderer() { + return cpdReportRenderer; + } + public Tokenizer tokenizer() { if (language == null) { throw new IllegalStateException("Language is null."); @@ -382,11 +413,22 @@ public class CPDConfiguration extends AbstractConfiguration { this.cpdRenderer = null; } + /** + * @deprecated Use {@link #setCPDReportRenderer(CPDReportRenderer)} instead + * @param renderer + */ + @Deprecated public void setCPDRenderer(CPDRenderer renderer) { this.cpdRenderer = renderer; this.renderer = null; } + public void setCPDReportRenderer(CPDReportRenderer renderer) { + this.renderer = null; + this.cpdRenderer = null; + this.cpdReportRenderer = renderer; + } + public boolean isIgnoreLiterals() { return ignoreLiterals; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java new file mode 100644 index 0000000000..8725162c1f --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.util.Iterator; +import java.util.Map; + +public class CPDReport { + private final Iterator matches; + private final Map numberOfTokensPerFile; + + public CPDReport(final Iterator matches, final Map numberOfTokensPerFile) { + this.matches = matches; + this.numberOfTokensPerFile = numberOfTokensPerFile; + } + + public Iterator getMatches() { + return matches; + } + + public Map getNumberOfTokensPerFile() { + return numberOfTokensPerFile; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java index 18a39fdb3f..da1a9fe1a2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java @@ -23,7 +23,8 @@ import org.apache.tools.ant.Task; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.types.FileSet; -import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.cpd.renderer.CPDRendererAdapter; +import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; /** * CPDTask @@ -130,7 +131,8 @@ public class CPDTask extends Task { if (!cpd.getMatches().hasNext()) { log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO); } - CPDRenderer renderer = createRenderer(); + CPDReportRenderer renderer = createRenderer(); + CPDReport report = new CPDReport(cpd.getMatches(), cpd.getNumberOfTokensPerFile()); try { // will be closed via BufferedWriter/OutputStreamWriter chain down below @@ -148,7 +150,7 @@ public class CPDTask extends Task { } try (Writer writer = new BufferedWriter(new OutputStreamWriter(os, encoding))) { - renderer.render(cpd.getMatches(), writer); + renderer.render(report, writer); } } catch (IOException ioe) { throw new ReportException(ioe); @@ -175,11 +177,11 @@ public class CPDTask extends Task { return stop - start; } - private CPDRenderer createRenderer() { + private CPDReportRenderer createRenderer() { if (TEXT_FORMAT.equals(format)) { - return new SimpleRenderer(); + return new CPDRendererAdapter(new SimpleRenderer()); } else if (CSV_FORMAT.equals(format)) { - return new CSVRenderer(); + return new CPDRendererAdapter(new CSVRenderer()); } return new XMLRenderer(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java index b970880aa0..8be97c21a4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java @@ -66,7 +66,7 @@ import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import net.sourceforge.pmd.PMDVersion; -import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; public class GUI implements CPDListener { @@ -74,25 +74,25 @@ public class GUI implements CPDListener { // String render(Iterator items); // } - private static final Object[][] RENDERER_SETS = new Object[][] { { "Text", new CPDRenderer() { + private static final Object[][] RENDERER_SETS = new Object[][] { { "Text", new CPDReportRenderer() { @Override - public void render(Iterator items, Writer writer) throws IOException { - new SimpleRenderer().render(items, writer); + public void render(CPDReport report, Writer writer) throws IOException { + new SimpleRenderer().render(report.getMatches(), writer); } - }, }, { "XML", new CPDRenderer() { + }, }, { "XML", new CPDReportRenderer() { @Override - public void render(Iterator items, Writer writer) throws IOException { - new XMLRenderer().render(items, writer); + public void render(CPDReport report, Writer writer) throws IOException { + new XMLRenderer().render(report, writer); } - }, }, { "CSV (comma)", new CPDRenderer() { + }, }, { "CSV (comma)", new CPDReportRenderer() { @Override - public void render(Iterator items, Writer writer) throws IOException { - new CSVRenderer(',').render(items, writer); + public void render(CPDReport report, Writer writer) throws IOException { + new CSVRenderer(',').render(report.getMatches(), writer); } - }, }, { "CSV (tab)", new CPDRenderer() { + }, }, { "CSV (tab)", new CPDReportRenderer() { @Override - public void render(Iterator items, Writer writer) throws IOException { - new CSVRenderer('\t').render(items, writer); + public void render(CPDReport report, Writer writer) throws IOException { + new CSVRenderer('\t').render(report.getMatches(), writer); } }, }, }; @@ -280,9 +280,9 @@ public class GUI implements CPDListener { private class SaveListener implements ActionListener { - final CPDRenderer renderer; + final CPDReportRenderer renderer; - SaveListener(CPDRenderer theRenderer) { + SaveListener(CPDReportRenderer theRenderer) { renderer = theRenderer; } @@ -296,8 +296,9 @@ public class GUI implements CPDListener { } if (!f.canWrite()) { + final CPDReport report = new CPDReport(matches.iterator(), numberOfTokensPerFile); try (PrintWriter pw = new PrintWriter(Files.newOutputStream(f.toPath()))) { - renderer.render(matches.iterator(), pw); + renderer.render(report, pw); pw.flush(); JOptionPane.showMessageDialog(frame, "Saved " + matches.size() + " matches"); } catch (IOException e) { @@ -372,14 +373,15 @@ public class GUI implements CPDListener { private boolean trimLeadingWhitespace; private List matches = new ArrayList<>(); + private Map numberOfTokensPerFile; private void addSaveOptionsTo(JMenu menu) { JMenuItem saveItem; - for (int i = 0; i < RENDERER_SETS.length; i++) { - saveItem = new JMenuItem("Save as " + RENDERER_SETS[i][0]); - saveItem.addActionListener(new SaveListener((CPDRenderer) RENDERER_SETS[i][1])); + for (final Object[] rendererSet : RENDERER_SETS) { + saveItem = new JMenuItem("Save as " + rendererSet[0]); + saveItem.addActionListener(new SaveListener((CPDReportRenderer) rendererSet[1])); menu.add(saveItem); } } @@ -728,6 +730,7 @@ public class GUI implements CPDListener { cpd.go(); t.stop(); + numberOfTokensPerFile = cpd.getNumberOfTokensPerFile(); matches = new ArrayList<>(); for (Iterator i = cpd.getMatches(); i.hasNext();) { Match match = i.next(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java index 23bece7739..7ded7a7186 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -7,7 +7,12 @@ package net.sourceforge.pmd.cpd; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.Iterator; +import java.util.List; +import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -22,6 +27,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; import net.sourceforge.pmd.util.StringUtil; /** @@ -29,7 +35,7 @@ import net.sourceforge.pmd.util.StringUtil; * @author Romain Pelisse - javax.xml implementation * */ -public final class XMLRenderer implements Renderer, CPDRenderer { +public final class XMLRenderer implements Renderer, CPDRenderer, CPDReportRenderer { private String encoding; @@ -101,10 +107,31 @@ public final class XMLRenderer implements Renderer, CPDRenderer { @Override public void render(Iterator matches, Writer writer) throws IOException { - Document doc = createDocument(); - Element root = doc.createElement("pmd-cpd"); + render(new CPDReport(matches, Collections.emptyMap()), writer); + } + + @Override + public void render(final CPDReport report, final Writer writer) throws IOException { + final Document doc = createDocument(); + final Element root = doc.createElement("pmd-cpd"); + final Map numberOfTokensPerFile = report.getNumberOfTokensPerFile(); + final Iterator matches = report.getMatches(); doc.appendChild(root); + final List> entries = new ArrayList<>(numberOfTokensPerFile.entrySet()); + Collections.sort(entries, new Comparator>() { + @Override + public int compare(final Map.Entry entry1, final Map.Entry entry2) { + return entry1.getKey().compareTo(entry2.getKey()); + } + }); + for (final Map.Entry pair : entries) { + final Element fileElement = doc.createElement("file"); + fileElement.setAttribute("path", pair.getKey()); + fileElement.setAttribute("totalNumberOfTokens", String.valueOf(pair.getValue())); + root.appendChild(fileElement); + } + Match match; while (matches.hasNext()) { match = matches.next(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java index 4f3b601f04..d8d880284e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java @@ -10,6 +10,10 @@ import java.util.Iterator; import net.sourceforge.pmd.cpd.Match; +/** + * @deprecated Use {@link CPDReportRenderer} + */ +@Deprecated public interface CPDRenderer { void render(Iterator matches, Writer writer) throws IOException; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java new file mode 100644 index 0000000000..8a358ae634 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd.renderer; + +import java.io.IOException; +import java.io.Writer; + +import net.sourceforge.pmd.cpd.CPDReport; + +public class CPDRendererAdapter implements CPDReportRenderer { + private final CPDRenderer renderer; + + public CPDRendererAdapter(final CPDRenderer renderer) { + this.renderer = renderer; + } + + @Override + public void render(final CPDReport report, final Writer writer) throws IOException { + renderer.render(report.getMatches(), writer); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDReportRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDReportRenderer.java new file mode 100644 index 0000000000..ccae3203c0 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDReportRenderer.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd.renderer; + +import java.io.IOException; +import java.io.Writer; + +import net.sourceforge.pmd.cpd.CPDReport; + +public interface CPDReportRenderer { + void render(CPDReport report, Writer writer) throws IOException; +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java index 2a04e0120b..ce680e4c5b 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java @@ -11,7 +11,11 @@ import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; import org.junit.Assert; import org.junit.Rule; @@ -22,8 +26,16 @@ import org.junit.rules.TemporaryFolder; import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.junit.JavaUtilLoggingRule; +import com.google.common.collect.ImmutableMap; + public class CPDCommandLineInterfaceTest { private static final String SRC_DIR = "src/test/resources/net/sourceforge/pmd/cpd/files/"; + private static final Map NUMBER_OF_TOKENS = ImmutableMap.of( + new File(SRC_DIR, "dup1.java").getAbsolutePath(), 126, + new File(SRC_DIR, "dup2.java").getAbsolutePath(), 126, + new File(SRC_DIR, "file_with_ISO-8859-1_encoding.java").getAbsolutePath(), 32, + new File(SRC_DIR, "file_with_utf8_bom.java").getAbsolutePath(), 29 + ); @Rule public final SystemOutRule log = new SystemOutRule().enableLog().muteForSuccessfulTests(); @@ -33,25 +45,43 @@ public class CPDCommandLineInterfaceTest { public TemporaryFolder tempDir = new TemporaryFolder(); + private String getExpectedFileEntryXml(final String filename) { + final int numberOfTokens = NUMBER_OF_TOKENS.get(filename); + return String.format(" \n", + new File(filename).getAbsolutePath(), + numberOfTokens); + } + + private String getExpectedFileEntriesXml(final Collection filenames) { + final StringBuilder expectedFilesXmlBuilder = new StringBuilder(); + for (final String filename : filenames) { + expectedFilesXmlBuilder.append(getExpectedFileEntryXml(filename)); + } + return expectedFilesXmlBuilder.toString(); + } + @Test public void testEmptyResultRendering() { CPD.StatusCode statusCode = CPD.runCpd("--minimum-tokens", "340", "--language", "java", "--files", SRC_DIR, "--format", "xml"); + final String expectedFilesXml = getExpectedFileEntriesXml(NUMBER_OF_TOKENS.keySet()); Assert.assertEquals(CPD.StatusCode.OK, statusCode); - Assert.assertEquals("" + "\n" + "", log.getLog()); + Assert.assertEquals("" + "\n" + "" + "\n" + expectedFilesXml + "", log.getLog()); } @Test public void testDeprecatedOptionsWarning() throws IOException { - File filelist = new File(tempDir.getRoot(), "cpd-test-file-list.txt"); - Files.write(filelist.toPath(), Arrays.asList( + final List filepaths = Arrays.asList( new File(SRC_DIR, "dup1.java").getAbsolutePath(), - new File(SRC_DIR, "dup2.java").getAbsolutePath()), StandardCharsets.UTF_8); + new File(SRC_DIR, "dup2.java").getAbsolutePath()); + final Path filelist = tempDir.getRoot().toPath().resolve("cpd-test-file-list.txt"); + Files.write(filelist, filepaths, StandardCharsets.UTF_8); + final String expectedFilesXml = getExpectedFileEntriesXml(filepaths); CPD.StatusCode statusCode = CPD.runCpd("--minimum-tokens", "340", "--language", "java", "--filelist", - filelist.getAbsolutePath(), "--format", "xml", "-failOnViolation", "true"); + filelist.toAbsolutePath().toString(), "--format", "xml", "-failOnViolation", "true"); Assert.assertEquals(CPD.StatusCode.OK, statusCode); - Assert.assertEquals("" + "\n" + "", log.getLog()); + Assert.assertEquals("" + "\n" + "" + "\n" + expectedFilesXml + "", log.getLog()); assertTrue(loggingRule.getLog().contains("Some deprecated options were used on the command-line, including -failOnViolation")); assertTrue(loggingRule.getLog().contains("Consider replacing it with --fail-on-violation")); // only one parameter is logged diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java index 406c8183aa..59eda45229 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java @@ -13,15 +13,19 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; import org.junit.Test; import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import net.sourceforge.pmd.cpd.renderer.CPDRenderer; +import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; /** * @author Philippe T'Seyen @@ -184,6 +188,37 @@ public class XMLRendererTest { } } + @Test + public void testFilesWithNumberOfTokens() throws IOException { + final CPDReportRenderer renderer = new XMLRenderer(); + final List matches = new ArrayList<>(); + final String filename = "/var/Foo.java"; + final int lineCount = 6; + final String codeFragment = "code\nfragment"; + final Mark mark1 = createMark("public", filename, 1, lineCount, codeFragment, 2, 3); + final Mark mark2 = createMark("stuff", filename, 73, lineCount, codeFragment, 4, 5); + final Match match = new Match(75, mark1, mark2); + matches.add(match); + final Map numberOfTokensPerFile = new HashMap<>(); + numberOfTokensPerFile.put(filename, 888); + final CPDReport report = new CPDReport(matches.iterator(), numberOfTokensPerFile); + final StringWriter writer = new StringWriter(); + renderer.render(report, writer); + final String xmlOutput = writer.toString(); + try { + final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new ByteArrayInputStream(xmlOutput.getBytes(ENCODING))); + final NodeList files = doc.getElementsByTagName("file"); + final Node file = files.item(0); + final NamedNodeMap attributes = file.getAttributes(); + assertEquals("/var/Foo.java", attributes.getNamedItem("path").getNodeValue()); + assertEquals("888", attributes.getNamedItem("totalNumberOfTokens").getNodeValue()); + } catch (Exception e) { + e.printStackTrace(); + fail(e.getMessage()); + } + } + @Test public void testRendererEncodedPath() throws IOException { CPDRenderer renderer = new XMLRenderer(); From d544efee83f309af619289855eab557aa31b3cda Mon Sep 17 00:00:00 2001 From: Maikel Steneker Date: Thu, 30 Jun 2022 11:11:58 +0200 Subject: [PATCH 02/13] Improve encapsulation of CPD report contents --- .../src/main/java/net/sourceforge/pmd/cpd/CPD.java | 10 +++++----- .../java/net/sourceforge/pmd/cpd/CPDConfiguration.java | 8 ++++---- .../main/java/net/sourceforge/pmd/cpd/CPDReport.java | 3 ++- .../src/main/java/net/sourceforge/pmd/cpd/CPDTask.java | 2 +- .../src/main/java/net/sourceforge/pmd/cpd/GUI.java | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index b0bb3a1dd7..25feb9d737 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -63,10 +63,6 @@ public class CPD { return matchAlgorithm.matches(); } - public Map getNumberOfTokensPerFile() { - return numberOfTokensPerFile; - } - public void addAllInDirectory(File dir) throws IOException { addDirectory(dir, false); } @@ -222,7 +218,7 @@ public class CPD { // legacy writer System.out.println(arguments.getRenderer().render(cpd.getMatches())); } else { - final CPDReport report = new CPDReport(cpd.getMatches(), cpd.numberOfTokensPerFile); + final CPDReport report = cpd.toReport(); renderer.render(report, new BufferedWriter(new OutputStreamWriter(System.out))); } if (cpd.getMatches().hasNext()) { @@ -242,6 +238,10 @@ public class CPD { return statusCode; } + public CPDReport toReport() { + return new CPDReport(this.getMatches(), numberOfTokensPerFile); + } + public enum StatusCode { OK(0), ERROR(1), diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java index 0ce6fc4908..a9f6ef8967 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -176,7 +176,7 @@ public class CPDConfiguration extends AbstractConfiguration { if (getRenderer() == null && getCPDRenderer() == null) { try { try { - setCPDReportRenderer(getCPDReportRendererFromString(getRendererName(), getEncoding())); + setRenderer(getCPDReportRendererFromString(getRendererName(), getEncoding())); } catch (ClassCastException e) { // The renderer class configured is not using the new CPDReportRenderer interface... setCPDRenderer(getCPDRendererFromString(getRendererName(), getEncoding())); @@ -357,7 +357,7 @@ public class CPDConfiguration extends AbstractConfiguration { return cpdRenderer; } - public CPDReportRenderer getCPDReportRenderer() { + CPDReportRenderer getCPDReportRenderer() { return cpdReportRenderer; } @@ -414,7 +414,7 @@ public class CPDConfiguration extends AbstractConfiguration { } /** - * @deprecated Use {@link #setCPDReportRenderer(CPDReportRenderer)} instead + * @deprecated Use {@link #setRenderer(CPDReportRenderer)} instead * @param renderer */ @Deprecated @@ -423,7 +423,7 @@ public class CPDConfiguration extends AbstractConfiguration { this.renderer = null; } - public void setCPDReportRenderer(CPDReportRenderer renderer) { + public void setRenderer(CPDReportRenderer renderer) { this.renderer = null; this.cpdRenderer = null; this.cpdReportRenderer = renderer; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java index 8725162c1f..2ffe596ee5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.cpd; +import java.util.Collections; import java.util.Iterator; import java.util.Map; @@ -21,6 +22,6 @@ public class CPDReport { } public Map getNumberOfTokensPerFile() { - return numberOfTokensPerFile; + return Collections.unmodifiableMap(numberOfTokensPerFile); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java index da1a9fe1a2..1999ba4291 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java @@ -132,7 +132,7 @@ public class CPDTask extends Task { log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO); } CPDReportRenderer renderer = createRenderer(); - CPDReport report = new CPDReport(cpd.getMatches(), cpd.getNumberOfTokensPerFile()); + CPDReport report = cpd.toReport(); try { // will be closed via BufferedWriter/OutputStreamWriter chain down below diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java index 8be97c21a4..bf77515797 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java @@ -730,7 +730,7 @@ public class GUI implements CPDListener { cpd.go(); t.stop(); - numberOfTokensPerFile = cpd.getNumberOfTokensPerFile(); + numberOfTokensPerFile = cpd.toReport().getNumberOfTokensPerFile(); matches = new ArrayList<>(); for (Iterator i = cpd.getMatches(); i.hasNext();) { Match match = i.next(); From a15758fcf998b7f6a5d65cab1892de4e9ea8f063 Mon Sep 17 00:00:00 2001 From: Maikel Steneker Date: Thu, 30 Jun 2022 11:56:44 +0200 Subject: [PATCH 03/13] Hide CPDReport constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Fournier --- pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java index 2ffe596ee5..52ad6a5722 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java @@ -12,7 +12,7 @@ public class CPDReport { private final Iterator matches; private final Map numberOfTokensPerFile; - public CPDReport(final Iterator matches, final Map numberOfTokensPerFile) { + CPDReport(final Iterator matches, final Map numberOfTokensPerFile) { this.matches = matches; this.numberOfTokensPerFile = numberOfTokensPerFile; } From 157e623c68da708dc63ead612771be9af266dbb7 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 30 Jun 2022 19:24:47 +0200 Subject: [PATCH 04/13] [core] If report is written to stdout, stdout should not be closed Fixes #4031 --- docs/pages/release_notes.md | 2 ++ .../java/net/sourceforge/pmd/util/IOUtil.java | 10 +++++-- .../net/sourceforge/pmd/cli/CoreCliTest.java | 25 ++++++++++++++--- .../net/sourceforge/pmd/util/IOUtilTest.java | 28 +++++++++++++++++++ 4 files changed, 59 insertions(+), 6 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b8f8783555..f44db3c391 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -15,6 +15,8 @@ This is a {{ site.pmd.release_type }} release. ### New and noteworthy ### Fixed Issues +* core + * [#4031](https://github.com/pmd/pmd/issues/4031): \[core] If report is written to stdout, stdout should not be closed ### API Changes diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java index ebe98f9dbe..9867d43bc7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java @@ -8,6 +8,7 @@ import java.io.BufferedReader; import java.io.Closeable; import java.io.File; import java.io.FilterInputStream; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -50,7 +51,7 @@ public final class IOUtil { } public static Writer createWriter() { - return new OutputStreamWriter(System.out); + return createWriter(null); } /** @@ -103,7 +104,12 @@ public final class IOUtil { public static Writer createWriter(Charset charset, String reportFile) { try { if (StringUtils.isBlank(reportFile)) { - return new OutputStreamWriter(System.out, charset); + return new OutputStreamWriter(new FilterOutputStream(System.out) { + @Override + public void close() { + // do nothing, avoid closing stdout + } + }, charset); } Path path = new File(reportFile).toPath().toAbsolutePath(); Files.createDirectories(path.getParent()); // ensure parent dir exists diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java index 5b595e0f5b..f113bc3037 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java @@ -8,12 +8,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import java.io.FilterOutputStream; import java.io.IOException; +import java.io.PrintStream; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystems; @@ -190,6 +189,24 @@ public class CoreCliTest { } } + @Test + public void testReportToStdoutNotClosing() { + PrintStream originalOut = System.out; + PrintStream out = new PrintStream(new FilterOutputStream(originalOut) { + @Override + public void close() { + fail("Stream must not be closed"); + } + }); + try { + System.setOut(out); + startCapturingErrAndOut(); + runPmd(StatusCode.VIOLATIONS_FOUND, "--no-cache", "--dir", srcDir, "--rulesets", "dummy-basic"); + } finally { + System.setOut(originalOut); + } + } + @Test public void testDeprecatedRulesetSyntaxOnCommandLine() { startCapturingErrAndOut(); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java index b577de8f11..c98d484546 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java @@ -7,12 +7,15 @@ package net.sourceforge.pmd.util; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.CharArrayReader; +import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintStream; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; +import java.io.Writer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -23,6 +26,9 @@ import org.junit.Assert; import org.junit.Test; import org.junit.function.ThrowingRunnable; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + public class IOUtilTest { @Test @@ -296,4 +302,26 @@ public class IOUtilTest { InputStream stream = new ByteArrayInputStream(testString.getBytes(StandardCharsets.UTF_8)); Assert.assertEquals(testString, IOUtil.readToString(stream, StandardCharsets.UTF_8)); } + + @Test + public void testCreateWriterStdout() throws IOException { + PrintStream originalOut = System.out; + ByteArrayOutputStream data = new ByteArrayOutputStream(); + PrintStream out = new PrintStream(new FilterOutputStream(data) { + @Override + public void close() { + fail("Stream must not be closed"); + } + }); + + try { + System.setOut(out); + Writer writer = IOUtil.createWriter(); + writer.write("Test"); + writer.close(); + assertEquals("Test", data.toString()); + } finally { + System.setOut(originalOut); + } + } } From 5d3ffca1fc6c644ecb39bda21bc2d4bab353286b Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 30 Jun 2022 19:43:40 +0200 Subject: [PATCH 05/13] Fix imports (checkstyle) --- .../src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java | 6 +++++- .../src/test/java/net/sourceforge/pmd/util/IOUtilTest.java | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java index f113bc3037..0c5f6a62e3 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/CoreCliTest.java @@ -8,7 +8,11 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsStringIgnoringCase; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.FilterOutputStream; import java.io.IOException; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java index c98d484546..d29cc270ef 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/util/IOUtilTest.java @@ -4,6 +4,9 @@ package net.sourceforge.pmd.util; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.CharArrayReader; @@ -26,9 +29,6 @@ import org.junit.Assert; import org.junit.Test; import org.junit.function.ThrowingRunnable; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - public class IOUtilTest { @Test From fb79f527e83b8a70075456d8638881256f36aa0c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 30 Jun 2022 19:48:26 +0200 Subject: [PATCH 06/13] [core] Avoid closing stdout for CPD as well --- pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java | 5 ++--- .../src/main/java/net/sourceforge/pmd/util/IOUtil.java | 8 ++++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index 47bc1c021c..02ce60bb57 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -4,11 +4,10 @@ package net.sourceforge.pmd.cpd; -import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.OutputStreamWriter; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -211,7 +210,7 @@ public class CPD { // legacy writer System.out.println(arguments.getRenderer().render(cpd.getMatches())); } else { - arguments.getCPDRenderer().render(cpd.getMatches(), new BufferedWriter(new OutputStreamWriter(System.out))); + arguments.getCPDRenderer().render(cpd.getMatches(), IOUtil.createWriter(Charset.defaultCharset(), null)); } if (cpd.getMatches().hasNext()) { if (arguments.isFailOnViolation()) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java index 9867d43bc7..1206963cd8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/IOUtil.java @@ -50,6 +50,14 @@ public final class IOUtil { private IOUtil() { } + /** + * Creates a writer that writes to stdout using the system default charset. + * + * @return a writer, never null + * + * @see #createWriter(String) + * @see #createWriter(Charset, String) + */ public static Writer createWriter() { return createWriter(null); } From f4dd873fc20c86f0c0fa87585686881bc8cbc88e Mon Sep 17 00:00:00 2001 From: Maikel Steneker Date: Fri, 1 Jul 2022 10:34:17 +0200 Subject: [PATCH 07/13] Remove catch from test case --- .../sourceforge/pmd/cpd/XMLRendererTest.java | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java index 59eda45229..31c53fa03e 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java @@ -17,12 +17,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; import net.sourceforge.pmd.cpd.renderer.CPDRenderer; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; @@ -189,7 +191,7 @@ public class XMLRendererTest { } @Test - public void testFilesWithNumberOfTokens() throws IOException { + public void testFilesWithNumberOfTokens() throws IOException, ParserConfigurationException, SAXException { final CPDReportRenderer renderer = new XMLRenderer(); final List matches = new ArrayList<>(); final String filename = "/var/Foo.java"; @@ -205,18 +207,13 @@ public class XMLRendererTest { final StringWriter writer = new StringWriter(); renderer.render(report, writer); final String xmlOutput = writer.toString(); - try { - final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() - .parse(new ByteArrayInputStream(xmlOutput.getBytes(ENCODING))); - final NodeList files = doc.getElementsByTagName("file"); - final Node file = files.item(0); - final NamedNodeMap attributes = file.getAttributes(); - assertEquals("/var/Foo.java", attributes.getNamedItem("path").getNodeValue()); - assertEquals("888", attributes.getNamedItem("totalNumberOfTokens").getNodeValue()); - } catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage()); - } + final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() + .parse(new ByteArrayInputStream(xmlOutput.getBytes(ENCODING))); + final NodeList files = doc.getElementsByTagName("file"); + final Node file = files.item(0); + final NamedNodeMap attributes = file.getAttributes(); + assertEquals("/var/Foo.java", attributes.getNamedItem("path").getNodeValue()); + assertEquals("888", attributes.getNamedItem("totalNumberOfTokens").getNodeValue()); } @Test From 4d7890112d6d017c630f791f57196e6d7900b3e5 Mon Sep 17 00:00:00 2001 From: Maikel Steneker Date: Fri, 1 Jul 2022 10:34:26 +0200 Subject: [PATCH 08/13] Move sorting to CPDReport class --- .../src/main/java/net/sourceforge/pmd/cpd/CPDReport.java | 5 +++-- .../src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java | 7 ------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java index 52ad6a5722..6a883f1e47 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.cpd; import java.util.Collections; import java.util.Iterator; import java.util.Map; +import java.util.TreeMap; public class CPDReport { private final Iterator matches; @@ -14,7 +15,7 @@ public class CPDReport { CPDReport(final Iterator matches, final Map numberOfTokensPerFile) { this.matches = matches; - this.numberOfTokensPerFile = numberOfTokensPerFile; + this.numberOfTokensPerFile = Collections.unmodifiableMap(new TreeMap<>(numberOfTokensPerFile)); } public Iterator getMatches() { @@ -22,6 +23,6 @@ public class CPDReport { } public Map getNumberOfTokensPerFile() { - return Collections.unmodifiableMap(numberOfTokensPerFile); + return numberOfTokensPerFile; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java index 7ded7a7186..3c4344930d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -9,7 +9,6 @@ import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -119,12 +118,6 @@ public final class XMLRenderer implements Renderer, CPDRenderer, CPDReportRender doc.appendChild(root); final List> entries = new ArrayList<>(numberOfTokensPerFile.entrySet()); - Collections.sort(entries, new Comparator>() { - @Override - public int compare(final Map.Entry entry1, final Map.Entry entry2) { - return entry1.getKey().compareTo(entry2.getKey()); - } - }); for (final Map.Entry pair : entries) { final Element fileElement = doc.createElement("file"); fileElement.setAttribute("path", pair.getKey()); From ee8622e54f01bb502c0a1a0f157ff641daefdb35 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 21 Jul 2022 15:45:42 +0200 Subject: [PATCH 09/13] [core] Internalize methods in CPDConfiguration and CPDRendererAdapter --- docs/pages/release_notes.md | 13 +++++++++++ .../sourceforge/pmd/cpd/CPDConfiguration.java | 23 ++++++++++++------- .../pmd/cpd/renderer/CPDRendererAdapter.java | 9 ++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 56690a23a0..7202d238fa 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -69,6 +69,19 @@ Being based on a proper Antlr grammar, CPD can: * {% jdoc java::lang.java.ast.ASTRecordPattern %} * {% jdoc java::lang.java.ast.ASTComponentPatternList %} +#### Internal API + +Those APIs are not intended to be used by clients, and will be hidden or removed with PMD 7.0.0. +You can identify them with the `@InternalApi` annotation. You'll also get a deprecation warning. + +* {%jdoc !!core::cpd.CPDConfiguration#setRenderer(net.sourceforge.pmd.cpd.Renderer) %} +* {%jdoc !!core::cpd.CPDConfiguration#setCPDRenderer(net.sourceforge.pmd.cpd.renderer.CPDRenderer) %} +* {%jdoc !!core::cpd.CPDConfiguration#getRenderer() %} +* {%jdoc !!core::cpd.CPDConfiguration#getCPDRenderer() %} +* {%jdoc !!core::cpd.CPDConfiguration#getRendererFromString(java.lang.String,java.lang.String) %} +* {%jdoc !!core::cpd.CPDConfiguration#getCPDRendererFromString(java.lang.String,java.lang.String) %} +* {%jdoc core::cpd.renderer.CPDRendererAdapter %} + ### External Contributions * [#3984](https://github.com/pmd/pmd/pull/3984): \[java] Fix AddEmptyString false-negative issue - [@LiGaOg](https://github.com/LiGaOg) * [#3988](https://github.com/pmd/pmd/pull/3988): \[java] Modify WhileLoopWithLiteralBoolean to meet the missing case #3455 - [@VoidxHoshi](https://github.com/VoidxHoshi) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java index a9f6ef8967..aa82be9ff1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -21,6 +21,7 @@ import java.util.Properties; import java.util.Set; import net.sourceforge.pmd.AbstractConfiguration; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.cpd.renderer.CPDRenderer; import net.sourceforge.pmd.cpd.renderer.CPDRendererAdapter; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; @@ -189,9 +190,10 @@ public class CPDConfiguration extends AbstractConfiguration { } /** - * @deprecated Use {@link #getCPDRendererFromString(String, String)} instead + * @deprecated Internal API */ @Deprecated + @InternalApi public static Renderer getRendererFromString(String name, String encoding) { String clazzname = name; if (clazzname == null || "".equals(clazzname)) { @@ -218,9 +220,10 @@ public class CPDConfiguration extends AbstractConfiguration { } /** - * @deprecated use {@link #getCPDReportRendererFromString(String, String)} + * @deprecated Internal API */ @Deprecated + @InternalApi public static CPDRenderer getCPDRendererFromString(String name, String encoding) { String clazzname = name; if (clazzname == null || "".equals(clazzname)) { @@ -245,7 +248,7 @@ public class CPDConfiguration extends AbstractConfiguration { } } - public static CPDReportRenderer getCPDReportRendererFromString(String name, String encoding) { + static CPDReportRenderer getCPDReportRendererFromString(String name, String encoding) { final CPDRenderer renderer = getCPDRendererFromString(name, encoding); if (renderer instanceof CPDReportRenderer) { return (CPDReportRenderer) renderer; @@ -342,17 +345,19 @@ public class CPDConfiguration extends AbstractConfiguration { } /** - * @deprecated Use {@link #getCPDRenderer()} instead + * @deprecated Internal API. */ @Deprecated + @InternalApi public Renderer getRenderer() { return renderer; } /** - * @deprecated Use {@link #getCPDReportRenderer()} instead + * @deprecated Internal API. */ @Deprecated + @InternalApi public CPDRenderer getCPDRenderer() { return cpdRenderer; } @@ -404,26 +409,28 @@ public class CPDConfiguration extends AbstractConfiguration { } /** - * @deprecated Use {@link #setCPDRenderer(CPDRenderer)} instead + * @deprecated Internal API. Use {@link #setRendererName(String)} instead. * @param renderer */ @Deprecated + @InternalApi public void setRenderer(Renderer renderer) { this.renderer = renderer; this.cpdRenderer = null; } /** - * @deprecated Use {@link #setRenderer(CPDReportRenderer)} instead + * @deprecated Internal API. Use {@link #setRendererName(String)} instead. * @param renderer */ @Deprecated + @InternalApi public void setCPDRenderer(CPDRenderer renderer) { this.cpdRenderer = renderer; this.renderer = null; } - public void setRenderer(CPDReportRenderer renderer) { + void setRenderer(CPDReportRenderer renderer) { this.renderer = null; this.cpdRenderer = null; this.cpdReportRenderer = renderer; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java index 8a358ae634..8e3e6b5fe3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java @@ -7,8 +7,17 @@ package net.sourceforge.pmd.cpd.renderer; import java.io.IOException; import java.io.Writer; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.cpd.CPDReport; +/** + * Adapter to convert an old {@link CPDRenderer} into a {@link CPDReportRenderer}. + * + * @deprecated This is internal API. If you want to write your own renderer, directly implement + * {@link CPDReportRenderer}. + */ +@Deprecated +@InternalApi public class CPDRendererAdapter implements CPDReportRenderer { private final CPDRenderer renderer; From 33bfd00303daa93eebc91a1d0b31567192becdcc Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 21 Jul 2022 16:27:02 +0200 Subject: [PATCH 10/13] [core] Refactor how CPD Renderers are determined --- .../sourceforge/pmd/cpd/CPDConfiguration.java | 109 ++++++++---------- .../java/net/sourceforge/pmd/cpd/GUI.java | 32 +---- 2 files changed, 51 insertions(+), 90 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java index aa82be9ff1..abda86e5e0 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -42,7 +42,7 @@ public class CPDConfiguration extends AbstractConfiguration { public static final String DEFAULT_LANGUAGE = "java"; public static final String DEFAULT_RENDERER = "text"; - private static final Map> RENDERERS = new HashMap<>(); + private static final Map> RENDERERS = new HashMap<>(); static { RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class); @@ -175,48 +175,55 @@ public class CPDConfiguration extends AbstractConfiguration { setRendererName(DEFAULT_RENDERER); } if (getRenderer() == null && getCPDRenderer() == null) { - try { - try { - setRenderer(getCPDReportRendererFromString(getRendererName(), getEncoding())); - } catch (ClassCastException e) { - // The renderer class configured is not using the new CPDReportRenderer interface... - setCPDRenderer(getCPDRendererFromString(getRendererName(), getEncoding())); - } - } catch (ClassCastException e) { - // The renderer class configured is not using the new CPDRenderer interface... - setRenderer(getRendererFromString(getRendererName(), getEncoding())); + Object renderer = createRendererByName(getRendererName(), getEncoding()); + String className = getRendererName(); + + if (renderer instanceof CPDReportRenderer) { + setRenderer((CPDReportRenderer) renderer); + } else if (renderer instanceof CPDRenderer) { + setCPDRenderer((CPDRenderer) renderer); + } else if (renderer instanceof Renderer) { + setRenderer((Renderer) renderer); + } else { + System.err.println("Class '" + className + "' is not a supported renderer, defaulting to SimpleRenderer."); + setRenderer(new SimpleRenderer()); } } } + private static Object createRendererByName(String name, String encoding) { + if (name == null || "".equals(name)) { + name = DEFAULT_RENDERER; + } + Class rendererClass = RENDERERS.get(name.toLowerCase(Locale.ROOT)); + if (rendererClass == null) { + try { + rendererClass = Class.forName(name); + } catch (ClassNotFoundException e) { + System.err.println("Can't find class '" + name + "', defaulting to SimpleRenderer."); + rendererClass = SimpleRenderer.class; + } + } + + Object renderer = null; + try { + renderer = rendererClass.getDeclaredConstructor().newInstance(); + setRendererEncoding(renderer, encoding); + } catch (Exception e) { + System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e); + renderer = new SimpleRenderer(); + } + return renderer; + } + /** * @deprecated Internal API */ @Deprecated @InternalApi public static Renderer getRendererFromString(String name, String encoding) { - String clazzname = name; - if (clazzname == null || "".equals(clazzname)) { - clazzname = DEFAULT_RENDERER; - } - @SuppressWarnings("unchecked") // Safe, all standard implementations implement both interfaces - Class clazz = (Class) RENDERERS.get(clazzname.toLowerCase(Locale.ROOT)); - if (clazz == null) { - try { - clazz = Class.forName(clazzname).asSubclass(Renderer.class); - } catch (ClassNotFoundException e) { - System.err.println("Can't find class '" + name + "', defaulting to SimpleRenderer."); - clazz = SimpleRenderer.class; - } - } - try { - Renderer renderer = clazz.getDeclaredConstructor().newInstance(); - setRendererEncoding(renderer, encoding); - return renderer; - } catch (Exception e) { - System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e); - return new SimpleRenderer(); - } + // will throw a ClassCastException if the renderer is of wrong type + return (Renderer) createRendererByName(name, encoding); } /** @@ -225,36 +232,8 @@ public class CPDConfiguration extends AbstractConfiguration { @Deprecated @InternalApi public static CPDRenderer getCPDRendererFromString(String name, String encoding) { - String clazzname = name; - if (clazzname == null || "".equals(clazzname)) { - clazzname = DEFAULT_RENDERER; - } - Class clazz = RENDERERS.get(clazzname.toLowerCase(Locale.ROOT)); - if (clazz == null) { - try { - clazz = Class.forName(clazzname).asSubclass(CPDRenderer.class); - } catch (ClassNotFoundException e) { - System.err.println("Can't find class '" + name + "', defaulting to SimpleRenderer."); - clazz = SimpleRenderer.class; - } - } - try { - CPDRenderer renderer = clazz.getDeclaredConstructor().newInstance(); - setRendererEncoding(renderer, encoding); - return renderer; - } catch (Exception e) { - System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e); - return new SimpleRenderer(); - } - } - - static CPDReportRenderer getCPDReportRendererFromString(String name, String encoding) { - final CPDRenderer renderer = getCPDRendererFromString(name, encoding); - if (renderer instanceof CPDReportRenderer) { - return (CPDReportRenderer) renderer; - } else { - return new CPDRendererAdapter(renderer); - } + // will throw a ClassCastException if the renderer is of wrong type + return (CPDRenderer) createRendererByName(name, encoding); } private static void setRendererEncoding(Object renderer, String encoding) @@ -417,6 +396,7 @@ public class CPDConfiguration extends AbstractConfiguration { public void setRenderer(Renderer renderer) { this.renderer = renderer; this.cpdRenderer = null; + this.cpdReportRenderer = null; } /** @@ -426,8 +406,9 @@ public class CPDConfiguration extends AbstractConfiguration { @Deprecated @InternalApi public void setCPDRenderer(CPDRenderer renderer) { - this.cpdRenderer = renderer; this.renderer = null; + this.cpdRenderer = renderer; + this.cpdReportRenderer = new CPDRendererAdapter(renderer); } void setRenderer(CPDReportRenderer renderer) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java index bf77515797..4142e6bbb5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java @@ -20,7 +20,6 @@ import java.awt.event.MouseEvent; import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import java.io.Writer; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; @@ -66,35 +65,16 @@ import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; import net.sourceforge.pmd.PMDVersion; +import net.sourceforge.pmd.cpd.renderer.CPDRendererAdapter; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; public class GUI implements CPDListener { - // private interface Renderer { - // String render(Iterator items); - // } - - private static final Object[][] RENDERER_SETS = new Object[][] { { "Text", new CPDReportRenderer() { - @Override - public void render(CPDReport report, Writer writer) throws IOException { - new SimpleRenderer().render(report.getMatches(), writer); - } - }, }, { "XML", new CPDReportRenderer() { - @Override - public void render(CPDReport report, Writer writer) throws IOException { - new XMLRenderer().render(report, writer); - } - }, }, { "CSV (comma)", new CPDReportRenderer() { - @Override - public void render(CPDReport report, Writer writer) throws IOException { - new CSVRenderer(',').render(report.getMatches(), writer); - } - }, }, { "CSV (tab)", new CPDReportRenderer() { - @Override - public void render(CPDReport report, Writer writer) throws IOException { - new CSVRenderer('\t').render(report.getMatches(), writer); - } - }, }, }; + private static final Object[][] RENDERER_SETS = new Object[][] { + { "Text", new CPDRendererAdapter(new SimpleRenderer()), }, + { "XML", new XMLRenderer(), }, + { "CSV (comma)", new CPDRendererAdapter(new CSVRenderer(',')), }, + { "CSV (tab)", new CPDRendererAdapter(new CSVRenderer('\t')), }, }; private abstract static class LanguageConfig { public abstract Language languageFor(Properties p); From 94c19d2251f91b0258f6c7f423f770283cbe3592 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 21 Jul 2022 16:34:05 +0200 Subject: [PATCH 11/13] [core] Refactor CPDReport to use a List --- .../src/main/java/net/sourceforge/pmd/cpd/CPD.java | 2 +- .../java/net/sourceforge/pmd/cpd/CPDReport.java | 13 ++++++++----- .../src/main/java/net/sourceforge/pmd/cpd/GUI.java | 2 +- .../net/sourceforge/pmd/cpd/MatchAlgorithm.java | 4 ++++ .../java/net/sourceforge/pmd/cpd/XMLRenderer.java | 9 ++++----- .../pmd/cpd/renderer/CPDRendererAdapter.java | 2 +- .../net/sourceforge/pmd/cpd/XMLRendererTest.java | 2 +- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java index 1d7f5cac04..0ffc7b397d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPD.java @@ -238,7 +238,7 @@ public class CPD { } public CPDReport toReport() { - return new CPDReport(this.getMatches(), numberOfTokensPerFile); + return new CPDReport(matchAlgorithm.getMatches(), numberOfTokensPerFile); } public enum StatusCode { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java index 6a883f1e47..ff1256e7a6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDReport.java @@ -5,20 +5,23 @@ package net.sourceforge.pmd.cpd; import java.util.Collections; -import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.TreeMap; +/** + * @since 6.48.0 + */ public class CPDReport { - private final Iterator matches; + private final List matches; private final Map numberOfTokensPerFile; - CPDReport(final Iterator matches, final Map numberOfTokensPerFile) { - this.matches = matches; + CPDReport(final List matches, final Map numberOfTokensPerFile) { + this.matches = Collections.unmodifiableList(matches); this.numberOfTokensPerFile = Collections.unmodifiableMap(new TreeMap<>(numberOfTokensPerFile)); } - public Iterator getMatches() { + public List getMatches() { return matches; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java index 4142e6bbb5..2f2b3e3437 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java @@ -276,7 +276,7 @@ public class GUI implements CPDListener { } if (!f.canWrite()) { - final CPDReport report = new CPDReport(matches.iterator(), numberOfTokensPerFile); + final CPDReport report = new CPDReport(matches, numberOfTokensPerFile); try (PrintWriter pw = new PrintWriter(Files.newOutputStream(f.toPath()))) { renderer.render(report, pw); pw.flush(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/MatchAlgorithm.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/MatchAlgorithm.java index 299d87e202..2869d60b47 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/MatchAlgorithm.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/MatchAlgorithm.java @@ -47,6 +47,10 @@ public class MatchAlgorithm { return matches.iterator(); } + List getMatches() { + return matches; + } + public TokenEntry tokenAt(int offset, TokenEntry m) { return code.get(offset + m.getIndex()); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java index 3c4344930d..d7d69382e5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -27,6 +27,7 @@ import org.w3c.dom.Element; import net.sourceforge.pmd.cpd.renderer.CPDRenderer; import net.sourceforge.pmd.cpd.renderer.CPDReportRenderer; +import net.sourceforge.pmd.util.CollectionUtil; import net.sourceforge.pmd.util.StringUtil; /** @@ -106,7 +107,7 @@ public final class XMLRenderer implements Renderer, CPDRenderer, CPDReportRender @Override public void render(Iterator matches, Writer writer) throws IOException { - render(new CPDReport(matches, Collections.emptyMap()), writer); + render(new CPDReport(CollectionUtil.toList(matches), Collections.emptyMap()), writer); } @Override @@ -114,7 +115,7 @@ public final class XMLRenderer implements Renderer, CPDRenderer, CPDReportRender final Document doc = createDocument(); final Element root = doc.createElement("pmd-cpd"); final Map numberOfTokensPerFile = report.getNumberOfTokensPerFile(); - final Iterator matches = report.getMatches(); + final List matches = report.getMatches(); doc.appendChild(root); final List> entries = new ArrayList<>(numberOfTokensPerFile.entrySet()); @@ -125,9 +126,7 @@ public final class XMLRenderer implements Renderer, CPDRenderer, CPDReportRender root.appendChild(fileElement); } - Match match; - while (matches.hasNext()) { - match = matches.next(); + for (Match match : matches) { root.appendChild(addCodeSnippet(doc, addFilesToDuplicationElement(doc, createDuplicationElement(doc, match), match), match)); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java index 8e3e6b5fe3..c69c8b192b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRendererAdapter.java @@ -27,6 +27,6 @@ public class CPDRendererAdapter implements CPDReportRenderer { @Override public void render(final CPDReport report, final Writer writer) throws IOException { - renderer.render(report.getMatches(), writer); + renderer.render(report.getMatches().iterator(), writer); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java index 31c53fa03e..1e29e7961b 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java @@ -203,7 +203,7 @@ public class XMLRendererTest { matches.add(match); final Map numberOfTokensPerFile = new HashMap<>(); numberOfTokensPerFile.put(filename, 888); - final CPDReport report = new CPDReport(matches.iterator(), numberOfTokensPerFile); + final CPDReport report = new CPDReport(matches, numberOfTokensPerFile); final StringWriter writer = new StringWriter(); renderer.render(report, writer); final String xmlOutput = writer.toString(); From 43a17337fcf7c033506f00d0e34379c21287a499 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 21 Jul 2022 16:48:23 +0200 Subject: [PATCH 12/13] [doc] Add deprecation notice for CPDRenderer --- docs/pages/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 7202d238fa..4d4bac564b 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -60,6 +60,8 @@ Being based on a proper Antlr grammar, CPD can: * The experimental Java AST class {% jdoc java::lang.java.ast.ASTGuardedPattern %} has been deprecated and will be removed. It was introduced for Java 17 and Java 18 Preview as part of pattern matching for switch, but it is no longer supported with Java 19 Preview. +* The interface {% jdoc core::cpd.renderer.CPDRenderer %} is deprecated. For custom CPD renderers + the new interface {% jdoc core::cpd.renderer.CPDReportRenderer %} should be used. #### Experimental APIs From 4568176f2132c7ac727c235ee7b8071b3c92964c Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 21 Jul 2022 16:48:40 +0200 Subject: [PATCH 13/13] [doc] Update release notes (#4021) --- docs/pages/release_notes.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 4d4bac564b..2966069424 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -42,6 +42,7 @@ Being based on a proper Antlr grammar, CPD can: ### Fixed Issues * core + * [#4021](https://github.com/pmd/pmd/pull/4021): \[core] CPD: Add total number of tokens to XML reports * [#4031](https://github.com/pmd/pmd/issues/4031): \[core] If report is written to stdout, stdout should not be closed * java * [#4015](https://github.com/pmd/pmd/issues/4015): \[java] Support JDK 19 @@ -90,6 +91,7 @@ You can identify them with the `@InternalApi` annotation. You'll also get a depr * [#3992](https://github.com/pmd/pmd/pull/3992): \[java] FinalFieldCouldBeStatic - fix false negative with unnecessary parenthesis - [@dalizi007](https://github.com/dalizi007) * [#3994](https://github.com/pmd/pmd/pull/3994): \[java] TooManyMethods - improve getter/setter detection (#3729) - [@341816041](https://github.com/341816041) * [#4017](https://github.com/pmd/pmd/pull/4017): Add Gherkin support to CPD - [@ASBrouwers](https://github.com/ASBrouwers) +* [#4021](https://github.com/pmd/pmd/pull/4021): \[core] CPD: Add total number of tokens to XML reports - [@maikelsteneker](https://github.com/maikelsteneker) {% endtocmaker %}