From 8d91875b28c2ef7c274781a9507edf512716a372 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Fri, 1 Aug 2014 11:26:22 +0200 Subject: [PATCH] SONAR-5389 Improve duplication API --- .../org/sonar/plugins/cpd/JavaCpdEngine.java | 4 +- .../sonar/plugins/cpd/JavaCpdEngineTest.java | 22 ++- .../plugins/cpd/medium/CpdMediumTest.java | 2 +- .../sonar/xoo/lang/XooTokenizerSensor.java | 4 +- .../duplication/DefaultTokenBuilder.java | 4 +- .../DuplicationBlockValueCoder.java | 1 + .../batch/duplication/DuplicationCache.java | 11 +- .../batch/duplication/DuplicationGroup.java | 76 --------- .../DuplicationGroupValueCoder.java | 3 +- .../batch/index/DuplicationPersister.java | 6 +- .../batch/mediumtest/BatchMediumTester.java | 4 +- .../batch/scan/SensorContextAdaptor.java | 21 ++- .../batch/scan2/DefaultSensorContext.java | 21 ++- .../duplication/DuplicationCacheTest.java | 4 +- .../batch/index/DuplicationPersisterTest.java | 6 +- .../batch/fs/internal/DefaultInputFile.java | 4 +- .../sonar/api/batch/sensor/SensorContext.java | 15 +- .../duplication/DuplicationBuilder.java | 25 ++- .../sensor/duplication/DuplicationGroup.java | 148 ++++++++++++++++++ ...lder.java => DuplicationTokenBuilder.java} | 15 +- .../internal}/DefaultDuplicationBuilder.java | 23 +-- .../duplication/internal/package-info.java | 21 +++ 22 files changed, 307 insertions(+), 133 deletions(-) delete mode 100644 sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationGroup.java rename sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/{TokenBuilder.java => DuplicationTokenBuilder.java} (78%) rename {sonar-batch/src/main/java/org/sonar/batch/duplication => sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal}/DefaultDuplicationBuilder.java (83%) create mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/package-info.java diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java index c74f41bccc9..43585d4db4f 100644 --- a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java +++ b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/JavaCpdEngine.java @@ -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()); } } 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 index 5a0e6f65815..dcada895aab 100644 --- 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 @@ -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) { 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 index 71b5288c6b3..1344179bfb2 100644 --- 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 @@ -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; 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 index 1ee33e560ef..1098625783f 100644 --- 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 @@ -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/DefaultTokenBuilder.java b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java index 6c7836d9e39..b4af6917440 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultTokenBuilder.java @@ -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); 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 index ab1dad47009..26c857a5d23 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationBlockValueCoder.java @@ -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 { 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 index e0265215efc..573ac7a7a13 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationCache.java @@ -20,20 +20,21 @@ 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> cache; + private final Cache> 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>> entries() { + public Iterable>> entries() { return cache.entries(); } @CheckForNull - public ArrayList byComponent(String effectiveKey) { + public List byComponent(String effectiveKey) { return cache.get(effectiveKey); } - public DuplicationCache put(String effectiveKey, ArrayList blocks) { + public DuplicationCache put(String effectiveKey, List 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 index dc9a7aa2602..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroup.java +++ /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 duplicates = new ArrayList(); - - public DuplicationGroup(Block originBlock) { - this.originBlock = originBlock; - } - - public void setDuplicates(List duplicates) { - this.duplicates = duplicates; - } - - public DuplicationGroup addDuplicate(Block anotherBlock) { - this.duplicates.add(anotherBlock); - return this; - } - - public Block originBlock() { - return originBlock; - } - - public List 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 index b813d728354..5b6ed6d8401 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java +++ b/sonar-batch/src/main/java/org/sonar/batch/duplication/DuplicationGroupValueCoder.java @@ -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; diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DuplicationPersister.java b/sonar-batch/src/main/java/org/sonar/batch/index/DuplicationPersister.java index 9ac3085880c..9461d46dfd7 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DuplicationPersister.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DuplicationPersister.java @@ -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> entry : duplicationCache.entries()) { + for (Entry> 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); 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 58cc4f461af..a42ca1ae1b0 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 @@ -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> entry : duplicationCache.entries()) { + for (Entry> entry : duplicationCache.entries()) { String effectiveKey = entry.key()[0].toString(); duplications.put(effectiveKey, entry.value()); } 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 d285a6c8646..1cf87b1d6d3 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 @@ -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 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) { 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 73d6429bfcd..afd21a8d358 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 @@ -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 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) { 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 index ade701fce45..6317093f00a 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/duplication/DuplicationCacheTest.java @@ -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 entry = cache.byComponent("foo"); + List entry = cache.byComponent("foo"); assertThat(entry.get(0).originBlock().resourceKey()).isEqualTo("foo"); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/index/DuplicationPersisterTest.java b/sonar-batch/src/test/java/org/sonar/batch/index/DuplicationPersisterTest.java index 1860732e80d..644d7400999 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/index/DuplicationPersisterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/index/DuplicationPersisterTest.java @@ -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.>>asList(new Cache.Entry(new String[] {"foo:org/foo/Bar.java"}, Arrays.asList(group)))); + Arrays.>>asList(new Cache.Entry(new String[] {"foo:org/foo/Bar.java"}, Arrays.asList(group)))); duplicationPersister.persist(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java index 2e472cabf17..39888e030c3 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/fs/internal/DefaultInputFile.java @@ -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; } 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 647cab3f3f9..d572d974af4 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 @@ -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 duplications); + } 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 index 2f37220b1a3..aa27ff41b28 100644 --- 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 @@ -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: + *
+ * 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());
+ * 
* @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 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 index 00000000000..9061e9165e3 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationGroup.java @@ -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 duplicates = new ArrayList(); + + /** + * For unit test and internal use only. + */ + public DuplicationGroup(Block originBlock) { + this.originBlock = originBlock; + } + + public void setDuplicates(List duplicates) { + this.duplicates = duplicates; + } + + public DuplicationGroup addDuplicate(Block anotherBlock) { + this.duplicates.add(anotherBlock); + return this; + } + + public Block originBlock() { + return originBlock; + } + + public List 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/TokenBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationTokenBuilder.java similarity index 78% rename from sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/TokenBuilder.java rename to sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/DuplicationTokenBuilder.java index f4de9fffc8c..6dfd437c552 100644 --- 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/DuplicationTokenBuilder.java @@ -19,19 +19,30 @@ */ 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: + *
+ * DuplicationTokenBuilder tokenBuilder = context.duplicationTokenBuilder(inputFile)
+ *  .addToken(1, "public")
+ *  .addToken(1, "class")
+ *  .addToken(1, "Foo")
+ *  .addToken(1, "{")
+ *  .addToken(2, "}")
+ *  .done();
+ * 
* @since 4.5 */ -public interface TokenBuilder { +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. */ - TokenBuilder addToken(int line, String image); + DuplicationTokenBuilder addToken(int line, String image); /** * Call this method only once when your are done with defining tokens of the file. diff --git a/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/DefaultDuplicationBuilder.java similarity index 83% rename from sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java rename to sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/DefaultDuplicationBuilder.java index d6dd18026a2..77392b735b6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/duplication/DefaultDuplicationBuilder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/DefaultDuplicationBuilder.java @@ -17,26 +17,25 @@ * 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; +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 final DuplicationCache duplicationCache; - private boolean done = false; private DuplicationGroup current = null; - private ArrayList duplications; + private List duplications; - public DefaultDuplicationBuilder(InputFile inputFile, DuplicationCache duplicationCache) { + public DefaultDuplicationBuilder(InputFile inputFile) { this.inputFile = inputFile; - this.duplicationCache = duplicationCache; duplications = new ArrayList(); } @@ -65,10 +64,16 @@ public class DefaultDuplicationBuilder implements DuplicationBuilder { } @Override - public void done() { - Preconditions.checkState(!done, "done() already called"); + public List build() { Preconditions.checkNotNull(current, "Call originBlock() first"); duplications.add(current); - duplicationCache.put(((DefaultInputFile) inputFile).key(), duplications); + List result = duplications; + reset(); + return result; + } + + private void reset() { + duplications = new ArrayList(); + 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 index 00000000000..229366c3e9d --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/duplication/internal/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.internal; -- 2.39.5