• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 380

29 Jan 2026 03:55PM UTC coverage: 78.964%. Remained the same
380

push

github

adangel
[doc] ADR 3: Clarify javadoc tags (#6392)

18537 of 24358 branches covered (76.1%)

Branch coverage included in aggregate %.

40391 of 50268 relevant lines covered (80.35%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.93
/pmd-ant/src/main/java/net/sourceforge/pmd/ant/CPDTask.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.ant;
6

7
import java.io.BufferedWriter;
8
import java.io.File;
9
import java.io.IOException;
10
import java.io.OutputStream;
11
import java.io.OutputStreamWriter;
12
import java.io.Writer;
13
import java.nio.charset.Charset;
14
import java.nio.file.Files;
15
import java.nio.file.Path;
16
import java.util.ArrayList;
17
import java.util.List;
18

19
import org.apache.tools.ant.BuildException;
20
import org.apache.tools.ant.DirectoryScanner;
21
import org.apache.tools.ant.Project;
22
import org.apache.tools.ant.Task;
23
import org.apache.tools.ant.types.EnumeratedAttribute;
24
import org.apache.tools.ant.types.FileSet;
25

26
import net.sourceforge.pmd.cpd.CPDConfiguration;
27
import net.sourceforge.pmd.cpd.CPDReport;
28
import net.sourceforge.pmd.cpd.CPDReportRenderer;
29
import net.sourceforge.pmd.cpd.CSVRenderer;
30
import net.sourceforge.pmd.cpd.CpdAnalysis;
31
import net.sourceforge.pmd.cpd.SimpleRenderer;
32
import net.sourceforge.pmd.cpd.XMLOldRenderer;
33
import net.sourceforge.pmd.cpd.XMLRenderer;
34
import net.sourceforge.pmd.lang.Language;
35
import net.sourceforge.pmd.lang.LanguageRegistry;
36

37
/**
38
 * CPD Ant task. Setters of this class are interpreted by Ant as properties
39
 * settable in the XML. This is therefore published API.
40
 *
41
 * <p>Runs the CPD utility via ant. The ant task looks like this:</p>
42
 *
43
 * <pre>{@code
44
 *   <project name="CPDProject" default="main" basedir=".">
45
 *     <path id="pmd.classpath">
46
 *         <fileset dir="/home/joe/pmd-bin-VERSION/lib">
47
 *             <include name="*.jar"/>
48
 *         </fileset>
49
 *     </path>
50
 *     <taskdef name="cpd" classname="net.sourceforge.pmd.ant.CPDTask" classpathref="pmd.classpath" />
51
 *
52
 *     <target name="main">
53
 *       <cpd encoding="UTF-16LE" language="java" ignoreIdentifiers="true"
54
 *            ignoreLiterals="true" ignoreAnnotations="true" minimumTokenCount="100"
55
 *            outputFile="c:\cpdrun.txt">
56
 *         <fileset dir="/path/to/my/src">
57
 *           <include name="*.java"/>
58
 *         </fileset>
59
 *       </cpd>
60
 *     </target>
61
 *   </project>
62
 * }</pre>
63
 *
64
 * <p>Required: minimumTokenCount, outputFile, and at least one file</p>
65
 */
66
public class CPDTask extends Task {
1✔
67

68
    private static final String TEXT_FORMAT = "text";
69
    private static final String XML_FORMAT = "xml";
70
    /**
71
     * @deprecated Since 7.3.0.
72
     */
73
    @Deprecated
74
    private static final String XMLOLD_FORMAT = "xmlold";
75
    private static final String CSV_FORMAT = "csv";
76

77
    private String format = TEXT_FORMAT;
1✔
78
    private String language = "java";
1✔
79
    private int minimumTokenCount;
80
    private boolean ignoreLiterals;
81
    private boolean ignoreIdentifiers;
82
    private boolean ignoreAnnotations;
83
    private boolean ignoreUsings;
84
    /**
85
     * @deprecated Since 7.3.0.
86
     */
87
    @Deprecated
88
    private boolean skipLexicalErrors;
89
    private boolean skipDuplicateFiles;
90
    private boolean skipBlocks = true;
1✔
91
    private String skipBlocksPattern;
92
    private File outputFile;
93
    private String encoding = System.getProperty("file.encoding");
1✔
94
    private List<FileSet> filesets = new ArrayList<>();
1✔
95
    private boolean failOnError = true;
1✔
96

97
    @Override
98
    public void execute() throws BuildException {
99
        ClassLoader oldClassloader = Thread.currentThread().getContextClassLoader();
1✔
100
        Thread.currentThread().setContextClassLoader(CPDTask.class.getClassLoader());
1✔
101

102
        try {
103
            validateFields();
1✔
104

105
            log("Starting run, minimumTokenCount is " + minimumTokenCount, Project.MSG_INFO);
1✔
106

107
            log("Tokenizing files", Project.MSG_INFO);
1✔
108
            CPDConfiguration config = new CPDConfiguration();
1✔
109
            config.setMinimumTileSize(minimumTokenCount);
1✔
110
            config.setOnlyRecognizeLanguage(config.getLanguageRegistry().getLanguageById(language));
1✔
111
            config.setSourceEncoding(Charset.forName(encoding));
1✔
112
            config.setSkipDuplicates(skipDuplicateFiles);
1✔
113

114
            if (skipLexicalErrors) {
1!
115
                log("skipLexicalErrors is deprecated since 7.3.0 and the property is ignored. "
×
116
                        + "Lexical errors are now skipped by default and the build is failed. "
117
                        + "Use failOnError=\"false\" to not fail the build.", Project.MSG_WARN);
118
            }
119

120
            config.setIgnoreAnnotations(ignoreAnnotations);
1✔
121
            config.setIgnoreLiterals(ignoreLiterals);
1✔
122
            config.setIgnoreIdentifiers(ignoreIdentifiers);
1✔
123
            config.setIgnoreUsings(ignoreUsings);
1✔
124
            if (skipBlocks) {
1!
125
                config.setSkipBlocksPattern(skipBlocksPattern);
1✔
126
            }
127

128
            try (CpdAnalysis cpd = CpdAnalysis.create(config)) {
1✔
129
                addFiles(cpd);
1✔
130

131
                log("Starting to analyze code", Project.MSG_INFO);
1✔
132
                long start = System.currentTimeMillis();
1✔
133
                cpd.performAnalysis(this::report);
1✔
134
                long timeTaken = System.currentTimeMillis() - start;
1✔
135
                log("Done analyzing code; that took " + timeTaken + " milliseconds");
1✔
136

137
                int errors = config.getReporter().numErrors();
1✔
138
                if (errors > 0) {
1✔
139
                    String message = String.format("There were %d recovered errors during analysis.", errors);
1✔
140
                    if (failOnError) {
1✔
141
                        throw new BuildException(message + " Ignore these with failOnError=\"true\".");
1✔
142
                    } else {
143
                        log(message + " Not failing build, because failOnError=\"false\".", Project.MSG_WARN);
1✔
144
                    }
145
                }
146
            }
147
        } catch (IOException ioe) {
×
148
            log(ioe.toString(), Project.MSG_ERR);
×
149
            throw new BuildException("IOException during task execution", ioe);
×
150
        } catch (ReportException re) {
×
151
            log(re.toString(), Project.MSG_ERR);
×
152
            throw new BuildException("ReportException during task execution", re);
×
153
        } finally {
154
            Thread.currentThread().setContextClassLoader(oldClassloader);
1✔
155
        }
156
    }
1✔
157

158
    private void report(CPDReport report) throws ReportException {
159
        if (report.getMatches().isEmpty()) {
1!
160
            log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO);
×
161
        }
162
        log("Generating report", Project.MSG_INFO);
1✔
163
        CPDReportRenderer renderer = createRenderer();
1✔
164

165
        try {
166
            // will be closed via BufferedWriter/OutputStreamWriter chain down below
167
            final OutputStream os;
168
            if (outputFile == null) {
1!
169
                os = System.out;
×
170
            } else if (outputFile.isAbsolute()) {
1!
171
                os = Files.newOutputStream(outputFile.toPath());
1✔
172
            } else {
173
                os = Files.newOutputStream(new File(getProject().getBaseDir(), outputFile.toString()).toPath());
×
174
            }
175

176
            if (encoding == null) {
1!
177
                encoding = System.getProperty("file.encoding");
×
178
            }
179

180
            try (Writer writer = new BufferedWriter(new OutputStreamWriter(os, encoding))) {
1✔
181
                renderer.render(report, writer);
1✔
182
            }
183
        } catch (IOException ioe) {
×
184
            throw new ReportException(ioe);
×
185
        }
1✔
186
    }
1✔
187

188
    private void addFiles(CpdAnalysis cpd) throws IOException {
189
        for (FileSet fileSet : filesets) {
1✔
190
            DirectoryScanner directoryScanner = fileSet.getDirectoryScanner(getProject());
1✔
191
            String[] includedFiles = directoryScanner.getIncludedFiles();
1✔
192
            for (String includedFile : includedFiles) {
1✔
193
                Path file = directoryScanner.getBasedir().toPath().resolve(includedFile);
1✔
194
                cpd.files().addFile(file);
1✔
195
            }
196
        }
1✔
197
    }
1✔
198

199
    private CPDReportRenderer createRenderer() {
200
        if (TEXT_FORMAT.equals(format)) {
1!
201
            return new SimpleRenderer();
1✔
202
        } else if (CSV_FORMAT.equals(format)) {
×
203
            return new CSVRenderer();
×
204
        } else if (XMLOLD_FORMAT.equals(format)) {
×
205
            return new XMLOldRenderer();
×
206
        }
207
        return new XMLRenderer();
×
208
    }
209

210
    private void validateFields() throws BuildException {
211
        if (minimumTokenCount == 0) {
1!
212
            throw new BuildException("minimumTokenCount is required and must be greater than zero");
×
213
        }
214

215
        if (filesets.isEmpty()) {
1!
216
            throw new BuildException("Must include at least one FileSet");
×
217
        }
218

219
        if (LanguageRegistry.CPD.getLanguageById(language) == null) {
1!
220
            throw new BuildException("Language " + language + " is not supported. Available languages: "
×
221
                    + LanguageRegistry.CPD.commaSeparatedList(Language::getId));
×
222
        }
223
    }
1✔
224

225
    public void addFileset(FileSet set) {
226
        filesets.add(set);
1✔
227
    }
1✔
228

229
    public void setMinimumTokenCount(int minimumTokenCount) {
230
        this.minimumTokenCount = minimumTokenCount;
1✔
231
    }
1✔
232

233
    public void setIgnoreLiterals(boolean value) {
234
        this.ignoreLiterals = value;
×
235
    }
×
236

237
    public void setIgnoreIdentifiers(boolean value) {
238
        this.ignoreIdentifiers = value;
×
239
    }
×
240

241
    public void setIgnoreAnnotations(boolean value) {
242
        this.ignoreAnnotations = value;
×
243
    }
×
244

245
    public void setIgnoreUsings(boolean value) {
246
        this.ignoreUsings = value;
×
247
    }
×
248

249
    /**
250
     * @deprecated Since 7.3.0. Use {@link #setFailOnError(boolean)} instead.
251
     */
252
    @Deprecated
253
    public void setSkipLexicalErrors(boolean skipLexicalErrors) {
254
        this.skipLexicalErrors = skipLexicalErrors;
×
255
    }
×
256

257
    public void setSkipDuplicateFiles(boolean skipDuplicateFiles) {
258
        this.skipDuplicateFiles = skipDuplicateFiles;
×
259
    }
×
260

261
    public void setOutputFile(File outputFile) {
262
        this.outputFile = outputFile;
1✔
263
    }
1✔
264

265
    public void setFormat(FormatAttribute formatAttribute) {
266
        this.format = formatAttribute.getValue();
×
267
    }
×
268

269
    public void setLanguage(String language) {
270
        this.language = language;
1✔
271
    }
1✔
272

273
    public void setEncoding(String encoding) {
274
        this.encoding = encoding;
×
275
    }
×
276

277
    public void setSkipBlocks(boolean skipBlocks) {
278
        this.skipBlocks = skipBlocks;
×
279
    }
×
280

281
    public void setSkipBlocksPattern(String skipBlocksPattern) {
282
        this.skipBlocksPattern = skipBlocksPattern;
×
283
    }
×
284

285
    /**
286
     * Whether to fail the build if any recoverable errors occurred while processing the files.
287
     *
288
     * @since 7.3.0
289
     */
290
    public void setFailOnError(boolean failOnError) {
291
        this.failOnError = failOnError;
1✔
292
    }
1✔
293

294
    public static class FormatAttribute extends EnumeratedAttribute {
×
295
        private static final String[] FORMATS = new String[] { XML_FORMAT, TEXT_FORMAT, CSV_FORMAT, XMLOLD_FORMAT };
×
296

297
        @Override
298
        public String[] getValues() {
299
            return FORMATS;
×
300
        }
301
    }
302
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc