View Javadoc

1   /*
2    * Sonar, entreprise quality control tool.
3    * Copyright (C) 2007-2008 Hortis-GRC SA
4    * mailto:be_agile HAT hortis DOT ch
5    *
6    * Sonar is free software; you can redistribute it and/or
7    * modify it under the terms of the GNU Lesser General Public
8    * License as published by the Free Software Foundation; either
9    * version 3 of the License, or (at your option) any later version.
10   *
11   * Sonar is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with Sonar; if not, write to the Free Software
18   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
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     * only for unit tests
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     * only for unit tests
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       // First step : the file is read with system charset encoding. It should resolve the problem in most cases
114       String xml = FileUtils.readFileToString(file);
115 
116       // second step : remove CDATA nodes that contain wrong characters. Those nodes are not needed by the collector.
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       //recorder.createClassMeasure(namespace, filename, false, new MeasureKey(MetricsRepository.DUPLICATIONS), duplication);
188       recorder.createClassMeasure(namespace, filename, false, new MeasureKey(CoreMetrics.DUPLICATED_LINES), duplicatedLines);
189       //recorder.createClassMeasure(namespace, filename, false, new MeasureKey(MetricsRepository.DUPLICATED_TOKENS), duplicatedTokens);
190     }
191   }
192 }