configErrors = synchronizedList(new ArrayList<>());
+
+ @DeprecatedUntil700
+ @InternalApi
+ public Report() { // NOPMD - UnnecessaryConstructor
+ // TODO: should be package-private, you have to use a listener to build a report.
+ }
+
+ /**
+ * Represents a configuration error.
+ */
+ public static class ConfigurationError {
+
+ private final Rule rule;
+ private final String issue;
+
+ /**
+ * Creates a new configuration error for a specific rule.
+ *
+ * @param theRule
+ * the rule which is configured wrongly
+ * @param theIssue
+ * the reason, why the configuration is wrong
+ */
+ public ConfigurationError(Rule theRule, String theIssue) {
+ rule = theRule;
+ issue = theIssue;
+ }
+
+ /**
+ * Gets the wrongly configured rule
+ *
+ * @return the wrongly configured rule
+ */
+ public Rule rule() {
+ return rule;
+ }
+
+ /**
+ * Gets the reason for the configuration error.
+ *
+ * @return the issue
+ */
+ public String issue() {
+ return issue;
+ }
+ }
+
+ /**
+ * Represents a processing error, such as a parse error.
+ */
+ public static class ProcessingError {
+
+ private final Throwable error;
+ private final FileId file;
+
+ /**
+ * Creates a new processing error
+ *
+ * @param error
+ * the error
+ * @param file
+ * the file during which the error occurred
+ */
+ public ProcessingError(Throwable error, FileId file) {
+ this.error = error;
+ this.file = file;
+ }
+
+ public String getMsg() {
+ return error.getClass().getSimpleName() + ": " + error.getMessage();
+ }
+
+ public String getDetail() {
+ try (StringWriter stringWriter = new StringWriter();
+ PrintWriter writer = new PrintWriter(stringWriter)) {
+ error.printStackTrace(writer);
+ return stringWriter.toString();
+ } catch (IOException e) {
+ // IOException on close - should never happen when using StringWriter
+ throw new RuntimeException(e);
+ }
+ }
+
+ public FileId getFileId() {
+ return file;
+ }
+
+ public Throwable getError() {
+ return error;
+ }
+ }
+
+ /**
+ * Represents a violation, that has been suppressed.
+ */
+ public static class SuppressedViolation {
+
+ private final RuleViolation rv;
+ private final String userMessage;
+ private final ViolationSuppressor suppressor;
+
+ /**
+ * Creates a suppressed violation.
+ *
+ * @param rv The violation, that has been suppressed
+ * @param suppressor The suppressor which suppressed the violation
+ * @param userMessage Any relevant info given by the suppressor
+ */
+ public SuppressedViolation(RuleViolation rv, ViolationSuppressor suppressor, String userMessage) {
+ this.suppressor = suppressor;
+ this.rv = rv;
+ this.userMessage = userMessage;
+ }
+
+ public ViolationSuppressor getSuppressor() {
+ return suppressor;
+ }
+
+ public RuleViolation getRuleViolation() {
+ return this.rv;
+ }
+
+ public String getUserMessage() {
+ return userMessage;
+ }
+ }
+
+ /**
+ * Adds a new rule violation to the report and notify the listeners.
+ *
+ * @param violation the violation to add
+ *
+ * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7.
+ */
+ @DeprecatedUntil700
+ @Deprecated
+ @InternalApi
+ public void addRuleViolation(RuleViolation violation) {
+ synchronized (violations) {
+ // note that this binary search is inefficient as we usually
+ // report violations file by file.
+ int index = Collections.binarySearch(violations, violation, RuleViolation.DEFAULT_COMPARATOR);
+ violations.add(index < 0 ? -index - 1 : index, violation);
+ }
+ }
+
+ /**
+ * Adds a new suppressed violation.
+ */
+ private void addSuppressedViolation(SuppressedViolation sv) {
+ suppressedRuleViolations.add(sv);
+ }
+
+ /**
+ * Adds a new configuration error to the report.
+ *
+ * @param error the error to add
+ *
+ * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7.
+ */
+ @DeprecatedUntil700
+ @Deprecated
+ @InternalApi
+ public void addConfigError(ConfigurationError error) {
+ configErrors.add(error);
+ }
+
+ /**
+ * Adds a new processing error to the report.
+ *
+ * @param error
+ * the error to add
+ * @deprecated PMD's way of creating a report is internal and may be changed in pmd 7.
+ */
+ @DeprecatedUntil700
+ @Deprecated
+ @InternalApi
+ public void addError(ProcessingError error) {
+ errors.add(error);
+ }
+
+ /**
+ * Merges the given report into this report. This might be necessary, if a
+ * summary over all violations is needed as PMD creates one report per file
+ * by default.
+ *
+ * This is synchronized on an internal lock (note that other mutation
+ * operations are not synchronized, todo for pmd 7).
+ *
+ * @param r the report to be merged into this.
+ *
+ * @see AbstractAccumulatingRenderer
+ *
+ * @deprecated Convert Renderer to use the reports.
+ */
+ @Deprecated
+ public void merge(Report r) {
+ errors.addAll(r.errors);
+ configErrors.addAll(r.configErrors);
+ suppressedRuleViolations.addAll(r.suppressedRuleViolations);
+
+ for (RuleViolation violation : r.getViolations()) {
+ addRuleViolation(violation);
+ }
+ }
+
+
+ /**
+ * Returns an unmodifiable list of violations that were suppressed.
+ */
+ public List getSuppressedViolations() {
+ return Collections.unmodifiableList(suppressedRuleViolations);
+ }
+
+ /**
+ * Returns an unmodifiable list of violations that have been
+ * recorded until now. None of those violations were suppressed.
+ *
+ * The violations list is sorted with {@link RuleViolation#DEFAULT_COMPARATOR}.
+ */
+ public List getViolations() {
+ return Collections.unmodifiableList(violations);
+ }
+
+
+ /**
+ * Returns an unmodifiable list of processing errors that have been
+ * recorded until now.
+ */
+ public List getProcessingErrors() {
+ return Collections.unmodifiableList(errors);
+ }
+
+
+ /**
+ * Returns an unmodifiable list of configuration errors that have
+ * been recorded until now.
+ */
+ public List getConfigurationErrors() {
+ return Collections.unmodifiableList(configErrors);
+ }
+
+ /**
+ * Create a report by making side effects on a {@link FileAnalysisListener}.
+ * This wraps a {@link ReportBuilderListener}.
+ */
+ public static Report buildReport(Consumer super FileAnalysisListener> lambda) {
+ return BaseResultProducingCloseable.using(new ReportBuilderListener(), lambda);
+ }
+
+ /**
+ * A {@link FileAnalysisListener} that accumulates events into a
+ * {@link Report}.
+ */
+ public static final class ReportBuilderListener extends BaseResultProducingCloseable implements FileAnalysisListener {
+
+ private final Report report;
+
+ public ReportBuilderListener() {
+ this(new Report());
+ }
+
+ ReportBuilderListener(Report report) {
+ this.report = report;
+ }
+
+ @Override
+ protected Report getResultImpl() {
+ return report;
+ }
+
+ @Override
+ public void onRuleViolation(RuleViolation violation) {
+ report.addRuleViolation(violation);
+ }
+
+ @Override
+ public void onSuppressedRuleViolation(SuppressedViolation violation) {
+ report.addSuppressedViolation(violation);
+ }
+
+ @Override
+ public void onError(ProcessingError error) {
+ report.addError(error);
+ }
+
+ @Override
+ public String toString() {
+ return "ReportBuilderListener";
+ }
+ }
+
+ /**
+ * A {@link GlobalAnalysisListener} that accumulates the events of
+ * all files into a {@link Report}.
+ */
+ public static final class GlobalReportBuilderListener extends BaseResultProducingCloseable implements GlobalAnalysisListener {
+
+ private final Report report = new Report();
+
+ @Override
+ public FileAnalysisListener startFileAnalysis(TextFile file) {
+ // note that the report is shared, but Report is now thread-safe
+ return new ReportBuilderListener(this.report);
+ }
+
+ @Override
+ public void onConfigError(ConfigurationError error) {
+ report.addConfigError(error);
+ }
+
+ @Override
+ protected Report getResultImpl() {
+ return report;
+ }
+ }
+
+ /**
+ * Creates a new report taking all the information from this report,
+ * but filtering the violations.
+ *
+ * @param filter when true, the violation will be kept.
+ * @return copy of this report
+ */
+ @Experimental
+ public Report filterViolations(Predicate filter) {
+ Report copy = new Report();
+
+ for (RuleViolation violation : violations) {
+ if (filter.test(violation)) {
+ copy.addRuleViolation(violation);
+ }
+ }
+
+ copy.suppressedRuleViolations.addAll(suppressedRuleViolations);
+ copy.errors.addAll(errors);
+ copy.configErrors.addAll(configErrors);
+ return copy;
+ }
+
+ /**
+ * Creates a new report by combining this report with another report.
+ * This is similar to {@link #merge(Report)}, but instead a new report
+ * is created. The lowest start time and greatest end time are kept in the copy.
+ *
+ * @param other the other report to combine
+ * @return
+ */
+ @Experimental
+ public Report union(Report other) {
+ Report copy = new Report();
+
+ for (RuleViolation violation : violations) {
+ copy.addRuleViolation(violation);
+ }
+ for (RuleViolation violation : other.violations) {
+ copy.addRuleViolation(violation);
+ }
+
+ copy.suppressedRuleViolations.addAll(suppressedRuleViolations);
+ copy.suppressedRuleViolations.addAll(other.suppressedRuleViolations);
+
+ copy.errors.addAll(errors);
+ copy.errors.addAll(other.errors);
+ copy.configErrors.addAll(configErrors);
+ copy.configErrors.addAll(other.configErrors);
+
+ return copy;
+ }
+
+ // ------------------- compat extensions --------------------
+ @Deprecated
+ public Report filterViolations(net.sourceforge.pmd.util.Predicate filter) {
+ Predicate javaPredicate = filter::test;
+ return filterViolations(javaPredicate);
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java
new file mode 100644
index 0000000000..dcb3030052
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/RuleViolation.java
@@ -0,0 +1,196 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This class has been taken from 7.0.0-SNAPSHOT
+// Changes: getFilename
+
+package net.sourceforge.pmd;
+
+import java.util.Comparator;
+import java.util.Map;
+
+import net.sourceforge.pmd.annotation.DeprecatedUntil700;
+import net.sourceforge.pmd.lang.document.FileId;
+import net.sourceforge.pmd.lang.document.FileLocation;
+
+/**
+ * A RuleViolation is created by a Rule when it identifies a violation of the
+ * Rule constraints. RuleViolations are simple data holders that are collected
+ * into a {@link Report}.
+ *
+ * Since PMD 6.21.0, implementations of this interface are considered internal
+ * API and hence deprecated. Clients should exclusively use this interface.
+ *
+ * @see Rule
+ */
+public interface RuleViolation {
+ // todo move to package reporting
+
+ /**
+ * A comparator for rule violations. This compares all exposed attributes
+ * of a violation, filename first. The remaining parameters are compared
+ * in an unspecified order.
+ */
+ Comparator DEFAULT_COMPARATOR =
+ Comparator.comparing(RuleViolation::getFileId)
+ .thenComparingInt(RuleViolation::getBeginLine)
+ .thenComparingInt(RuleViolation::getBeginColumn)
+ .thenComparing(RuleViolation::getDescription, Comparator.nullsLast(Comparator.naturalOrder()))
+ .thenComparingInt(RuleViolation::getEndLine)
+ .thenComparingInt(RuleViolation::getEndColumn)
+ .thenComparing(rv -> rv.getRule().getName());
+
+
+ /**
+ * Key in {@link #getAdditionalInfo()} for the name of the class in
+ * which the violation was identified.
+ */
+ String CLASS_NAME = "className";
+ /**
+ * Key in {@link #getAdditionalInfo()} for the name of the variable
+ * related to the violation.
+ */
+ String VARIABLE_NAME = "variableName";
+ /**
+ * Key in {@link #getAdditionalInfo()} for the name of the method in
+ * which the violation was identified.
+ */
+ String METHOD_NAME = "methodName";
+ /**
+ * Key in {@link #getAdditionalInfo()} for the name of the package in
+ * which the violation was identified.
+ */
+ String PACKAGE_NAME = "packageName";
+
+ /**
+ * Get the Rule which identified this violation.
+ *
+ * @return The identifying Rule.
+ */
+ Rule getRule();
+
+ /**
+ * Get the description of this violation.
+ *
+ * @return The description.
+ */
+ String getDescription();
+
+
+ /**
+ * Returns the location where the violation should be reported.
+ */
+ FileLocation getLocation();
+
+ /**
+ * Return the ID of the file where the violation was found.
+ */
+ default FileId getFileId() {
+ return getLocation().getFileId();
+ }
+
+ /**
+ * Get the begin line number in the source file in which this violation was
+ * identified.
+ *
+ * @return Begin line number.
+ */
+ default int getBeginLine() {
+ return getLocation().getStartPos().getLine();
+ }
+
+ /**
+ * Get the column number of the begin line in the source file in which this
+ * violation was identified.
+ *
+ * @return Begin column number.
+ */
+ default int getBeginColumn() {
+ return getLocation().getStartPos().getColumn();
+ }
+
+ /**
+ * Get the end line number in the source file in which this violation was
+ * identified.
+ *
+ * @return End line number.
+ */
+ default int getEndLine() {
+ return getLocation().getEndPos().getLine();
+ }
+
+ /**
+ * Get the column number of the end line in the source file in which this
+ * violation was identified.
+ *
+ * @return End column number.
+ */
+ default int getEndColumn() {
+ return getLocation().getEndPos().getColumn();
+ }
+
+ /**
+ * A map of additional key-value pairs known about this violation.
+ * What data is in there is language specific. Common keys supported
+ * by several languages are defined as constants on this interface.
+ * The map is unmodifiable.
+ */
+ Map getAdditionalInfo();
+
+
+ /**
+ * Get the package name of the Class in which this violation was identified.
+ *
+ * @return The package name.
+ *
+ * @deprecated Use {@link #PACKAGE_NAME}
+ */
+ @Deprecated
+ @DeprecatedUntil700
+ default String getPackageName() {
+ return getAdditionalInfo().get(PACKAGE_NAME);
+ }
+
+ /**
+ * Get the name of the Class in which this violation was identified.
+ *
+ * @return The Class name.
+ * @deprecated Use {@link #CLASS_NAME}
+ */
+ @Deprecated
+ @DeprecatedUntil700
+ default String getClassName() {
+ return getAdditionalInfo().get(CLASS_NAME);
+ }
+
+ /**
+ * Get the method name in which this violation was identified.
+ *
+ * @return The method name.
+ * @deprecated Use {@link #METHOD_NAME}
+ */
+ @Deprecated
+ @DeprecatedUntil700
+ default String getMethodName() {
+ return getAdditionalInfo().get(METHOD_NAME);
+ }
+
+ /**
+ * Get the variable name on which this violation was identified.
+ *
+ * @return The variable name.
+ * @deprecated Use {@link #VARIABLE_NAME}
+ */
+ @Deprecated
+ @DeprecatedUntil700
+ default String getVariableName() {
+ return getAdditionalInfo().get(VARIABLE_NAME);
+ }
+
+ // ------------------- compat extensions --------------------
+ @Deprecated
+ default String getFilename() {
+ return getLocation().getFileId().getFileName();
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java
new file mode 100644
index 0000000000..c6d1d84760
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/AbstractLanguage.java
@@ -0,0 +1,60 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.cpd;
+
+import java.io.FilenameFilter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+import net.sourceforge.pmd.util.filter.Filters;
+
+public abstract class AbstractLanguage implements Language {
+ private final String name;
+ private final String terseName;
+ private final Tokenizer tokenizer;
+ private final FilenameFilter fileFilter;
+ private final List extensions;
+
+ public AbstractLanguage(String name, String terseName, Tokenizer tokenizer, String... extensions) {
+ this.name = name;
+ this.terseName = terseName;
+ this.tokenizer = tokenizer;
+ fileFilter = Filters.toFilenameFilter(Filters.getFileExtensionOrDirectoryFilter(extensions));
+ this.extensions = Arrays.asList(extensions);
+ }
+
+ @Override
+ public FilenameFilter getFileFilter() {
+ return fileFilter;
+ }
+
+ @Override
+ public Tokenizer getTokenizer() {
+ return tokenizer;
+ }
+
+ @Override
+ public void setProperties(Properties properties) {
+ // needs to be implemented by subclasses.
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getTerseName() {
+ return terseName;
+ }
+
+ @Override
+ public List getExtensions() {
+ return extensions;
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java
new file mode 100644
index 0000000000..c570dcec07
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPD.java
@@ -0,0 +1,97 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.cpd;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import net.sourceforge.pmd.internal.util.FileFinder;
+import net.sourceforge.pmd.internal.util.IOUtil;
+
+/**
+ * Adapter for PMD 7. This exposes CPD interface of PMD 6 but runs PMD 7 under the hood.
+ */
+public class CPD {
+ private final CPDConfiguration configuration;
+ private final List files = new ArrayList<>();
+ private Set current = new HashSet<>();
+ private CPDReport report;
+
+ public CPD(CPDConfiguration configuration) {
+ this.configuration = configuration;
+ }
+
+ public void addAllInDirectory(File dir) throws IOException {
+ addDirectory(dir, false);
+ }
+
+ public void addRecursively(File dir) throws IOException {
+ addDirectory(dir, true);
+ }
+
+ public void add(List files) throws IOException {
+ for (File f : files) {
+ add(f);
+ }
+ }
+
+ private void addDirectory(File dir, boolean recurse) throws IOException {
+ if (!dir.exists()) {
+ throw new FileNotFoundException("Couldn't find directory " + dir);
+ }
+ FileFinder finder = new FileFinder();
+ // TODO - could use SourceFileSelector here
+ add(finder.findFilesFrom(dir, configuration.filenameFilter(), recurse));
+ }
+
+ public void add(File file) throws IOException {
+ if (configuration.isSkipDuplicates()) {
+ // TODO refactor this thing into a separate class
+ String signature = file.getName() + '_' + file.length();
+ if (current.contains(signature)) {
+ System.err.println("Skipping " + file.getAbsolutePath()
+ + " since it appears to be a duplicate file and --skip-duplicate-files is set");
+ return;
+ }
+ current.add(signature);
+ }
+
+ if (!IOUtil.equalsNormalizedPaths(file.getAbsoluteFile().getCanonicalPath(), file.getAbsolutePath())) {
+ System.err.println("Skipping " + file + " since it appears to be a symlink");
+ return;
+ }
+
+ if (!file.exists()) {
+ System.err.println("Skipping " + file + " since it doesn't exist (broken symlink?)");
+ return;
+ }
+
+ files.add(file.toPath());
+ }
+
+ public void go() {
+ try (CpdAnalysis cpd = CpdAnalysis.create(configuration)) {
+ files.forEach(cpd.files()::addFile);
+ cpd.performAnalysis(this::collectReport);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void collectReport(CPDReport cpdReport) {
+ this.report = cpdReport;
+ }
+
+ public Iterator getMatches() {
+ return report.getMatches().iterator();
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java
new file mode 100644
index 0000000000..a747e69b2c
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java
@@ -0,0 +1,341 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This class has been taken from 7.0.0-SNAPSHOT
+// Changes: setLanguage, setSourceEncoding, filenameFilter
+
+package net.sourceforge.pmd.cpd;
+
+import java.beans.IntrospectionException;
+import java.beans.PropertyDescriptor;
+import java.io.File;
+import java.io.FilenameFilter;
+import java.lang.reflect.Method;
+import java.nio.charset.Charset;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+import org.slf4j.LoggerFactory;
+
+import net.sourceforge.pmd.AbstractConfiguration;
+import net.sourceforge.pmd.internal.util.FileFinder;
+import net.sourceforge.pmd.internal.util.FileUtil;
+import net.sourceforge.pmd.lang.LanguageRegistry;
+import net.sourceforge.pmd.lang.ecmascript.EcmascriptLanguageModule;
+import net.sourceforge.pmd.lang.java.JavaLanguageModule;
+import net.sourceforge.pmd.lang.jsp.JspLanguageModule;
+import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter;
+
+/**
+ *
+ * @author Brian Remedios
+ * @author Romain Pelisse - <belaran@gmail.com>
+ */
+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<>();
+
+
+ static {
+ RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class);
+ RENDERERS.put("xml", XMLRenderer.class);
+ RENDERERS.put("csv", CSVRenderer.class);
+ RENDERERS.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class);
+ RENDERERS.put("vs", VSRenderer.class);
+ }
+
+
+ private int minimumTileSize;
+
+ private boolean skipDuplicates;
+
+ private String rendererName = DEFAULT_RENDERER;
+
+ private @Nullable CPDReportRenderer cpdReportRenderer;
+
+ private boolean ignoreLiterals;
+
+ private boolean ignoreIdentifiers;
+
+ private boolean ignoreAnnotations;
+
+ private boolean ignoreUsings;
+
+ private boolean ignoreLiteralSequences = false;
+
+ private boolean ignoreIdentifierAndLiteralSequences = false;
+
+ private boolean skipLexicalErrors = false;
+
+ private boolean noSkipBlocks = false;
+
+ private String skipBlocksPattern = CpdLanguageProperties.DEFAULT_SKIP_BLOCKS_PATTERN;
+
+ private boolean help;
+
+ private boolean failOnViolation = true;
+
+
+ public CPDConfiguration() {
+ this(LanguageRegistry.CPD);
+ }
+
+ public CPDConfiguration(LanguageRegistry languageRegistry) {
+ super(languageRegistry, new SimpleMessageReporter(LoggerFactory.getLogger(CpdAnalysis.class)));
+ }
+
+ @Override
+ public void setSourceEncoding(Charset sourceEncoding) {
+ super.setSourceEncoding(sourceEncoding);
+ if (cpdReportRenderer != null) {
+ setRendererEncoding(cpdReportRenderer, sourceEncoding);
+ }
+ }
+
+ static CPDReportRenderer createRendererByName(String name, Charset encoding) {
+ if (name == null || "".equals(name)) {
+ name = DEFAULT_RENDERER;
+ }
+ Class extends CPDReportRenderer> rendererClass = RENDERERS.get(name.toLowerCase(Locale.ROOT));
+ if (rendererClass == null) {
+ Class> klass;
+ try {
+ klass = Class.forName(name);
+ if (CPDReportRenderer.class.isAssignableFrom(klass)) {
+ rendererClass = (Class) klass;
+ } else {
+ throw new IllegalArgumentException("Class " + name + " does not implement " + CPDReportRenderer.class);
+ }
+ } catch (ClassNotFoundException e) {
+ throw new IllegalArgumentException("Cannot find class " + name);
+ }
+ }
+
+ CPDReportRenderer renderer;
+ 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;
+ }
+
+ private static void setRendererEncoding(@NonNull Object renderer, Charset encoding) {
+ try {
+ PropertyDescriptor encodingProperty = new PropertyDescriptor("encoding", renderer.getClass());
+ Method method = encodingProperty.getWriteMethod();
+ if (method == null) {
+ return;
+ }
+ if (method.getParameterTypes()[0] == Charset.class) {
+ method.invoke(renderer, encoding);
+ } else if (method.getParameterTypes()[0] == String.class) {
+ method.invoke(renderer, encoding.name());
+ }
+ } catch (IntrospectionException | ReflectiveOperationException ignored) {
+ // ignored - maybe this renderer doesn't have a encoding property
+ }
+ }
+
+ public static Set getRenderers() {
+ return Collections.unmodifiableSet(RENDERERS.keySet());
+ }
+
+ public int getMinimumTileSize() {
+ return minimumTileSize;
+ }
+
+ public void setMinimumTileSize(int minimumTileSize) {
+ this.minimumTileSize = minimumTileSize;
+ }
+
+ public boolean isSkipDuplicates() {
+ return skipDuplicates;
+ }
+
+ public void setSkipDuplicates(boolean skipDuplicates) {
+ this.skipDuplicates = skipDuplicates;
+ }
+
+ public String getRendererName() {
+ return rendererName;
+ }
+
+ public void setRendererName(String rendererName) {
+ this.rendererName = rendererName;
+ if (rendererName == null) {
+ this.cpdReportRenderer = null;
+ }
+ this.cpdReportRenderer = createRendererByName(rendererName, getSourceEncoding());
+ }
+
+
+ public CPDReportRenderer getCPDReportRenderer() {
+ return cpdReportRenderer;
+ }
+
+ void setRenderer(CPDReportRenderer renderer) {
+ this.cpdReportRenderer = renderer;
+ }
+
+ public boolean isIgnoreLiterals() {
+ return ignoreLiterals;
+ }
+
+ public void setIgnoreLiterals(boolean ignoreLiterals) {
+ this.ignoreLiterals = ignoreLiterals;
+ }
+
+ public boolean isIgnoreIdentifiers() {
+ return ignoreIdentifiers;
+ }
+
+ public void setIgnoreIdentifiers(boolean ignoreIdentifiers) {
+ this.ignoreIdentifiers = ignoreIdentifiers;
+ }
+
+ public boolean isIgnoreAnnotations() {
+ return ignoreAnnotations;
+ }
+
+ public void setIgnoreAnnotations(boolean ignoreAnnotations) {
+ this.ignoreAnnotations = ignoreAnnotations;
+ }
+
+ public boolean isIgnoreUsings() {
+ return ignoreUsings;
+ }
+
+ public void setIgnoreUsings(boolean ignoreUsings) {
+ this.ignoreUsings = ignoreUsings;
+ }
+
+ public boolean isIgnoreLiteralSequences() {
+ return ignoreLiteralSequences;
+ }
+
+ public void setIgnoreLiteralSequences(boolean ignoreLiteralSequences) {
+ this.ignoreLiteralSequences = ignoreLiteralSequences;
+ }
+
+ public boolean isIgnoreIdentifierAndLiteralSequences() {
+ return ignoreIdentifierAndLiteralSequences;
+ }
+
+ public void setIgnoreIdentifierAndLiteralSequences(boolean ignoreIdentifierAndLiteralSequences) {
+ this.ignoreIdentifierAndLiteralSequences = ignoreIdentifierAndLiteralSequences;
+ }
+
+ public boolean isSkipLexicalErrors() {
+ return skipLexicalErrors;
+ }
+
+ public void setSkipLexicalErrors(boolean skipLexicalErrors) {
+ this.skipLexicalErrors = skipLexicalErrors;
+ }
+
+ public boolean isHelp() {
+ return help;
+ }
+
+ public void setHelp(boolean help) {
+ this.help = help;
+ }
+
+ public boolean isNoSkipBlocks() {
+ return noSkipBlocks;
+ }
+
+ public void setNoSkipBlocks(boolean noSkipBlocks) {
+ this.noSkipBlocks = noSkipBlocks;
+ }
+
+ public String getSkipBlocksPattern() {
+ return skipBlocksPattern;
+ }
+
+ public void setSkipBlocksPattern(String skipBlocksPattern) {
+ this.skipBlocksPattern = skipBlocksPattern;
+ }
+
+ public boolean isFailOnViolation() {
+ return failOnViolation;
+ }
+
+ public void setFailOnViolation(boolean failOnViolation) {
+ this.failOnViolation = failOnViolation;
+ }
+
+ // ------------------- compat extensions --------------------
+ private FilenameFilter filenameFilter;
+
+ public void setLanguage(Language language) {
+ if (language instanceof JavaLanguage) {
+ filenameFilter = language.getFileFilter();
+ setForceLanguageVersion(JavaLanguageModule.getInstance().getDefaultVersion());
+ } else if (language instanceof EcmascriptLanguage) {
+ filenameFilter = language.getFileFilter();
+ setForceLanguageVersion(EcmascriptLanguageModule.getInstance().getDefaultVersion());
+ } else if (language instanceof JSPLanguage) {
+ filenameFilter = language.getFileFilter();
+ setForceLanguageVersion(JspLanguageModule.getInstance().getDefaultVersion());
+ } else {
+ throw new UnsupportedOperationException("Language " + language.getName() + " is not supported");
+ }
+ }
+
+ public void setSourceEncoding(String sourceEncoding) {
+ setSourceEncoding(Charset.forName(Objects.requireNonNull(sourceEncoding)));
+ }
+
+ public FilenameFilter filenameFilter() {
+ if (getForceLanguageVersion() == null) {
+ throw new IllegalStateException("Language is null.");
+ }
+
+ final FilenameFilter languageFilter = filenameFilter;
+ final Set exclusions = new HashSet<>();
+
+ if (getExcludes() != null) {
+ FileFinder finder = new FileFinder();
+ for (Path excludedFile : getExcludes()) {
+ if (Files.isDirectory(excludedFile)) {
+ List files = finder.findFilesFrom(excludedFile.toFile(), languageFilter, true);
+ for (File f : files) {
+ exclusions.add(FileUtil.normalizeFilename(f.getAbsolutePath()));
+ }
+ } else {
+ exclusions.add(FileUtil.normalizeFilename(excludedFile.toAbsolutePath().toString()));
+ }
+ }
+ }
+
+ return new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ File f = new File(dir, name);
+ if (exclusions.contains(FileUtil.normalizeFilename(f.getAbsolutePath()))) {
+ System.err.println("Excluding " + f.getAbsolutePath());
+ return false;
+ }
+ return languageFilter.accept(dir, name);
+ }
+ };
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java
new file mode 100644
index 0000000000..ecd2a5a2cf
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptLanguage.java
@@ -0,0 +1,17 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.cpd;
+
+/**
+ *
+ * @author Zev Blut zb@ubit.com
+ */
+public class EcmascriptLanguage extends AbstractLanguage {
+ public EcmascriptLanguage() {
+ super("JavaScript", "ecmascript", new EcmascriptTokenizer(), ".js");
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java
new file mode 100644
index 0000000000..f75312c469
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java
@@ -0,0 +1,8 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.cpd;
+
+public class EcmascriptTokenizer extends net.sourceforge.pmd.lang.ecmascript.cpd.EcmascriptTokenizer {
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java
new file mode 100644
index 0000000000..e1501bad5d
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java
@@ -0,0 +1,13 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.cpd;
+
+public class JSPLanguage extends AbstractLanguage {
+ public JSPLanguage() {
+ super("JSP", "jsp", new JSPTokenizer(), ".jsp", ".jspx", ".jspf", ".tag");
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java
new file mode 100644
index 0000000000..76b768afd8
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java
@@ -0,0 +1,8 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.cpd;
+
+public class JSPTokenizer extends net.sourceforge.pmd.lang.jsp.cpd.JSPTokenizer {
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java
new file mode 100644
index 0000000000..344a5d9bbc
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java
@@ -0,0 +1,26 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+// Changes: setProperties doesn't work, provide properties in constructor already
+
+package net.sourceforge.pmd.cpd;
+
+import java.util.Properties;
+
+public class JavaLanguage extends AbstractLanguage {
+ public JavaLanguage() {
+ this(System.getProperties());
+ }
+
+ public JavaLanguage(Properties properties) {
+ super("Java", "java", new JavaTokenizer(properties), ".java");
+ }
+
+ @Override
+ public final void setProperties(Properties properties) {
+ // note: this might be actually incompatible
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java
new file mode 100644
index 0000000000..113663cffa
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java
@@ -0,0 +1,33 @@
+/*
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.cpd;
+
+import java.util.Properties;
+
+import net.sourceforge.pmd.lang.java.JavaLanguageModule;
+import net.sourceforge.pmd.lang.java.internal.JavaLanguageProperties;
+
+public class JavaTokenizer extends net.sourceforge.pmd.lang.java.cpd.JavaTokenizer {
+ public JavaTokenizer(Properties properties) {
+ super(convertLanguageProperties(properties));
+ }
+
+ private static final String IGNORE_LITERALS = "ignore_literals";
+ private static final String IGNORE_IDENTIFIERS = "ignore_identifiers";
+ private static final String IGNORE_ANNOTATIONS = "ignore_annotations";
+
+ private static JavaLanguageProperties convertLanguageProperties(Properties properties) {
+ boolean ignoreAnnotations = Boolean.parseBoolean(properties.getProperty(IGNORE_ANNOTATIONS, "false"));
+ boolean ignoreLiterals = Boolean.parseBoolean(properties.getProperty(IGNORE_LITERALS, "false"));
+ boolean ignoreIdentifiers = Boolean.parseBoolean(properties.getProperty(IGNORE_IDENTIFIERS, "false"));
+
+ JavaLanguageProperties javaLanguageProperties = (JavaLanguageProperties) JavaLanguageModule.getInstance().newPropertyBundle();
+ javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_IGNORE_METADATA, ignoreAnnotations);
+ javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_ANONYMIZE_LITERALS, ignoreLiterals);
+ javaLanguageProperties.setProperty(CpdLanguageProperties.CPD_ANONYMIZE_IDENTIFIERS, ignoreIdentifiers);
+
+ return javaLanguageProperties;
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java
new file mode 100644
index 0000000000..d2ee070bb5
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Language.java
@@ -0,0 +1,25 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.cpd;
+
+import java.io.FilenameFilter;
+import java.util.List;
+import java.util.Properties;
+
+public interface Language {
+ String getName();
+
+ String getTerseName();
+
+ Tokenizer getTokenizer();
+
+ FilenameFilter getFileFilter();
+
+ void setProperties(Properties properties);
+
+ List getExtensions();
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java
new file mode 100644
index 0000000000..2bcfdc15ea
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/Mark.java
@@ -0,0 +1,104 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This class has been taken from 7.0.0-SNAPSHOT
+// Changes: getFilename
+
+package net.sourceforge.pmd.cpd;
+
+import java.util.Objects;
+
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import net.sourceforge.pmd.lang.document.FileId;
+import net.sourceforge.pmd.lang.document.FileLocation;
+import net.sourceforge.pmd.lang.document.TextRange2d;
+
+/**
+ * A range of tokens in a source file, identified by a start and end
+ * token (both included in the range). The start and end token may be
+ * the same token.
+ */
+public final class Mark implements Comparable {
+
+ private final @NonNull TokenEntry token;
+ private @Nullable TokenEntry endToken;
+
+ Mark(@NonNull TokenEntry token) {
+ this.token = token;
+ }
+
+ @NonNull TokenEntry getToken() {
+ return this.token;
+ }
+
+ @NonNull TokenEntry getEndToken() {
+ return endToken == null ? token : endToken;
+ }
+
+ /**
+ * Return the location of this source range in the source file.
+ */
+ public FileLocation getLocation() {
+ TokenEntry endToken = getEndToken();
+ return FileLocation.range(
+ getFileId(),
+ TextRange2d.range2d(token.getBeginLine(), token.getBeginColumn(),
+ endToken.getEndLine(), endToken.getEndColumn()));
+ }
+
+ FileId getFileId() {
+ return token.getFileId();
+ }
+
+ public int getBeginTokenIndex() {
+ return this.token.getIndex();
+ }
+
+ public int getEndTokenIndex() {
+ return getEndToken().getIndex();
+ }
+
+ void setEndToken(@NonNull TokenEntry endToken) {
+ assert endToken.getFileId().equals(token.getFileId())
+ : "Tokens are not from the same file";
+ this.endToken = endToken;
+ }
+
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + token.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ Mark other = (Mark) obj;
+ return Objects.equals(token, other.token)
+ && Objects.equals(endToken, other.endToken);
+ }
+
+ @Override
+ public int compareTo(Mark other) {
+ return getToken().compareTo(other.getToken());
+ }
+
+ // ------------------- compat extensions --------------------
+ public String getFilename() {
+ return this.token.getFileId().getOriginalPath();
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java
new file mode 100644
index 0000000000..4ebab60230
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java
@@ -0,0 +1,193 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This class has been taken from 7.0.0-SNAPSHOT
+// Changes: implements old interface CPDRenderer, old render(Iterator matches, Writer writer) method
+
+package net.sourceforge.pmd.cpd;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import net.sourceforge.pmd.cpd.renderer.CPDRenderer;
+import net.sourceforge.pmd.lang.document.Chars;
+import net.sourceforge.pmd.lang.document.FileId;
+import net.sourceforge.pmd.lang.document.FileLocation;
+import net.sourceforge.pmd.lang.document.TextFile;
+import net.sourceforge.pmd.lang.java.JavaLanguageModule;
+import net.sourceforge.pmd.util.StringUtil;
+
+/**
+ * @author Philippe T'Seyen - original implementation
+ * @author Romain Pelisse - javax.xml implementation
+ *
+ */
+public final class XMLRenderer implements CPDReportRenderer, CPDRenderer {
+
+ private String encoding;
+
+ /**
+ * Creates a XML Renderer with the default (platform dependent) encoding.
+ */
+ public XMLRenderer() {
+ this(null);
+ }
+
+ /**
+ * Creates a XML Renderer with a specific output encoding.
+ *
+ * @param encoding
+ * the encoding to use or null. If null, default (platform
+ * dependent) encoding is used.
+ */
+ public XMLRenderer(String encoding) {
+ setEncoding(encoding);
+ }
+
+ public void setEncoding(String encoding) {
+ if (encoding != null) {
+ this.encoding = encoding;
+ } else {
+ this.encoding = System.getProperty("file.encoding");
+ }
+ }
+
+ public String getEncoding() {
+ return this.encoding;
+ }
+
+ private Document createDocument() {
+ try {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder parser = factory.newDocumentBuilder();
+ return parser.newDocument();
+ } catch (ParserConfigurationException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void dumpDocToWriter(Document doc, Writer writer) {
+ try {
+ TransformerFactory tf = TransformerFactory.newInstance();
+ Transformer transformer = tf.newTransformer();
+ transformer.setOutputProperty(OutputKeys.VERSION, "1.0");
+ transformer.setOutputProperty(OutputKeys.METHOD, "xml");
+ transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
+ transformer.setOutputProperty(OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "codefragment");
+ transformer.transform(new DOMSource(doc), new StreamResult(writer));
+ } catch (TransformerException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+
+ @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();
+ doc.appendChild(root);
+
+ for (final Map.Entry pair : numberOfTokensPerFile.entrySet()) {
+ final Element fileElement = doc.createElement("file");
+ fileElement.setAttribute("path", report.getDisplayName(pair.getKey()));
+ fileElement.setAttribute("totalNumberOfTokens", String.valueOf(pair.getValue()));
+ root.appendChild(fileElement);
+ }
+
+ for (Match match : report.getMatches()) {
+ Element dupElt = createDuplicationElement(doc, match);
+ addFilesToDuplicationElement(doc, dupElt, match, report);
+ addCodeSnippet(doc, dupElt, match, report);
+ root.appendChild(dupElt);
+ }
+ dumpDocToWriter(doc, writer);
+ writer.flush();
+ }
+
+ private void addFilesToDuplicationElement(Document doc, Element duplication, Match match, CPDReport report) {
+ for (Mark mark : match) {
+ final Element file = doc.createElement("file");
+ FileLocation loc = mark.getLocation();
+ file.setAttribute("line", String.valueOf(loc.getStartLine()));
+ // only remove invalid characters, escaping is done by the DOM impl.
+ String filenameXml10 = StringUtil.removedInvalidXml10Characters(report.getDisplayName(loc.getFileId()));
+ file.setAttribute("path", filenameXml10);
+ file.setAttribute("endline", String.valueOf(loc.getEndLine()));
+ file.setAttribute("column", String.valueOf(loc.getStartColumn()));
+ file.setAttribute("endcolumn", String.valueOf(loc.getEndColumn()));
+ file.setAttribute("begintoken", String.valueOf(mark.getBeginTokenIndex()));
+ file.setAttribute("endtoken", String.valueOf(mark.getEndTokenIndex()));
+ duplication.appendChild(file);
+ }
+ }
+
+ private void addCodeSnippet(Document doc, Element duplication, Match match, CPDReport report) {
+ Chars codeSnippet = report.getSourceCodeSlice(match.getFirstMark());
+ if (codeSnippet != null) {
+ // the code snippet has normalized line endings
+ String platformSpecific = codeSnippet.toString().replace("\n", System.lineSeparator());
+ Element codefragment = doc.createElement("codefragment");
+ // only remove invalid characters, escaping is not necessary in CDATA.
+ // if the string contains the end marker of a CDATA section, then the DOM impl will
+ // create two cdata sections automatically.
+ codefragment.appendChild(doc.createCDATASection(StringUtil.removedInvalidXml10Characters(platformSpecific)));
+ duplication.appendChild(codefragment);
+ }
+ }
+
+ private Element createDuplicationElement(Document doc, Match match) {
+ Element duplication = doc.createElement("duplication");
+ duplication.setAttribute("lines", String.valueOf(match.getLineCount()));
+ duplication.setAttribute("tokens", String.valueOf(match.getTokenCount()));
+ return duplication;
+ }
+
+ // ------------------- compat extensions --------------------
+ @Override
+ public void render(Iterator matches, Writer writer) throws IOException {
+ List matchesList = new ArrayList<>();
+ matches.forEachRemaining(matchesList::add);
+
+ List textFiles = new ArrayList<>();
+ Set paths = new HashSet<>();
+ for (Match match : matchesList) {
+ for (Mark mark : match.getMarkSet()) {
+ paths.add(mark.getFilename());
+ }
+ }
+ for (String path : paths) {
+ textFiles.add(TextFile.forPath(Paths.get(path), StandardCharsets.UTF_8, JavaLanguageModule.getInstance().getDefaultVersion()));
+ }
+
+ try (SourceManager sourcManager = new SourceManager(textFiles)) {
+ CPDReport report = new CPDReport(sourcManager, matchesList, Collections.emptyMap());
+ render(report, writer);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java
new file mode 100644
index 0000000000..f79d108642
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java
@@ -0,0 +1,21 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.cpd.renderer;
+
+import java.io.IOException;
+import java.io.Writer;
+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-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java
new file mode 100644
index 0000000000..851c4bb492
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractCompoundFilter.java
@@ -0,0 +1,65 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A base class for Filters which implements behavior using a List of other
+ * Filters.
+ *
+ * @param
+ * The underlying type on which the filter applies.
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public abstract class AbstractCompoundFilter implements Filter {
+
+ protected List> filters;
+
+ public AbstractCompoundFilter() {
+ filters = new ArrayList<>(2);
+ }
+
+ public AbstractCompoundFilter(Filter... filters) {
+ this.filters = Arrays.asList(filters);
+ }
+
+ public List> getFilters() {
+ return filters;
+ }
+
+ public void setFilters(List> filters) {
+ this.filters = filters;
+ }
+
+ public void addFilter(Filter filter) {
+ filters.add(filter);
+ }
+
+ protected abstract String getOperator();
+
+ @Override
+ public String toString() {
+
+ if (filters.isEmpty()) {
+ return "()";
+ }
+
+ StringBuilder builder = new StringBuilder();
+ builder.append('(').append(filters.get(0));
+
+ for (int i = 1; i < filters.size(); i++) {
+ builder.append(' ').append(getOperator()).append(' ');
+ builder.append(filters.get(i));
+ }
+ builder.append(')');
+ return builder.toString();
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java
new file mode 100644
index 0000000000..e6ca4e47d2
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java
@@ -0,0 +1,48 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+/**
+ * A base class for Filters which implements behavior using delegation to an
+ * underlying filter.
+ *
+ * @param
+ * The underlying type on which the filter applies.
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public abstract class AbstractDelegateFilter implements Filter {
+ protected Filter filter;
+
+ public AbstractDelegateFilter() {
+ // default constructor
+ }
+
+ public AbstractDelegateFilter(Filter filter) {
+ this.filter = filter;
+ }
+
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public void setFilter(Filter filter) {
+ this.filter = filter;
+ }
+
+ // Subclass should override to do something other the simply delegate.
+ @Override
+ public boolean filter(T obj) {
+ return filter.filter(obj);
+ }
+
+ // Subclass should override to do something other the simply delegate.
+ @Override
+ public String toString() {
+ return filter.toString();
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java
new file mode 100644
index 0000000000..be8ff31fea
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/AndFilter.java
@@ -0,0 +1,43 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+/**
+ * A logical AND of a list of Filters. This implementation is short circuiting.
+ *
+ * @param
+ * The underlying type on which the filter applies.
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public class AndFilter extends AbstractCompoundFilter {
+
+ public AndFilter() {
+ super();
+ }
+
+ public AndFilter(Filter... filters) {
+ super(filters);
+ }
+
+ @Override
+ public boolean filter(T obj) {
+ boolean match = true;
+ for (Filter filter : filters) {
+ if (!filter.filter(obj)) {
+ match = false;
+ break;
+ }
+ }
+ return match;
+ }
+
+ @Override
+ protected String getOperator() {
+ return "and";
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java
new file mode 100644
index 0000000000..c1669e91d3
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/DirectoryFilter.java
@@ -0,0 +1,31 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+import java.io.File;
+
+/**
+ * Directory filter.
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public final class DirectoryFilter implements Filter {
+ public static final DirectoryFilter INSTANCE = new DirectoryFilter();
+
+ private DirectoryFilter() {
+ }
+
+ @Override
+ public boolean filter(File file) {
+ return file.isDirectory();
+ }
+
+ @Override
+ public String toString() {
+ return "is Directory";
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java
new file mode 100644
index 0000000000..06259afce3
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java
@@ -0,0 +1,54 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+import java.io.File;
+import java.util.Locale;
+
+/**
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public class FileExtensionFilter implements Filter {
+ protected final String[] extensions;
+ protected final boolean ignoreCase;
+
+ /**
+ * Matches any files with the given extensions, ignoring case
+ */
+ public FileExtensionFilter(String... extensions) {
+ this(true, extensions);
+ }
+
+ /**
+ * Matches any files with the given extensions, optionally ignoring case.
+ */
+ public FileExtensionFilter(boolean ignoreCase, String... extensions) {
+ this.extensions = extensions;
+ this.ignoreCase = ignoreCase;
+ if (ignoreCase) {
+ for (int i = 0; i < this.extensions.length; i++) {
+ this.extensions[i] = this.extensions[i].toUpperCase(Locale.ROOT);
+ }
+ }
+ }
+
+ @Override
+ public boolean filter(File file) {
+ boolean accept = extensions == null;
+ if (!accept) {
+ for (String extension : extensions) {
+ String name = file.getName();
+ if (ignoreCase ? name.toUpperCase(Locale.ROOT).endsWith(extension) : name.endsWith(extension)) {
+ accept = true;
+ break;
+ }
+ }
+ }
+ return accept;
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java
new file mode 100644
index 0000000000..e99a14bb44
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filter.java
@@ -0,0 +1,20 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+/**
+ * A Filter interface, used for filtering arbitrary objects.
+ *
+ * @param
+ * The underlying type on which the filter applies.
+ *
+ * @deprecated Will be replaced with standard java.util.function.Predicate with 7.0.0
+ */
+@Deprecated
+public interface Filter {
+ boolean filter(T obj);
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java
new file mode 100644
index 0000000000..699b614308
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/Filters.java
@@ -0,0 +1,245 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import net.sourceforge.pmd.annotation.InternalApi;
+
+/**
+ * Utility class for working with Filters. Contains builder style methods, apply
+ * methods, as well as mechanisms for adapting Filters and FilenameFilters.
+ *
+ * @deprecated Internal API, see {@link Filter}
+ */
+@Deprecated
+@InternalApi
+public final class Filters {
+
+ private Filters() { }
+
+ /**
+ * Filter a given Collection.
+ *
+ * @param
+ * Type of the Collection.
+ * @param filter
+ * A Filter upon the Type of objects in the Collection.
+ * @param collection
+ * The Collection to filter.
+ * @return A List containing only those objects for which the Filter
+ * returned true
.
+ */
+ public static List filter(Filter filter, Collection collection) {
+ List list = new ArrayList<>();
+ for (T obj : collection) {
+ if (filter.filter(obj)) {
+ list.add(obj);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Get a File Filter for files with the given extensions, ignoring case.
+ *
+ * @param extensions
+ * The extensions to filter.
+ * @return A File Filter.
+ */
+ public static Filter getFileExtensionFilter(String... extensions) {
+ return new FileExtensionFilter(extensions);
+ }
+
+ /**
+ * Get a File Filter for directories.
+ *
+ * @return A File Filter.
+ */
+ public static Filter getDirectoryFilter() {
+ return DirectoryFilter.INSTANCE;
+ }
+
+ /**
+ * Get a File Filter for directories or for files with the given extensions,
+ * ignoring case.
+ *
+ * @param extensions
+ * The extensions to filter.
+ * @return A File Filter.
+ */
+ public static Filter getFileExtensionOrDirectoryFilter(String... extensions) {
+ return new OrFilter<>(getFileExtensionFilter(extensions), getDirectoryFilter());
+ }
+
+ /**
+ * Given a String Filter, expose as a File Filter. The File paths are
+ * normalized to a standard pattern using /
as a path separator
+ * which can be used cross platform easily in a regular expression based
+ * String Filter.
+ *
+ * @param filter
+ * A String Filter.
+ * @return A File Filter.
+ */
+ public static Filter toNormalizedFileFilter(final Filter filter) {
+ return new Filter() {
+ @Override
+ public boolean filter(File file) {
+ String path = file.getPath();
+ path = path.replace('\\', '/');
+ return filter.filter(path);
+ }
+
+ @Override
+ public String toString() {
+ return filter.toString();
+ }
+ };
+ }
+
+ /**
+ * Given a String Filter, expose as a Filter on another type. The
+ * toString()
method is called on the objects of the other type
+ * and delegated to the String Filter.
+ *
+ * @param
+ * The desired type.
+ * @param filter
+ * The existing String Filter.
+ * @return A Filter on the desired type.
+ */
+ public static Filter fromStringFilter(final Filter filter) {
+ return new Filter() {
+ @Override
+ public boolean filter(T obj) {
+ return filter.filter(obj.toString());
+ }
+
+ @Override
+ public String toString() {
+ return filter.toString();
+ }
+ };
+ }
+
+ /**
+ * Given a File Filter, expose as a FilenameFilter.
+ *
+ * @param filter
+ * The File Filter.
+ * @return A FilenameFilter.
+ */
+ public static FilenameFilter toFilenameFilter(final Filter filter) {
+ return new FilenameFilter() {
+ @Override
+ public boolean accept(File dir, String name) {
+ return filter.filter(new File(dir, name));
+ }
+
+ @Override
+ public String toString() {
+ return filter.toString();
+ }
+ };
+ }
+
+ /**
+ * Given a FilenameFilter, expose as a File Filter.
+ *
+ * @param filter
+ * The FilenameFilter.
+ * @return A File Filter.
+ */
+ public static Filter toFileFilter(final FilenameFilter filter) {
+ return new Filter() {
+ @Override
+ public boolean filter(File file) {
+ return filter.accept(file.getParentFile(), file.getName());
+ }
+
+ @Override
+ public String toString() {
+ return filter.toString();
+ }
+ };
+ }
+
+ /**
+ * Construct a String Filter using set of include and exclude regular
+ * expressions. If there are no include regular expressions provide, then a
+ * regular expression is added which matches every String by default. A
+ * String is included as long as it matches an include regular expression
+ * and does not match an exclude regular expression.
+ *
+ * In other words, exclude patterns override include patterns.
+ *
+ * @param includeRegexes
+ * The include regular expressions. May be null
.
+ * @param excludeRegexes
+ * The exclude regular expressions. May be null
.
+ * @return A String Filter.
+ */
+ public static Filter buildRegexFilterExcludeOverInclude(List includeRegexes,
+ List excludeRegexes) {
+ OrFilter includeFilter = new OrFilter<>();
+ if (includeRegexes == null || includeRegexes.isEmpty()) {
+ includeFilter.addFilter(new RegexStringFilter(".*"));
+ } else {
+ for (String includeRegex : includeRegexes) {
+ includeFilter.addFilter(new RegexStringFilter(includeRegex));
+ }
+ }
+
+ OrFilter excludeFilter = new OrFilter<>();
+ if (excludeRegexes != null) {
+ for (String excludeRegex : excludeRegexes) {
+ excludeFilter.addFilter(new RegexStringFilter(excludeRegex));
+ }
+ }
+
+ return new AndFilter<>(includeFilter, new NotFilter<>(excludeFilter));
+ }
+
+ /**
+ * Construct a String Filter using set of include and exclude regular
+ * expressions. If there are no include regular expressions provide, then a
+ * regular expression is added which matches every String by default. A
+ * String is included as long as the case that there is an include which
+ * matches or there is not an exclude which matches.
+ *
+ * In other words, include patterns override exclude patterns.
+ *
+ * @param includeRegexes
+ * The include regular expressions. May be null
.
+ * @param excludeRegexes
+ * The exclude regular expressions. May be null
.
+ * @return A String Filter.
+ */
+ public static Filter buildRegexFilterIncludeOverExclude(List includeRegexes,
+ List excludeRegexes) {
+ OrFilter includeFilter = new OrFilter<>();
+ if (includeRegexes != null) {
+ for (String includeRegex : includeRegexes) {
+ includeFilter.addFilter(new RegexStringFilter(includeRegex));
+ }
+ }
+
+ OrFilter excludeFilter = new OrFilter<>();
+ if (excludeRegexes != null) {
+ for (String excludeRegex : excludeRegexes) {
+ excludeFilter.addFilter(new RegexStringFilter(excludeRegex));
+ }
+ }
+
+ return new OrFilter<>(includeFilter, new NotFilter<>(excludeFilter));
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java
new file mode 100644
index 0000000000..26cc124571
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/NotFilter.java
@@ -0,0 +1,35 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+/**
+ * A logical NEGATION of a Filter.
+ *
+ * @param
+ * The underlying type on which the filter applies.
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public class NotFilter extends net.sourceforge.pmd.util.filter.AbstractDelegateFilter {
+ public NotFilter() {
+ super();
+ }
+
+ public NotFilter(Filter filter) {
+ super(filter);
+ }
+
+ @Override
+ public boolean filter(T obj) {
+ return !filter.filter(obj);
+ }
+
+ @Override
+ public String toString() {
+ return "not (" + filter + ")";
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java
new file mode 100644
index 0000000000..9c57d99a1f
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/OrFilter.java
@@ -0,0 +1,43 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+/**
+ * A logical OR of a list of Filters. This implementation is short circuiting.
+ *
+ * @param
+ * The underlying type on which the filter applies.
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public class OrFilter extends AbstractCompoundFilter {
+
+ public OrFilter() {
+ super();
+ }
+
+ public OrFilter(Filter... filters) {
+ super(filters);
+ }
+
+ @Override
+ public boolean filter(T obj) {
+ boolean match = false;
+ for (Filter filter : filters) {
+ if (filter.filter(obj)) {
+ match = true;
+ break;
+ }
+ }
+ return match;
+ }
+
+ @Override
+ protected String getOperator() {
+ return "or";
+ }
+}
diff --git a/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java
new file mode 100644
index 0000000000..fd8b933cb4
--- /dev/null
+++ b/pmd-compat6/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java
@@ -0,0 +1,98 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+// This file has been taken from 6.55.0
+
+package net.sourceforge.pmd.util.filter;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A filter which uses a regular expression to match Strings. Invalid regular
+ * expressions will match nothing.
+ *
+ * Because regular expression matching is slow, and a common usage is to match
+ * some sort of relative file path, the regular expression is checked to see if
+ * it can be evaluated using much faster calls to
+ * {@link String#endsWith(String)}.
+ * @deprecated See {@link Filter}
+ */
+@Deprecated
+public class RegexStringFilter implements Filter {
+ /**
+ * Matches regular expressions begin with an optional {@code ^}, then
+ * {@code .*}, then a literal path, with an optional file extension, and
+ * finally an optional {@code $} at the end. The {@code .} in the extension
+ * may or may not be preceded by a {@code \} escape. The literal path
+ * portion is determine by the absence of any of the following characters:
+ * \ [ ( . * ? + | { $
+ *
+ * There are two capturing groups in the expression. The first is for the
+ * literal path. The second is for the file extension, without the escaping.
+ * The concatenation of these two captures creates the {@link String} which
+ * can be used with {@link String#endsWith(String)}.
+ *
+ * For ease of reference, the non-Java escaped form of this pattern is:
+ * \^?\.\*([^\\\[\(\.\*\?\+\|\{\$]+)(?:\\?(\.\w+))?\$?
+ */
+ private static final Pattern ENDS_WITH = Pattern
+ .compile("\\^?\\.\\*([^\\\\\\[\\(\\.\\*\\?\\+\\|\\{\\$]+)(?:\\\\?(\\.\\w+))?\\$?");
+
+ protected String regex;
+ protected Pattern pattern;
+ protected String endsWith;
+
+ public RegexStringFilter(String regex) {
+ this.regex = regex;
+ optimize();
+ }
+
+ public String getRegex() {
+ return this.regex;
+ }
+
+ public String getEndsWith() {
+ return this.endsWith;
+ }
+
+ protected void optimize() {
+ final Matcher matcher = ENDS_WITH.matcher(this.regex);
+ if (matcher.matches()) {
+ final String literalPath = matcher.group(1);
+ final String fileExtension = matcher.group(2);
+ if (fileExtension != null) {
+ this.endsWith = literalPath + fileExtension;
+ } else {
+ this.endsWith = literalPath;
+ }
+ } else {
+ try {
+ this.pattern = Pattern.compile(this.regex);
+ } catch (PatternSyntaxException ignored) {
+ // If the regular expression is invalid, then pattern will be
+ // null.
+ }
+ }
+ }
+
+ @Override
+ public boolean filter(String obj) {
+ if (this.endsWith != null) {
+ return obj.endsWith(this.endsWith);
+ } else if (this.pattern != null) {
+ return this.pattern.matcher(obj).matches();
+ } else {
+ // The regular expression must have been bad, so it will match
+ // nothing.
+ return false;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "matches " + this.regex;
+ }
+}
diff --git a/pom.xml b/pom.xml
index ef6b71625d..81421c67c4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1185,6 +1185,18 @@
pmd-dist
+
+
+ pmd-compat6-module
+
+
+ !skipPmdCompat6
+
+
+
+ pmd-compat6
+
+