]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-5389 Improve duplication API
authorJulien HENRY <julien.henry@sonarsource.com>
Fri, 1 Aug 2014 09:26:22 +0000 (11:26 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Fri, 1 Aug 2014 09:49:42 +0000 (11:49 +0200)
24 files changed:
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java
plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/JavaCpdEngineTest.java
plugins/sonar-cpd-plugin/src/test/java/org/sonar/plugins/cpd/medium/CpdMediumTest.java
plugins/sonar-xoo-plugin/src/main/java/org/sonar/xoo/lang/XooTokenizerSensor.java
sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java
sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java
sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java
sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java
sonar-batch/src/main/java/org/sonar/batch/index/DuplicationPersister.java
sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java
sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java
sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java
sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java
sonar-batch/src/test/java/org/sonar/batch/index/DuplicationPersisterTest.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationBuilder.java
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationGroup.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationTokenBuilder.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java [deleted file]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/DefaultDuplicationBuilder.java [new file with mode: 0644]
sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/package-info.java [new file with mode: 0644]

index c74f41bccc9cfa810fdf99f34aefe0b38807baf7..43585d4db4fe3dd37ca7721b0bddbdf19ec9c4db 100644 (file)
@@ -32,13 +32,13 @@ 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.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
 import org.sonar.api.config.Settings;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.FileLinesContext;
 import org.sonar.api.measures.FileLinesContextFactory;
 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;
@@ -243,7 +243,7 @@ public class JavaCpdEngine extends CpdEngine {
         }
       }
     }
-    builder.done();
+    context.saveDuplications(inputFile, builder.build());
   }
 
 }
index 5a0e6f658152973d2df98b7b41a47e3264f4a107..dcada895aabfa8b617e1b2e47f49e749e4eef490 100644 (file)
@@ -28,12 +28,12 @@ import org.mockito.Mockito;
 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.DuplicationGroup;
+import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
 import org.sonar.api.batch.sensor.measure.internal.DefaultMeasureBuilder;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.FileLinesContext;
 import org.sonar.api.measures.FileLinesContextFactory;
-import org.sonar.batch.duplication.DefaultDuplicationBuilder;
-import org.sonar.batch.duplication.DuplicationCache;
 import org.sonar.duplications.index.CloneGroup;
 import org.sonar.duplications.index.ClonePart;
 
