Merge branch 'master' into pmd/7.0.x
This commit is contained in:
17 files changed
+382
-111
No files matched your search
@@ -101,6 +101,8 @@ Example:
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<pmd-cpd>
|
||||
<file path="/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java" totalNumberOfTokens="523"/>
|
||||
<file path="/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java" totalNumberOfTokens="120"/>
|
||||
<duplication lines="33" tokens="239">
|
||||
<file column="29" endcolumn="75" endline="64" line="32"
|
||||
path="/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java"/>
|
||||
|
||||
@@ -46,6 +46,9 @@ Being based on a proper Antlr grammar, CPD can:
|
||||
* honor [comment-based suppressions](pmd_userdocs_cpd.html#suppression)
|
||||
|
||||
### 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
|
||||
* java-bestpractices
|
||||
@@ -63,6 +66,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
|
||||
|
||||
@@ -72,12 +77,26 @@ 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)
|
||||
* [#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 %}
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
|
||||
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.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
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;
|
||||
@@ -39,6 +40,8 @@ public class CPD {
|
||||
private Tokens tokens = new Tokens();
|
||||
private MatchAlgorithm matchAlgorithm;
|
||||
private Set<String> current = new HashSet<>();
|
||||
private final Map<String, Integer> numberOfTokensPerFile = new HashMap<>();
|
||||
private int lastTokenSize = 0;
|
||||
|
||||
public CPD(CPDConfiguration theConfiguration) {
|
||||
configuration = theConfiguration;
|
||||
@@ -146,6 +149,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 {
|
||||
@@ -208,11 +213,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 = cpd.toReport();
|
||||
renderer.render(report, IOUtil.createWriter(Charset.defaultCharset(), null));
|
||||
}
|
||||
if (cpd.getMatches().hasNext()) {
|
||||
if (arguments.isFailOnViolation()) {
|
||||
@@ -231,6 +238,10 @@ public class CPD {
|
||||
return statusCode;
|
||||
}
|
||||
|
||||
public CPDReport toReport() {
|
||||
return new CPDReport(matchAlgorithm.getMatches(), numberOfTokensPerFile);
|
||||
}
|
||||
|
||||
public enum StatusCode {
|
||||
OK(0),
|
||||
ERROR(1),
|
||||
|
||||
@@ -21,7 +21,10 @@ 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;
|
||||
import net.sourceforge.pmd.util.FileFinder;
|
||||
import net.sourceforge.pmd.util.FileUtil;
|
||||
|
||||
@@ -39,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<String, Class<? extends CPDRenderer>> RENDERERS = new HashMap<>();
|
||||
private static final Map<String, Class<?>> RENDERERS = new HashMap<>();
|
||||
|
||||
static {
|
||||
RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class);
|
||||
@@ -72,8 +75,11 @@ public class CPDConfiguration extends AbstractConfiguration {
|
||||
@Deprecated
|
||||
private Renderer renderer;
|
||||
|
||||
@Deprecated
|
||||
private CPDRenderer cpdRenderer;
|
||||
|
||||
private CPDReportRenderer cpdReportRenderer;
|
||||
|
||||
private String encoding;
|
||||
|
||||
@Parameter(names = "--ignore-literals",
|
||||
@@ -169,66 +175,65 @@ public class CPDConfiguration extends AbstractConfiguration {
|
||||
setRendererName(DEFAULT_RENDERER);
|
||||
}
|
||||
if (getRenderer() == null && getCPDRenderer() == null) {
|
||||
try {
|
||||
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 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)) {
|
||||
clazzname = DEFAULT_RENDERER;
|
||||
}
|
||||
@SuppressWarnings("unchecked") // Safe, all standard implementations implement both interfaces
|
||||
Class<? extends Renderer> clazz = (Class<? extends Renderer>) 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Internal API
|
||||
*/
|
||||
@Deprecated
|
||||
@InternalApi
|
||||
public static CPDRenderer getCPDRendererFromString(String name, String encoding) {
|
||||
String clazzname = name;
|
||||
if (clazzname == null || "".equals(clazzname)) {
|
||||
clazzname = DEFAULT_RENDERER;
|
||||
}
|
||||
Class<? extends CPDRenderer> 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();
|
||||
}
|
||||
// will throw a ClassCastException if the renderer is of wrong type
|
||||
return (CPDRenderer) createRendererByName(name, encoding);
|
||||
}
|
||||
|
||||
private static void setRendererEncoding(Object renderer, String encoding)
|
||||
@@ -319,17 +324,27 @@ public class CPDConfiguration extends AbstractConfiguration {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getCPDRenderer()} instead
|
||||
* @deprecated Internal API.
|
||||
*/
|
||||
@Deprecated
|
||||
@InternalApi
|
||||
public Renderer getRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Internal API.
|
||||
*/
|
||||
@Deprecated
|
||||
@InternalApi
|
||||
public CPDRenderer getCPDRenderer() {
|
||||
return cpdRenderer;
|
||||
}
|
||||
|
||||
CPDReportRenderer getCPDReportRenderer() {
|
||||
return cpdReportRenderer;
|
||||
}
|
||||
|
||||
public Tokenizer tokenizer() {
|
||||
if (language == null) {
|
||||
throw new IllegalStateException("Language is null.");
|
||||
@@ -373,18 +388,33 @@ 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;
|
||||
this.cpdReportRenderer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Internal API. Use {@link #setRendererName(String)} instead.
|
||||
* @param renderer
|
||||
*/
|
||||
@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) {
|
||||
this.renderer = null;
|
||||
this.cpdRenderer = null;
|
||||
this.cpdReportRenderer = renderer;
|
||||
}
|
||||
|
||||
public boolean isIgnoreLiterals() {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.cpd;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* @since 6.48.0
|
||||
*/
|
||||
public class CPDReport {
|
||||
private final List<Match> matches;
|
||||
private final Map<String, Integer> numberOfTokensPerFile;
|
||||
|
||||
CPDReport(final List<Match> matches, final Map<String, Integer> numberOfTokensPerFile) {
|
||||
this.matches = Collections.unmodifiableList(matches);
|
||||
this.numberOfTokensPerFile = Collections.unmodifiableMap(new TreeMap<>(numberOfTokensPerFile));
|
||||
}
|
||||
|
||||
public List<Match> getMatches() {
|
||||
return matches;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getNumberOfTokensPerFile() {
|
||||
return numberOfTokensPerFile;
|
||||
}
|
||||
}
|
||||
@@ -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 = cpd.toReport();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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.CPDRenderer;
|
||||
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<Match> items);
|
||||
// }
|
||||
|
||||
private static final Object[][] RENDERER_SETS = new Object[][] { { "Text", new CPDRenderer() {
|
||||
@Override
|
||||
public void render(Iterator<Match> items, Writer writer) throws IOException {
|
||||
new SimpleRenderer().render(items, writer);
|
||||
}
|
||||
}, }, { "XML", new CPDRenderer() {
|
||||
@Override
|
||||
public void render(Iterator<Match> items, Writer writer) throws IOException {
|
||||
new XMLRenderer().render(items, writer);
|
||||
}
|
||||
}, }, { "CSV (comma)", new CPDRenderer() {
|
||||
@Override
|
||||
public void render(Iterator<Match> items, Writer writer) throws IOException {
|
||||
new CSVRenderer(',').render(items, writer);
|
||||
}
|
||||
}, }, { "CSV (tab)", new CPDRenderer() {
|
||||
@Override
|
||||
public void render(Iterator<Match> items, Writer writer) throws IOException {
|
||||
new CSVRenderer('\t').render(items, 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);
|
||||
@@ -280,9 +260,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 +276,9 @@ public class GUI implements CPDListener {
|
||||
}
|
||||
|
||||
if (!f.canWrite()) {
|
||||
final CPDReport report = new CPDReport(matches, 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 +353,15 @@ public class GUI implements CPDListener {
|
||||
private boolean trimLeadingWhitespace;
|
||||
|
||||
private List<Match> matches = new ArrayList<>();
|
||||
private Map<String, Integer> 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 +710,7 @@ public class GUI implements CPDListener {
|
||||
cpd.go();
|
||||
t.stop();
|
||||
|
||||
numberOfTokensPerFile = cpd.toReport().getNumberOfTokensPerFile();
|
||||
matches = new ArrayList<>();
|
||||
for (Iterator<Match> i = cpd.getMatches(); i.hasNext();) {
|
||||
Match match = i.next();
|
||||
|
||||
@@ -46,6 +46,10 @@ public class MatchAlgorithm {
|
||||
return matches.iterator();
|
||||
}
|
||||
|
||||
List<Match> getMatches() {
|
||||
return matches;
|
||||
}
|
||||
|
||||
public TokenEntry tokenAt(int offset, TokenEntry m) {
|
||||
return code.get(offset + m.getIndex());
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ 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.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 +26,8 @@ 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.CollectionUtil;
|
||||
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,13 +107,26 @@ public final class XMLRenderer implements Renderer, CPDRenderer {
|
||||
|
||||
@Override
|
||||
public void render(Iterator<Match> matches, Writer writer) throws IOException {
|
||||
Document doc = createDocument();
|
||||
Element root = doc.createElement("pmd-cpd");
|
||||
render(new CPDReport(CollectionUtil.toList(matches), Collections.<String, Integer>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<String, Integer> numberOfTokensPerFile = report.getNumberOfTokensPerFile();
|
||||
final List<Match> matches = report.getMatches();
|
||||
doc.appendChild(root);
|
||||
|
||||
Match match;
|
||||
while (matches.hasNext()) {
|
||||
match = matches.next();
|
||||
final List<Map.Entry<String, Integer>> entries = new ArrayList<>(numberOfTokensPerFile.entrySet());
|
||||
for (final Map.Entry<String, Integer> pair : entries) {
|
||||
final Element fileElement = doc.createElement("file");
|
||||
fileElement.setAttribute("path", pair.getKey());
|
||||
fileElement.setAttribute("totalNumberOfTokens", String.valueOf(pair.getValue()));
|
||||
root.appendChild(fileElement);
|
||||
}
|
||||
|
||||
for (Match match : matches) {
|
||||
root.appendChild(addCodeSnippet(doc,
|
||||
addFilesToDuplicationElement(doc, createDuplicationElement(doc, match), match), match));
|
||||
}
|
||||
|
||||
@@ -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<Match> matches, Writer writer) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.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;
|
||||
|
||||
public CPDRendererAdapter(final CPDRenderer renderer) {
|
||||
this.renderer = renderer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(final CPDReport report, final Writer writer) throws IOException {
|
||||
renderer.render(report.getMatches().iterator(), writer);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ package net.sourceforge.pmd.util;
|
||||
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;
|
||||
@@ -54,8 +55,16 @@ 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 new OutputStreamWriter(System.out);
|
||||
return createWriter(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +117,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
|
||||
|
||||
@@ -12,8 +12,11 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
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;
|
||||
@@ -209,6 +212,24 @@ class CoreCliTest {
|
||||
assertThat(log, containsString("Ruleset reference 'dummy-basic' uses a deprecated form, use 'rulesets/dummy/basic.xml' instead"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testReportToStdoutNotClosing() throws Exception {
|
||||
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);
|
||||
SystemLambda.tapSystemErrAndOut(() -> {
|
||||
runPmd(StatusCode.VIOLATIONS_FOUND, "--no-cache", "--dir", srcDir, "--rulesets", "dummy-basic");
|
||||
});
|
||||
} finally {
|
||||
System.setOut(originalOut);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWrongCliOptionsDoNotPrintUsage() throws Exception {
|
||||
@@ -262,5 +283,4 @@ class CoreCliTest {
|
||||
assertEquals(expectedExitCode, actualExitCode, "Exit code");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -13,15 +13,25 @@ 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.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import com.github.stefanbirkner.systemlambda.SystemLambda;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
|
||||
class CPDCommandLineInterfaceTest {
|
||||
private static final String SRC_DIR = "src/test/resources/net/sourceforge/pmd/cpd/files/";
|
||||
private static final Map<String, Integer> 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
|
||||
);
|
||||
|
||||
@TempDir
|
||||
private Path tempDir;
|
||||
@@ -34,6 +44,7 @@ class CPDCommandLineInterfaceTest {
|
||||
|
||||
@Test
|
||||
void testEmptyResultRendering() throws Exception {
|
||||
final String expectedFilesXml = getExpectedFileEntriesXml(NUMBER_OF_TOKENS.keySet());
|
||||
String stdout = SystemLambda.tapSystemOut(() -> {
|
||||
SystemLambda.tapSystemErr(() -> {
|
||||
CPD.StatusCode statusCode = CPD.runCpd("--minimum-tokens", "340", "--language", "java", "--files",
|
||||
@@ -41,23 +52,41 @@ class CPDCommandLineInterfaceTest {
|
||||
assertEquals(CPD.StatusCode.OK, statusCode);
|
||||
});
|
||||
});
|
||||
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "\n" + "<pmd-cpd/>", stdout.trim());
|
||||
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "\n" + "<pmd-cpd>\n" + expectedFilesXml + "</pmd-cpd>", stdout.trim());
|
||||
}
|
||||
|
||||
private String getExpectedFileEntryXml(final String filename) {
|
||||
final int numberOfTokens = NUMBER_OF_TOKENS.get(filename);
|
||||
return String.format(" <file path=\"%s\"\n totalNumberOfTokens=\"%d\"/>\n",
|
||||
new File(filename).getAbsolutePath(),
|
||||
numberOfTokens);
|
||||
}
|
||||
|
||||
private String getExpectedFileEntriesXml(final Collection<String> filenames) {
|
||||
final StringBuilder expectedFilesXmlBuilder = new StringBuilder();
|
||||
for (final String filename : filenames) {
|
||||
expectedFilesXmlBuilder.append(getExpectedFileEntryXml(filename));
|
||||
}
|
||||
return expectedFilesXmlBuilder.toString();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void testDeprecatedOptionsWarning() throws Exception {
|
||||
File filelist = new File(tempDir.toFile(), "cpd-test-file-list.txt");
|
||||
Files.write(filelist.toPath(), Arrays.asList(
|
||||
final List<String> 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());
|
||||
Path filelist = tempDir.resolve("cpd-test-file-list.txt");
|
||||
Files.write(filelist, filepaths, StandardCharsets.UTF_8);
|
||||
final String expectedFilesXml = getExpectedFileEntriesXml(filepaths);
|
||||
|
||||
String stderr = SystemLambda.tapSystemErr(() -> {
|
||||
String stdout = SystemLambda.tapSystemOut(() -> {
|
||||
CPD.StatusCode statusCode = CPD.runCpd("--minimum-tokens", "340", "--language", "java", "--filelist",
|
||||
filelist.getAbsolutePath(), "--format", "xml", "-failOnViolation", "true");
|
||||
filelist.toAbsolutePath().toString(), "--format", "xml", "-failOnViolation", "true");
|
||||
assertEquals(CPD.StatusCode.OK, statusCode);
|
||||
});
|
||||
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "\n" + "<pmd-cpd/>", stdout.trim());
|
||||
assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "\n" + "<pmd-cpd>\n" + expectedFilesXml + "</pmd-cpd>", stdout.trim());
|
||||
});
|
||||
assertTrue(stderr.contains("Some deprecated options were used on the command-line, including -failOnViolation"));
|
||||
assertTrue(stderr.contains("Consider replacing it with --fail-on-violation"));
|
||||
|
||||
@@ -12,17 +12,21 @@ 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 javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.junit.jupiter.api.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;
|
||||
|
||||
/**
|
||||
* @author Philippe T'Seyen
|
||||
@@ -186,6 +190,33 @@ class XMLRendererTest {
|
||||
assertFalse(report.contains(FORM_FEED_ENTITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testFilesWithNumberOfTokens() throws IOException, ParserConfigurationException, SAXException {
|
||||
final CPDReportRenderer renderer = new XMLRenderer();
|
||||
final List<Match> 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<String, Integer> numberOfTokensPerFile = new HashMap<>();
|
||||
numberOfTokensPerFile.put(filename, 888);
|
||||
final CPDReport report = new CPDReport(matches, numberOfTokensPerFile);
|
||||
final StringWriter writer = new StringWriter();
|
||||
renderer.render(report, writer);
|
||||
final String xmlOutput = writer.toString();
|
||||
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
|
||||
void testRendererXMLEscaping() throws IOException {
|
||||
String codefragment = "code fragment" + FORM_FEED + "\nline2\nline3\nno & escaping necessary in CDATA\nx=\"]]>\";";
|
||||
|
||||
@@ -11,16 +11,20 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
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;
|
||||
@@ -297,4 +301,26 @@ class IOUtilTest {
|
||||
InputStream stream = new ByteArrayInputStream(testString.getBytes(StandardCharsets.UTF_8));
|
||||
assertEquals(testString, IOUtil.readToString(stream, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@Test
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user