1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.pmd.exec;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.ObjectInputStream;
26 import java.io.ObjectOutputStream;
27 import java.io.OutputStreamWriter;
28 import java.io.Writer;
29 import java.nio.charset.Charset;
30 import java.util.Objects;
31 import java.util.function.Predicate;
32
33 import net.sourceforge.pmd.cpd.CPDConfiguration;
34 import net.sourceforge.pmd.cpd.CPDReport;
35 import net.sourceforge.pmd.cpd.CPDReportRenderer;
36 import net.sourceforge.pmd.cpd.CSVRenderer;
37 import net.sourceforge.pmd.cpd.CpdAnalysis;
38 import net.sourceforge.pmd.cpd.Match;
39 import net.sourceforge.pmd.cpd.SimpleRenderer;
40 import net.sourceforge.pmd.cpd.XMLRenderer;
41 import net.sourceforge.pmd.lang.Language;
42 import org.apache.maven.plugin.MojoExecutionException;
43 import org.apache.maven.plugins.pmd.ExcludeDuplicationsFromFile;
44 import org.apache.maven.reporting.MavenReportException;
45 import org.codehaus.plexus.util.FileUtils;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49
50
51
52 public class CpdExecutor extends Executor {
53 private static final Logger LOG = LoggerFactory.getLogger(CpdExecutor.class);
54
55 public static CpdResult execute(CpdRequest request) throws MavenReportException {
56 if (request.getJavaExecutable() != null) {
57 return fork(request);
58 }
59
60 ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
61 try {
62 Thread.currentThread().setContextClassLoader(CpdExecutor.class.getClassLoader());
63 CpdExecutor cpdExecutor = new CpdExecutor(request);
64 return cpdExecutor.run();
65 } finally {
66 Thread.currentThread().setContextClassLoader(origLoader);
67 }
68 }
69
70 private static CpdResult fork(CpdRequest request) throws MavenReportException {
71 File basePmdDir = new File(request.getTargetDirectory(), "pmd");
72 basePmdDir.mkdirs();
73 File cpdRequestFile = new File(basePmdDir, "cpdrequest.bin");
74 try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(cpdRequestFile))) {
75 out.writeObject(request);
76 } catch (IOException e) {
77 throw new MavenReportException(e.getMessage(), e);
78 }
79
80 String classpath = buildClasspath();
81 ProcessBuilder pb = new ProcessBuilder();
82
83 pb.environment().put("CLASSPATH", classpath);
84 pb.command().add(request.getJavaExecutable());
85 pb.command().add(CpdExecutor.class.getName());
86 pb.command().add(cpdRequestFile.getAbsolutePath());
87
88 LOG.debug("Executing: CLASSPATH={}, command={}", classpath, pb.command());
89 try {
90 final Process p = pb.start();
91
92
93 ProcessStreamHandler.start(p.getInputStream(), System.out);
94 ProcessStreamHandler.start(p.getErrorStream(), System.err);
95 int exit = p.waitFor();
96 LOG.debug("CpdExecutor exit code: {}", exit);
97 if (exit != 0) {
98 throw new MavenReportException("CpdExecutor exited with exit code " + exit);
99 }
100 return new CpdResult(new File(request.getTargetDirectory(), "cpd.xml"), request.getOutputEncoding());
101 } catch (IOException e) {
102 throw new MavenReportException(e.getMessage(), e);
103 } catch (InterruptedException e) {
104 Thread.currentThread().interrupt();
105 throw new MavenReportException(e.getMessage(), e);
106 }
107 }
108
109
110
111
112
113
114
115
116
117
118
119
120 public static void main(String[] args) {
121 File requestFile = new File(args[0]);
122 try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(requestFile))) {
123 CpdRequest request = (CpdRequest) in.readObject();
124 CpdExecutor cpdExecutor = new CpdExecutor(request);
125 cpdExecutor.setupLogLevel(request.getLogLevel());
126 cpdExecutor.run();
127 System.exit(0);
128 } catch (IOException | ClassNotFoundException | MavenReportException e) {
129 LOG.error(e.getMessage(), e);
130 }
131 System.exit(1);
132 }
133
134 private final CpdRequest request;
135
136
137 private final ExcludeDuplicationsFromFile excludeDuplicationsFromFile = new ExcludeDuplicationsFromFile();
138
139 public CpdExecutor(CpdRequest request) {
140 this.request = Objects.requireNonNull(request);
141 }
142
143 private CpdResult run() throws MavenReportException {
144 try {
145 excludeDuplicationsFromFile.loadExcludeFromFailuresData(request.getExcludeFromFailureFile());
146 } catch (MojoExecutionException e) {
147 throw new MavenReportException("Error loading exclusions", e);
148 }
149
150 CPDConfiguration cpdConfiguration = new CPDConfiguration();
151 cpdConfiguration.setMinimumTileSize(request.getMinimumTokens());
152 cpdConfiguration.setIgnoreAnnotations(request.isIgnoreAnnotations());
153 cpdConfiguration.setIgnoreLiterals(request.isIgnoreLiterals());
154 cpdConfiguration.setIgnoreIdentifiers(request.isIgnoreIdentifiers());
155
156 String languageId = request.getLanguage();
157 if ("javascript".equals(languageId)) {
158 languageId = "ecmascript";
159 } else if (languageId == null) {
160 languageId = "java";
161 }
162 Language cpdLanguage = cpdConfiguration.getLanguageRegistry().getLanguageById(languageId);
163
164 cpdConfiguration.setOnlyRecognizeLanguage(cpdLanguage);
165 cpdConfiguration.setSourceEncoding(Charset.forName(request.getSourceEncoding()));
166
167 request.getFiles().forEach(f -> cpdConfiguration.addInputPath(f.toPath()));
168
169 LOG.debug("Executing CPD...");
170
171
172
173 try (CpdAnalysis cpd = CpdAnalysis.create(cpdConfiguration)) {
174 cpd.performAnalysis(report -> {
175 try {
176 writeXmlReport(report);
177
178
179 String format = request.getFormat();
180 if (!"html".equals(format) && !"xml".equals(format)) {
181 writeFormattedReport(report);
182 }
183 } catch (MavenReportException e) {
184 LOG.error("Error while writing CPD report", e);
185 }
186 });
187 } catch (IOException e) {
188 LOG.error("Error while executing CPD", e);
189 }
190 LOG.debug("CPD finished.");
191
192 return new CpdResult(new File(request.getTargetDirectory(), "cpd.xml"), request.getOutputEncoding());
193 }
194
195 private void writeXmlReport(CPDReport cpd) throws MavenReportException {
196 File targetFile = writeReport(cpd, new XMLRenderer(request.getOutputEncoding()), "xml");
197 if (request.isIncludeXmlInSite()) {
198 File siteDir = new File(request.getReportOutputDirectory());
199 siteDir.mkdirs();
200 try {
201 FileUtils.copyFile(targetFile, new File(siteDir, "cpd.xml"));
202 } catch (IOException e) {
203 throw new MavenReportException(e.getMessage(), e);
204 }
205 }
206 }
207
208 private File writeReport(CPDReport cpd, CPDReportRenderer r, String extension) throws MavenReportException {
209 if (r == null) {
210 return null;
211 }
212
213 File targetDir = new File(request.getTargetDirectory());
214 targetDir.mkdirs();
215 File targetFile = new File(targetDir, "cpd." + extension);
216 try (Writer writer = new OutputStreamWriter(new FileOutputStream(targetFile), request.getOutputEncoding())) {
217 r.render(cpd.filterMatches(filterMatches()), writer);
218 writer.flush();
219 } catch (IOException ioe) {
220 throw new MavenReportException(ioe.getMessage(), ioe);
221 }
222 return targetFile;
223 }
224
225 private void writeFormattedReport(CPDReport cpd) throws MavenReportException {
226 CPDReportRenderer r = createRenderer(request.getFormat(), request.getOutputEncoding());
227 writeReport(cpd, r, request.getFormat());
228 }
229
230
231
232
233
234
235
236 public static CPDReportRenderer createRenderer(String format, String outputEncoding) throws MavenReportException {
237 CPDReportRenderer renderer = null;
238 if ("xml".equals(format)) {
239 renderer = new XMLRenderer(outputEncoding);
240 } else if ("csv".equals(format)) {
241 renderer = new CSVRenderer();
242 } else if ("txt".equals(format)) {
243 renderer = new SimpleRenderer();
244 } else if (!"".equals(format) && !"none".equals(format)) {
245 try {
246 renderer = (CPDReportRenderer)
247 Class.forName(format).getConstructor().newInstance();
248 } catch (Exception e) {
249 throw new MavenReportException(
250 "Can't find CPD custom format " + format + ": "
251 + e.getClass().getName(),
252 e);
253 }
254 }
255
256 return renderer;
257 }
258
259 private Predicate<Match> filterMatches() {
260 return (Match match) -> {
261 LOG.debug("Filtering duplications. Using " + excludeDuplicationsFromFile.countExclusions()
262 + " configured exclusions.");
263
264 if (excludeDuplicationsFromFile.isExcludedFromFailure(match)) {
265 LOG.debug("Excluded " + match + " duplications.");
266 return false;
267 } else {
268 return true;
269 }
270 };
271 }
272 }