aboutsummaryrefslogtreecommitdiffstats
path: root/plugins/sonar-cpd-plugin
diff options
context:
space:
mode:
authorsimonbrandhof <simon.brandhof@gmail.com>2010-09-06 14:08:06 +0000
committersimonbrandhof <simon.brandhof@gmail.com>2010-09-06 14:08:06 +0000
commitaeadc1f9129274949daaa57738c7c4550bdfbc7b (patch)
tree08dadf5ef7474fc41d1d48f74648f1ba8b55f34d /plugins/sonar-cpd-plugin
downloadsonarqube-aeadc1f9129274949daaa57738c7c4550bdfbc7b.tar.gz
sonarqube-aeadc1f9129274949daaa57738c7c4550bdfbc7b.zip
SONAR-236 remove deprecated code from checkstyle plugin + display default value of rule parameters in Q profile console
Diffstat (limited to 'plugins/sonar-cpd-plugin')
-rw-r--r--plugins/sonar-cpd-plugin/pom.xml79
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java150
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdException.java26
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java86
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java123
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdMapping.java65
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecorator.java90
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecorator.java55
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java281
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java32
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java106
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecoratorTest.java80
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecoratorTest.java66
13 files changed, 1239 insertions, 0 deletions
diff --git a/plugins/sonar-cpd-plugin/pom.xml b/plugins/sonar-cpd-plugin/pom.xml
new file mode 100644
index 00000000000..f15ec0e824d
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar</artifactId>
+ <version>2.3-SNAPSHOT</version>
+ <relativePath>../..</relativePath>
+ </parent>
+ <groupId>org.codehaus.sonar.plugins</groupId>
+ <artifactId>sonar-cpd-plugin</artifactId>
+ <name>Sonar :: Plugins :: CPD</name>
+ <packaging>sonar-plugin</packaging>
+ <description>Find duplicated source code within project.</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>pmd</groupId>
+ <artifactId>pmd</artifactId>
+ <version>4.2.5</version>
+ <exclusions>
+ <exclusion>
+ <groupId>jaxen</groupId>
+ <artifactId>jaxen</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>ant</groupId>
+ <artifactId>ant</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-duplications</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-plugin-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-testing-harness</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <testResources>
+ <testResource>
+ <directory>${basedir}/src/main/resources</directory>
+ </testResource>
+ <testResource>
+ <directory>${basedir}/src/test/resources</directory>
+ </testResource>
+ </testResources>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.sonar</groupId>
+ <artifactId>sonar-packaging-maven-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <pluginKey>cpd</pluginKey>
+ <pluginName>Duplications</pluginName>
+ <pluginClass>org.sonar.plugins.cpd.CpdPlugin</pluginClass>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project> \ No newline at end of file
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java
new file mode 100644
index 00000000000..4d1da130abb
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdAnalyser.java
@@ -0,0 +1,150 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.sourceforge.pmd.cpd.TokenEntry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.CpdMapping;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.duplications.cpd.Match;
+
+public class CpdAnalyser {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CpdAnalyser.class);
+
+ private CpdMapping mapping;
+ private SensorContext context;
+ private Project project;
+
+ public CpdAnalyser(Project project, SensorContext context, CpdMapping mapping) {
+ this.mapping = mapping;
+ this.context = context;
+ this.project = project;
+ }
+
+ public void analyse(Iterator<Match> matches) {
+ Map<Resource, DuplicationsData> duplicationsData = new HashMap<Resource, DuplicationsData>();
+ while (matches.hasNext()) {
+ Match match = matches.next();
+
+ for (TokenEntry firstMark : match.getMarkSet()) {
+ String firstAbsolutePath = firstMark.getTokenSrcID();
+ int firstLine = firstMark.getBeginLine();
+
+ Resource firstFile = mapping.createResource(new File(firstAbsolutePath), project.getFileSystem().getSourceDirs());
+ if (firstFile == null) {
+ LOG.warn("CPD - File not found : {}", firstAbsolutePath);
+ continue;
+ }
+
+ for (TokenEntry tokenEntry : match.getMarkSet()) {
+ String secondAbsolutePath = tokenEntry.getTokenSrcID();
+ int secondLine = tokenEntry.getBeginLine();
+ if (secondAbsolutePath.equals(firstAbsolutePath) && firstLine == secondLine) {
+ continue;
+ }
+ Resource secondFile = mapping.createResource(new File(secondAbsolutePath), project.getFileSystem().getSourceDirs());
+ if (secondFile == null) {
+ LOG.warn("CPD - File not found : {}", secondAbsolutePath);
+ continue;
+ }
+
+ processClassMeasure(duplicationsData, firstFile, firstLine, secondFile, secondLine, match.getLineCount());
+ }
+ }
+ }
+
+ for (DuplicationsData data : duplicationsData.values()) {
+ data.saveUsing(context);
+ }
+ }
+
+ private void processClassMeasure(Map<Resource, DuplicationsData> fileContainer, Resource file, int duplicationStartLine,
+ Resource targetFile, int targetDuplicationStartLine, int duplicatedLines) {
+ if (file != null && targetFile != null) {
+ DuplicationsData data = fileContainer.get(file);
+ if (data == null) {
+ data = new DuplicationsData(file, context);
+ fileContainer.put(file, data);
+ }
+ data.cumulate(targetFile, targetDuplicationStartLine, duplicationStartLine, duplicatedLines);
+ }
+ }
+
+ private static final class DuplicationsData {
+
+ protected Set<Integer> duplicatedLines = new HashSet<Integer>();
+ protected double duplicatedBlocks;
+ protected Resource resource;
+ private SensorContext context;
+ private List<StringBuilder> duplicationXMLEntries = new ArrayList<StringBuilder>();
+
+ private DuplicationsData(Resource resource, SensorContext context) {
+ this.context = context;
+ this.resource = resource;
+ }
+
+ protected void cumulate(Resource targetResource, int targetDuplicationStartLine, int duplicationStartLine, int duplicatedLines) {
+ StringBuilder xml = new StringBuilder();
+ xml.append("<duplication lines=\"").append(duplicatedLines).append("\" start=\"").append(duplicationStartLine).append(
+ "\" target-start=\"").append(targetDuplicationStartLine).append("\" target-resource=\"").append(
+ context.saveResource(targetResource)).append("\"/>");
+
+ duplicationXMLEntries.add(xml);
+
+ int duplicatedLinesBefore = this.duplicatedLines.size();
+ for (int duplicatedLine = duplicationStartLine; duplicatedLine < duplicationStartLine + duplicatedLines; duplicatedLine++) {
+ this.duplicatedLines.add(duplicatedLine);
+ }
+ this.duplicatedBlocks++;
+ }
+
+ protected void saveUsing(SensorContext context) {
+ context.saveMeasure(resource, CoreMetrics.DUPLICATED_FILES, 1d);
+ context.saveMeasure(resource, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size());
+ context.saveMeasure(resource, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks);
+ context.saveMeasure(resource, new Measure(CoreMetrics.DUPLICATIONS_DATA, getDuplicationXMLData()));
+ }
+
+ private String getDuplicationXMLData() {
+ StringBuilder duplicationXML = new StringBuilder("<duplications>");
+ for (StringBuilder xmlEntry : duplicationXMLEntries) {
+ duplicationXML.append(xmlEntry);
+ }
+ duplicationXML.append("</duplications>");
+ return duplicationXML.toString();
+ }
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdException.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdException.java
new file mode 100644
index 00000000000..389a4c59c08
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdException.java
@@ -0,0 +1,26 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+public class CpdException extends RuntimeException {
+ public CpdException(Throwable throwable) {
+ super(throwable);
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
new file mode 100644
index 00000000000..e4a8c54a4ad
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
@@ -0,0 +1,86 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+import org.sonar.api.*;
+import org.sonar.plugins.cpd.decorators.DuplicationDensityDecorator;
+import org.sonar.plugins.cpd.decorators.SumDuplicationsDecorator;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Properties({
+ @Property(
+ key = CoreProperties.CPD_MINIMUM_TOKENS_PROPERTY,
+ defaultValue = CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE + "",
+ name = "Minimum tokens",
+ description = "The number of duplicate tokens above which a block is considered as a duplication.",
+ project = true,
+ module = true,
+ global = true),
+ @Property(
+ key = CoreProperties.CPD_IGNORE_LITERALS_PROPERTY,
+ defaultValue = CoreProperties.CPD_IGNORE_LITERALS_DEFAULT_VALUE + "",
+ name = "Ignore literals",
+ description = "if true, CPD ignores literal value differences when evaluating a duplicate block. " +
+ "This means that foo=\"first string\"; and foo=\"second string\"; will be seen as equivalent.",
+ project = true,
+ module = true,
+ global = true),
+ @Property(
+ key = CoreProperties.CPD_IGNORE_IDENTIFIERS_PROPERTY,
+ defaultValue = CoreProperties.CPD_IGNORE_IDENTIFIERS_DEFAULT_VALUE + "",
+ name = "Ignore identifiers",
+ description = "Similar to 'Ignore literals' but for identifiers; i.e., variable names, methods names, and so forth.",
+ project = true,
+ module = true,
+ global = true),
+ @Property(
+ key = CoreProperties.CPD_SKIP_PROPERTY,
+ defaultValue = "false",
+ name = "Skip detection of duplicated code",
+ description = "Searching for duplicated code is memory hungry therefore for very big projects it can be necessary to turn the functionality off.",
+ project = true,
+ module = true,
+ global = true)
+})
+public class CpdPlugin implements Plugin {
+
+ public String getKey() {
+ return CoreProperties.CPD_PLUGIN;
+ }
+
+ public String getName() {
+ return "Duplications";
+ }
+
+ public String getDescription() {
+ return "Find duplicated source code within project.";
+ }
+
+ public List<Class<? extends Extension>> getExtensions() {
+ List<Class<? extends Extension>> list = new ArrayList<Class<? extends Extension>>();
+ list.add(CpdSensor.class);
+ list.add(SumDuplicationsDecorator.class);
+ list.add(DuplicationDensityDecorator.class);
+ list.add(JavaCpdMapping.class);
+ return list;
+ }
+} \ No newline at end of file
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
new file mode 100644
index 00000000000..db232383622
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
@@ -0,0 +1,123 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+import net.sourceforge.pmd.cpd.AbstractLanguage;
+import net.sourceforge.pmd.cpd.TokenEntry;
+import org.apache.commons.configuration.Configuration;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.CpdMapping;
+import org.sonar.api.batch.Sensor;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Project;
+import org.sonar.duplications.cpd.CPD;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+public class CpdSensor implements Sensor {
+
+ private CpdMapping[] mappings;
+
+ public CpdSensor(CpdMapping[] mappings) {
+ this.mappings = mappings;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ CpdMapping mapping = getMapping(project.getLanguage());
+ if (mapping == null) {
+ LoggerFactory.getLogger(getClass()).info("Detection of duplication code is not supported for {}.", project.getLanguage());
+ return false;
+ }
+
+ if (isSkipped(project)) {
+ LoggerFactory.getLogger(getClass()).info("Detection of duplicated code is skipped");
+ return false;
+ }
+
+ return true;
+ }
+
+ boolean isSkipped(Project project) {
+ Configuration conf = project.getConfiguration();
+ return conf.getBoolean("sonar.cpd." + project.getLanguageKey() + ".skip",
+ conf.getBoolean("sonar.cpd.skip", false));
+ }
+
+ public void analyse(Project project, SensorContext context) {
+ CpdMapping mapping = getMapping(project.getLanguage());
+ CPD cpd = executeCPD(project, mapping, project.getFileSystem().getSourceCharset());
+ saveResults(cpd, mapping, project, context);
+ }
+
+ private CpdMapping getMapping(Language language) {
+ for (CpdMapping cpdMapping : mappings) {
+ if (cpdMapping.getLanguage().equals(language)) {
+ return cpdMapping;
+ }
+ }
+ return null;
+ }
+
+ private void saveResults(CPD cpd, CpdMapping mapping, Project project, SensorContext context) {
+ CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, mapping);
+ cpdAnalyser.analyse(cpd.getMatches());
+ }
+
+ private CPD executeCPD(Project project, CpdMapping mapping, Charset encoding) {
+ try {
+ CPD cpd = configureCPD(project, mapping, encoding);
+ cpd.go();
+ return cpd;
+
+ } catch (Exception e) {
+ throw new CpdException(e);
+ }
+ }
+
+ private CPD configureCPD(Project project, CpdMapping mapping, Charset encoding) throws IOException {
+ // To avoid a cpd bug generating error as "java.lang.IndexOutOfBoundsException: Index: 259, Size: 248"
+ // See http://sourceforge.net/tracker/?func=detail&atid=479921&aid=1947823&group_id=56262 for more details
+ TokenEntry.clearImages();
+
+ int minTokens = getMinimumTokens(project);
+ AbstractLanguage cpdLanguage = new AbstractLanguage(mapping.getTokenizer()) {
+ };
+
+ CPD cpd = new CPD(minTokens, cpdLanguage);
+ cpd.setEncoding(encoding.name());
+ cpd.setLoadSourceCodeSlices(false);
+ cpd.add(project.getFileSystem().getSourceFiles(project.getLanguage()));
+ return cpd;
+ }
+
+ int getMinimumTokens(Project project) {
+ Configuration conf = project.getConfiguration();
+ return conf.getInt("sonar.cpd." + project.getLanguageKey() + ".minimumTokens",
+ conf.getInt("sonar.cpd.minimumTokens", CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE));
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName();
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdMapping.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdMapping.java
new file mode 100644
index 00000000000..5faa5ab2a1d
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdMapping.java
@@ -0,0 +1,65 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+import java.io.File;
+import java.util.List;
+import java.util.Properties;
+
+import net.sourceforge.pmd.cpd.JavaTokenizer;
+import net.sourceforge.pmd.cpd.Tokenizer;
+
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.CpdMapping;
+import org.sonar.api.resources.Java;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Language;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+public class JavaCpdMapping implements CpdMapping {
+
+ private String ignore_literals;
+ private String ignore_identifiers;
+
+ public JavaCpdMapping(Project project) {
+ ignore_literals = project.getConfiguration().getString(CoreProperties.CPD_IGNORE_LITERALS_PROPERTY,
+ CoreProperties.CPD_IGNORE_LITERALS_DEFAULT_VALUE);
+ ignore_identifiers = project.getConfiguration().getString(CoreProperties.CPD_IGNORE_IDENTIFIERS_PROPERTY,
+ CoreProperties.CPD_IGNORE_IDENTIFIERS_DEFAULT_VALUE);
+ }
+
+ public Tokenizer getTokenizer() {
+ Properties props = new Properties();
+ props.setProperty(JavaTokenizer.IGNORE_LITERALS, ignore_literals);
+ props.setProperty(JavaTokenizer.IGNORE_IDENTIFIERS, ignore_identifiers);
+ JavaTokenizer tokenizer = new JavaTokenizer();
+ tokenizer.setProperties(props);
+ return tokenizer;
+ }
+
+ public Resource createResource(File file, List<File> sourceDirs) {
+ return JavaFile.fromIOFile(file, sourceDirs, false);
+ }
+
+ public Language getLanguage() {
+ return Java.INSTANCE;
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecorator.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecorator.java
new file mode 100644
index 00000000000..c05835cfb13
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecorator.java
@@ -0,0 +1,90 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd.decorators;
+
+import org.sonar.api.batch.Decorator;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.batch.DependsUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class DuplicationDensityDecorator implements Decorator {
+
+ @DependsUpon
+ public List<Metric> dependsUponMetrics() {
+ return Arrays.asList(
+ CoreMetrics.NCLOC,
+ CoreMetrics.COMMENT_LINES,
+ CoreMetrics.DUPLICATED_LINES,
+ CoreMetrics.LINES);
+ }
+
+ @DependedUpon
+ public Metric generatesMetric() {
+ return CoreMetrics.DUPLICATED_LINES_DENSITY;
+ }
+
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ public void decorate(Resource resource, DecoratorContext context) {
+ Measure nbDuplicatedLines = context.getMeasure(CoreMetrics.DUPLICATED_LINES);
+ if (nbDuplicatedLines == null) {
+ return;
+ }
+
+ Double divisor = getNbLinesFromLocOrNcloc(context);
+ if (divisor != null && divisor > 0.0) {
+ context.saveMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY, calculate(nbDuplicatedLines.getValue(), divisor));
+ }
+ }
+
+ private Double getNbLinesFromLocOrNcloc(DecoratorContext context) {
+ Measure nbLoc = context.getMeasure(CoreMetrics.LINES);
+ if (nbLoc != null) {
+ // TODO test this branch
+ return nbLoc.getValue();
+ }
+ Measure nbNcloc = context.getMeasure(CoreMetrics.NCLOC);
+ if (nbNcloc != null) {
+ Measure nbComments = context.getMeasure(CoreMetrics.COMMENT_LINES);
+ Double nbLines = nbNcloc.getValue();
+ return nbComments != null ? nbLines + nbComments.getValue() : nbLines;
+ }
+ return null;
+ }
+
+ protected Double calculate(Double dividend, Double divisor) {
+ Double result = 100.0 * dividend / divisor;
+ if (result < 100.0) {
+ return result;
+ }
+ return 100.0;
+ }
+
+}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecorator.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecorator.java
new file mode 100644
index 00000000000..c47b18a7110
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecorator.java
@@ -0,0 +1,55 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd.decorators;
+
+import org.sonar.api.batch.AbstractSumChildrenDecorator;
+import org.sonar.api.batch.DependedUpon;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Metric;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.resources.ResourceUtils;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class SumDuplicationsDecorator extends AbstractSumChildrenDecorator {
+
+ @Override
+ @DependedUpon
+ public List<Metric> generatesMetrics() {
+ return Arrays.asList(CoreMetrics.DUPLICATED_BLOCKS, CoreMetrics.DUPLICATED_FILES, CoreMetrics.DUPLICATED_LINES);
+ }
+
+ @Override
+ protected boolean shouldSaveZeroIfNoChildMeasures() {
+ return true;
+ }
+
+ @Override
+ public boolean shouldExecuteOnProject(Project project) {
+ return true;
+ }
+
+ @Override
+ public boolean shouldDecorateResource(Resource resource) {
+ return !ResourceUtils.isUnitTestClass(resource);
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java
new file mode 100644
index 00000000000..cc4e4681e85
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdAnalyserTest.java
@@ -0,0 +1,281 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+import static org.mockito.Matchers.anyList;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import net.sourceforge.pmd.cpd.TokenEntry;
+
+import org.junit.Test;
+import org.sonar.api.batch.CpdMapping;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Project;
+import org.sonar.api.resources.ProjectFileSystem;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.test.IsMeasure;
+import org.sonar.duplications.cpd.Match;
+
+public class CpdAnalyserTest {
+
+ @Test
+ public void testOneSimpleDuplicationBetweenTwoFiles() throws Exception {
+ ProjectFileSystem fileSystem = mock(ProjectFileSystem.class);
+ when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList());
+ File file1 = new File("target/tmp/file1.ext");
+ File file2 = new File("target/tmp/file2.ext");
+
+ Project project = new Project("key").setFileSystem(fileSystem);
+
+ SensorContext context = mock(SensorContext.class);
+
+ CpdMapping cpdMapping = mock(CpdMapping.class);
+ Resource resource1 = new JavaFile("foo.Foo");
+ Resource resource2 = new JavaFile("foo.Bar");
+ when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource2).thenReturn(resource2)
+ .thenReturn(resource1);
+ when(context.saveResource(resource1)).thenReturn("key1");
+ when(context.saveResource(resource2)).thenReturn("key2");
+
+ Match match1 = new Match(5, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file2.getAbsolutePath(), 15));
+ match1.setLineCount(200);
+
+ CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping);
+ cpdAnalyser.analyse(Arrays.asList(match1).iterator());
+
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 1d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(
+ eq(resource1),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
+ + "<duplication lines=\"200\" start=\"5\" target-start=\"15\" target-resource=\"key2\"/>" + "</duplications>")));
+
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_BLOCKS, 1d);
+ verify(context).saveMeasure(
+ eq(resource2),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA,
+ "<duplications><duplication lines=\"200\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/></duplications>")));
+
+ verify(context, atLeastOnce()).saveResource(resource1);
+ verify(context, atLeastOnce()).saveResource(resource2);
+ }
+
+ @Test
+ public void testClassicalCaseWithTwoDuplicatedBlocsInvolvingThreeFiles() throws Exception {
+ ProjectFileSystem fileSystem = mock(ProjectFileSystem.class);
+ when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList());
+ File file1 = new File("target/tmp/file1.ext");
+ File file2 = new File("target/tmp/file2.ext");
+ File file3 = new File("target/tmp/file3.ext");
+
+ Project project = new Project("key").setFileSystem(fileSystem);
+
+ SensorContext context = mock(SensorContext.class);
+
+ CpdMapping cpdMapping = mock(CpdMapping.class);
+ Resource resource1 = new JavaFile("foo.Foo");
+ Resource resource2 = new JavaFile("foo.Bar");
+ Resource resource3 = new JavaFile("foo.Hotel");
+ when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource2).thenReturn(resource2)
+ .thenReturn(resource1).thenReturn(resource1).thenReturn(resource3).thenReturn(resource3).thenReturn(resource1);
+ when(context.saveResource(resource1)).thenReturn("key1");
+ when(context.saveResource(resource2)).thenReturn("key2");
+ when(context.saveResource(resource3)).thenReturn("key3");
+
+ Match match1 = new Match(5, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file2.getAbsolutePath(), 15));
+ match1.setLineCount(200);
+ Match match2 = new Match(5, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file3.getAbsolutePath(), 15));
+ match2.setLineCount(100);
+
+ CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping);
+ cpdAnalyser.analyse(Arrays.asList(match1, match2).iterator());
+
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 2d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(
+ eq(resource1),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
+ + "<duplication lines=\"200\" start=\"5\" target-start=\"15\" target-resource=\"key2\"/>"
+ + "<duplication lines=\"100\" start=\"5\" target-start=\"15\" target-resource=\"key3\"/>" + "</duplications>")));
+
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_BLOCKS, 1d);
+ verify(context).saveMeasure(
+ eq(resource2),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA,
+ "<duplications><duplication lines=\"200\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/></duplications>")));
+
+ verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_LINES, 100d);
+ verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_BLOCKS, 1d);
+ verify(context).saveMeasure(
+ eq(resource3),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA,
+ "<duplications><duplication lines=\"100\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/></duplications>")));
+
+ verify(context, atLeastOnce()).saveResource(resource1);
+ verify(context, atLeastOnce()).saveResource(resource2);
+ verify(context, atLeastOnce()).saveResource(resource3);
+ }
+
+ @Test
+ public void testOneDuplicatedBlocInvolvingMoreThanTwoFiles() throws Exception {
+ ProjectFileSystem fileSystem = mock(ProjectFileSystem.class);
+ when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList());
+ File file1 = new File("target/tmp/file1.ext");
+ File file2 = new File("target/tmp/file2.ext");
+ File file3 = new File("target/tmp/file3.ext");
+ File file4 = new File("target/tmp/file4.ext");
+
+ Project project = new Project("key").setFileSystem(fileSystem);
+
+ SensorContext context = mock(SensorContext.class);
+
+ CpdMapping cpdMapping = mock(CpdMapping.class);
+ Resource resource1 = new JavaFile("foo.Foo");
+ Resource resource2 = new JavaFile("foo.Bar");
+ Resource resource3 = new JavaFile("foo.Hotel");
+ Resource resource4 = new JavaFile("foo.Coffee");
+ when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource2).thenReturn(resource3)
+ .thenReturn(resource4).thenReturn(resource2).thenReturn(resource1).thenReturn(resource3).thenReturn(resource4)
+ .thenReturn(resource3).thenReturn(resource1).thenReturn(resource2).thenReturn(resource4).thenReturn(resource4)
+ .thenReturn(resource1).thenReturn(resource2).thenReturn(resource3);
+ when(context.saveResource(resource1)).thenReturn("key1");
+ when(context.saveResource(resource2)).thenReturn("key2");
+ when(context.saveResource(resource3)).thenReturn("key3");
+ when(context.saveResource(resource4)).thenReturn("key4");
+
+ Match match = new Match(5, createTokenEntry(file1.getAbsolutePath(), 5), createTokenEntry(file2.getAbsolutePath(), 15));
+ match.setLineCount(200);
+ Set<TokenEntry> tokenEntries = new LinkedHashSet<TokenEntry>();
+ tokenEntries.add(createTokenEntry(file1.getAbsolutePath(), 5));
+ tokenEntries.add(createTokenEntry(file2.getAbsolutePath(), 15));
+ tokenEntries.add(createTokenEntry(file3.getAbsolutePath(), 7));
+ tokenEntries.add(createTokenEntry(file4.getAbsolutePath(), 10));
+ match.setMarkSet(tokenEntries);
+
+ CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping);
+ cpdAnalyser.analyse(Arrays.asList(match).iterator());
+
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 3d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(
+ eq(resource1),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
+ + "<duplication lines=\"200\" start=\"5\" target-start=\"15\" target-resource=\"key2\"/>"
+ + "<duplication lines=\"200\" start=\"5\" target-start=\"7\" target-resource=\"key3\"/>"
+ + "<duplication lines=\"200\" start=\"5\" target-start=\"10\" target-resource=\"key4\"/>" + "</duplications>")));
+
+ verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(resource3, CoreMetrics.DUPLICATED_BLOCKS, 3d);
+ verify(context).saveMeasure(
+ eq(resource2),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
+ + "<duplication lines=\"200\" start=\"15\" target-start=\"5\" target-resource=\"key1\"/>"
+ + "<duplication lines=\"200\" start=\"15\" target-start=\"7\" target-resource=\"key3\"/>"
+ + "<duplication lines=\"200\" start=\"15\" target-start=\"10\" target-resource=\"key4\"/>" + "</duplications>")));
+
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(resource2, CoreMetrics.DUPLICATED_BLOCKS, 3d);
+ verify(context).saveMeasure(
+ eq(resource3),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
+ + "<duplication lines=\"200\" start=\"7\" target-start=\"5\" target-resource=\"key1\"/>"
+ + "<duplication lines=\"200\" start=\"7\" target-start=\"15\" target-resource=\"key2\"/>"
+ + "<duplication lines=\"200\" start=\"7\" target-start=\"10\" target-resource=\"key4\"/>" + "</duplications>")));
+
+ verify(context).saveMeasure(resource4, CoreMetrics.DUPLICATED_LINES, 200d);
+ verify(context).saveMeasure(resource4, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource4, CoreMetrics.DUPLICATED_BLOCKS, 3d);
+ verify(context).saveMeasure(
+ eq(resource4),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
+ + "<duplication lines=\"200\" start=\"10\" target-start=\"5\" target-resource=\"key1\"/>"
+ + "<duplication lines=\"200\" start=\"10\" target-start=\"15\" target-resource=\"key2\"/>"
+ + "<duplication lines=\"200\" start=\"10\" target-start=\"7\" target-resource=\"key3\"/>" + "</duplications>")));
+
+ verify(context, atLeastOnce()).saveResource(resource1);
+ verify(context, atLeastOnce()).saveResource(resource2);
+ verify(context, atLeastOnce()).saveResource(resource3);
+ verify(context, atLeastOnce()).saveResource(resource4);
+ }
+
+ @Test
+ public void testDuplicationOnSameFile() throws Exception {
+ ProjectFileSystem fileSystem = mock(ProjectFileSystem.class);
+ when(fileSystem.getSourceDirs()).thenReturn(Collections.<File> emptyList());
+ File file1 = new File("target/tmp/file1.ext");
+
+ Project project = new Project("key").setFileSystem(fileSystem);
+
+ SensorContext context = mock(SensorContext.class);
+
+ CpdMapping cpdMapping = mock(CpdMapping.class);
+ Resource resource1 = new JavaFile("foo.Foo");
+ when(cpdMapping.createResource((File) anyObject(), anyList())).thenReturn(resource1).thenReturn(resource1);
+ when(context.saveResource(resource1)).thenReturn("key1");
+
+ Match match1 = new Match(304, new TokenEntry(null, file1.getAbsolutePath(), 5), new TokenEntry(null, file1.getAbsolutePath(), 215));
+ match1.setLineCount(200);
+
+ CpdAnalyser cpdAnalyser = new CpdAnalyser(project, context, cpdMapping);
+ cpdAnalyser.analyse(Arrays.asList(match1).iterator());
+
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_FILES, 1d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_BLOCKS, 2d);
+ verify(context).saveMeasure(resource1, CoreMetrics.DUPLICATED_LINES, 400d);
+ verify(context).saveMeasure(
+ eq(resource1),
+ argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
+ + "<duplication lines=\"200\" start=\"5\" target-start=\"215\" target-resource=\"key1\"/>"
+ + "<duplication lines=\"200\" start=\"215\" target-start=\"5\" target-resource=\"key1\"/>"
+ + "</duplications>")));
+
+ verify(context, atLeastOnce()).saveResource(resource1);
+ }
+
+ private static TokenEntry createTokenEntry(String sourceId, int line) {
+ TokenEntry entry = new TokenEntry(null, sourceId, line);
+ entry.setHashCode(sourceId.hashCode() + line);
+ return entry;
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java
new file mode 100644
index 00000000000..4fa0132b48d
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java
@@ -0,0 +1,32 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+import static org.hamcrest.number.OrderingComparisons.greaterThan;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+
+public class CpdPluginTest {
+
+ @Test
+ public void getExtensions() {
+ assertThat(new CpdPlugin().getExtensions().size(), greaterThan(1));
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
new file mode 100644
index 00000000000..3687a26b3a1
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java
@@ -0,0 +1,106 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd;
+
+import org.apache.commons.configuration.PropertiesConfiguration;
+import org.junit.Test;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.CpdMapping;
+import org.sonar.api.resources.Project;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+public class CpdSensorTest {
+
+ @Test
+ public void generalSkip() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.cpd.skip", "true");
+
+ Project project = createJavaProject().setConfiguration(conf);
+
+ CpdSensor sensor = new CpdSensor(new CpdMapping[0]);
+ assertTrue(sensor.isSkipped(project));
+ }
+
+ @Test
+ public void doNotSkipByDefault() {
+ Project project = createJavaProject().setConfiguration(new PropertiesConfiguration());
+
+ CpdSensor sensor = new CpdSensor(new CpdMapping[0]);
+ assertFalse(sensor.isSkipped(project));
+ }
+
+ @Test
+ public void skipByLanguage() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.cpd.skip", "false");
+ conf.setProperty("sonar.cpd.php.skip", "true");
+
+ Project phpProject = createPhpProject().setConfiguration(conf);
+ Project javaProject = createJavaProject().setConfiguration(conf);
+
+ CpdSensor sensor = new CpdSensor(new CpdMapping[0]);
+ assertTrue(sensor.isSkipped(phpProject));
+ assertFalse(sensor.isSkipped(javaProject));
+ }
+
+ @Test
+ public void defaultMinimumTokens() {
+ Project project = createJavaProject().setConfiguration(new PropertiesConfiguration());
+
+ CpdSensor sensor = new CpdSensor(new CpdMapping[0]);
+ assertEquals(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE, sensor.getMinimumTokens(project));
+ }
+
+ @Test
+ public void generalMinimumTokens() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.cpd.minimumTokens", "33");
+ Project project = createJavaProject().setConfiguration(conf);
+
+ CpdSensor sensor = new CpdSensor(new CpdMapping[0]);
+ assertEquals(33, sensor.getMinimumTokens(project));
+ }
+
+ @Test
+ public void minimumTokensByLanguage() {
+ PropertiesConfiguration conf = new PropertiesConfiguration();
+ conf.setProperty("sonar.cpd.minimumTokens", "100");
+ conf.setProperty("sonar.cpd.php.minimumTokens", "33");
+
+ Project phpProject = createPhpProject().setConfiguration(conf);
+ Project javaProject = createJavaProject().setConfiguration(conf);
+
+ CpdSensor sensor = new CpdSensor(new CpdMapping[0]);
+ assertEquals(100, sensor.getMinimumTokens(javaProject));
+ assertEquals(33, sensor.getMinimumTokens(phpProject));
+ }
+
+ private Project createJavaProject() {
+ return new Project("java_project").setLanguageKey("java");
+ }
+
+ private Project createPhpProject() {
+ return new Project("php_project").setLanguageKey("php");
+ }
+}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecoratorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecoratorTest.java
new file mode 100644
index 00000000000..f958c8549fe
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/DuplicationDensityDecoratorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd.decorators;
+
+import org.junit.Test;
+import static org.mockito.Mockito.*;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+
+public class DuplicationDensityDecoratorTest {
+
+ @Test
+ public void densityIsBalancedByNclocAndCommentLines() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 40.0));
+ when(context.getMeasure(CoreMetrics.COMMENT_LINES)).thenReturn(new Measure(CoreMetrics.COMMENT_LINES, 10.0));
+ when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.DUPLICATED_LINES, 10.0));
+
+ DuplicationDensityDecorator decorator = new DuplicationDensityDecorator();
+ decorator.decorate(null, context);
+
+ verify(context).saveMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY, 20.0);
+ }
+
+
+ @Test
+ public void densityEvenIfNoComments() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 40.0));
+ when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.DUPLICATED_LINES, 10.0));
+
+ DuplicationDensityDecorator decorator = new DuplicationDensityDecorator();
+ decorator.decorate(null, context);
+
+ verify(context).saveMeasure(CoreMetrics.DUPLICATED_LINES_DENSITY, 25.0);
+ }
+
+ @Test
+ public void noDensityIfNoDuplicationMeasure() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 45.0));
+
+ DuplicationDensityDecorator decorator = new DuplicationDensityDecorator();
+ decorator.decorate(null, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.DUPLICATED_LINES_DENSITY), anyDouble());
+ }
+
+ @Test
+ public void noDensityWhenZeroNclocAndComments() {
+ DecoratorContext context = mock(DecoratorContext.class);
+ when(context.getMeasure(CoreMetrics.NCLOC)).thenReturn(new Measure(CoreMetrics.NCLOC, 0.0));
+ when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.COMMENT_LINES, 0.0));
+ when(context.getMeasure(CoreMetrics.DUPLICATED_LINES)).thenReturn(new Measure(CoreMetrics.DUPLICATED_LINES, 10.0));
+
+ DuplicationDensityDecorator decorator = new DuplicationDensityDecorator();
+ decorator.decorate(null, context);
+
+ verify(context, never()).saveMeasure(eq(CoreMetrics.DUPLICATED_LINES_DENSITY), anyDouble());
+ }
+
+}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecoratorTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecoratorTest.java
new file mode 100644
index 00000000000..32cf781e366
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/decorators/SumDuplicationsDecoratorTest.java
@@ -0,0 +1,66 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2009 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.plugins.cpd.decorators;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.number.OrderingComparisons.greaterThan;
+import static org.junit.Assert.assertThat;
+import org.junit.Test;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.*;
+import org.sonar.api.batch.DecoratorContext;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.api.measures.Measure;
+import org.sonar.api.resources.JavaFile;
+import org.sonar.api.resources.Resource;
+import org.sonar.api.test.IsMeasure;
+
+public class SumDuplicationsDecoratorTest {
+
+ @Test
+ public void parapets() {
+ SumDuplicationsDecorator decorator = new SumDuplicationsDecorator();
+ assertThat(decorator.generatesMetrics().size(), greaterThan(0));
+ assertThat(decorator.shouldSaveZeroIfNoChildMeasures(), is(true));
+ }
+
+ @Test
+ public void doNotSetDuplicationsOnUnitTests() {
+ SumDuplicationsDecorator decorator = new SumDuplicationsDecorator();
+ Resource unitTest = new JavaFile("org.foo.BarTest", true);
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(unitTest, context);
+
+ verify(context, never()).saveMeasure((Measure) anyObject());
+ }
+
+ @Test
+ public void saveZeroIfNoDuplications() {
+ SumDuplicationsDecorator decorator = new SumDuplicationsDecorator();
+ Resource unitTest = new JavaFile("org.foo.BarTest", false);
+ DecoratorContext context = mock(DecoratorContext.class);
+
+ decorator.decorate(unitTest, context);
+
+ verify(context).saveMeasure(argThat(new IsMeasure(CoreMetrics.DUPLICATED_LINES, 0.0)));
+ }
+}