1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.sonar.maven.cpd;
21
22 import ch.hortis.sonar.service.MeasureKey;
23 import org.apache.commons.io.FileUtils;
24 import org.apache.commons.lang.StringUtils;
25 import org.apache.maven.project.MavenProject;
26 import org.sonar.commons.metrics.CoreMetrics;
27 import org.sonar.maven.AbstractCollector;
28 import org.sonar.plugins.api.Languages;
29 import org.sonar.plugins.java.JavaMeasuresRecorder;
30 import org.sonar.plugins.maven.MavenUtils;
31 import org.sonar.plugins.utils.XmlParserException;
32 import org.sonar.plugins.utils.XmlReportParser;
33 import org.w3c.dom.Element;
34 import org.w3c.dom.NodeList;
35
36 import java.io.File;
37 import java.io.IOException;
38 import java.text.ParseException;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43
44 public class CpdCollector extends AbstractCollector {
45 private XmlReportParser parser;
46 private JavaMeasuresRecorder recorder;
47 private List<String> sourceDirs;
48
49 public CpdCollector(MavenProject project, JavaMeasuresRecorder recorder) {
50 File xmlFile = findFileFromBuildDirectory(project, "cpd.xml");
51 if (xmlFile != null && xmlFile.exists()) {
52 parser = new XmlReportParser();
53 parser.parse(readXmlWithoutEncodingErrors(xmlFile));
54 sourceDirs = project.getCompileSourceRoots();
55 this.recorder = recorder;
56 }
57 }
58
59 public boolean shouldStopOnFailure() {
60 return true;
61 }
62
63 public boolean shouldExecuteOn(MavenProject mavenProject) {
64 return MavenUtils.getLanguage(mavenProject).equals(Languages.JAVA);
65 }
66
67
68
69
70 public CpdCollector(File xmlFile, String[] sourceDirs, JavaMeasuresRecorder recorder) {
71 parser = new XmlReportParser();
72 parser.parse(readXmlWithoutEncodingErrors(xmlFile));
73 this.sourceDirs = Arrays.asList(sourceDirs);
74 this.recorder = recorder;
75 }
76
77
78
79
80
81 protected CpdCollector() {
82 }
83
84 public void collect() {
85 try {
86 if (parser != null) {
87 collectFileMeasures();
88 }
89
90 } catch (ParseException e) {
91 throw new XmlParserException(e);
92 }
93 }
94
95 private void collectFileMeasures() throws ParseException {
96 NodeList duplications = parser.executeXPathNodeList("/pmd-cpd/duplication");
97 Map<String, ClassTempData> classData = new HashMap<String, ClassTempData>();
98 for (int i=0; i < duplications.getLength(); i++) {
99 Element duplication = (Element) duplications.item(i);
100
101 NodeList files = parser.executeXPathNodeList(duplication, "file");
102 processFileMeasure(classData, (Element) files.item(0), duplication);
103 processFileMeasure(classData, (Element) files.item(1), duplication);
104 }
105
106 for (ClassTempData data : classData.values()) {
107 data.saveUsing(recorder);
108 }
109 }
110
111 private String readXmlWithoutEncodingErrors(File file) {
112 try {
113
114 String xml = FileUtils.readFileToString(file);
115
116
117 return removeCDataNodes(xml);
118
119 } catch (IOException e) {
120 throw new XmlParserException("can not read the file " + file.getAbsolutePath(), e);
121 }
122 }
123
124 String removeCDataNodes(String xml) {
125 String result = xml;
126 String startNode = "<codefragment>";
127 String endNode = "</codefragment>";
128 String[] subs = StringUtils.substringsBetween(xml, startNode, endNode);
129 if (subs != null) {
130 for (String sub : subs) {
131 result = StringUtils.remove(result, startNode + sub + endNode);
132 }
133 }
134 return result;
135 }
136
137 private void processFileMeasure(Map<String, ClassTempData> fileContainer, Element fileEl, Element duplication) throws ParseException {
138 String filePath = fileEl.getAttribute("path").replace('\\', '/');
139 String fileName = StringUtils.substringAfterLast(filePath, "/");
140 fileName = StringUtils.substringBeforeLast(fileName, ".java");
141 String packageName = StringUtils.substringBeforeLast(filePath, "/");
142 if (sourceDirs != null) {
143 for (String sourceDir : sourceDirs) {
144 String source = sourceDir.replace('\\', '/');
145 if (packageName.startsWith(source) || packageName.contains(source)) {
146 if (!source.endsWith("/")) {
147 source += "/";
148 }
149 packageName = StringUtils.substringAfter(packageName, source).replace('/', '.');
150 packageName = packageName.replace('/', '.');
151
152 cumulateClassMeasures(packageName, fileName, duplication, fileContainer);
153 }
154 }
155 }
156 }
157
158 private void cumulateClassMeasures(String packageName, String fileName, Element duplication, Map<String, ClassTempData> fileContainer) throws ParseException {
159 ClassTempData data = fileContainer.get(packageName + fileName);
160 if (data == null) {
161 data = new ClassTempData(packageName, fileName);
162 fileContainer.put(packageName + fileName, data);
163 }
164
165 data.cumulate(1, parseNumber(duplication.getAttribute("lines")), parseNumber(duplication.getAttribute("tokens")));
166 }
167
168 private static class ClassTempData {
169 protected double duplication;
170 protected double duplicatedLines;
171 protected double duplicatedTokens;
172 protected String namespace;
173 protected String filename;
174
175 private ClassTempData(String namespace, String filename) {
176 this.namespace = namespace;
177 this.filename = filename;
178 }
179
180 protected void cumulate(double duplication, double duplicatedLines, double duplicatedTokens) {
181 this.duplication += duplication;
182 this.duplicatedLines += duplicatedLines;
183 this.duplicatedTokens += duplicatedTokens;
184 }
185
186 protected void saveUsing(JavaMeasuresRecorder recorder) {
187
188 recorder.createClassMeasure(namespace, filename, false, new MeasureKey(CoreMetrics.DUPLICATED_LINES), duplicatedLines);
189
190 }
191 }
192 }