/*
* This program is copyright (c) 2007 Hortis-GRC SA.
*
* This file is part of Sonar.
* Sonar is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package ch.hortis.sonar.mvn.mc;
import ch.hortis.sonar.model.Collectable;
import ch.hortis.sonar.model.File;
import ch.hortis.sonar.model.FileMeasure;
import ch.hortis.sonar.model.Metric;
import ch.hortis.sonar.model.Metrics;
import ch.hortis.sonar.model.ProjectMeasure;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPathConstants;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
public class CPDCollector extends BaseMeasuresCollector {
private Metric duplicationMetric;
private Metric duplicatedLinesMetric;
private Metric duplicatedTokensMetric;
private XmlReportParser parser;
private List sourcesDir;
public boolean initialize(MavenProject project) {
java.io.File cpdFile = findFileFromBuildDirectory(project, "cpd.xml");
boolean ok = false;
if (cpdFile != null) {
String xml = readXmlWithoutEncodingErrors(cpdFile);
parser = new XmlReportParser();
parser.parse(xml);
sourcesDir = project.getCompileSourceRoots();
duplicationMetric = loadMetric(Metrics.PMD_DUPLICATION);
duplicatedLinesMetric = loadMetric(Metrics.PMD_DUPLICATED_LINES);
duplicatedTokensMetric = loadMetric(Metrics.PMD_DUPLICATED_TOKENS);
ok = true;
}
return ok;
}
public List<Collectable> collect() throws MojoExecutionException {
ArrayList<Collectable> result = new ArrayList<Collectable>();
result.addAll(collectProjectMeasures());
result.addAll(collectFileMeasures());
return result;
}
protected String readXmlWithoutEncodingErrors(java.io.File file) {
try {
// First step : the file is read with system charset encoding. It should resolve the problem in most cases
String xml = FileUtils.readFileToString(file);
// second step : remove CDATA nodes that contain wrong characters. Those nodes are not needed by the collector.
return removeCDataNodes(xml);
} catch (IOException e) {
throw new XmlParserException("can not read the file " + file.getAbsolutePath(), e);
}
}
protected String removeCDataNodes(String xml) {
String result = xml;
String startNode = "<codefragment>";
String endNode = "</codefragment>";
String[] subs = StringUtils.substringsBetween(xml, startNode, endNode);
if (subs!=null) {
for (String sub:subs) {
result = StringUtils.remove(result, startNode + sub + endNode);
}
}
return result;
}
protected List<ProjectMeasure> collectProjectMeasures() throws MojoExecutionException {
NodeList duplications = parser.executeXPathNodeList("/pmd-cpd/duplication");
CPDReportDataContainer projectContainer = new CPDReportDataContainer();
for (int i = 0; i < duplications.getLength(); i++) {
Node duplication = duplications.item(i);
Number lines = (Number) parser.executeXPath(duplication, XPathConstants.NUMBER, "@lines");
Number tokens = (Number) parser.executeXPath(duplication, XPathConstants.NUMBER, "@tokens");
projectContainer.cumulate(1, lines, tokens);
}
return projectContainer.asMeasures();
}
public List<FileMeasure> collectFileMeasures() throws MojoExecutionException {
ArrayList<FileMeasure> result = new ArrayList<FileMeasure>();
NodeList duplications = parser.executeXPathNodeList("/pmd-cpd/duplication");
Map<String, CPDFileReportDataContainer> fileContainer = new HashMap<String, CPDFileReportDataContainer>();
for (int i = 0; i < duplications.getLength(); i++) {
Element duplication = (Element) duplications.item(i);
NodeList files = parser.executeXPathNodeList(duplication, "file");
processFileMeasure(fileContainer, (Element) files.item(0), duplication);
processFileMeasure(fileContainer, (Element) files.item(1), duplication);
}
for (CPDFileReportDataContainer data : fileContainer.values()) {
result.addAll(data.asMeasures());
}
return result;
}
private void processFileMeasure(Map<String, CPDFileReportDataContainer> fileContainer, Element fileEl, Element duplication) throws MojoExecutionException {
String filePath = fileEl.getAttribute("path").replace('\\', '/');
String fileName = StringUtils.substringAfterLast(filePath, "/");
String packageName = StringUtils.substringBeforeLast(filePath, "/");
if (sourcesDir != null) {
for (Iterator<String> srcItr = sourcesDir.iterator(); srcItr.hasNext();) {
String sourceDir = srcItr.next().replace('\\', '/');
if (packageName.startsWith(sourceDir) || packageName.contains(sourceDir)) {
if (!sourceDir.endsWith("/")) {
sourceDir += "/";
}
packageName = StringUtils.substringAfter(packageName, sourceDir).replace('/', '.');
packageName = packageName.replace('/', '.');
CPDFileReportDataContainer data = fileContainer.get(packageName + fileName);
if (data == null) {
File file = getFilesRepository().getFile(packageName, fileName);
if (file == null) {
throw new MojoExecutionException("Unable to find file '" + fileName + "' in package '" + packageName + "'");
}
data = new CPDFileReportDataContainer(file);
fileContainer.put(packageName + fileName, data);
}
try {
data.cumulate(1, parseNumber(duplication.getAttribute("lines")), parseNumber(duplication.getAttribute("tokens")));
} catch (ParseException e) {
throw new MojoExecutionException("Number parsing exception", e);
}
}
}
}
}
private class CPDReportDataContainer {
protected double duplication;
protected double duplicatedLines;
protected double duplicatedTokens;
protected void cumulate(Number duplication, Number duplicatedLines, Number duplicatedTokens) {
this.duplication += duplication.doubleValue();
this.duplicatedLines += duplicatedLines.doubleValue();
this.duplicatedTokens += duplicatedTokens.doubleValue();
}
private List<ProjectMeasure> asMeasures() {
List<ProjectMeasure> measures = new ArrayList<ProjectMeasure>();
ProjectMeasure measure = new ProjectMeasure();
measure.setMetric(duplicationMetric);
measure.setValue(duplication);
measures.add(measure);
measure = new ProjectMeasure();
measure.setMetric(duplicatedLinesMetric);
measure.setValue(duplicatedLines);
measures.add(measure);
measure = new ProjectMeasure();
measure.setMetric(duplicatedTokensMetric);
measure.setValue(duplicatedTokens);
measures.add(measure);
return measures;
}
}
private class CPDFileReportDataContainer extends CPDReportDataContainer {
private File file;
private CPDFileReportDataContainer(File file) {
this.file = file;
}
private Collection<FileMeasure> asMeasures() {
Collection<FileMeasure> measures = new ArrayList<FileMeasure>();
FileMeasure measure = new FileMeasure();
measure.setMetric(duplicationMetric);
measure.setFile(file);
measure.setValue(duplication);
measures.add(measure);
measure = new FileMeasure();
measure.setMetric(duplicatedLinesMetric);
measure.setFile(file);
measure.setValue(duplicatedLines);
measures.add(measure);
measure = new FileMeasure();
measure.setMetric(duplicatedTokensMetric);
measure.setFile(file);
measure.setValue(duplicatedTokens);
measures.add(measure);
return measures;
}
}
}