aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2014-07-25 16:31:45 +0200
committerJulien HENRY <julien.henry@sonarsource.com>2014-07-30 18:03:36 +0200
commit6074164392edd3db2dfdfd21d05cd56c19e2b0e6 (patch)
treeb9314796d68c4c396dcf45a1ab689b06490fd4a2
parent12f243728f42a5eb1e714ff15f0240109193f1d8 (diff)
downloadsonarqube-6074164392edd3db2dfdfd21d05cd56c19e2b0e6.tar.gz
sonarqube-6074164392edd3db2dfdfd21d05cd56c19e2b0e6.zip
SONAR-5389 New duplication API
-rw-r--r--plugins/sonar-cpd-plugin/pom.xml5
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java5
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java51
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java7
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java26
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java (renamed from plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java)70
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java (renamed from plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java)81
-rw-r--r--plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java33
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdPluginTest.java2
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/CpdSensorTest.java9
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java (renamed from plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java)34
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java155
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java160
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java22
-rw-r--r--plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java119
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/XooConstants.java)2
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java22
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java)6
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivitySensor.java)11
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java)8
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java)6
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java84
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/CreateIssueByInternalKeySensor.java)7
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java)7
-rw-r--r--plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java)7
-rw-r--r--plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java2
-rw-r--r--pom.xml6
-rw-r--r--sonar-batch/pom.xml5
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java10
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/BlockCache.java56
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java74
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java77
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java43
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java58
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java76
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java54
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/FileBlocksValueCoder.java69
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/duplication/package-info.java (renamed from sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/Xoo.java)23
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java3
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java3
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/Cache.java13
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/Caches.java3
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java49
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java14
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java12
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java6
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java59
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java10
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultInputFileValueCoder.java76
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java2
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java24
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java7
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultMeasureValueCoder.java64
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java44
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java6
-rw-r--r--sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java82
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java9
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java2
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java53
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java5
-rw-r--r--sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java16
-rw-r--r--sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java1
-rw-r--r--sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java5
-rw-r--r--sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java8
-rw-r--r--sonar-duplications/src/main/java/org/sonar/duplications/block/FileBlocks.java45
-rw-r--r--sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java13
-rw-r--r--sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java21
-rw-r--r--sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java2
-rw-r--r--sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java3
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java4
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java3
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java17
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java38
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java40
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/package-info.java21
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java1
82 files changed, 1822 insertions, 466 deletions
diff --git a/plugins/sonar-cpd-plugin/pom.xml b/plugins/sonar-cpd-plugin/pom.xml
index 2903ae5a4be..334122c4b91 100644
--- a/plugins/sonar-cpd-plugin/pom.xml
+++ b/plugins/sonar-cpd-plugin/pom.xml
@@ -54,6 +54,11 @@
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar.plugins</groupId>
+ <artifactId>sonar-xoo-plugin</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java
index 7548282c87b..2f2a7633f40 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdEngine.java
@@ -21,14 +21,13 @@ package org.sonar.plugins.cpd;
import org.slf4j.Logger;
import org.sonar.api.BatchExtension;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.resources.Project;
+import org.sonar.api.batch.sensor.SensorContext;
public abstract class CpdEngine implements BatchExtension {
abstract boolean isLanguageSupported(String language);
- abstract void analyse(Project project, String language, SensorContext context);
+ abstract void analyse(String language, SensorContext context);
protected void logExclusions(String[] exclusions, Logger logger) {
if (exclusions.length > 0) {
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java
new file mode 100644
index 00000000000..3f0f9d83b8c
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdMappings.java
@@ -0,0 +1,51 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.plugins.cpd;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.CpdMapping;
+
+import javax.annotation.CheckForNull;
+
+public class CpdMappings implements BatchComponent {
+
+ private final CpdMapping[] mappings;
+
+ public CpdMappings(CpdMapping[] mappings) {
+ this.mappings = mappings;
+ }
+
+ public CpdMappings() {
+ this(new CpdMapping[0]);
+ }
+
+ @CheckForNull
+ public CpdMapping getMapping(String language) {
+ if (mappings != null) {
+ for (CpdMapping cpdMapping : mappings) {
+ if (cpdMapping.getLanguage().getKey().equals(language)) {
+ return cpdMapping;
+ }
+ }
+ }
+ return null;
+ }
+
+}
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
index 5f9df9621e1..defa9fe260e 100644
--- 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
@@ -35,7 +35,7 @@ public final class CpdPlugin extends SonarPlugin {
public List getExtensions() {
return ImmutableList.of(
- PropertyDefinition.builder(CoreProperties.CPD_CROSS_RPOJECT)
+ PropertyDefinition.builder(CoreProperties.CPD_CROSS_PROJECT)
.defaultValue(CoreProperties.CPD_CROSS_RPOJECT_DEFAULT_VALUE + "")
.name("Cross project duplication detection")
.description("SonarQube supports the detection of cross project duplications. Activating this property will slightly increase each SonarQube analysis time.")
@@ -65,11 +65,12 @@ public final class CpdPlugin extends SonarPlugin {
.build(),
CpdSensor.class,
+ CpdMappings.class,
SumDuplicationsDecorator.class,
DuplicationDensityDecorator.class,
IndexFactory.class,
- SonarEngine.class,
- SonarBridgeEngine.class);
+ JavaCpdEngine.class,
+ DefaultCpdEngine.class);
}
}
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
index 53a45416cb7..dfeabd65635 100644
--- 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
@@ -23,12 +23,14 @@ import com.google.common.annotations.VisibleForTesting;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.Sensor;
-import org.sonar.api.batch.SensorContext;
+import org.sonar.api.batch.Phase;
import org.sonar.api.batch.fs.FileSystem;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.config.Settings;
-import org.sonar.api.resources.Project;
+@Phase(name = Phase.Name.POST)
public class CpdSensor implements Sensor {
private static final Logger LOG = LoggerFactory.getLogger(CpdSensor.class);
@@ -38,15 +40,17 @@ public class CpdSensor implements Sensor {
private Settings settings;
private FileSystem fs;
- public CpdSensor(SonarEngine sonarEngine, SonarBridgeEngine sonarBridgeEngine, Settings settings, FileSystem fs) {
+ public CpdSensor(JavaCpdEngine sonarEngine, DefaultCpdEngine sonarBridgeEngine, Settings settings, FileSystem fs) {
this.sonarEngine = sonarEngine;
this.sonarBridgeEngine = sonarBridgeEngine;
this.settings = settings;
this.fs = fs;
}
- public boolean shouldExecuteOnProject(Project project) {
- return true;
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor.name("CPD Sensor");
+
}
@VisibleForTesting
@@ -66,7 +70,8 @@ public class CpdSensor implements Sensor {
return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY);
}
- public void analyse(Project project, SensorContext context) {
+ @Override
+ public void execute(SensorContext context) {
for (String language : fs.languages()) {
if (isSkipped(language)) {
LOG.info("Detection of duplicated code is skipped for {}", language);
@@ -79,13 +84,8 @@ public class CpdSensor implements Sensor {
continue;
}
LOG.info("{} is used for {}", engine, language);
- engine.analyse(project, language, context);
+ engine.analyse(language, context);
}
}
- @Override
- public String toString() {
- return getClass().getSimpleName();
- }
-
}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java
index df4bc036115..33558ec898a 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/DefaultCpdEngine.java
@@ -27,22 +27,25 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
import org.sonar.api.batch.CpdMapping;
-import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
+import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.SonarException;
+import org.sonar.batch.duplication.BlockCache;
import org.sonar.duplications.DuplicationPredicates;
import org.sonar.duplications.block.Block;
+import org.sonar.duplications.block.FileBlocks;
import org.sonar.duplications.index.CloneGroup;
import org.sonar.duplications.internal.pmd.TokenizerBridge;
import org.sonar.plugins.cpd.index.IndexFactory;
import org.sonar.plugins.cpd.index.SonarDuplicationsIndex;
-import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ExecutionException;
@@ -51,9 +54,9 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-public class SonarBridgeEngine extends CpdEngine {
+public class DefaultCpdEngine extends CpdEngine {
- private static final Logger LOG = LoggerFactory.getLogger(SonarBridgeEngine.class);
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultCpdEngine.class);
/**
* Limit of time to analyse one file (in seconds).
@@ -61,28 +64,32 @@ public class SonarBridgeEngine extends CpdEngine {
private static final int TIMEOUT = 5 * 60;
private final IndexFactory indexFactory;
- private final CpdMapping[] mappings;
+ private final CpdMappings mappings;
private final FileSystem fs;
private final Settings settings;
+ private final BlockCache duplicationCache;
+ private final Project project;
- public SonarBridgeEngine(IndexFactory indexFactory, CpdMapping[] mappings, FileSystem fs, Settings settings) {
+ public DefaultCpdEngine(@Nullable Project project, IndexFactory indexFactory, CpdMappings mappings, FileSystem fs, Settings settings, BlockCache duplicationCache) {
+ this.project = project;
this.indexFactory = indexFactory;
this.mappings = mappings;
this.fs = fs;
this.settings = settings;
+ this.duplicationCache = duplicationCache;
}
- public SonarBridgeEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) {
- this(indexFactory, new CpdMapping[0], fs, settings);
+ public DefaultCpdEngine(IndexFactory indexFactory, CpdMappings mappings, FileSystem fs, Settings settings, BlockCache duplicationCache) {
+ this(null, indexFactory, mappings, fs, settings, duplicationCache);
}
@Override
public boolean isLanguageSupported(String language) {
- return getMapping(language) != null;
+ return true;
}
@Override
- public void analyse(Project project, String languageKey, SensorContext context) {
+ public void analyse(String languageKey, SensorContext context) {
String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS);
logExclusions(cpdExclusions, LOG);
FilePredicates p = fs.predicates();
@@ -90,29 +97,34 @@ public class SonarBridgeEngine extends CpdEngine {
p.hasType(InputFile.Type.MAIN),
p.hasLanguage(languageKey),
p.doesNotMatchPathPatterns(cpdExclusions)
- )));
+ )));
if (sourceFiles.isEmpty()) {
return;
}
- CpdMapping mapping = getMapping(languageKey);
- if (mapping == null) {
- return;
- }
+ CpdMapping mapping = mappings.getMapping(languageKey);
// Create index
SonarDuplicationsIndex index = indexFactory.create(project, languageKey);
- TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(project, languageKey));
+ TokenizerBridge bridge = null;
+ if (mapping != null) {
+ bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey));
+ }
for (InputFile inputFile : sourceFiles) {
LOG.debug("Populating index from {}", inputFile);
String resourceEffectiveKey = ((DeprecatedDefaultInputFile) inputFile).key();
- List<Block> blocks = bridge.chunk(resourceEffectiveKey, inputFile.file());
- index.insert(inputFile, blocks);
+ FileBlocks fileBlocks = duplicationCache.byComponent(resourceEffectiveKey);
+ if (fileBlocks != null) {
+ index.insert(inputFile, fileBlocks.blocks());
+ } else if (bridge != null) {
+ List<Block> blocks2 = bridge.chunk(resourceEffectiveKey, inputFile.file());
+ index.insert(inputFile, blocks2);
+ }
}
// Detect
- Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(project, languageKey));
+ Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(languageKey));
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
@@ -123,7 +135,7 @@ public class SonarBridgeEngine extends CpdEngine {
Iterable<CloneGroup> filtered;
try {
- List<CloneGroup> duplications = executorService.submit(new SonarEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS);
+ List<CloneGroup> duplications = executorService.submit(new JavaCpdEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS);
filtered = Iterables.filter(duplications, minimumTokensPredicate);
} catch (TimeoutException e) {
filtered = null;
@@ -134,7 +146,7 @@ public class SonarBridgeEngine extends CpdEngine {
throw new SonarException("Fail during detection of duplication for " + inputFile, e);
}
- SonarEngine.save(context, inputFile, filtered);
+ JavaCpdEngine.save(context, inputFile, filtered);
}
} finally {
executorService.shutdown();
@@ -142,7 +154,7 @@ public class SonarBridgeEngine extends CpdEngine {
}
@VisibleForTesting
- int getBlockSize(Project project, String languageKey) {
+ int getBlockSize(String languageKey) {
int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");
if (blockSize == 0) {
blockSize = getDefaultBlockSize(languageKey);
@@ -162,7 +174,7 @@ public class SonarBridgeEngine extends CpdEngine {
}
@VisibleForTesting
- int getMinimumTokens(Project project, String languageKey) {
+ int getMinimumTokens(String languageKey) {
int minimumTokens = settings.getInt("sonar.cpd." + languageKey + ".minimumTokens");
if (minimumTokens == 0) {
minimumTokens = settings.getInt(CoreProperties.CPD_MINIMUM_TOKENS_PROPERTY);
@@ -174,16 +186,4 @@ public class SonarBridgeEngine extends CpdEngine {
return minimumTokens;
}
- @CheckForNull
- private CpdMapping getMapping(String language) {
- if (mappings != null) {
- for (CpdMapping cpdMapping : mappings) {
- if (cpdMapping.getLanguage().getKey().equals(language)) {
- return cpdMapping;
- }
- }
- }
- return null;
- }
-
}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java
index 130206427fc..2ea908dbb27 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarEngine.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java
@@ -23,21 +23,20 @@ package org.sonar.plugins.cpd;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.CoreProperties;
-import org.sonar.api.batch.SensorContext;
import org.sonar.api.batch.fs.FilePredicates;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
import org.sonar.api.config.Settings;
import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.measures.Measure;
-import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.SonarException;
+import org.sonar.batch.duplication.DefaultDuplicationBuilder;
import org.sonar.duplications.block.Block;
import org.sonar.duplications.block.BlockChunker;
import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm;
@@ -53,6 +52,7 @@ import org.sonar.plugins.cpd.index.IndexFactory;
import org.sonar.plugins.cpd.index.SonarDuplicationsIndex;
import javax.annotation.Nullable;
+
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
@@ -61,11 +61,16 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.*;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
-public class SonarEngine extends CpdEngine {
+public class JavaCpdEngine extends CpdEngine {
- private static final Logger LOG = LoggerFactory.getLogger(SonarEngine.class);
+ private static final Logger LOG = LoggerFactory.getLogger(JavaCpdEngine.class);
private static final int BLOCK_SIZE = 10;
@@ -77,20 +82,26 @@ public class SonarEngine extends CpdEngine {
private final IndexFactory indexFactory;
private final FileSystem fs;
private final Settings settings;
+ private final Project project;
- public SonarEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) {
+ public JavaCpdEngine(@Nullable Project project, IndexFactory indexFactory, FileSystem fs, Settings settings) {
+ this.project = project;
this.indexFactory = indexFactory;
this.fs = fs;
this.settings = settings;
}
+ public JavaCpdEngine(IndexFactory indexFactory, FileSystem fs, Settings settings) {
+ this(null, indexFactory, fs, settings);
+ }
+
@Override
public boolean isLanguageSupported(String language) {
return "java".equals(language);
}
@Override
- public void analyse(Project project, String languageKey, SensorContext context) {
+ public void analyse(String languageKey, SensorContext context) {
String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS);
logExclusions(cpdExclusions, LOG);
FilePredicates p = fs.predicates();
@@ -98,7 +109,7 @@ public class SonarEngine extends CpdEngine {
p.hasType(InputFile.Type.MAIN),
p.hasLanguage(languageKey),
p.doesNotMatchPathPatterns(cpdExclusions)
- )));
+ )));
if (sourceFiles.isEmpty()) {
return;
}
@@ -106,7 +117,7 @@ public class SonarEngine extends CpdEngine {
detect(index, context, sourceFiles);
}
- private SonarDuplicationsIndex createIndex(Project project, String language, Iterable<InputFile> sourceFiles) {
+ private SonarDuplicationsIndex createIndex(@Nullable Project project, String language, Iterable<InputFile> sourceFiles) {
final SonarDuplicationsIndex index = indexFactory.create(project, language);
TokenChunker tokenChunker = JavaTokenProducer.build();
@@ -136,7 +147,7 @@ public class SonarEngine extends CpdEngine {
return index;
}
- private void detect(SonarDuplicationsIndex index, SensorContext context, List<InputFile> sourceFiles) {
+ private void detect(SonarDuplicationsIndex index, org.sonar.api.batch.sensor.SensorContext context, List<InputFile> sourceFiles) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
for (InputFile inputFile : sourceFiles) {
@@ -178,13 +189,13 @@ public class SonarEngine extends CpdEngine {
}
}
- static void save(SensorContext context, InputFile inputFile, @Nullable Iterable<CloneGroup> duplications) {
+ static void save(org.sonar.api.batch.sensor.SensorContext context, InputFile inputFile, @Nullable Iterable<CloneGroup> duplications) {
if (duplications == null || Iterables.isEmpty(duplications)) {
return;
}
// Calculate number of lines and blocks
Set<Integer> duplicatedLines = new HashSet<Integer>();
- double duplicatedBlocks = 0;
+ int duplicatedBlocks = 0;
for (CloneGroup clone : duplications) {
ClonePart origin = clone.getOriginPart();
for (ClonePart part : clone.getCloneParts()) {
@@ -197,30 +208,32 @@ public class SonarEngine extends CpdEngine {
}
}
// Save
- context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1.0);
- context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size());
- context.saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks);
-
- Measure data = new Measure(CoreMetrics.DUPLICATIONS_DATA, toXml(duplications))
- .setPersistenceMode(PersistenceMode.DATABASE);
- context.saveMeasure(inputFile, data);
- }
-
- private static String toXml(Iterable<CloneGroup> duplications) {
- StringBuilder xml = new StringBuilder();
- xml.append("<duplications>");
+ context.addMeasure(context.<Integer>measureBuilder()
+ .forMetric(CoreMetrics.DUPLICATED_FILES)
+ .onFile(inputFile)
+ .withValue(1)
+ .build());
+ context.addMeasure(context.<Integer>measureBuilder()
+ .forMetric(CoreMetrics.DUPLICATED_LINES)
+ .onFile(inputFile)
+ .withValue(duplicatedLines.size())
+ .build());
+ context.addMeasure(context.<Integer>measureBuilder()
+ .forMetric(CoreMetrics.DUPLICATED_BLOCKS)
+ .onFile(inputFile)
+ .withValue(duplicatedBlocks)
+ .build());
+
+ DuplicationBuilder builder = context.duplicationBuilder(inputFile);
for (CloneGroup duplication : duplications) {
- xml.append("<g>");
+ builder.originBlock(duplication.getOriginPart().getStartLine(), duplication.getOriginPart().getEndLine());
for (ClonePart part : duplication.getCloneParts()) {
- xml.append("<b s=\"").append(part.getStartLine())
- .append("\" l=\"").append(part.getLines())
- .append("\" r=\"").append(StringEscapeUtils.escapeXml(part.getResourceId()))
- .append("\"/>");
+ if (!part.equals(duplication.getOriginPart())) {
+ ((DefaultDuplicationBuilder) builder).isDuplicatedBy(part.getResourceId(), part.getStartLine(), part.getEndLine());
+ }
}
- xml.append("</g>");
}
- xml.append("</duplications>");
- return xml.toString();
+ builder.done();
}
}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java
index f6f8981f6c1..6d5a7f1201d 100644
--- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java
+++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/index/IndexFactory.java
@@ -23,29 +23,41 @@ import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.sonar.api.BatchExtension;
+import org.sonar.api.BatchComponent;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
+import org.sonar.batch.bootstrap.AnalysisMode;
import org.sonar.batch.index.ResourcePersister;
import org.sonar.core.duplication.DuplicationDao;
-public class IndexFactory implements BatchExtension {
+import javax.annotation.Nullable;
+
+public class IndexFactory implements BatchComponent {
private static final Logger LOG = LoggerFactory.getLogger(IndexFactory.class);
private final Settings settings;
private final ResourcePersister resourcePersister;
private final DuplicationDao dao;
+ private final AnalysisMode mode;
- public IndexFactory(Settings settings, ResourcePersister resourcePersister, DuplicationDao dao) {
+ public IndexFactory(AnalysisMode mode, Settings settings, @Nullable ResourcePersister resourcePersister, @Nullable DuplicationDao dao) {
+ this.mode = mode;
this.settings = settings;
this.resourcePersister = resourcePersister;
this.dao = dao;
}
- public SonarDuplicationsIndex create(Project project, String languageKey) {
- if (verifyCrossProject(project, LOG)) {
+ /**
+ * Used by new sensor mode
+ */
+ public IndexFactory(AnalysisMode mode, Settings settings) {
+ this(mode, settings, null, null);
+ }
+
+ public SonarDuplicationsIndex create(@Nullable Project project, String languageKey) {
+ if (verifyCrossProject(project, LOG) && dao != null && resourcePersister != null) {
return new SonarDuplicationsIndex(new DbDuplicationsIndex(resourcePersister, project, dao, languageKey));
}
return new SonarDuplicationsIndex();
@@ -55,11 +67,14 @@ public class IndexFactory implements BatchExtension {
boolean verifyCrossProject(Project project, Logger logger) {
boolean crossProject = false;
- if (settings.getBoolean(CoreProperties.CPD_CROSS_RPOJECT)) {
- if (settings.getBoolean(CoreProperties.DRY_RUN)) {
- logger.info("Cross-project analysis disabled. Not supported on dry runs.");
- } else if (StringUtils.isNotBlank(project.getBranch())) {
+ if (settings.getBoolean(CoreProperties.CPD_CROSS_PROJECT)) {
+ if (mode.isPreview()) {
+ logger.info("Cross-project analysis disabled. Not supported in preview mode.");
+ } else if (StringUtils.isNotBlank(settings.getString(CoreProperties.PROJECT_BRANCH_PROPERTY))) {
logger.info("Cross-project analysis disabled. Not supported on project branches.");
+ } else if (project == null) {
+ // New sensor mode
+ logger.info("Cross-project analysis disabled. Not supported in new sensor mode.");
} else {
logger.info("Cross-project analysis enabled");
crossProject = true;
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
index 74cb7f5fa5b..7d1bbeb4a5c 100644
--- 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
@@ -27,6 +27,6 @@ public class CpdPluginTest {
@Test
public void getExtensions() {
- assertThat(new CpdPlugin().getExtensions()).hasSize(9);
+ assertThat(new CpdPlugin().getExtensions()).hasSize(10);
}
}
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
index 89128b77a8a..7724fa6f725 100644
--- 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
@@ -25,6 +25,7 @@ import org.sonar.api.batch.fs.internal.DefaultFileSystem;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Java;
+import org.sonar.batch.duplication.BlockCache;
import org.sonar.plugins.cpd.index.IndexFactory;
import static org.fest.assertions.Assertions.assertThat;
@@ -32,16 +33,16 @@ import static org.mockito.Mockito.mock;
public class CpdSensorTest {
- SonarEngine sonarEngine;
- SonarBridgeEngine sonarBridgeEngine;
+ JavaCpdEngine sonarEngine;
+ DefaultCpdEngine sonarBridgeEngine;
CpdSensor sensor;
Settings settings;
@Before
public void setUp() {
IndexFactory indexFactory = mock(IndexFactory.class);
- sonarEngine = new SonarEngine(indexFactory, null, null);
- sonarBridgeEngine = new SonarBridgeEngine(indexFactory, null, null);
+ sonarEngine = new JavaCpdEngine(indexFactory, null, null);
+ sonarBridgeEngine = new DefaultCpdEngine(indexFactory, new CpdMappings(), null, null, mock(BlockCache.class));
settings = new Settings(new PropertyDefinitions(CpdPlugin.class));
DefaultFileSystem fs = new DefaultFileSystem();
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java
index 3923b53fe21..924aa9193c8 100644
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarBridgeEngineTest.java
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/DefaultCpdEngineTest.java
@@ -25,6 +25,7 @@ import org.slf4j.Logger;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
+import org.sonar.batch.duplication.BlockCache;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
@@ -33,15 +34,15 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-public class SonarBridgeEngineTest {
+public class DefaultCpdEngineTest {
- private SonarBridgeEngine engine;
+ private DefaultCpdEngine engine;
private Settings settings;
@Before
public void init() {
settings = new Settings();
- engine = new SonarBridgeEngine(null, null, null, settings);
+ engine = new DefaultCpdEngine(null, null, null, settings, mock(BlockCache.class));
}
@Test
@@ -61,53 +62,46 @@ public class SonarBridgeEngineTest {
@Test
public void shouldReturnDefaultBlockSize() {
- assertThat(SonarBridgeEngine.getDefaultBlockSize("cobol")).isEqualTo(30);
- assertThat(SonarBridgeEngine.getDefaultBlockSize("natur")).isEqualTo(20);
- assertThat(SonarBridgeEngine.getDefaultBlockSize("abap")).isEqualTo(20);
- assertThat(SonarBridgeEngine.getDefaultBlockSize("other")).isEqualTo(10);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("cobol")).isEqualTo(30);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("natur")).isEqualTo(20);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("abap")).isEqualTo(20);
+ assertThat(DefaultCpdEngine.getDefaultBlockSize("other")).isEqualTo(10);
}
@Test
public void defaultBlockSize() {
- Project project = newProject("foo");
- assertThat(engine.getBlockSize(project, "java")).isEqualTo(10);
+ assertThat(engine.getBlockSize("java")).isEqualTo(10);
}
@Test
public void blockSizeForCobol() {
- Project project = newProject("foo");
settings.setProperty("sonar.cpd.cobol.minimumLines", "42");
- assertThat(engine.getBlockSize(project, "cobol")).isEqualTo(42);
+ assertThat(engine.getBlockSize("cobol")).isEqualTo(42);
}
@Test
public void defaultMinimumTokens() {
- Project project = newProject("foo");
-
- assertThat(engine.getMinimumTokens(project, "java")).isEqualTo(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE);
+ assertThat(engine.getMinimumTokens("java")).isEqualTo(CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE);
}
@Test
public void generalMinimumTokens() {
- Project project = newProject("foo");
settings.setProperty("sonar.cpd.minimumTokens", 33);
- assertThat(engine.getMinimumTokens(project, "java")).isEqualTo(33);
+ assertThat(engine.getMinimumTokens("java")).isEqualTo(33);
}
@Test
public void minimumTokensByLanguage() {
- Project javaProject = newProject("foo");
settings.setProperty("sonar.cpd.java.minimumTokens", "42");
settings.setProperty("sonar.cpd.php.minimumTokens", "33");
- assertThat(engine.getMinimumTokens(javaProject, "java")).isEqualTo(42);
+ assertThat(engine.getMinimumTokens("java")).isEqualTo(42);
- Project phpProject = newProject("foo");
settings.setProperty("sonar.cpd.java.minimumTokens", "42");
settings.setProperty("sonar.cpd.php.minimumTokens", "33");
- assertThat(engine.getMinimumTokens(phpProject, "php")).isEqualTo(33);
+ assertThat(engine.getMinimumTokens("php")).isEqualTo(33);
}
private static Project newProject(String key) {
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java
new file mode 100644
index 00000000000..d0c50a99b4c
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java
@@ -0,0 +1,155 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.plugins.cpd;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.duplications.index.CloneGroup;
+import org.sonar.duplications.index.ClonePart;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class JavaCpdEngineTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ SensorContext context = mock(SensorContext.class);
+ DeprecatedDefaultInputFile inputFile;
+
+ @Before
+ public void before() throws IOException {
+ when(context.measureBuilder()).thenReturn(new DefaultMeasureBuilder());
+ inputFile = new DeprecatedDefaultInputFile("src/main/java/Foo.java");
+ inputFile.setFile(temp.newFile("Foo.java"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testNothingToSave() {
+ JavaCpdEngine.save(context, inputFile, null);
+ JavaCpdEngine.save(context, inputFile, Collections.EMPTY_LIST);
+
+ verifyZeroInteractions(context);
+ }
+
+ @Test
+ public void testOneSimpleDuplicationBetweenTwoFiles() {
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(200).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
+ + "</g></duplications>").build());
+ }
+
+ @Test
+ public void testDuplicationOnSameFile() throws Exception {
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key1", 0, 215, 414)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(2).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(400).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"215\" l=\"200\" r=\"key1\"/>"
+ + "</g></duplications>").build());
+
+ }
+
+ @Test
+ public void testOneDuplicatedGroupInvolvingMoreThanTwoFiles() throws Exception {
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214), new ClonePart("key3", 0, 25, 224)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(200).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
+ + "<b s=\"25\" l=\"200\" r=\"key3\"/>"
+ + "</g></duplications>").build());
+
+ }
+
+ @Test
+ public void testTwoDuplicatedGroupsInvolvingThreeFiles() throws Exception {
+ List<CloneGroup> groups = Arrays.asList(
+ newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)),
+ newCloneGroup(new ClonePart("key1", 0, 15, 214), new ClonePart("key3", 0, 15, 214)));
+ JavaCpdEngine.save(context, inputFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_FILES).onFile(inputFile).withValue(1).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_BLOCKS).onFile(inputFile).withValue(2).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATED_LINES).onFile(inputFile).withValue(210).build());
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(inputFile).withValue("<duplications>"
+ + "<g>"
+ + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
+ + "</g>"
+ + "<g>"
+ + "<b s=\"15\" l=\"200\" r=\"key1\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"key3\"/>"
+ + "</g>"
+ + "</duplications>").build());
+
+ }
+
+ @Test
+ public void shouldEscapeXmlEntities() throws IOException {
+ InputFile csharpFile = new DeprecatedDefaultInputFile("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs")
+ .setFile(temp.newFile("SubsRedsDelivery.cs"));
+ List<CloneGroup> groups = Arrays.asList(newCloneGroup(
+ new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs", 0, 5, 204),
+ new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery2.cs", 0, 15, 214)));
+ JavaCpdEngine.save(context, csharpFile, groups);
+
+ verify(context).addMeasure(new DefaultMeasureBuilder().forMetric(CoreMetrics.DUPLICATIONS_DATA).onFile(csharpFile).withValue("<duplications><g>"
+ + "<b s=\"5\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery.cs\"/>"
+ + "<b s=\"15\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery2.cs\"/>"
+ + "</g></duplications>").build());
+ }
+
+ private CloneGroup newCloneGroup(ClonePart... parts) {
+ return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build();
+ }
+
+}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java
deleted file mode 100644
index c8551a74166..00000000000
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/SonarEngineTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.plugins.cpd;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.batch.SensorContext;
-import org.sonar.api.batch.fs.InputFile;
-import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.api.test.IsMeasure;
-import org.sonar.duplications.index.CloneGroup;
-import org.sonar.duplications.index.ClonePart;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.*;
-
-public class SonarEngineTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- SensorContext context = mock(SensorContext.class);
- DeprecatedDefaultInputFile inputFile;
-
- @Before
- public void before() throws IOException {
- inputFile = new DeprecatedDefaultInputFile("src/main/java/Foo.java");
- inputFile.setFile(temp.newFile("Foo.java"));
- }
-
- @SuppressWarnings("unchecked")
- @Test
- public void testNothingToSave() {
- SonarEngine.save(context, inputFile, null);
- SonarEngine.save(context, inputFile, Collections.EMPTY_LIST);
-
- verifyZeroInteractions(context);
- }
-
- @Test
- public void testOneSimpleDuplicationBetweenTwoFiles() {
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 200d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
- + "</g></duplications>")));
- }
-
- @Test
- public void testDuplicationOnSameFile() throws Exception {
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key1", 0, 215, 414)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 400d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 2d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"215\" l=\"200\" r=\"key1\"/>"
- + "</g></duplications>")));
- }
-
- @Test
- public void testOneDuplicatedGroupInvolvingMoreThanTwoFiles() throws Exception {
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214), new ClonePart("key3", 0, 25, 224)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 200d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
- + "<b s=\"25\" l=\"200\" r=\"key3\"/>"
- + "</g></duplications>")));
- }
-
- @Test
- public void testTwoDuplicatedGroupsInvolvingThreeFiles() throws Exception {
- List<CloneGroup> groups = Arrays.asList(
- newCloneGroup(new ClonePart("key1", 0, 5, 204), new ClonePart("key2", 0, 15, 214)),
- newCloneGroup(new ClonePart("key1", 0, 15, 214), new ClonePart("key3", 0, 15, 214)));
- SonarEngine.save(context, inputFile, groups);
-
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_FILES, 1d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_BLOCKS, 2d);
- verify(context).saveMeasure(inputFile, CoreMetrics.DUPLICATED_LINES, 210d);
- verify(context).saveMeasure(
- eq(inputFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications>"
- + "<g>"
- + "<b s=\"5\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key2\"/>"
- + "</g>"
- + "<g>"
- + "<b s=\"15\" l=\"200\" r=\"key1\"/>"
- + "<b s=\"15\" l=\"200\" r=\"key3\"/>"
- + "</g>"
- + "</duplications>")));
- }
-
- @Test
- public void shouldEscapeXmlEntities() throws IOException {
- InputFile csharpFile = new DeprecatedDefaultInputFile("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs")
- .setFile(temp.newFile("SubsRedsDelivery.cs"));
- List<CloneGroup> groups = Arrays.asList(newCloneGroup(
- new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery.cs", 0, 5, 204),
- new ClonePart("Loads/File Loads/Subs & Reds/SubsRedsDelivery2.cs", 0, 15, 214)));
- SonarEngine.save(context, csharpFile, groups);
-
- verify(context).saveMeasure(
- eq(csharpFile),
- argThat(new IsMeasure(CoreMetrics.DUPLICATIONS_DATA, "<duplications><g>"
- + "<b s=\"5\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery.cs\"/>"
- + "<b s=\"15\" l=\"200\" r=\"Loads/File Loads/Subs &amp; Reds/SubsRedsDelivery2.cs\"/>"
- + "</g></duplications>")));
- }
-
- private CloneGroup newCloneGroup(ClonePart... parts) {
- return CloneGroup.builder().setLength(0).setOrigin(parts[0]).setParts(Arrays.asList(parts)).build();
- }
-
-}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java
index 45510dc40ac..7b9fa524fec 100644
--- a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/index/IndexFactoryTest.java
@@ -25,12 +25,14 @@ import org.slf4j.Logger;
import org.sonar.api.CoreProperties;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Project;
+import org.sonar.batch.bootstrap.AnalysisMode;
import org.sonar.batch.index.ResourcePersister;
import org.sonar.core.duplication.DuplicationDao;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
public class IndexFactoryTest {
@@ -38,41 +40,43 @@ public class IndexFactoryTest {
Settings settings;
IndexFactory factory;
Logger logger;
+ private AnalysisMode analysisMode;
@Before
public void setUp() {
project = new Project("foo");
settings = new Settings();
- factory = new IndexFactory(settings, mock(ResourcePersister.class), mock(DuplicationDao.class));
+ analysisMode = mock(AnalysisMode.class);
+ factory = new IndexFactory(analysisMode, settings, mock(ResourcePersister.class), mock(DuplicationDao.class));
logger = mock(Logger.class);
}
@Test
public void crossProjectEnabled() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true");
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
assertThat(factory.verifyCrossProject(project, logger)).isTrue();
verify(logger).info("Cross-project analysis enabled");
}
@Test
public void noCrossProjectWithBranch() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true");
- project.setBranch("branch");
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
+ settings.setProperty(CoreProperties.PROJECT_BRANCH_PROPERTY, "branch");
assertThat(factory.verifyCrossProject(project, logger)).isFalse();
verify(logger).info("Cross-project analysis disabled. Not supported on project branches.");
}
@Test
- public void cross_project_should_be_disabled_on_dry_run() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "true");
- settings.setProperty(CoreProperties.DRY_RUN, "true");
+ public void cross_project_should_be_disabled_on_preview() {
+ when(analysisMode.isPreview()).thenReturn(true);
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "true");
assertThat(factory.verifyCrossProject(project, logger)).isFalse();
- verify(logger).info("Cross-project analysis disabled. Not supported on dry runs.");
+ verify(logger).info("Cross-project analysis disabled. Not supported in preview mode.");
}
@Test
public void crossProjectDisabled() {
- settings.setProperty(CoreProperties.CPD_CROSS_RPOJECT, "false");
+ settings.setProperty(CoreProperties.CPD_CROSS_PROJECT, "false");
assertThat(factory.verifyCrossProject(project, logger)).isFalse();
verify(logger).info("Cross-project analysis disabled");
}
diff --git a/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java
new file mode 100644
index 00000000000..21876b8f3d5
--- /dev/null
+++ b/plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java
@@ -0,0 +1,119 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.plugins.cpd.medium;
+
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.io.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.batch.duplication.DuplicationGroup;
+import org.sonar.batch.mediumtest.BatchMediumTester;
+import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
+import org.sonar.plugins.cpd.CpdPlugin;
+import org.sonar.xoo.XooPlugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class CpdMediumTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ public BatchMediumTester tester = BatchMediumTester.builder()
+ .registerPlugin("xoo", new XooPlugin())
+ .registerPlugin("cpd", new CpdPlugin())
+ .addDefaultQProfile("xoo", "Sonar Way")
+ .bootstrapProperties(ImmutableMap.of("sonar.analysis.mode", "sensor"))
+ .build();
+
+ private File baseDir;
+
+ private ImmutableMap.Builder<String, String> builder;
+
+ @Before
+ public void prepare() throws IOException {
+ tester.start();
+
+ baseDir = temp.newFolder();
+
+ builder = ImmutableMap.<String, String>builder()
+ .put("sonar.task", "scan")
+ .put("sonar.projectBaseDir", baseDir.getAbsolutePath())
+ .put("sonar.projectKey", "com.foo.project")
+ .put("sonar.projectName", "Foo Project")
+ .put("sonar.projectVersion", "1.0-SNAPSHOT")
+ .put("sonar.projectDescription", "Description of Foo Project");
+ }
+
+ @After
+ public void stop() {
+ tester.stop();
+ }
+
+ @Test
+ public void testDuplications() throws IOException {
+ File srcDir = new File(baseDir, "src");
+ srcDir.mkdir();
+
+ String duplicatedStuff = "Sample xoo\ncontent\nfoo\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti\nbar\ntoto\ntiti\nfoo\nbar\ntoto\ntiti";
+
+ File xooFile1 = new File(srcDir, "sample1.xoo");
+ FileUtils.write(xooFile1, duplicatedStuff);
+
+ File xooFile2 = new File(srcDir, "sample2.xoo");
+ FileUtils.write(xooFile2, duplicatedStuff);
+
+ TaskResult result = tester.newTask()
+ .properties(builder
+ .put("sonar.sources", "src")
+ .put("sonar.cpd.xoo.minimumTokens", "10")
+ .put("sonar.verbose", "true")
+ .build())
+ .start();
+
+ assertThat(result.inputFiles()).hasSize(2);
+
+ // 4 measures per file
+ assertThat(result.measures()).hasSize(6);
+
+ InputFile inputFile = result.inputFiles().get(0);
+ // One clone group
+ List<DuplicationGroup> duplicationGroups = result.duplicationsFor(inputFile);
+ assertThat(duplicationGroups).hasSize(1);
+
+ DuplicationGroup cloneGroup = duplicationGroups.get(0);
+ assertThat(cloneGroup.duplicates()).hasSize(1);
+ assertThat(cloneGroup.originBlock().startLine()).isEqualTo(1);
+ assertThat(cloneGroup.originBlock().length()).isEqualTo(17);
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/XooConstants.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java
index c59091fb0f6..62f7d2ba782 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/XooConstants.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooConstants.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.base;
+package org.sonar.xoo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
index 95b65dd31a3..3477fab825c 100644
--- a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/XooPlugin.java
@@ -20,6 +20,14 @@
package org.sonar.xoo;
import org.sonar.api.SonarPlugin;
+import org.sonar.xoo.lang.MeasureSensor;
+import org.sonar.xoo.lang.ScmActivitySensor;
+import org.sonar.xoo.lang.SymbolReferencesSensor;
+import org.sonar.xoo.lang.SyntaxHighlightingSensor;
+import org.sonar.xoo.lang.XooTokenizerSensor;
+import org.sonar.xoo.rule.CreateIssueByInternalKeySensor;
+import org.sonar.xoo.rule.OneIssueOnDirPerFileSensor;
+import org.sonar.xoo.rule.OneIssuePerLineSensor;
import org.sonar.xoo.rule.XooQualityProfile;
import org.sonar.xoo.rule.XooRulesDefinition;
@@ -39,7 +47,19 @@ public class XooPlugin extends SonarPlugin {
return Arrays.asList(
Xoo.class,
XooRulesDefinition.class,
- XooQualityProfile.class);
+ XooQualityProfile.class,
+
+ // sensors
+ MeasureSensor.class,
+ ScmActivitySensor.class,
+ SyntaxHighlightingSensor.class,
+ SymbolReferencesSensor.class,
+ XooTokenizerSensor.class,
+
+ OneIssuePerLineSensor.class,
+ OneIssueOnDirPerFileSensor.class,
+ CreateIssueByInternalKeySensor.class
+ );
}
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java
index c28ebd5a05c..56e013b3716 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/MeasureSensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/MeasureSensor.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.lang;
+package org.sonar.xoo.lang;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
@@ -29,8 +29,8 @@ import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.MeasureBuilder;
import org.sonar.api.measures.CoreMetrics;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
-import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivitySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java
index b3352dba62a..12663b0b697 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/ScmActivitySensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/ScmActivitySensor.java
@@ -17,11 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.lang;
-
-import org.sonar.api.batch.sensor.Sensor;
-import org.sonar.api.batch.sensor.SensorContext;
-import org.sonar.api.batch.sensor.SensorDescriptor;
+package org.sonar.xoo.lang;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
@@ -31,11 +27,14 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.FileLinesContext;
import org.sonar.api.measures.FileLinesContextFactory;
import org.sonar.api.utils.DateUtils;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
+import org.sonar.xoo.Xoo;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java
index 91fa61e5c78..c8cafc2d705 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SymbolReferencesSensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SymbolReferencesSensor.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.lang;
+package org.sonar.xoo.lang;
import com.google.common.base.Splitter;
import org.apache.commons.io.FileUtils;
@@ -29,8 +29,8 @@ import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.symbol.Symbol;
import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.measures.CoreMetrics;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
-import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
import java.io.File;
import java.io.IOException;
@@ -83,7 +83,7 @@ public class SymbolReferencesSensor implements Sensor {
@Override
public void describe(SensorDescriptor descriptor) {
descriptor
- .name("Xoo Highlighting Sensor")
+ .name("Xoo Symbol Reference Sensor")
.provides(CoreMetrics.LINES)
.workOnLanguages(Xoo.KEY)
.workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java
index 5b78759dbb9..0ae23954442 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/lang/SyntaxHighlightingSensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/SyntaxHighlightingSensor.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.lang;
+package org.sonar.xoo.lang;
import com.google.common.base.Splitter;
import org.apache.commons.io.FileUtils;
@@ -28,8 +28,8 @@ import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.measures.CoreMetrics;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
-import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
import java.io.File;
import java.io.IOException;
diff --git a/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java
new file mode 100644
index 00000000000..1ee33e560ef
--- /dev/null
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java
@@ -0,0 +1,84 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.xoo.lang;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.Lists;
+import org.apache.commons.io.FileUtils;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.fs.FilePredicates;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.SensorDescriptor;
+import org.sonar.api.batch.sensor.duplication.TokenBuilder;
+import org.sonar.xoo.Xoo;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Parse files *.xoo.highlighting
+ */
+public class XooTokenizerSensor implements Sensor {
+
+ private void computeTokens(InputFile inputFile, SensorContext context) {
+ TokenBuilder tokenBuilder = context.tokenBuilder(inputFile);
+ File ioFile = inputFile.file();
+ int lineId = 0;
+ try {
+ for (String line : FileUtils.readLines(ioFile)) {
+ lineId++;
+ for (String token : Splitter.on(" ").split(line)) {
+ tokenBuilder.addToken(lineId, token);
+ }
+ }
+ tokenBuilder.done();
+ } catch (IOException e) {
+ throw new IllegalStateException("Unable to read file " + ioFile, e);
+ }
+ }
+
+ @Override
+ public void describe(SensorDescriptor descriptor) {
+ descriptor
+ .name("Xoo Tokenizer Sensor")
+ .workOnLanguages(Xoo.KEY)
+ .workOnFileTypes(InputFile.Type.MAIN);
+ }
+
+ @Override
+ public void execute(SensorContext context) {
+ String[] cpdExclusions = context.settings().getStringArray(CoreProperties.CPD_EXCLUSIONS);
+ FilePredicates p = context.fileSystem().predicates();
+ List<InputFile> sourceFiles = Lists.newArrayList(context.fileSystem().inputFiles(p.and(
+ p.hasType(InputFile.Type.MAIN),
+ p.hasLanguage(Xoo.KEY),
+ p.doesNotMatchPathPatterns(cpdExclusions)
+ )));
+ if (sourceFiles.isEmpty()) {
+ return;
+ }
+ for (InputFile file : sourceFiles) {
+ computeTokens(file, context);
+ }
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/CreateIssueByInternalKeySensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java
index 1ee150a5c01..c3e7d2641e6 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/CreateIssueByInternalKeySensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/CreateIssueByInternalKeySensor.java
@@ -17,15 +17,15 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.rule;
+package org.sonar.xoo.rule;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.rule.ActiveRule;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
-import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
public class CreateIssueByInternalKeySensor implements Sensor {
@@ -36,6 +36,7 @@ public class CreateIssueByInternalKeySensor implements Sensor {
descriptor
.name("CreateIssueByInternalKeySensor")
.workOnLanguages(Xoo.KEY)
+ .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
.workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java
index 2a02ebef9dd..c2478830975 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssueOnDirPerFileSensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssueOnDirPerFileSensor.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.rule;
+package org.sonar.xoo.rule;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputFile;
@@ -25,8 +25,8 @@ import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.batch.sensor.SensorDescriptor;
import org.sonar.api.rule.RuleKey;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
-import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
public class OneIssueOnDirPerFileSensor implements Sensor {
@@ -37,6 +37,7 @@ public class OneIssueOnDirPerFileSensor implements Sensor {
descriptor
.name("One Issue On Dir Per File")
.workOnLanguages(Xoo.KEY)
+ .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
.workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
index 2a811068e54..bc0697b64b4 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/rule/OneIssuePerLineSensor.java
+++ b/plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/rule/OneIssuePerLineSensor.java
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.rule;
+package org.sonar.xoo.rule;
import org.slf4j.LoggerFactory;
import org.sonar.api.batch.fs.InputFile;
@@ -28,8 +28,8 @@ import org.sonar.api.batch.sensor.issue.IssueBuilder;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.rule.RuleKey;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
-import org.sonar.batch.mediumtest.xoo.plugin.base.XooConstants;
+import org.sonar.xoo.Xoo;
+import org.sonar.xoo.XooConstants;
public class OneIssuePerLineSensor implements Sensor {
@@ -43,6 +43,7 @@ public class OneIssuePerLineSensor implements Sensor {
.name("One Issue Per Line")
.dependsOn(CoreMetrics.LINES)
.workOnLanguages(Xoo.KEY)
+ .createIssuesForRuleRepositories(XooConstants.REPOSITORY_KEY)
.workOnFileTypes(InputFile.Type.MAIN, InputFile.Type.TEST);
}
diff --git a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
index b65ad573daa..bfe1fd99afa 100644
--- a/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
+++ b/plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/XooPluginTest.java
@@ -27,6 +27,6 @@ public class XooPluginTest {
@Test
public void provide_extensions() {
- assertThat(new XooPlugin().getExtensions()).hasSize(3);
+ assertThat(new XooPlugin().getExtensions()).hasSize(11);
}
}
diff --git a/pom.xml b/pom.xml
index 20de056d4d3..02da68aa17a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -575,6 +575,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.codehaus.sonar.plugins</groupId>
+ <artifactId>sonar-xoo-plugin</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-squid</artifactId>
<version>4.1</version>
diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml
index 1ba3a32d61f..89f7c9252a4 100644
--- a/sonar-batch/pom.xml
+++ b/sonar-batch/pom.xml
@@ -149,5 +149,10 @@
<artifactId>jsonassert</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.sonar.plugins</groupId>
+ <artifactId>sonar-xoo-plugin</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java
index 7317c9afd08..32bebf6182e 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java
@@ -22,6 +22,7 @@ package org.sonar.batch.bootstrap;
import com.google.common.collect.Lists;
import org.apache.commons.lang.ClassUtils;
import org.sonar.api.batch.CheckProject;
+import org.sonar.api.batch.Phase;
import org.sonar.api.batch.sensor.Sensor;
import org.sonar.api.batch.sensor.SensorContext;
import org.sonar.api.platform.ComponentContainer;
@@ -56,6 +57,15 @@ public class BatchExtensionDictionnary extends org.sonar.api.batch.BatchExtensio
return result;
}
+ @Override
+ protected Phase.Name evaluatePhase(Object extension) {
+ if (extension instanceof SensorWrapper) {
+ return super.evaluatePhase(((SensorWrapper) extension).wrappedSensor());
+ } else {
+ return super.evaluatePhase(extension);
+ }
+ }
+
private <T> List<T> getFilteredExtensions(Class<T> type, @Nullable Project project, @Nullable ExtensionMatcher matcher) {
List<T> result = Lists.newArrayList();
for (Object extension : getExtensions(type)) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/BlockCache.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/BlockCache.java
new file mode 100644
index 00000000000..f6a4e3d18bf
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/BlockCache.java
@@ -0,0 +1,56 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.batch.index.Cache;
+import org.sonar.batch.index.Cache.Entry;
+import org.sonar.batch.index.Caches;
+import org.sonar.duplications.block.FileBlocks;
+
+import javax.annotation.CheckForNull;
+
+/**
+ * Cache of duplication blocks. This cache is shared amongst all project modules.
+ */
+public class BlockCache implements BatchComponent {
+
+ private final Cache<FileBlocks> cache;
+
+ public BlockCache(Caches caches) {
+ caches.registerValueCoder(FileBlocks.class, new FileBlocksValueCoder());
+ cache = caches.createCache("blocks");
+ }
+
+ public Iterable<Entry<FileBlocks>> entries() {
+ return cache.entries();
+ }
+
+ @CheckForNull
+ public FileBlocks byComponent(String effectiveKey) {
+ return cache.get(effectiveKey);
+ }
+
+ public BlockCache put(String effectiveKey, FileBlocks blocks) {
+ cache.put(effectiveKey, blocks);
+ return this;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java
new file mode 100644
index 00000000000..d6dd18026a2
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java
@@ -0,0 +1,74 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import com.google.common.base.Preconditions;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
+
+import java.util.ArrayList;
+
+public class DefaultDuplicationBuilder implements DuplicationBuilder {
+
+ private final InputFile inputFile;
+ private final DuplicationCache duplicationCache;
+ private boolean done = false;
+ private DuplicationGroup current = null;
+ private ArrayList<DuplicationGroup> duplications;
+
+ public DefaultDuplicationBuilder(InputFile inputFile, DuplicationCache duplicationCache) {
+ this.inputFile = inputFile;
+ this.duplicationCache = duplicationCache;
+ duplications = new ArrayList<DuplicationGroup>();
+ }
+
+ @Override
+ public DuplicationBuilder originBlock(int startLine, int endLine) {
+ if (current != null) {
+ duplications.add(current);
+ }
+ current = new DuplicationGroup(new DuplicationGroup.Block(((DefaultInputFile) inputFile).key(), startLine, endLine - startLine + 1));
+ return this;
+ }
+
+ @Override
+ public DuplicationBuilder isDuplicatedBy(InputFile sameOrOtherFile, int startLine, int endLine) {
+ return isDuplicatedBy(((DefaultInputFile) sameOrOtherFile).key(), startLine, endLine);
+ }
+
+ /**
+ * For internal use. Global duplications are referencing files outside of current project so
+ * no way to manipulate an InputFile.
+ */
+ public DuplicationBuilder isDuplicatedBy(String fileKey, int startLine, int endLine) {
+ Preconditions.checkNotNull(current, "Call originBlock() first");
+ current.addDuplicate(new DuplicationGroup.Block(fileKey, startLine, endLine - startLine + 1));
+ return this;
+ }
+
+ @Override
+ public void done() {
+ Preconditions.checkState(!done, "done() already called");
+ Preconditions.checkNotNull(current, "Call originBlock() first");
+ duplications.add(current);
+ duplicationCache.put(((DefaultInputFile) inputFile).key(), duplications);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java
new file mode 100644
index 00000000000..6c7836d9e39
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java
@@ -0,0 +1,77 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import com.google.common.base.Preconditions;
+import net.sourceforge.pmd.cpd.TokenEntry;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.sensor.duplication.TokenBuilder;
+import org.sonar.duplications.block.Block;
+import org.sonar.duplications.block.FileBlocks;
+import org.sonar.duplications.internal.pmd.PmdBlockChunker;
+import org.sonar.duplications.internal.pmd.TokenizerBridge;
+import org.sonar.duplications.internal.pmd.TokensLine;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultTokenBuilder implements TokenBuilder {
+
+ private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenBuilder.class);
+
+ private final BlockCache cache;
+ private final InputFile inputFile;
+ private final List<TokenEntry> tokens = new ArrayList<TokenEntry>();
+ private final PmdBlockChunker blockChunker;
+ private boolean done = false;
+ private int previousLine = 0;
+
+ public DefaultTokenBuilder(InputFile inputFile, BlockCache cache, PmdBlockChunker blockChunker) {
+ this.inputFile = inputFile;
+ this.cache = cache;
+ this.blockChunker = blockChunker;
+ TokenEntry.clearImages();
+ }
+
+ @Override
+ public DefaultTokenBuilder addToken(int line, String image) {
+ Preconditions.checkState(!done, "done() already called");
+ Preconditions.checkState(line >= previousLine, "Token should be created in order. Previous line was " + previousLine + " and you tried to create a token at line " + line);
+ TokenEntry cpdToken = new TokenEntry(image, inputFile.absolutePath(), line);
+ tokens.add(cpdToken);
+ previousLine = line;
+ return this;
+ }
+
+ @Override
+ public void done() {
+ Preconditions.checkState(!done, "done() already called");
+ tokens.add(TokenEntry.getEOF());
+ TokenEntry.clearImages();
+ List<TokensLine> tokensLines = TokenizerBridge.convert(tokens);
+ ArrayList<Block> blocks = blockChunker.chunk(((DefaultInputFile) inputFile).key(), tokensLines);
+
+ cache.put(((DefaultInputFile) inputFile).key(), new FileBlocks(((DefaultInputFile) inputFile).key(), blocks));
+ tokens.clear();
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java
new file mode 100644
index 00000000000..ab1dad47009
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import com.persistit.Value;
+import com.persistit.encoding.CoderContext;
+import com.persistit.encoding.ValueCoder;
+
+class DuplicationBlockValueCoder implements ValueCoder {
+
+ @Override
+ public void put(Value value, Object object, CoderContext context) {
+ DuplicationGroup.Block b = (DuplicationGroup.Block) object;
+ value.putUTF(b.resourceKey());
+ value.put(b.startLine());
+ value.put(b.length());
+ }
+
+ @Override
+ public Object get(Value value, Class clazz, CoderContext context) {
+ String resourceKey = value.getString();
+ int startLine = value.getInt();
+ int length = value.getInt();
+ return new DuplicationGroup.Block(resourceKey, startLine, length);
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java
new file mode 100644
index 00000000000..e0265215efc
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java
@@ -0,0 +1,58 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import org.sonar.api.BatchComponent;
+import org.sonar.batch.index.Cache;
+import org.sonar.batch.index.Cache.Entry;
+import org.sonar.batch.index.Caches;
+
+import javax.annotation.CheckForNull;
+
+import java.util.ArrayList;
+
+/**
+ * Cache of duplication blocks. This cache is shared amongst all project modules.
+ */
+public class DuplicationCache implements BatchComponent {
+
+ private final Cache<ArrayList<DuplicationGroup>> cache;
+
+ public DuplicationCache(Caches caches) {
+ caches.registerValueCoder(DuplicationGroup.class, new DuplicationGroupValueCoder());
+ caches.registerValueCoder(DuplicationGroup.Block.class, new DuplicationBlockValueCoder());
+ cache = caches.createCache("duplications");
+ }
+
+ public Iterable<Entry<ArrayList<DuplicationGroup>>> entries() {
+ return cache.entries();
+ }
+
+ @CheckForNull
+ public ArrayList<DuplicationGroup> byComponent(String effectiveKey) {
+ return cache.get(effectiveKey);
+ }
+
+ public DuplicationCache put(String effectiveKey, ArrayList<DuplicationGroup> blocks) {
+ cache.put(effectiveKey, blocks);
+ return this;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java
new file mode 100644
index 00000000000..dc9a7aa2602
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DuplicationGroup {
+
+ public static class Block {
+ private final String resourceKey;
+ private final int startLine;
+ private final int length;
+
+ public Block(String resourceKey, int startLine, int length) {
+ this.resourceKey = resourceKey;
+ this.startLine = startLine;
+ this.length = length;
+ }
+
+ public String resourceKey() {
+ return resourceKey;
+ }
+
+ public int startLine() {
+ return startLine;
+ }
+
+ public int length() {
+ return length;
+ }
+ }
+
+ private final Block originBlock;
+
+ private List<Block> duplicates = new ArrayList<DuplicationGroup.Block>();
+
+ public DuplicationGroup(Block originBlock) {
+ this.originBlock = originBlock;
+ }
+
+ public void setDuplicates(List<Block> duplicates) {
+ this.duplicates = duplicates;
+ }
+
+ public DuplicationGroup addDuplicate(Block anotherBlock) {
+ this.duplicates.add(anotherBlock);
+ return this;
+ }
+
+ public Block originBlock() {
+ return originBlock;
+ }
+
+ public List<Block> duplicates() {
+ return duplicates;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java
new file mode 100644
index 00000000000..244cffccd5f
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java
@@ -0,0 +1,54 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import com.persistit.Value;
+import com.persistit.encoding.CoderContext;
+import com.persistit.encoding.ValueCoder;
+import org.sonar.batch.duplication.DuplicationGroup.Block;
+
+import java.util.ArrayList;
+
+class DuplicationGroupValueCoder implements ValueCoder {
+
+ private DuplicationBlockValueCoder blockCoder = new DuplicationBlockValueCoder();
+
+ @Override
+ public void put(Value value, Object object, CoderContext context) {
+ DuplicationGroup c = (DuplicationGroup) object;
+ value.put(c.originBlock());
+ value.put(c.duplicates().size());
+ for (DuplicationGroup.Block block : c.duplicates()) {
+ blockCoder.put(value, block, context);
+ }
+ }
+
+ @Override
+ public Object get(Value value, Class clazz, CoderContext context) {
+ DuplicationGroup g = new DuplicationGroup((DuplicationGroup.Block) value.get());
+ int count = value.getInt();
+ ArrayList<DuplicationGroup.Block> blocks = new ArrayList<DuplicationGroup.Block>(count);
+ for (int i = 0; i < count; i++) {
+ blocks.add((Block) blockCoder.get(value, DuplicationGroup.Block.class, context));
+ }
+ g.setDuplicates(blocks);
+ return g;
+ }
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/FileBlocksValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/FileBlocksValueCoder.java
new file mode 100644
index 00000000000..99b3467c921
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/FileBlocksValueCoder.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import com.persistit.Value;
+import com.persistit.encoding.CoderContext;
+import com.persistit.encoding.ValueCoder;
+import org.sonar.duplications.block.Block;
+import org.sonar.duplications.block.ByteArray;
+import org.sonar.duplications.block.FileBlocks;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class FileBlocksValueCoder implements ValueCoder {
+
+ @Override
+ public void put(Value value, Object object, CoderContext context) {
+ FileBlocks blocks = (FileBlocks) object;
+ value.putUTF(blocks.resourceId());
+ value.put(blocks.blocks().size());
+ for (Block b : blocks.blocks()) {
+ value.putByteArray(b.getBlockHash().getBytes());
+ value.put(b.getIndexInFile());
+ value.put(b.getStartLine());
+ value.put(b.getEndLine());
+ value.put(b.getStartUnit());
+ value.put(b.getEndUnit());
+ }
+ }
+
+ @Override
+ public Object get(Value value, Class clazz, CoderContext context) {
+ String resourceId = value.getString();
+ int count = value.getInt();
+ List<Block> blocks = new ArrayList<Block>(count);
+ for (int i = 0; i < count; i++) {
+ Block.Builder b = Block.builder();
+ b.setResourceId(resourceId);
+ b.setBlockHash(new ByteArray(value.getByteArray()));
+ b.setIndexInFile(value.getInt());
+ int startLine = value.getInt();
+ int endLine = value.getInt();
+ b.setLines(startLine, endLine);
+ int startUnit = value.getInt();
+ int endUnit = value.getInt();
+ b.setUnit(startUnit, endUnit);
+ blocks.add(b.build());
+ }
+ return new FileBlocks(resourceId, blocks);
+ }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/Xoo.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/package-info.java
index 1922fd7aa35..103e90ab281 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/base/Xoo.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/package-info.java
@@ -17,24 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.batch.mediumtest.xoo.plugin.base;
+@ParametersAreNonnullByDefault
+package org.sonar.batch.duplication;
-import org.sonar.api.resources.AbstractLanguage;
-
-public class Xoo extends AbstractLanguage {
-
- public static final String KEY = "xoo";
- public static final String NAME = "Xoo";
-
- public Xoo() {
- super(KEY, NAME);
- }
-
- /**
- * ${@inheritDoc}
- */
- public String[] getFileSuffixes() {
- return XooConstants.FILE_SUFFIXES;
- }
-
-}
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java
index 38ae875af98..3a74dbf4884 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingDataBuilder.java
@@ -32,9 +32,6 @@ import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
-/**
- * @since 3.6
- */
public class SyntaxHighlightingDataBuilder {
private List<SyntaxHighlightingRule> syntaxHighlightingRuleSet;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java
index 08985752d60..e6faa7651b4 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/highlighting/SyntaxHighlightingRule.java
@@ -21,9 +21,6 @@ package org.sonar.batch.highlighting;
import java.io.Serializable;
-/**
- * @since 3.6
- */
public class SyntaxHighlightingRule implements Serializable {
private final int startPosition;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java
index ff57e4c065d..c98b88773e6 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/Cache.java
@@ -28,7 +28,6 @@ import org.apache.commons.lang.builder.ToStringBuilder;
import javax.annotation.CheckForNull;
-import java.io.Serializable;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
@@ -38,7 +37,7 @@ import java.util.Set;
* This cache is not thread-safe, due to direct usage of {@link com.persistit.Exchange}
* </p>
*/
-public class Cache<V extends Serializable> {
+public class Cache<V> {
private final String name;
private final Exchange exchange;
@@ -383,7 +382,7 @@ public class Cache<V extends Serializable> {
// LAZY ITERATORS AND ITERABLES
//
- private static class ValueIterable<T extends Serializable> implements Iterable<T> {
+ private static class ValueIterable<T> implements Iterable<T> {
private final Iterator<T> iterator;
private ValueIterable(Exchange exchange, KeyFilter keyFilter) {
@@ -396,7 +395,7 @@ public class Cache<V extends Serializable> {
}
}
- private static class ValueIterator<T extends Serializable> implements Iterator<T> {
+ private static class ValueIterator<T> implements Iterator<T> {
private final Exchange exchange;
private final KeyFilter keyFilter;
@@ -434,7 +433,7 @@ public class Cache<V extends Serializable> {
}
}
- private static class EntryIterable<T extends Serializable> implements Iterable<Entry<T>> {
+ private static class EntryIterable<T> implements Iterable<Entry<T>> {
private final EntryIterator<T> it;
private EntryIterable(Exchange exchange, KeyFilter keyFilter) {
@@ -447,7 +446,7 @@ public class Cache<V extends Serializable> {
}
}
- private static class EntryIterator<T extends Serializable> implements Iterator<Entry<T>> {
+ private static class EntryIterator<T> implements Iterator<Entry<T>> {
private final Exchange exchange;
private final KeyFilter keyFilter;
@@ -491,7 +490,7 @@ public class Cache<V extends Serializable> {
}
}
- public static class Entry<V extends Serializable> {
+ public static class Entry<V> {
private final Object[] key;
private final V value;
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java b/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java
index 1e8c53d8853..ca2a33a5014 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/Caches.java
@@ -36,7 +36,6 @@ import org.sonar.api.BatchComponent;
import org.sonar.api.utils.TempFolder;
import java.io.File;
-import java.io.Serializable;
import java.util.Properties;
import java.util.Set;
@@ -85,7 +84,7 @@ public class Caches implements BatchComponent, Startable {
cm.registerValueCoder(clazz, coder);
}
- public <V extends Serializable> Cache<V> createCache(String cacheName) {
+ public <V> Cache<V> createCache(String cacheName) {
Preconditions.checkState(volume != null && volume.isOpened(), "Caches are not initialized");
Preconditions.checkState(!cacheNames.contains(cacheName), "Cache is already created: " + cacheName);
try {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
index 2a63d4b8b9e..0f7691728a1 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/index/MeasurePersister.java
@@ -20,10 +20,13 @@
package org.sonar.batch.index;
import com.google.common.annotations.VisibleForTesting;
+import org.apache.commons.lang.StringEscapeUtils;
import org.sonar.api.database.model.MeasureMapper;
import org.sonar.api.database.model.MeasureModel;
import org.sonar.api.database.model.Snapshot;
+import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
+import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.measures.RuleMeasure;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.ResourceUtils;
@@ -31,6 +34,8 @@ import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.technicaldebt.batch.Characteristic;
+import org.sonar.batch.duplication.DuplicationCache;
+import org.sonar.batch.duplication.DuplicationGroup;
import org.sonar.batch.index.Cache.Entry;
import org.sonar.batch.scan.measure.MeasureCache;
import org.sonar.core.persistence.DbSession;
@@ -38,20 +43,27 @@ import org.sonar.core.persistence.MyBatis;
import javax.annotation.Nullable;
+import java.util.ArrayList;
+
public final class MeasurePersister implements ScanPersister {
private final MyBatis mybatis;
private final RuleFinder ruleFinder;
private final MeasureCache measureCache;
private final SnapshotCache snapshotCache;
private final ResourceCache resourceCache;
+ private final DuplicationCache duplicationCache;
+ private final org.sonar.api.measures.MetricFinder metricFinder;
public MeasurePersister(MyBatis mybatis, RuleFinder ruleFinder,
- MeasureCache measureCache, SnapshotCache snapshotCache, ResourceCache resourceCache) {
+ MeasureCache measureCache, SnapshotCache snapshotCache, ResourceCache resourceCache,
+ DuplicationCache duplicationCache, org.sonar.api.measures.MetricFinder metricFinder) {
this.mybatis = mybatis;
this.ruleFinder = ruleFinder;
this.measureCache = measureCache;
this.snapshotCache = snapshotCache;
this.resourceCache = resourceCache;
+ this.duplicationCache = duplicationCache;
+ this.metricFinder = metricFinder;
}
@Override
@@ -72,6 +84,19 @@ public final class MeasurePersister implements ScanPersister {
}
}
+ org.sonar.api.measures.Metric duplicationMetricWithId = metricFinder.findByKey(CoreMetrics.DUPLICATIONS_DATA_KEY);
+ for (Entry<ArrayList<DuplicationGroup>> entry : duplicationCache.entries()) {
+ String effectiveKey = entry.key()[0].toString();
+ Measure measure = new Measure(duplicationMetricWithId, toXml(entry.value())).setPersistenceMode(PersistenceMode.DATABASE);
+ Resource resource = resourceCache.get(effectiveKey);
+
+ if (shouldPersistMeasure(resource, measure)) {
+ Snapshot snapshot = snapshotCache.get(effectiveKey);
+ MeasureModel measureModel = model(measure).setSnapshotId(snapshot.getId());
+ mapper.insert(measureModel);
+ }
+ }
+
session.commit();
} catch (Exception e) {
throw new IllegalStateException("Unable to save some measures", e);
@@ -80,6 +105,28 @@ public final class MeasurePersister implements ScanPersister {
}
}
+ private static String toXml(Iterable<DuplicationGroup> duplications) {
+ StringBuilder xml = new StringBuilder();
+ xml.append("<duplications>");
+ for (DuplicationGroup duplication : duplications) {
+ xml.append("<g>");
+ toXml(xml, duplication.originBlock());
+ for (DuplicationGroup.Block part : duplication.duplicates()) {
+ toXml(xml, part);
+ }
+ xml.append("</g>");
+ }
+ xml.append("</duplications>");
+ return xml.toString();
+ }
+
+ private static void toXml(StringBuilder xml, DuplicationGroup.Block part) {
+ xml.append("<b s=\"").append(part.startLine())
+ .append("\" l=\"").append(part.length())
+ .append("\" r=\"").append(StringEscapeUtils.escapeXml(part.resourceKey()))
+ .append("\"/>");
+ }
+
@VisibleForTesting
static boolean shouldPersistMeasure(@Nullable Resource resource, @Nullable Measure measure) {
if (resource == null || measure == null) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
index fc7a89ebad8..58cc4f461af 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
@@ -41,8 +41,11 @@ import org.sonar.api.resources.Languages;
import org.sonar.batch.bootstrap.PluginsReferential;
import org.sonar.batch.bootstrapper.Batch;
import org.sonar.batch.bootstrapper.EnvironmentInformation;
+import org.sonar.batch.duplication.DuplicationCache;
+import org.sonar.batch.duplication.DuplicationGroup;
import org.sonar.batch.highlighting.SyntaxHighlightingData;
import org.sonar.batch.highlighting.SyntaxHighlightingRule;
+import org.sonar.batch.index.Cache.Entry;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.protocol.input.ActiveRule;
import org.sonar.batch.protocol.input.GlobalReferentials;
@@ -222,6 +225,7 @@ public class BatchMediumTester {
private List<Issue> issues = new ArrayList<Issue>();
private List<Measure> measures = new ArrayList<Measure>();
+ private Map<String, List<DuplicationGroup>> duplications = new HashMap<String, List<DuplicationGroup>>();
private List<InputFile> inputFiles = new ArrayList<InputFile>();
private List<InputDir> inputDirs = new ArrayList<InputDir>();
private Map<InputFile, SyntaxHighlightingData> highlightingPerFile = new HashMap<InputFile, SyntaxHighlightingData>();
@@ -259,6 +263,12 @@ public class BatchMediumTester {
}
}
+ DuplicationCache duplicationCache = container.getComponentByType(DuplicationCache.class);
+ for (Entry<ArrayList<DuplicationGroup>> entry : duplicationCache.entries()) {
+ String effectiveKey = entry.key()[0].toString();
+ duplications.put(effectiveKey, entry.value());
+ }
+
}
public List<Issue> issues() {
@@ -277,6 +287,10 @@ public class BatchMediumTester {
return inputDirs;
}
+ public List<DuplicationGroup> duplicationsFor(InputFile inputFile) {
+ return duplications.get(((DefaultInputFile) inputFile).key());
+ }
+
/**
* Get highlighting type at a given position in an inputfile
* @param charIndex 0-based offset in file
diff --git a/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java b/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java
index 75a063eb119..ac08e40f5fa 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/referential/ProjectReferentialsProvider.java
@@ -20,14 +20,24 @@
package org.sonar.batch.referential;
import org.picocontainer.injectors.ProviderAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.sonar.api.batch.bootstrap.ProjectReactor;
import org.sonar.api.config.Settings;
import org.sonar.api.resources.Languages;
+import org.sonar.api.utils.TimeProfiler;
import org.sonar.batch.protocol.input.ProjectReferentials;
public class ProjectReferentialsProvider extends ProviderAdapter {
+ private static final Logger LOG = LoggerFactory.getLogger(ProjectReferentialsProvider.class);
+
public ProjectReferentials provide(ProjectReferentialsLoader loader, ProjectReactor reactor, Settings settings, Languages languages) {
- return loader.load(reactor, settings, languages);
+ TimeProfiler profiler = new TimeProfiler(LOG).start("Load project referentials");
+ try {
+ return loader.load(reactor, settings, languages);
+ } finally {
+ profiler.stop();
+ }
}
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
index c5e21b89a66..3a209b91961 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java
@@ -42,6 +42,8 @@ import org.sonar.batch.bootstrap.MetricProvider;
import org.sonar.batch.components.PeriodsDefinition;
import org.sonar.batch.debt.DebtModelProvider;
import org.sonar.batch.debt.IssueChangelogDebtCalculator;
+import org.sonar.batch.duplication.BlockCache;
+import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.index.Caches;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.index.ComponentDataPersister;
@@ -194,6 +196,10 @@ public class ProjectScanContainer extends ComponentContainer {
new RulesProvider(),
new DebtModelProvider(),
+ // Duplications
+ BlockCache.class,
+ DuplicationCache.class,
+
ProjectSettings.class);
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java
index 1de8fd7201c..d285a6c8646 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java
@@ -27,6 +27,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
+import org.sonar.api.batch.sensor.duplication.TokenBuilder;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueBuilder;
@@ -49,9 +51,14 @@ import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.resources.Scopes;
import org.sonar.api.rule.RuleKey;
+import org.sonar.batch.duplication.BlockCache;
+import org.sonar.batch.duplication.DefaultDuplicationBuilder;
+import org.sonar.batch.duplication.DefaultTokenBuilder;
+import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.highlighting.DefaultHighlightingBuilder;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.symbol.DefaultSymbolTableBuilder;
+import org.sonar.duplications.internal.pmd.PmdBlockChunker;
import java.io.Serializable;
@@ -61,17 +68,20 @@ import java.io.Serializable;
*/
public class SensorContextAdaptor implements SensorContext {
- private org.sonar.api.batch.SensorContext sensorContext;
- private MetricFinder metricFinder;
- private Project project;
- private ResourcePerspectives perspectives;
- private Settings settings;
- private FileSystem fs;
- private ActiveRules activeRules;
- private ComponentDataCache componentDataCache;
+ private final org.sonar.api.batch.SensorContext sensorContext;
+ private final MetricFinder metricFinder;
+ private final Project project;
+ private final ResourcePerspectives perspectives;
+ private final Settings settings;
+ private final FileSystem fs;
+ private final ActiveRules activeRules;
+ private final ComponentDataCache componentDataCache;
+ private final BlockCache blockCache;
+ private final DuplicationCache duplicationCache;
public SensorContextAdaptor(org.sonar.api.batch.SensorContext sensorContext, MetricFinder metricFinder, Project project, ResourcePerspectives perspectives,
- Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache) {
+ Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache, BlockCache blockCache,
+ DuplicationCache duplicationCache) {
this.sensorContext = sensorContext;
this.metricFinder = metricFinder;
this.project = project;
@@ -80,6 +90,8 @@ public class SensorContextAdaptor implements SensorContext {
this.fs = fs;
this.activeRules = activeRules;
this.componentDataCache = componentDataCache;
+ this.blockCache = blockCache;
+ this.duplicationCache = duplicationCache;
}
@Override
@@ -254,4 +266,33 @@ public class SensorContextAdaptor implements SensorContext {
return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}
+ @Override
+ public TokenBuilder tokenBuilder(InputFile inputFile) {
+ PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language()));
+ return new DefaultTokenBuilder(inputFile, blockCache, blockChunker);
+ }
+
+ @Override
+ public DuplicationBuilder duplicationBuilder(InputFile inputFile) {
+ return new DefaultDuplicationBuilder(inputFile, duplicationCache);
+ }
+
+ private int getBlockSize(String languageKey) {
+ int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");
+ if (blockSize == 0) {
+ blockSize = getDefaultBlockSize(languageKey);
+ }
+ return blockSize;
+ }
+
+ private static int getDefaultBlockSize(String languageKey) {
+ if ("cobol".equals(languageKey)) {
+ return 30;
+ } else if ("abap".equals(languageKey) || "natur".equals(languageKey)) {
+ return 20;
+ } else {
+ return 10;
+ }
+ }
+
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java
index b5929008269..32a49a16492 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorWrapper.java
@@ -33,19 +33,23 @@ import java.util.List;
public class SensorWrapper implements org.sonar.api.batch.Sensor {
- private Sensor analyzer;
+ private Sensor wrappedSensor;
private SensorContext adaptor;
private DefaultSensorDescriptor descriptor;
private AnalyzerOptimizer optimizer;
public SensorWrapper(Sensor newSensor, SensorContext adaptor, AnalyzerOptimizer optimizer) {
- this.analyzer = newSensor;
+ this.wrappedSensor = newSensor;
this.optimizer = optimizer;
descriptor = new DefaultSensorDescriptor();
newSensor.describe(descriptor);
this.adaptor = adaptor;
}
+ public Sensor wrappedSensor() {
+ return wrappedSensor;
+ }
+
@DependedUpon
public List<Metric> provides() {
return Arrays.asList(descriptor.provides());
@@ -63,7 +67,7 @@ public class SensorWrapper implements org.sonar.api.batch.Sensor {
@Override
public void analyse(Project module, org.sonar.api.batch.SensorContext context) {
- analyzer.execute(adaptor);
+ wrappedSensor.execute(adaptor);
}
@Override
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultInputFileValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultInputFileValueCoder.java
new file mode 100644
index 00000000000..d60cbac0088
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/DefaultInputFileValueCoder.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.scan.filesystem;
+
+import com.persistit.Value;
+import com.persistit.encoding.CoderContext;
+import com.persistit.encoding.ValueCoder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
+
+import javax.annotation.Nullable;
+
+import java.io.File;
+
+class DefaultInputFileValueCoder implements ValueCoder {
+
+ @Override
+ public void put(Value value, Object object, CoderContext context) {
+ DeprecatedDefaultInputFile f = (DeprecatedDefaultInputFile) object;
+ putUTFOrNull(value, f.relativePath());
+ putUTFOrNull(value, f.getFileBaseDir().toString());
+ putUTFOrNull(value, f.deprecatedKey());
+ putUTFOrNull(value, f.sourceDirAbsolutePath());
+ putUTFOrNull(value, f.pathRelativeToSourceDir());
+ putUTFOrNull(value, f.absolutePath());
+ putUTFOrNull(value, f.language());
+ putUTFOrNull(value, f.type().name());
+ putUTFOrNull(value, f.status().name());
+ putUTFOrNull(value, f.hash());
+ value.put(f.lines());
+ putUTFOrNull(value, f.key());
+ }
+
+ private void putUTFOrNull(Value value, @Nullable String utfOrNull) {
+ if (utfOrNull != null) {
+ value.putUTF(utfOrNull);
+ } else {
+ value.putNull();
+ }
+ }
+
+ @Override
+ public Object get(Value value, Class clazz, CoderContext context) {
+ DeprecatedDefaultInputFile file = new DeprecatedDefaultInputFile(value.getString());
+ file.setBasedir(new File(value.getString()));
+ file.setDeprecatedKey(value.getString());
+ file.setSourceDirAbsolutePath(value.getString());
+ file.setPathRelativeToSourceDir(value.getString());
+ file.setAbsolutePath(value.getString());
+ file.setLanguage(value.getString());
+ file.setType(InputFile.Type.valueOf(value.getString()));
+ file.setStatus(InputFile.Status.valueOf(value.getString()));
+ file.setHash(value.getString());
+ file.setLines(value.getInt());
+ file.setKey(value.getString());
+ return file;
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
index 5d901a517ae..39672d7f7c5 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/filesystem/InputPathCache.java
@@ -23,6 +23,7 @@ import org.sonar.api.BatchComponent;
import org.sonar.api.batch.fs.InputDir;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.fs.InputPath;
+import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
import org.sonar.batch.index.Cache;
import org.sonar.batch.index.Caches;
@@ -44,6 +45,7 @@ public class InputPathCache implements BatchComponent {
private final Cache<InputPath> cache;
public InputPathCache(Caches caches) {
+ caches.registerValueCoder(DeprecatedDefaultInputFile.class, new DefaultInputFileValueCoder());
cache = caches.createCache("inputFiles");
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java
index 7f010d97d0a..c0906b51d6d 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan/measure/MeasureValueCoder.java
@@ -30,6 +30,8 @@ import org.sonar.api.technicaldebt.batch.Characteristic;
import org.sonar.api.technicaldebt.batch.Requirement;
import org.sonar.api.technicaldebt.batch.TechnicalDebtModel;
+import javax.annotation.Nullable;
+
class MeasureValueCoder implements ValueCoder {
private final MetricFinder metricFinder;
@@ -42,12 +44,12 @@ class MeasureValueCoder implements ValueCoder {
public void put(Value value, Object object, CoderContext context) {
Measure<?> m = (Measure) object;
- value.putString(m.getMetricKey());
+ value.putUTF(m.getMetricKey());
value.put(m.getValue());
- value.putString(m.getData());
- value.putString(m.getDescription());
- value.putString(m.getAlertStatus() != null ? m.getAlertStatus().name() : null);
- value.putString(m.getAlertText());
+ putUTFOrNull(value, m.getData());
+ putUTFOrNull(value, m.getDescription());
+ putUTFOrNull(value, m.getAlertStatus() != null ? m.getAlertStatus().name() : null);
+ putUTFOrNull(value, m.getAlertText());
value.put(m.getTendency());
value.putDate(m.getDate());
value.put(m.getVariation1());
@@ -55,7 +57,7 @@ class MeasureValueCoder implements ValueCoder {
value.put(m.getVariation3());
value.put(m.getVariation4());
value.put(m.getVariation5());
- value.putString(m.getUrl());
+ putUTFOrNull(value, m.getUrl());
Characteristic characteristic = m.getCharacteristic();
value.put(characteristic != null ? characteristic.id() : null);
Requirement requirement = m.getRequirement();
@@ -63,7 +65,15 @@ class MeasureValueCoder implements ValueCoder {
Integer personId = m.getPersonId();
value.put(personId != null ? personId.intValue() : null);
PersistenceMode persistenceMode = m.getPersistenceMode();
- value.putString(persistenceMode != null ? persistenceMode.name() : null);
+ putUTFOrNull(value, persistenceMode != null ? persistenceMode.name() : null);
+ }
+
+ private void putUTFOrNull(Value value, @Nullable String utfOrNull) {
+ if (utfOrNull != null) {
+ value.putUTF(utfOrNull);
+ } else {
+ value.putNull();
+ }
}
public Object get(Value value, Class clazz, CoderContext context) {
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java
index 75d0e31932c..801744de2c7 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/AnalyzerMeasureCache.java
@@ -19,10 +19,10 @@
*/
package org.sonar.batch.scan2;
-import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
-
import com.google.common.base.Preconditions;
import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.measure.MetricFinder;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
import org.sonar.batch.index.Cache;
import org.sonar.batch.index.Cache.Entry;
import org.sonar.batch.index.Caches;
@@ -35,7 +35,8 @@ public class AnalyzerMeasureCache implements BatchComponent {
// project key -> component key -> metric key -> measure
private final Cache<DefaultMeasure> cache;
- public AnalyzerMeasureCache(Caches caches) {
+ public AnalyzerMeasureCache(Caches caches, MetricFinder metricFinder) {
+ caches.registerValueCoder(DefaultMeasure.class, new DefaultMeasureValueCoder(metricFinder));
cache = caches.createCache("measures");
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultMeasureValueCoder.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultMeasureValueCoder.java
new file mode 100644
index 00000000000..8b82a684dd0
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultMeasureValueCoder.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.scan2;
+
+import com.persistit.Value;
+import com.persistit.encoding.CoderContext;
+import com.persistit.encoding.ValueCoder;
+import org.sonar.api.batch.fs.InputFile;
+import org.sonar.api.batch.measure.Metric;
+import org.sonar.api.batch.measure.MetricFinder;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
+import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
+
+import java.io.Serializable;
+
+class DefaultMeasureValueCoder implements ValueCoder {
+
+ private MetricFinder metricFinder;
+
+ public DefaultMeasureValueCoder(MetricFinder metricFinder) {
+ this.metricFinder = metricFinder;
+ }
+
+ @Override
+ public void put(Value value, Object object, CoderContext context) {
+ DefaultMeasure m = (DefaultMeasure) object;
+ value.put(m.inputFile());
+ value.putUTF(m.metric().key());
+ value.put(m.value());
+ }
+
+ @Override
+ public Object get(Value value, Class clazz, CoderContext context) {
+ DefaultMeasureBuilder builder = new DefaultMeasureBuilder();
+ InputFile f = (InputFile) value.get();
+ if (f != null) {
+ builder.onFile(f);
+ } else {
+ builder.onProject();
+ }
+ Metric m = metricFinder.findByKey(value.getString());
+ builder.forMetric(m);
+ builder.withValue((Serializable) value.get());
+ return builder.build();
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java
index dcd6efd1ca4..73d6429bfcd 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java
@@ -28,6 +28,8 @@ import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.rule.ActiveRules;
import org.sonar.api.batch.rule.internal.DefaultActiveRule;
import org.sonar.api.batch.sensor.SensorContext;
+import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
+import org.sonar.api.batch.sensor.duplication.TokenBuilder;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueBuilder;
@@ -41,12 +43,17 @@ import org.sonar.api.batch.sensor.symbol.SymbolTableBuilder;
import org.sonar.api.config.Settings;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.utils.MessageException;
+import org.sonar.batch.duplication.BlockCache;
+import org.sonar.batch.duplication.DefaultDuplicationBuilder;
+import org.sonar.batch.duplication.DefaultTokenBuilder;
+import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.highlighting.DefaultHighlightingBuilder;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.issue.IssueFilters;
import org.sonar.batch.scan.SensorContextAdaptor;
import org.sonar.batch.symbol.DefaultSymbolTableBuilder;
import org.sonar.core.component.ComponentKeys;
+import org.sonar.duplications.internal.pmd.PmdBlockChunker;
import java.io.Serializable;
@@ -60,9 +67,12 @@ public class DefaultSensorContext implements SensorContext {
private final ActiveRules activeRules;
private final IssueFilters issueFilters;
private final ComponentDataCache componentDataCache;
+ private final BlockCache blockCache;
+ private final DuplicationCache duplicationCache;
public DefaultSensorContext(ProjectDefinition def, AnalyzerMeasureCache measureCache, AnalyzerIssueCache issueCache,
- Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters, ComponentDataCache componentDataCache) {
+ Settings settings, FileSystem fs, ActiveRules activeRules, IssueFilters issueFilters, ComponentDataCache componentDataCache,
+ BlockCache blockCache, DuplicationCache duplicationCache) {
this.def = def;
this.measureCache = measureCache;
this.issueCache = issueCache;
@@ -71,6 +81,8 @@ public class DefaultSensorContext implements SensorContext {
this.activeRules = activeRules;
this.issueFilters = issueFilters;
this.componentDataCache = componentDataCache;
+ this.blockCache = blockCache;
+ this.duplicationCache = duplicationCache;
}
@Override
@@ -175,4 +187,34 @@ public class DefaultSensorContext implements SensorContext {
return new DefaultSymbolTableBuilder(((DefaultInputFile) inputFile).key(), componentDataCache);
}
+ @Override
+ public TokenBuilder tokenBuilder(InputFile inputFile) {
+ PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language()));
+
+ return new DefaultTokenBuilder(inputFile, blockCache, blockChunker);
+ }
+
+ @Override
+ public DuplicationBuilder duplicationBuilder(InputFile inputFile) {
+ return new DefaultDuplicationBuilder(inputFile, duplicationCache);
+ }
+
+ private int getBlockSize(String languageKey) {
+ int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines");
+ if (blockSize == 0) {
+ blockSize = getDefaultBlockSize(languageKey);
+ }
+ return blockSize;
+ }
+
+ private static int getDefaultBlockSize(String languageKey) {
+ if ("cobol".equals(languageKey)) {
+ return 30;
+ } else if ("abap".equals(languageKey) || "natur".equals(languageKey)) {
+ return 20;
+ } else {
+ return 10;
+ }
+ }
+
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java
index e13fca37d28..b0a2b3e0af5 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/ProjectScanContainer.java
@@ -33,6 +33,8 @@ import org.sonar.api.scan.filesystem.PathResolver;
import org.sonar.batch.bootstrap.ExtensionInstaller;
import org.sonar.batch.bootstrap.ExtensionMatcher;
import org.sonar.batch.bootstrap.ExtensionUtils;
+import org.sonar.batch.duplication.BlockCache;
+import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.index.Caches;
import org.sonar.batch.index.ComponentDataCache;
import org.sonar.batch.languages.DefaultLanguagesReferential;
@@ -107,6 +109,10 @@ public class ProjectScanContainer extends ComponentContainer {
ComponentDataCache.class,
+ // Duplications
+ BlockCache.class,
+ DuplicationCache.class,
+
ScanTaskObservers.class);
}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java
index d437eadb6fd..ea872c4c1bf 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/SensorsExecutor.java
@@ -53,7 +53,7 @@ public class SensorsExecutor implements BatchComponent {
continue;
}
- LOG.info("Execute analyzer: " + descriptor.name());
+ LOG.info("Execute sensor: " + descriptor.name());
executeSensor(context, analyzer);
}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java
new file mode 100644
index 00000000000..ade701fce45
--- /dev/null
+++ b/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java
@@ -0,0 +1,82 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.batch.duplication;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.batch.index.Caches;
+import org.sonar.batch.index.CachesTest;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class DuplicationCacheTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ Caches caches;
+
+ @Before
+ public void start() throws Exception {
+ caches = CachesTest.createCacheOnTemp(temp);
+ caches.start();
+ }
+
+ @After
+ public void stop() {
+ caches.stop();
+ }
+
+ @Test
+ public void should_add_clone_groups() throws Exception {
+ DuplicationCache cache = new DuplicationCache(caches);
+
+ DuplicationGroup group1 = new DuplicationGroup(new DuplicationGroup.Block("foo", 1, 2))
+ .addDuplicate(new DuplicationGroup.Block("foo", 1, 2))
+ .addDuplicate(new DuplicationGroup.Block("foo2", 12, 22))
+ .addDuplicate(new DuplicationGroup.Block("foo3", 13, 23));
+
+ DuplicationGroup group2 = new DuplicationGroup(new DuplicationGroup.Block("2foo", 1, 2))
+ .addDuplicate(new DuplicationGroup.Block("2foo", 1, 2))
+ .addDuplicate(new DuplicationGroup.Block("2foo2", 12, 22))
+ .addDuplicate(new DuplicationGroup.Block("2foo3", 13, 23));
+
+ assertThat(cache.entries()).hasSize(0);
+
+ cache.put("foo", new ArrayList<DuplicationGroup>(Arrays.asList(group1, group2)));
+
+ assertThat(cache.entries()).hasSize(1);
+
+ ArrayList<DuplicationGroup> entry = cache.byComponent("foo");
+ assertThat(entry.get(0).originBlock().resourceKey()).isEqualTo("foo");
+
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
index fe8a1a45a65..dfed349e696 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/index/MeasurePersisterTest.java
@@ -27,6 +27,7 @@ import org.sonar.api.database.model.Snapshot;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
+import org.sonar.api.measures.MetricFinder;
import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.measures.RuleMeasure;
import org.sonar.api.resources.Directory;
@@ -35,10 +36,14 @@ import org.sonar.api.resources.Project;
import org.sonar.api.rules.Rule;
import org.sonar.api.rules.RuleFinder;
import org.sonar.api.rules.RulePriority;
+import org.sonar.batch.duplication.DuplicationCache;
+import org.sonar.batch.duplication.DuplicationGroup;
import org.sonar.batch.scan.measure.MeasureCache;
import org.sonar.core.persistence.AbstractDaoTestCase;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import static org.fest.assertions.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -70,6 +75,8 @@ public class MeasurePersisterTest extends AbstractDaoTestCase {
public void mockResourcePersister() {
snapshotCache = mock(SnapshotCache.class);
measureCache = mock(MeasureCache.class);
+ DuplicationCache duplicationCache = mock(DuplicationCache.class);
+ when(duplicationCache.entries()).thenReturn(Collections.<Cache.Entry<ArrayList<DuplicationGroup>>>emptyList());
ResourceCache resourceCache = mock(ResourceCache.class);
when(snapshotCache.get("foo")).thenReturn(projectSnapshot);
when(snapshotCache.get("foo:org/foo")).thenReturn(packageSnapshot);
@@ -77,7 +84,7 @@ public class MeasurePersisterTest extends AbstractDaoTestCase {
when(resourceCache.get("foo:org/foo/Bar.java")).thenReturn(aFile);
when(resourceCache.get("foo:org/foo")).thenReturn(aDirectory);
- measurePersister = new MeasurePersister(getMyBatis(), ruleFinder, measureCache, snapshotCache, resourceCache);
+ measurePersister = new MeasurePersister(getMyBatis(), ruleFinder, measureCache, snapshotCache, resourceCache, duplicationCache, mock(MetricFinder.class));
}
@Test
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java
index 674f472d0b5..19d7aea020a 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/fs/FileSystemMediumTest.java
@@ -31,8 +31,8 @@ import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.utils.MessageException;
import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
-import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin;
import org.sonar.batch.protocol.input.ActiveRule;
+import org.sonar.xoo.XooPlugin;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java
index 259b2ffcec0..b9fab7ba450 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/highlighting/HighlightingMediumTest.java
@@ -29,7 +29,7 @@ import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
-import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin;
+import org.sonar.xoo.XooPlugin;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java
index 875e7e6d510..f3eebf971e3 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesMediumTest.java
@@ -29,8 +29,8 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
-import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin;
import org.sonar.batch.protocol.input.ActiveRule;
+import org.sonar.xoo.XooPlugin;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java
index fdf6b15e857..fb3e8d16edd 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/issues/IssuesOnDirMediumTest.java
@@ -28,8 +28,8 @@ import org.junit.rules.TemporaryFolder;
import org.sonar.api.batch.fs.internal.DefaultInputDir;
import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
-import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin;
import org.sonar.batch.protocol.input.ActiveRule;
+import org.sonar.xoo.XooPlugin;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
index bb93ae4b7ba..146b89eb316 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/measures/MeasuresMediumTest.java
@@ -30,7 +30,7 @@ import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
-import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin;
+import org.sonar.xoo.XooPlugin;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java
index c3875c625f3..8772d371ba2 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/symbol/SymbolMediumTest.java
@@ -28,7 +28,7 @@ import org.junit.rules.TemporaryFolder;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.batch.mediumtest.BatchMediumTester;
import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
-import org.sonar.batch.mediumtest.xoo.plugin.XooPlugin;
+import org.sonar.xoo.XooPlugin;
import java.io.File;
import java.io.IOException;
diff --git a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java b/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java
deleted file mode 100644
index 78718da3682..00000000000
--- a/sonar-batch/src/test/java/org/sonar/batch/mediumtest/xoo/plugin/XooPlugin.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.batch.mediumtest.xoo.plugin;
-
-import org.sonar.api.SonarPlugin;
-import org.sonar.batch.mediumtest.xoo.plugin.base.Xoo;
-import org.sonar.batch.mediumtest.xoo.plugin.lang.MeasureSensor;
-import org.sonar.batch.mediumtest.xoo.plugin.lang.ScmActivitySensor;
-import org.sonar.batch.mediumtest.xoo.plugin.lang.SymbolReferencesSensor;
-import org.sonar.batch.mediumtest.xoo.plugin.lang.SyntaxHighlightingSensor;
-import org.sonar.batch.mediumtest.xoo.plugin.rule.CreateIssueByInternalKeySensor;
-import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssueOnDirPerFileSensor;
-import org.sonar.batch.mediumtest.xoo.plugin.rule.OneIssuePerLineSensor;
-
-import java.util.Arrays;
-import java.util.List;
-
-public final class XooPlugin extends SonarPlugin {
-
- @Override
- public List getExtensions() {
- return Arrays.asList(
- // language
- MeasureSensor.class,
- ScmActivitySensor.class,
- SyntaxHighlightingSensor.class,
- SymbolReferencesSensor.class,
- Xoo.class,
-
- // sensors
- OneIssuePerLineSensor.class,
- OneIssueOnDirPerFileSensor.class,
- CreateIssueByInternalKeySensor.class
- );
- }
-}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java
index 7b7eb21ee58..a93bf9bae83 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java
@@ -42,6 +42,8 @@ import org.sonar.api.measures.MetricFinder;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
import org.sonar.api.rule.RuleKey;
+import org.sonar.batch.duplication.BlockCache;
+import org.sonar.batch.duplication.DuplicationCache;
import org.sonar.batch.index.ComponentDataCache;
import static org.fest.assertions.Assertions.assertThat;
@@ -71,8 +73,9 @@ public class SensorContextAdapterTest {
settings = new Settings();
resourcePerspectives = mock(ResourcePerspectives.class);
ComponentDataCache componentDataCache = mock(ComponentDataCache.class);
+ BlockCache blockCache = mock(BlockCache.class);
adaptor = new SensorContextAdaptor(sensorContext, metricFinder, new Project("myProject"),
- resourcePerspectives, settings, fs, activeRules, componentDataCache);
+ resourcePerspectives, settings, fs, activeRules, componentDataCache, blockCache, mock(DuplicationCache.class));
}
@Test
diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java
index f1bf43f37e3..03dbec79af8 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/scan/filesystem/InputPathCacheTest.java
@@ -24,6 +24,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.fs.InputFile.Status;
+import org.sonar.api.batch.fs.InputFile.Type;
import org.sonar.api.batch.fs.InputPath;
import org.sonar.api.batch.fs.internal.DefaultInputFile;
import org.sonar.api.batch.fs.internal.DeprecatedDefaultInputFile;
@@ -55,7 +57,18 @@ public class InputPathCacheTest {
InputPathCache cache = new InputPathCache(caches);
DefaultInputFile fooFile = new DefaultInputFile("src/main/java/Foo.java").setFile(temp.newFile("Foo.java"));
cache.put("struts", fooFile);
- cache.put("struts-core", new DeprecatedDefaultInputFile("src/main/java/Bar.java").setFile(temp.newFile("Bar.java")));
+ cache.put("struts-core", new DeprecatedDefaultInputFile("src/main/java/Bar.java")
+ .setBasedir(temp.newFolder())
+ .setDeprecatedKey("foo")
+ .setSourceDirAbsolutePath("foo")
+ .setPathRelativeToSourceDir("foo")
+ .setLanguage("bla")
+ .setType(Type.MAIN)
+ .setStatus(Status.ADDED)
+ .setHash("xyz")
+ .setLines(1)
+ .setKey("foo")
+ .setFile(temp.newFile("Bar.java")));
assertThat(cache.getFile("struts", "src/main/java/Foo.java").relativePath())
.isEqualTo("src/main/java/Foo.java");
@@ -75,5 +88,4 @@ public class InputPathCacheTest {
assertThat(cache.filesByModule("struts-core")).hasSize(1);
assertThat(cache.all()).hasSize(1);
}
-
}
diff --git a/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java b/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java
index 745b21b8c97..cb7ff46ff60 100644
--- a/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java
+++ b/sonar-core/src/main/java/org/sonar/core/source/SnapshotDataTypes.java
@@ -24,6 +24,7 @@ public interface SnapshotDataTypes {
String SYNTAX_HIGHLIGHTING = "highlight_syntax";
String SYMBOL_HIGHLIGHTING = "symbol";
+ String TOKEN = "token";
/**
* Key-values [relative path, hash] of all files. Stored on modules.
diff --git a/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java b/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java
index ffd90b34de2..34c5d6bbdbd 100644
--- a/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java
+++ b/sonar-deprecated/src/main/java/org/sonar/api/batch/BatchExtensionDictionnary.java
@@ -19,9 +19,6 @@
*/
package org.sonar.api.batch;
-import org.sonar.api.batch.sensor.Sensor;
-import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
-
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
@@ -29,6 +26,8 @@ import org.apache.commons.lang.ClassUtils;
import org.sonar.api.BatchExtension;
import org.sonar.api.batch.maven.DependsUponMavenPlugin;
import org.sonar.api.batch.maven.MavenPluginHandler;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor;
import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.AnnotationUtils;
diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java b/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java
index aab2802c27b..a3deacff154 100644
--- a/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java
+++ b/sonar-duplications/src/main/java/org/sonar/duplications/block/ByteArray.java
@@ -60,7 +60,7 @@ public final class ByteArray {
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
- (byte) value };
+ (byte) value};
}
public ByteArray(int value) {
@@ -68,7 +68,7 @@ public final class ByteArray {
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
- (byte) value };
+ (byte) value};
}
public ByteArray(int[] intArray) {
@@ -79,6 +79,10 @@ public final class ByteArray {
this.bytes = bb.array();
}
+ public byte[] getBytes() {
+ return bytes;
+ }
+
public int[] toIntArray() {
// Pad the size to multiple of 4
int size = (bytes.length / 4) + (bytes.length % 4 == 0 ? 0 : 1);
diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/block/FileBlocks.java b/sonar-duplications/src/main/java/org/sonar/duplications/block/FileBlocks.java
new file mode 100644
index 00000000000..7b43988efc2
--- /dev/null
+++ b/sonar-duplications/src/main/java/org/sonar/duplications/block/FileBlocks.java
@@ -0,0 +1,45 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.duplications.block;
+
+import java.util.List;
+
+/**
+ * Represents all blocks in a file.
+ */
+public final class FileBlocks {
+
+ private final String resourceId;
+ private final List<Block> blocks;
+
+ public FileBlocks(String resourceId, List<Block> blocks) {
+ this.resourceId = resourceId;
+ this.blocks = blocks;
+ }
+
+ public String resourceId() {
+ return resourceId;
+ }
+
+ public List<Block> blocks() {
+ return blocks;
+ }
+
+}
diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java b/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java
index 7c9ad2a8278..01b7273da1c 100644
--- a/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java
+++ b/sonar-duplications/src/main/java/org/sonar/duplications/index/CloneGroup.java
@@ -20,8 +20,10 @@
package org.sonar.duplications.index;
import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -32,6 +34,7 @@ public class CloneGroup {
private final ClonePart originPart;
private final int cloneLength;
private final List<ClonePart> parts;
+ private int length;
/**
* Cache for hash code.
@@ -52,7 +55,7 @@ public class CloneGroup {
private ClonePart origin;
private int length;
private int lengthInUnits;
- private List<ClonePart> parts;
+ private List<ClonePart> parts = new ArrayList<ClonePart>();
public Builder setLength(int length) {
this.length = length;
@@ -69,6 +72,12 @@ public class CloneGroup {
return this;
}
+ public Builder addPart(ClonePart part) {
+ Preconditions.checkNotNull(part);
+ this.parts.add(part);
+ return this;
+ }
+
public Builder setLengthInUnits(int length) {
this.lengthInUnits = length;
return this;
@@ -90,8 +99,6 @@ public class CloneGroup {
return originPart;
}
- private int length;
-
/**
* Length of duplication measured in original units, e.g. for token-based detection - in tokens.
*
diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java
index 1c7c0ee4437..b0ae419332a 100644
--- a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java
+++ b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/PmdBlockChunker.java
@@ -23,7 +23,7 @@ import com.google.common.collect.Lists;
import org.sonar.duplications.block.Block;
import org.sonar.duplications.block.ByteArray;
-import java.util.Collections;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -48,7 +48,10 @@ public class PmdBlockChunker {
this.power = pow;
}
- public List<Block> chunk(String resourceId, List<TokensLine> fragments) {
+ /**
+ * @return ArrayList as we need a serializable object
+ */
+ public ArrayList<Block> chunk(String resourceId, List<TokensLine> fragments) {
List<TokensLine> filtered = Lists.newArrayList();
int i = 0;
while (i < fragments.size()) {
@@ -66,10 +69,10 @@ public class PmdBlockChunker {
fragments = filtered;
if (fragments.size() < blockSize) {
- return Collections.emptyList();
+ return Lists.newArrayList();
}
TokensLine[] fragmentsArr = fragments.toArray(new TokensLine[fragments.size()]);
- List<Block> blocks = Lists.newArrayListWithCapacity(fragmentsArr.length - blockSize + 1);
+ ArrayList<Block> blocks = Lists.newArrayListWithCapacity(fragmentsArr.length - blockSize + 1);
long hash = 0;
int first = 0;
int last = 0;
@@ -84,11 +87,11 @@ public class PmdBlockChunker {
hash = hash * PRIME_BASE + lastFragment.getHashCode();
// create block
Block block = blockBuilder
- .setBlockHash(new ByteArray(hash))
- .setIndexInFile(first)
- .setLines(firstFragment.getStartLine(), lastFragment.getEndLine())
- .setUnit(firstFragment.getStartUnit(), lastFragment.getEndUnit())
- .build();
+ .setBlockHash(new ByteArray(hash))
+ .setIndexInFile(first)
+ .setLines(firstFragment.getStartLine(), lastFragment.getEndLine())
+ .setUnit(firstFragment.getStartUnit(), lastFragment.getEndUnit())
+ .build();
blocks.add(block);
// remove first statement from hash
hash -= power * firstFragment.getHashCode();
diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java
index e8dcc4bad76..80fc8691717 100644
--- a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java
+++ b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokenizerBridge.java
@@ -69,7 +69,7 @@ public class TokenizerBridge {
* We expect that implementation of {@link Tokenizer} is correct:
* tokens ordered by occurrence in source code and last token is EOF.
*/
- private static List<TokensLine> convert(List<TokenEntry> tokens) {
+ public static List<TokensLine> convert(List<TokenEntry> tokens) {
ImmutableList.Builder<TokensLine> result = ImmutableList.builder();
StringBuilder sb = new StringBuilder();
int startLine = Integer.MIN_VALUE;
diff --git a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java
index 9c07b381240..d8f5a304af9 100644
--- a/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java
+++ b/sonar-duplications/src/main/java/org/sonar/duplications/internal/pmd/TokensLine.java
@@ -25,7 +25,7 @@ import org.sonar.duplications.CodeFragment;
/**
* Immutable code fragment, which formed from tokens of one line.
*/
-class TokensLine implements CodeFragment {
+public class TokensLine implements CodeFragment {
private final String value;
@@ -35,7 +35,6 @@ class TokensLine implements CodeFragment {
private final int startUnit;
private final int endUnit;
-
public TokensLine(int startUnit, int endUnit, int startLine, String value) {
Preconditions.checkArgument(startLine > 0);
// TODO do we have requirements for length and hashcode ?
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
index aad1de5ea03..b73e0a0ecee 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/CoreProperties.java
@@ -344,10 +344,10 @@ public interface CoreProperties {
/**
* @since 2.11
*/
- String CPD_CROSS_RPOJECT = "sonar.cpd.cross_project";
+ String CPD_CROSS_PROJECT = "sonar.cpd.cross_project";
/**
- * @see #CPD_CROSS_RPOJECT
+ * @see #CPD_CROSS_PROJECT
* @since 2.11
*/
boolean CPD_CROSS_RPOJECT_DEFAULT_VALUE = false;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java
index 156e140fa57..7c82aa501d0 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DeprecatedDefaultInputFile.java
@@ -50,8 +50,9 @@ public class DeprecatedDefaultInputFile extends DefaultInputFile implements org.
return new File(basedir);
}
- public void setBasedir(File basedir) {
+ public DeprecatedDefaultInputFile setBasedir(File basedir) {
this.basedir = PathUtils.sanitize(basedir.getAbsolutePath());
+ return this;
}
/**
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
index 787d2fddb3d..647cab3f3f9 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
@@ -24,6 +24,8 @@ import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
import org.sonar.api.batch.measure.Metric;
import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.sensor.duplication.DuplicationBuilder;
+import org.sonar.api.batch.sensor.duplication.TokenBuilder;
import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueBuilder;
@@ -126,4 +128,19 @@ public interface SensorContext {
*/
SymbolTableBuilder symbolTableBuilder(InputFile inputFile);
+ // ------------ DUPLICATIONS ------------
+
+ /**
+ * Builder to define tokens in a file. Tokens are used to compute duplication by the core.
+ * @since 4.5
+ */
+ TokenBuilder tokenBuilder(InputFile inputFile);
+
+ /**
+ * Builder to manually define duplications in a file. When duplication are manually computed then
+ * no need to use {@link #tokenBuilder(InputFile)}.
+ * @since 4.5
+ */
+ DuplicationBuilder duplicationBuilder(InputFile inputFile);
+
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java
new file mode 100644
index 00000000000..2f37220b1a3
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java
@@ -0,0 +1,38 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.api.batch.sensor.duplication;
+
+import org.sonar.api.batch.fs.InputFile;
+
+/**
+ * This builder is used to declare duplications on files of the project.
+ * @since 4.5
+ */
+public interface DuplicationBuilder {
+
+ DuplicationBuilder originBlock(int startLine, int endLine);
+
+ DuplicationBuilder isDuplicatedBy(InputFile sameOrOtherFile, int startLine, int endLine);
+
+ /**
+ * Call this method only once when your are done with defining all duplicates of origin block.
+ */
+ void done();
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java
new file mode 100644
index 00000000000..f4de9fffc8c
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.api.batch.sensor.duplication;
+
+/**
+ * This builder is used to define token on files. Tokens are later used to compute duplication.
+ * Tokens should be declared in sequential order.
+ * @since 4.5
+ */
+public interface TokenBuilder {
+
+ /**
+ * Call this method to register a new token.
+ * @param line Line number of the token. Line starts at 1.
+ * @param image Text of the token.
+ */
+ TokenBuilder addToken(int line, String image);
+
+ /**
+ * Call this method only once when your are done with defining tokens of the file.
+ */
+ void done();
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/package-info.java
new file mode 100644
index 00000000000..4973316830a
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.api.batch.sensor.duplication;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java
index 7a86f7b145c..870a8519271 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/Measure.java
@@ -343,6 +343,7 @@ public class Measure<G extends Serializable> implements Serializable {
/**
* @return the data field of the measure
*/
+ @CheckForNull
public String getData() {
return data;
}