@@ -64,10 +64,10 @@ public class JavaCpdEngineTest {
   public void before() throws IOException {
     when(context.measureBuilder()).thenReturn(new DefaultMeasureBuilder());
     inputFile = new DeprecatedDefaultInputFile("src/main/java/Foo.java");
-    DuplicationCache duplicationCache = mock(DuplicationCache.class);
-    duplicationBuilder = spy(new DefaultDuplicationBuilder(inputFile, duplicationCache));
+    duplicationBuilder = spy(new DefaultDuplicationBuilder(inputFile));
     when(context.duplicationBuilder(any(InputFile.class))).thenReturn(duplicationBuilder);
     inputFile.setFile(temp.newFile("Foo.java"));
+    inputFile.setKey("key1");
     contextFactory = mock(FileLinesContextFactory.class);
     linesContext = mock(FileLinesContext.class);
     when(contextFactory.createFor(inputFile)).thenReturn(linesContext);
@@ -95,7 +95,7 @@ public class JavaCpdEngineTest {
     InOrder inOrder = Mockito.inOrder(duplicationBuilder);
     inOrder.verify(duplicationBuilder).originBlock(5, 204);
     inOrder.verify(duplicationBuilder).isDuplicatedBy("key2", 15, 214);
-    inOrder.verify(duplicationBuilder).done();
+    inOrder.verify(duplicationBuilder).build();
 
     verify(linesContext).setIntValue(CoreMetrics.DUPLICATION_LINES_DATA_KEY, 1, 0);
     verify(linesContext).setIntValue(CoreMetrics.DUPLICATION_LINES_DATA_KEY, 4, 0);
@@ -116,7 +116,7 @@ public class JavaCpdEngineTest {
     InOrder inOrder = Mockito.inOrder(duplicationBuilder);
     inOrder.verify(duplicationBuilder).originBlock(5, 204);
     inOrder.verify(duplicationBuilder).isDuplicatedBy("key1", 215, 414);
-    inOrder.verify(duplicationBuilder).done();
+    inOrder.verify(duplicationBuilder).build();
   }
 
   @Test
@@ -132,7 +132,13 @@ public class JavaCpdEngineTest {
     inOrder.verify(duplicationBuilder).originBlock(5, 204);
     inOrder.verify(duplicationBuilder).isDuplicatedBy("key2", 15, 214);
     inOrder.verify(duplicationBuilder).isDuplicatedBy("key3", 25, 224);
-    inOrder.verify(duplicationBuilder).done();
+    inOrder.verify(duplicationBuilder).build();
+
+    verify(context).saveDuplications(inputFile, Arrays.asList(
+      new DuplicationGroup(new DuplicationGroup.Block("key1", 5, 200))
+        .addDuplicate(new DuplicationGroup.Block("key2", 15, 200))
+        .addDuplicate(new DuplicationGroup.Block("key3", 25, 200))
+      ));
   }
 
   @Test
@@ -151,7 +157,7 @@ public class JavaCpdEngineTest {
     inOrder.verify(duplicationBuilder).isDuplicatedBy("key2", 15, 214);
     inOrder.verify(duplicationBuilder).originBlock(15, 214);
     inOrder.verify(duplicationBuilder).isDuplicatedBy("key3", 15, 214);
-    inOrder.verify(duplicationBuilder).done();
+    inOrder.verify(duplicationBuilder).build();
   }
 
   private CloneGroup newCloneGroup(ClonePart... parts) {
index 71b5288c6b38ca9fef51d6d882f5b2b1261da931..1344179bfb21b7692e822233b4c95cf018dace30 100644 (file)
@@ -28,7 +28,7 @@ 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.api.batch.sensor.duplication.DuplicationGroup;
 import org.sonar.batch.mediumtest.BatchMediumTester;
 import org.sonar.batch.mediumtest.BatchMediumTester.TaskResult;
 import org.sonar.plugins.cpd.CpdPlugin;
index 1ee33e560ef0fed1fa6d0906c5a3c4ab45e29fbe..1098625783f1a83c0688a699b6c7bdf7a3a68df4 100644 (file)
@@ -28,7 +28,7 @@ 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.api.batch.sensor.duplication.DuplicationTokenBuilder;
 import org.sonar.xoo.Xoo;
 
 import java.io.File;
@@ -41,7 +41,7 @@ import java.util.List;
 public class XooTokenizerSensor implements Sensor {
 
   private void computeTokens(InputFile inputFile, SensorContext context) {
-    TokenBuilder tokenBuilder = context.tokenBuilder(inputFile);
+    DuplicationTokenBuilder tokenBuilder = context.duplicationTokenBuilder(inputFile);
     File ioFile = inputFile.file();
     int lineId = 0;
     try {
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
deleted file mode 100644 (file)
index d6dd180..0000000
+++ /dev/null
@@ -1,74 +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.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);
-  }
-}
index 6c7836d9e39a010599db2cdf7620cb8d8da61d1c..b4af69174404a03b8bd0bf795d7915b6be478ac7 100644 (file)
@@ -25,7 +25,7 @@ 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.api.batch.sensor.duplication.DuplicationTokenBuilder;
 import org.sonar.duplications.block.Block;
 import org.sonar.duplications.block.FileBlocks;
 import org.sonar.duplications.internal.pmd.PmdBlockChunker;
@@ -35,7 +35,7 @@ import org.sonar.duplications.internal.pmd.TokensLine;
 import java.util.ArrayList;
 import java.util.List;
 
-public class DefaultTokenBuilder implements TokenBuilder {
+public class DefaultTokenBuilder implements DuplicationTokenBuilder {
 
   private static final Logger LOG = LoggerFactory.getLogger(DefaultTokenBuilder.class);
 
index ab1dad470093f477434dc2268546c9453ffaec27..26c857a5d23d6be66bd3376fdfbde22efbcde95c 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.batch.duplication;
 import com.persistit.Value;
 import com.persistit.encoding.CoderContext;
 import com.persistit.encoding.ValueCoder;
+import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
 
 class DuplicationBlockValueCoder implements ValueCoder {
 
index e0265215efcaaa65f664fb0be59de7f906cf0fed..573ac7a7a1381be451d84cdc586cb245b12e7419 100644 (file)
 package org.sonar.batch.duplication;
 
 import org.sonar.api.BatchComponent;
+import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
 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;
+import java.util.List;
 
 /**
  * Cache of duplication blocks. This cache is shared amongst all project modules.
  */
 public class DuplicationCache implements BatchComponent {
 
-  private final Cache<ArrayList<DuplicationGroup>> cache;
+  private final Cache<List<DuplicationGroup>> cache;
 
   public DuplicationCache(Caches caches) {
     caches.registerValueCoder(DuplicationGroup.class, new DuplicationGroupValueCoder());
@@ -41,16 +42,16 @@ public class DuplicationCache implements BatchComponent {
     cache = caches.createCache("duplications");
   }
 
-  public Iterable<Entry<ArrayList<DuplicationGroup>>> entries() {
+  public Iterable<Entry<List<DuplicationGroup>>> entries() {
     return cache.entries();
   }
 
   @CheckForNull
-  public ArrayList<DuplicationGroup> byComponent(String effectiveKey) {
+  public List<DuplicationGroup> byComponent(String effectiveKey) {
     return cache.get(effectiveKey);
   }
 
-  public DuplicationCache put(String effectiveKey, ArrayList<DuplicationGroup> blocks) {
+  public DuplicationCache put(String effectiveKey, List<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
deleted file mode 100644 (file)
index dc9a7aa..0000000
+++ /dev/null
@@ -1,76 +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.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;
-  }
-
-}
index b813d7283545c2a55418d3626e71659fdeb1d7b4..5b6ed6d840159330a33167984f3db965dcad6a11 100644 (file)
@@ -22,7 +22,8 @@ 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 org.sonar.api.batch.sensor.duplication.DuplicationGroup;
+import org.sonar.api.batch.sensor.duplication.DuplicationGroup.Block;
 
 import java.util.ArrayList;
 
index 9ac3085880c381ff00985007bc771dd1f0951b49..9461d46dfd7783209305b695c21907c8589c2e18 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.batch.index;
 
 import org.apache.commons.lang.StringEscapeUtils;
+import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
 import org.sonar.api.database.model.MeasureMapper;
 import org.sonar.api.database.model.MeasureModel;
 import org.sonar.api.database.model.Snapshot;
@@ -29,12 +30,11 @@ import org.sonar.api.measures.PersistenceMode;
 import org.sonar.api.resources.Resource;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.batch.duplication.DuplicationCache;
-import org.sonar.batch.duplication.DuplicationGroup;
 import org.sonar.batch.index.Cache.Entry;
 import org.sonar.core.persistence.DbSession;
 import org.sonar.core.persistence.MyBatis;
 
-import java.util.ArrayList;
+import java.util.List;
 
 public final class DuplicationPersister implements ScanPersister {
   private final MyBatis mybatis;
@@ -62,7 +62,7 @@ public final class DuplicationPersister implements ScanPersister {
     try {
       MeasureMapper mapper = session.getMapper(MeasureMapper.class);
       org.sonar.api.measures.Metric duplicationMetricWithId = metricFinder.findByKey(CoreMetrics.DUPLICATIONS_DATA_KEY);
-      for (Entry<ArrayList<DuplicationGroup>> entry : duplicationCache.entries()) {
+      for (Entry<List<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);
index 58cc4f461afdf452c91c9c56297588cc325fac26..a42ca1ae1b073e6e9a1141b29e571eba72183b19 100644 (file)
@@ -29,6 +29,7 @@ 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.DefaultInputFile;
+import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
 import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
 import org.sonar.api.batch.sensor.issue.Issue;
 import org.sonar.api.batch.sensor.measure.Measure;
@@ -42,7 +43,6 @@ 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;
@@ -264,7 +264,7 @@ public class BatchMediumTester {
       }
 
       DuplicationCache duplicationCache = container.getComponentByType(DuplicationCache.class);
-      for (Entry<ArrayList<DuplicationGroup>> entry : duplicationCache.entries()) {
+      for (Entry<List<DuplicationGroup>> entry : duplicationCache.entries()) {
         String effectiveKey = entry.key()[0].toString();
         duplications.put(effectiveKey, entry.value());
       }
index d285a6c8646a253a2ced78f7f3cb2736cb02f179..1cf87b1d6d3e488039856615c05a3ef024bc1636 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.batch.scan;
 
+import com.google.common.base.Preconditions;
 import org.sonar.api.batch.fs.FileSystem;
 import org.sonar.api.batch.fs.InputDir;
 import org.sonar.api.batch.fs.InputFile;
@@ -28,7 +29,9 @@ 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.duplication.DuplicationGroup;
+import org.sonar.api.batch.sensor.duplication.DuplicationTokenBuilder;
+import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
 import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
 import org.sonar.api.batch.sensor.issue.Issue;
 import org.sonar.api.batch.sensor.issue.IssueBuilder;
@@ -52,7 +55,6 @@ 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;
@@ -61,6 +63,7 @@ import org.sonar.batch.symbol.DefaultSymbolTableBuilder;
 import org.sonar.duplications.internal.pmd.PmdBlockChunker;
 
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * Implements {@link SensorContext} but forward everything to {@link org.sonar.api.batch.SensorContext} for backward compatibility.
@@ -267,14 +270,24 @@ public class SensorContextAdaptor implements SensorContext {
   }
 
   @Override
-  public TokenBuilder tokenBuilder(InputFile inputFile) {
+  public DuplicationTokenBuilder duplicationTokenBuilder(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);
+    return new DefaultDuplicationBuilder(inputFile);
+  }
+
+  @Override
+  public void saveDuplications(InputFile inputFile, List<DuplicationGroup> duplications) {
+    Preconditions.checkState(duplications.size() > 0, "Empty duplications");
+    String effectiveKey = ((DefaultInputFile) inputFile).key();
+    for (DuplicationGroup duplicationGroup : duplications) {
+      Preconditions.checkState(effectiveKey.equals(duplicationGroup.originBlock().resourceKey()), "Invalid duplication group");
+    }
+    duplicationCache.put(effectiveKey, duplications);
   }
 
   private int getBlockSize(String languageKey) {
index 73d6429bfcd769e2b1d55978c6790800367e83b0..afd21a8d358c235cd139e61f106c5ba32a2099b4 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.batch.scan2;
 
+import com.google.common.base.Preconditions;
 import com.google.common.base.Strings;
 import org.sonar.api.batch.bootstrap.ProjectDefinition;
 import org.sonar.api.batch.fs.FileSystem;
@@ -29,7 +30,9 @@ 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.duplication.DuplicationGroup;
+import org.sonar.api.batch.sensor.duplication.DuplicationTokenBuilder;
+import org.sonar.api.batch.sensor.duplication.internal.DefaultDuplicationBuilder;
 import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
 import org.sonar.api.batch.sensor.issue.Issue;
 import org.sonar.api.batch.sensor.issue.IssueBuilder;
@@ -44,7 +47,6 @@ 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;
@@ -56,6 +58,7 @@ import org.sonar.core.component.ComponentKeys;
 import org.sonar.duplications.internal.pmd.PmdBlockChunker;
 
 import java.io.Serializable;
+import java.util.List;
 
 public class DefaultSensorContext implements SensorContext {
 
@@ -188,7 +191,7 @@ public class DefaultSensorContext implements SensorContext {
   }
 
   @Override
-  public TokenBuilder tokenBuilder(InputFile inputFile) {
+  public DuplicationTokenBuilder duplicationTokenBuilder(InputFile inputFile) {
     PmdBlockChunker blockChunker = new PmdBlockChunker(getBlockSize(inputFile.language()));
 
     return new DefaultTokenBuilder(inputFile, blockCache, blockChunker);
@@ -196,7 +199,17 @@ public class DefaultSensorContext implements SensorContext {
 
   @Override
   public DuplicationBuilder duplicationBuilder(InputFile inputFile) {
-    return new DefaultDuplicationBuilder(inputFile, duplicationCache);
+    return new DefaultDuplicationBuilder(inputFile);
+  }
+
+  @Override
+  public void saveDuplications(InputFile inputFile, List<DuplicationGroup> duplications) {
+    Preconditions.checkState(duplications.size() > 0, "Empty duplications");
+    String effectiveKey = ((DefaultInputFile) inputFile).key();
+    for (DuplicationGroup duplicationGroup : duplications) {
+      Preconditions.checkState(effectiveKey.equals(duplicationGroup.originBlock().resourceKey()), "Invalid duplication group");
+    }
+    duplicationCache.put(effectiveKey, duplications);
   }
 
   private int getBlockSize(String languageKey) {
index ade701fce45c26b9e9c23b2ad3b6b3ecc65408fa..6317093f00a4d55b97dec4a4efe8610919f9590d 100644 (file)
@@ -25,11 +25,13 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
 import org.sonar.batch.index.Caches;
 import org.sonar.batch.index.CachesTest;
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 import static org.fest.assertions.Assertions.assertThat;
 
@@ -74,7 +76,7 @@ public class DuplicationCacheTest {
 
     assertThat(cache.entries()).hasSize(1);
 
-    ArrayList<DuplicationGroup> entry = cache.byComponent("foo");
+    List<DuplicationGroup> entry = cache.byComponent("foo");
     assertThat(entry.get(0).originBlock().resourceKey()).isEqualTo("foo");
 
   }
index 1860732e80d7c24ecd8c8cf4b5518bd1a354bc97..644d7400999f766e8d5a4b1b912bd66ddc2aae34 100644 (file)
@@ -22,17 +22,17 @@ package org.sonar.batch.index;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.sonar.api.batch.sensor.duplication.DuplicationGroup;
 import org.sonar.api.database.model.Snapshot;
 import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.measures.MetricFinder;
 import org.sonar.api.resources.File;
 import org.sonar.api.rules.RuleFinder;
 import org.sonar.batch.duplication.DuplicationCache;
-import org.sonar.batch.duplication.DuplicationGroup;
 import org.sonar.core.persistence.AbstractDaoTestCase;
 
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -77,7 +77,7 @@ public class DuplicationPersisterTest extends AbstractDaoTestCase {
       .addDuplicate(new DuplicationGroup.Block("foo:org/foo/Foo.java", 5, 9));
 
     when(duplicationCache.entries()).thenReturn(
-      Arrays.<Cache.Entry<ArrayList<DuplicationGroup>>>asList(new Cache.Entry(new String[] {"foo:org/foo/Bar.java"}, Arrays.asList(group))));
+      Arrays.<Cache.Entry<List<DuplicationGroup>>>asList(new Cache.Entry(new String[] {"foo:org/foo/Bar.java"}, Arrays.asList(group))));
 
     duplicationPersister.persist();
 
index 2e472cabf17af93e5ad3bb6a5745c67d9157ac66..39888e030c39dc46bf344a1b8e79d597b696503f 100644 (file)
@@ -108,10 +108,8 @@ public class DefaultInputFile implements InputFile, Serializable {
   }
 
   /**
-   * Component key. It's marked as nullable just for the unit tests that
-   * do not previously call {@link #setKey(String)}.
+   * Component key.
    */
-  @CheckForNull
   public String key() {
     return key;
   }
index 647cab3f3f90fd59bc012081c11bf869f87cf3e2..d572d974af4b5c9644cb0d8bc947d752d8e67608 100644 (file)
@@ -25,7 +25,8 @@ 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.duplication.DuplicationGroup;
+import org.sonar.api.batch.sensor.duplication.DuplicationTokenBuilder;
 import org.sonar.api.batch.sensor.highlighting.HighlightingBuilder;
 import org.sonar.api.batch.sensor.issue.Issue;
 import org.sonar.api.batch.sensor.issue.IssueBuilder;
@@ -37,6 +38,7 @@ import org.sonar.api.config.Settings;
 import javax.annotation.CheckForNull;
 
 import java.io.Serializable;
+import java.util.List;
 
 /**
  * @since 4.4
@@ -92,6 +94,7 @@ public interface SensorContext {
 
   /**
    * Add a measure. Use {@link #measureBuilder()} to create the new measure.
+   * A measure for a given metric can only be saved once for the same resource.
    */
   void addMeasure(Measure<?> measure);
 
@@ -134,13 +137,19 @@ public interface SensorContext {
    * Builder to define tokens in a file. Tokens are used to compute duplication by the core.
    * @since 4.5
    */
-  TokenBuilder tokenBuilder(InputFile inputFile);
+  DuplicationTokenBuilder duplicationTokenBuilder(InputFile inputFile);
 
   /**
    * Builder to manually define duplications in a file. When duplication are manually computed then
-   * no need to use {@link #tokenBuilder(InputFile)}.
+   * no need to use {@link #duplicationTokenBuilder(InputFile)}.
    * @since 4.5
    */
   DuplicationBuilder duplicationBuilder(InputFile inputFile);
 
+  /**
+   * Register all duplications of an {@link InputFile}. Use {@link #duplicationBuilder(InputFile)} to create
+   * list of duplications.
+   */
+  void saveDuplications(InputFile inputFile, List<DuplicationGroup> duplications);
+
 }
index 2f37220b1a3f084fc22b2f9e09a6f87d261dbcb7..aa27ff41b284a74407ccc61314fd288ae1f80766 100644 (file)
@@ -21,18 +21,39 @@ package org.sonar.api.batch.sensor.duplication;
 
 import org.sonar.api.batch.fs.InputFile;
 
+import java.util.List;
+
 /**
  * This builder is used to declare duplications on files of the project.
+ * Usage:
+ * <code><pre>
+ * DuplicationBuilder builder = context.duplicationBuilder(inputFile);
+ *   .originBlock(2, 10)
+ *   .isDuplicatedBy(inputFile, 14, 22)
+ *   .isDuplicatedBy(anotherInputFile, 3, 11)
+ *   // Start another duplication
+ *   .originBlock(45, 50)
+ *   .isDuplicatedBy(yetAnotherInputFile, 10, 15);
+ *   context.saveDuplications(inputFile, builder.build());
+ * </pre></code>
  * @since 4.5
  */
 public interface DuplicationBuilder {
 
+  /**
+   * Declare duplication origin block. Then call {@link #isDuplicatedBy(InputFile, int, int)} to reference all duplicates of this block.
+   * Then call again {@link #originBlock(int, int)} to declare another duplication.
+   */
   DuplicationBuilder originBlock(int startLine, int endLine);
 
+  /**
+   * Declare duplicate block of the previously declared {@link #originBlock(int, int)}.
+   * @param sameOrOtherFile duplicate can be in the same file or in another file.
+   */
   DuplicationBuilder isDuplicatedBy(InputFile sameOrOtherFile, int startLine, int endLine);
 
   /**
-   * Call this method only once when your are done with defining all duplicates of origin block.
+   * Call this method when you have declared all duplications of the file.
    */
-  void done();
+  List<DuplicationGroup> build();
 }
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationGroup.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationGroup.java
new file mode 100644 (file)
index 0000000..9061e91
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * 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.apache.commons.lang.builder.EqualsBuilder;
+import org.apache.commons.lang.builder.ToStringBuilder;
+import org.apache.commons.lang.builder.ToStringStyle;
+import org.sonar.api.batch.sensor.SensorContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A {@link DuplicationGroup} is a list of duplicated {@link Block}s. One block
+ * is considered as the original code and all others are duplicates.
+ * Use {@link SensorContext#duplicationBuilder(org.sonar.api.batch.fs.InputFile)} and
+ * {@link SensorContext#saveDuplications(org.sonar.api.batch.fs.InputFile, List)}.
+ * @since 4.5
+ */
+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;
+    }
+
+    // Just for unit tests
+    @Override
+    public boolean equals(Object obj) {
+      if (obj == null) {
+        return false;
+      }
+      if (obj == this) {
+        return true;
+      }
+      if (obj.getClass() != getClass()) {
+        return false;
+      }
+      Block rhs = (Block) obj;
+      return new EqualsBuilder()
+        .append(resourceKey, rhs.resourceKey)
+        .append(startLine, rhs.startLine)
+        .append(length, rhs.length).isEquals();
+    }
+
+    @Override
+    public String toString() {
+      return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).
+        append("resourceKey", resourceKey).
+        append("startLine", startLine).
+        append("length", length).
+        toString();
+    }
+  }
+
+  private final Block originBlock;
+  private List<Block> duplicates = new ArrayList<DuplicationGroup.Block>();
+
+  /**
+   * For unit test and internal use only.
+   */
+  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;
+  }
+
+  // Just for unit tests
+  @Override
+  public boolean equals(Object obj) {
+    if (obj == null) {
+      return false;
+    }
+    if (obj == this) {
+      return true;
+    }
+    if (obj.getClass() != getClass()) {
+      return false;
+    }
+    DuplicationGroup rhs = (DuplicationGroup) obj;
+    EqualsBuilder equalsBuilder = new EqualsBuilder()
+      .append(originBlock, rhs.originBlock)
+      .append(duplicates.size(), rhs.duplicates.size());
+    for (int i = 0; i < duplicates.size(); i++) {
+      equalsBuilder.append(duplicates.get(i), rhs.duplicates.get(i));
+    }
+    return equalsBuilder.isEquals();
+  }
+
+  @Override
+  public String toString() {
+    return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).
+      append("origin", originBlock).
+      append("duplicates", duplicates, true).
+      toString();
+  }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationTokenBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationTokenBuilder.java
new file mode 100644 (file)
index 0000000..6dfd437
--- /dev/null
@@ -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.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.
+ * Example:
+ * <code><pre>
+ * DuplicationTokenBuilder tokenBuilder = context.duplicationTokenBuilder(inputFile)
+ *  .addToken(1, "public")
+ *  .addToken(1, "class")
+ *  .addToken(1, "Foo")
+ *  .addToken(1, "{")
+ *  .addToken(2, "}")
+ *  .done();
+ * </pre></code>
+ * @since 4.5
+ */
+public interface DuplicationTokenBuilder {
+
+  /**
+   * 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.
+   */
+  DuplicationTokenBuilder 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/TokenBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java
deleted file mode 100644 (file)
index f4de9ff..0000000
+++ /dev/null
@@ -1,40 +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.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/internal/DefaultDuplicationBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/DefaultDuplicationBuilder.java
new file mode 100644 (file)
index 0000000..77392b7
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.internal;
+
+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 org.sonar.api.batch.sensor.duplication.DuplicationGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DefaultDuplicationBuilder implements DuplicationBuilder {
+
+  private final InputFile inputFile;
+  private DuplicationGroup current = null;
+  private List<DuplicationGroup> duplications;
+
+  public DefaultDuplicationBuilder(InputFile inputFile) {
+    this.inputFile = inputFile;
+    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 List<DuplicationGroup> build() {
+    Preconditions.checkNotNull(current, "Call originBlock() first");
+    duplications.add(current);
+    List<DuplicationGroup> result = duplications;
+    reset();
+    return result;
+  }
+
+  private void reset() {
+    duplications = new ArrayList<DuplicationGroup>();
+    current = null;
+  }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/package-info.java
new file mode 100644 (file)
index 0000000..229366c
--- /dev/null
@@ -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.internal;