From: Julien HENRY Date: Mon, 21 Mar 2016 11:19:23 +0000 (+0100) Subject: SONAR-7389 Support CPD exclusions in new API X-Git-Tag: 5.5-M11~54 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=26a0ddd444595027c8fdf1e143173cff69828316;p=sonarqube.git SONAR-7389 Support CPD exclusions in new API --- diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java index a177a554fa1..954f2869671 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokens.java @@ -22,16 +22,20 @@ package org.sonar.api.batch.sensor.cpd.internal; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import java.util.List; +import org.sonar.api.CoreProperties; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.TextRange; import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.PathPattern; import org.sonar.api.batch.sensor.cpd.NewCpdTokens; import org.sonar.api.batch.sensor.internal.DefaultStorable; import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.config.Settings; import org.sonar.duplications.internal.pmd.TokensLine; public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens { + private final Settings settings; private final ImmutableList.Builder result = ImmutableList.builder(); private DefaultInputFile inputFile; private int startLine = Integer.MIN_VALUE; @@ -39,18 +43,39 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens { private int currentIndex = 0; private StringBuilder sb = new StringBuilder(); private TextRange lastRange; + private boolean excluded; - public DefaultCpdTokens(SensorStorage storage) { + public DefaultCpdTokens(Settings settings, SensorStorage storage) { super(storage); + this.settings = settings; } @Override public DefaultCpdTokens onFile(InputFile inputFile) { Preconditions.checkNotNull(inputFile, "file can't be null"); this.inputFile = (DefaultInputFile) inputFile; + String language = inputFile.language(); + if (language != null && isSkipped(language)) { + this.excluded = true; + } else { + String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); + for (PathPattern cpdExclusion : PathPattern.create(cpdExclusions)) { + if (cpdExclusion.match(inputFile)) { + this.excluded = true; + } + } + } return this; } + boolean isSkipped(String language) { + String key = "sonar.cpd." + language + ".skip"; + if (settings.hasKey(key)) { + return settings.getBoolean(key); + } + return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY); + } + public InputFile inputFile() { return inputFile; } @@ -60,6 +85,9 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens { Preconditions.checkNotNull(range, "Range should not be null"); Preconditions.checkNotNull(image, "Image should not be null"); Preconditions.checkState(inputFile != null, "Call onFile() first"); + if (excluded) { + return this; + } Preconditions.checkState(lastRange == null || lastRange.end().compareTo(range.start()) <= 0, "Tokens of file %s should be provided in order.\nPrevious token: %s\nLast token: %s", inputFile, lastRange, range); @@ -92,6 +120,9 @@ public class DefaultCpdTokens extends DefaultStorable implements NewCpdTokens { @Override protected void doSave() { Preconditions.checkState(inputFile != null, "Call onFile() first"); + if (excluded) { + return; + } addNewTokensLine(result, startIndex, currentIndex, startLine, sb); storage.store(this); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java index 82d42584141..24a06a241bd 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java @@ -201,7 +201,7 @@ public class SensorContextTester implements SensorContext { @Override public NewCpdTokens newCpdTokens() { - return new DefaultCpdTokens(sensorStorage); + return new DefaultCpdTokens(settings, sensorStorage); } public List highlightingTypeAt(String componentKey, int line, int lineOffset) { diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokensTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokensTest.java index 5f09cf064d9..94494b95923 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokensTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/cpd/internal/DefaultCpdTokensTest.java @@ -22,24 +22,27 @@ package org.sonar.api.batch.sensor.cpd.internal; import org.junit.Test; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.sensor.internal.SensorStorage; +import org.sonar.api.config.Settings; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; public class DefaultCpdTokensTest { private static final DefaultInputFile INPUT_FILE = new DefaultInputFile("foo", "src/Foo.java") .setLines(2) + .setLanguage("java") .setOriginalLineOffsets(new int[] {0, 50}) .setLastValidOffset(100); @Test public void save_no_tokens() { SensorStorage sensorStorage = mock(SensorStorage.class); - DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage) + DefaultCpdTokens tokens = new DefaultCpdTokens(new Settings(), sensorStorage) .onFile(INPUT_FILE); tokens.save(); @@ -52,7 +55,7 @@ public class DefaultCpdTokensTest { @Test public void save_one_token() { SensorStorage sensorStorage = mock(SensorStorage.class); - DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage) + DefaultCpdTokens tokens = new DefaultCpdTokens(new Settings(), sensorStorage) .onFile(INPUT_FILE) .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo"); @@ -63,10 +66,58 @@ public class DefaultCpdTokensTest { assertThat(tokens.getTokenLines()).extracting("value", "startLine", "hashCode", "startUnit", "endUnit").containsExactly(tuple("foo", 1, "foo".hashCode(), 1, 1)); } + @Test + public void handle_exclusions_by_pattern() { + SensorStorage sensorStorage = mock(SensorStorage.class); + Settings settings = new Settings(); + settings.setProperty("sonar.cpd.exclusions", "src/Foo.java,another"); + DefaultCpdTokens tokens = new DefaultCpdTokens(settings, sensorStorage) + .onFile(INPUT_FILE) + .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo"); + + tokens.save(); + + verifyZeroInteractions(sensorStorage); + + assertThat(tokens.getTokenLines()).isEmpty(); + } + + @Test + public void handle_exclusions_by_language() { + SensorStorage sensorStorage = mock(SensorStorage.class); + Settings settings = new Settings(); + settings.setProperty("sonar.cpd.java.skip", "true"); + DefaultCpdTokens tokens = new DefaultCpdTokens(settings, sensorStorage) + .onFile(INPUT_FILE) + .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo"); + + tokens.save(); + + verifyZeroInteractions(sensorStorage); + + assertThat(tokens.getTokenLines()).isEmpty(); + } + + @Test + public void handle_exclusions() { + SensorStorage sensorStorage = mock(SensorStorage.class); + Settings settings = new Settings(); + settings.setProperty("sonar.cpd.skip", "true"); + DefaultCpdTokens tokens = new DefaultCpdTokens(settings, sensorStorage) + .onFile(INPUT_FILE) + .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo"); + + tokens.save(); + + verifyZeroInteractions(sensorStorage); + + assertThat(tokens.getTokenLines()).isEmpty(); + } + @Test public void save_many_tokens() { SensorStorage sensorStorage = mock(SensorStorage.class); - DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage) + DefaultCpdTokens tokens = new DefaultCpdTokens(new Settings(), sensorStorage) .onFile(INPUT_FILE) .addToken(INPUT_FILE.newRange(1, 2, 1, 5), "foo") .addToken(INPUT_FILE.newRange(1, 6, 1, 10), "bar") @@ -87,7 +138,7 @@ public class DefaultCpdTokensTest { @Test public void basic_validation() { SensorStorage sensorStorage = mock(SensorStorage.class); - DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage); + DefaultCpdTokens tokens = new DefaultCpdTokens(new Settings(), sensorStorage); try { tokens.save(); fail("Expected exception"); @@ -117,7 +168,7 @@ public class DefaultCpdTokensTest { @Test public void validate_tokens_order() { SensorStorage sensorStorage = mock(SensorStorage.class); - DefaultCpdTokens tokens = new DefaultCpdTokens(sensorStorage) + DefaultCpdTokens tokens = new DefaultCpdTokens(new Settings(), sensorStorage) .onFile(INPUT_FILE) .addToken(INPUT_FILE.newRange(1, 6, 1, 10), "bar"); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdComponents.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdComponents.java index 30f4863783d..a204f14d9fe 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdComponents.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdComponents.java @@ -21,6 +21,10 @@ package org.sonar.batch.cpd; import com.google.common.collect.ImmutableList; import java.util.List; +import org.sonar.batch.cpd.deprecated.CpdMappings; +import org.sonar.batch.cpd.deprecated.DefaultCpdBlockIndexer; +import org.sonar.batch.cpd.deprecated.DeprecatedCpdBlockIndexerSensor; +import org.sonar.batch.cpd.deprecated.JavaCpdBlockIndexer; public final class CpdComponents { @@ -29,7 +33,7 @@ public final class CpdComponents { public static List> all() { return ImmutableList.of( - CpdSensor.class, + DeprecatedCpdBlockIndexerSensor.class, CpdMappings.class, JavaCpdBlockIndexer.class, DefaultCpdBlockIndexer.class); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdIndexer.java deleted file mode 100644 index 9ebf80c88a7..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdIndexer.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import org.slf4j.Logger; -import org.sonar.api.batch.BatchSide; - -@BatchSide -public abstract class CpdIndexer { - - abstract boolean isLanguageSupported(String language); - - abstract void index(String language); - - protected void logExclusions(String[] exclusions, Logger logger) { - if (exclusions.length > 0) { - StringBuilder message = new StringBuilder("Copy-paste detection exclusions:"); - for (String exclusion : exclusions) { - message.append("\n "); - message.append(exclusion); - } - - logger.info(message.toString()); - } - } - - @Override - public String toString() { - return getClass().getSimpleName(); - } - -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdMappings.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdMappings.java deleted file mode 100644 index 9a930d62fb5..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdMappings.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import org.sonar.api.batch.BatchSide; -import org.sonar.api.batch.CpdMapping; - -import javax.annotation.CheckForNull; - -@BatchSide -public class CpdMappings { - - 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/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdSensor.java deleted file mode 100644 index 9ea1bac9254..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/CpdSensor.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import com.google.common.annotations.VisibleForTesting; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.CoreProperties; -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; - -@Phase(name = Phase.Name.POST) -public class CpdSensor implements Sensor { - - private static final Logger LOG = LoggerFactory.getLogger(CpdSensor.class); - - private CpdIndexer sonarEngine; - private CpdIndexer sonarBridgeEngine; - private Settings settings; - private FileSystem fs; - - public CpdSensor(JavaCpdBlockIndexer sonarEngine, DefaultCpdBlockIndexer sonarBridgeEngine, Settings settings, FileSystem fs) { - this.sonarEngine = sonarEngine; - this.sonarBridgeEngine = sonarBridgeEngine; - this.settings = settings; - this.fs = fs; - } - - @Override - public void describe(SensorDescriptor descriptor) { - descriptor.name("CPD Sensor"); - } - - @VisibleForTesting - CpdIndexer getEngine(String language) { - if (sonarEngine.isLanguageSupported(language)) { - return sonarEngine; - } - return sonarBridgeEngine; - } - - @VisibleForTesting - boolean isSkipped(String language) { - String key = "sonar.cpd." + language + ".skip"; - if (settings.hasKey(key)) { - return settings.getBoolean(key); - } - return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY); - } - - @Override - public void execute(SensorContext context) { - if (settings.hasKey(CoreProperties.CPD_SKIP_PROPERTY)) { - LOG.warn("\"sonar.cpd.skip\" property is deprecated and will be removed. Please set \"sonar.cpd.exclusions=**\" instead to disable duplication mechanism."); - } - - for (String language : fs.languages()) { - if (settings.hasKey("sonar.cpd." + language + ".skip")) { - LOG - .warn("\"sonar.cpd." + language + ".skip\" property is deprecated and will be removed. Please set \"sonar.cpd.exclusions=**\" instead to disable duplication mechanism."); - } - - if (isSkipped(language)) { - LOG.info("Detection of duplicated code is skipped for {}", language); - continue; - } - - CpdIndexer engine = getEngine(language); - if (!engine.isLanguageSupported(language)) { - LOG.debug("Detection of duplicated code is not supported for {}", language); - continue; - } - LOG.info("{} is used for {}", engine, language); - engine.index(language); - } - } - -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DefaultCpdBlockIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DefaultCpdBlockIndexer.java deleted file mode 100644 index 33b2f269595..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/DefaultCpdBlockIndexer.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.CoreProperties; -import org.sonar.api.batch.CpdMapping; -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.DefaultInputFile; -import org.sonar.api.config.Settings; -import org.sonar.batch.cpd.index.SonarCpdBlockIndex; -import org.sonar.duplications.block.Block; -import org.sonar.duplications.internal.pmd.TokenizerBridge; - -public class DefaultCpdBlockIndexer extends CpdIndexer { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultCpdBlockIndexer.class); - - private final CpdMappings mappings; - private final FileSystem fs; - private final Settings settings; - private final SonarCpdBlockIndex index; - - public DefaultCpdBlockIndexer(CpdMappings mappings, FileSystem fs, Settings settings, SonarCpdBlockIndex index) { - this.mappings = mappings; - this.fs = fs; - this.settings = settings; - this.index = index; - } - - @Override - public boolean isLanguageSupported(String language) { - return true; - } - - @Override - public void index(String languageKey) { - CpdMapping mapping = mappings.getMapping(languageKey); - if (mapping == null) { - LOG.debug("No CpdMapping for language " + languageKey); - return; - } - - String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); - logExclusions(cpdExclusions, LOG); - FilePredicates p = fs.predicates(); - List sourceFiles = Lists.newArrayList(fs.inputFiles(p.and( - p.hasType(InputFile.Type.MAIN), - p.hasLanguage(languageKey), - p.doesNotMatchPathPatterns(cpdExclusions)))); - if (sourceFiles.isEmpty()) { - return; - } - - // Create index - populateIndex(languageKey, sourceFiles, mapping); - } - - private void populateIndex(String languageKey, List sourceFiles, CpdMapping mapping) { - TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey)); - for (InputFile inputFile : sourceFiles) { - if (!index.isIndexed(inputFile)) { - LOG.debug("Populating index from {}", inputFile); - String resourceEffectiveKey = ((DefaultInputFile) inputFile).key(); - List blocks = bridge.chunk(resourceEffectiveKey, inputFile.file()); - index.insert(inputFile, blocks); - } - } - } - - @VisibleForTesting - int getBlockSize(String languageKey) { - int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines"); - if (blockSize == 0) { - blockSize = getDefaultBlockSize(languageKey); - } - return blockSize; - } - - @VisibleForTesting - public static int getDefaultBlockSize(String languageKey) { - if ("cobol".equals(languageKey)) { - return 30; - } else if ("abap".equals(languageKey)) { - return 20; - } else { - return 10; - } - } - -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/JavaCpdBlockIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/JavaCpdBlockIndexer.java deleted file mode 100644 index a2dde817c74..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/JavaCpdBlockIndexer.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import com.google.common.collect.Lists; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.CoreProperties; -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.DefaultInputFile; -import org.sonar.api.config.Settings; -import org.sonar.batch.cpd.index.SonarCpdBlockIndex; -import org.sonar.duplications.block.Block; -import org.sonar.duplications.block.BlockChunker; -import org.sonar.duplications.java.JavaStatementBuilder; -import org.sonar.duplications.java.JavaTokenProducer; -import org.sonar.duplications.statement.Statement; -import org.sonar.duplications.statement.StatementChunker; -import org.sonar.duplications.token.TokenChunker; - -public class JavaCpdBlockIndexer extends CpdIndexer { - - private static final Logger LOG = LoggerFactory.getLogger(JavaCpdBlockIndexer.class); - - private static final int BLOCK_SIZE = 10; - - private final FileSystem fs; - private final Settings settings; - private final SonarCpdBlockIndex index; - - public JavaCpdBlockIndexer(FileSystem fs, Settings settings, SonarCpdBlockIndex index) { - this.fs = fs; - this.settings = settings; - this.index = index; - } - - @Override - public boolean isLanguageSupported(String language) { - return "java".equals(language); - } - - @Override - public void index(String languageKey) { - String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); - logExclusions(cpdExclusions, LOG); - FilePredicates p = fs.predicates(); - List sourceFiles = Lists.newArrayList(fs.inputFiles(p.and( - p.hasType(InputFile.Type.MAIN), - p.hasLanguage(languageKey), - p.doesNotMatchPathPatterns(cpdExclusions)))); - if (sourceFiles.isEmpty()) { - return; - } - createIndex(sourceFiles); - } - - private void createIndex(Iterable sourceFiles) { - TokenChunker tokenChunker = JavaTokenProducer.build(); - StatementChunker statementChunker = JavaStatementBuilder.build(); - BlockChunker blockChunker = new BlockChunker(BLOCK_SIZE); - - for (InputFile inputFile : sourceFiles) { - LOG.debug("Populating index from {}", inputFile); - String resourceEffectiveKey = ((DefaultInputFile) inputFile).key(); - - List statements; - - try(Reader reader = new InputStreamReader(new FileInputStream(inputFile.file()), fs.encoding())) { - statements = statementChunker.chunk(tokenChunker.chunk(reader)); - } catch (FileNotFoundException e) { - throw new IllegalStateException("Cannot find file " + inputFile.file(), e); - } catch (IOException e ) { - throw new IllegalStateException("Exception hnadling file: " + inputFile.file(), e); - } - - List blocks = blockChunker.chunk(resourceEffectiveKey, statements); - index.insert(inputFile, blocks); - } - } -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/CpdBlockIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/CpdBlockIndexer.java new file mode 100644 index 00000000000..fe58cb6558b --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/CpdBlockIndexer.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import org.slf4j.Logger; +import org.sonar.api.batch.BatchSide; + +@BatchSide +public abstract class CpdBlockIndexer { + + abstract boolean isLanguageSupported(String language); + + abstract void index(String language); + + protected void logExclusions(String[] exclusions, Logger logger) { + if (exclusions.length > 0) { + StringBuilder message = new StringBuilder("Copy-paste detection exclusions:"); + for (String exclusion : exclusions) { + message.append("\n "); + message.append(exclusion); + } + + logger.info(message.toString()); + } + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/CpdMappings.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/CpdMappings.java new file mode 100644 index 00000000000..ad2275e399c --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/CpdMappings.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import org.sonar.api.batch.BatchSide; +import org.sonar.api.batch.CpdMapping; + +import javax.annotation.CheckForNull; + +@BatchSide +public class CpdMappings { + + 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/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/DefaultCpdBlockIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/DefaultCpdBlockIndexer.java new file mode 100644 index 00000000000..db51246e1c8 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/DefaultCpdBlockIndexer.java @@ -0,0 +1,114 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +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.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.internal.pmd.TokenizerBridge; + +public class DefaultCpdBlockIndexer extends CpdBlockIndexer { + + private static final Logger LOG = LoggerFactory.getLogger(DefaultCpdBlockIndexer.class); + + private final CpdMappings mappings; + private final FileSystem fs; + private final Settings settings; + private final SonarCpdBlockIndex index; + + public DefaultCpdBlockIndexer(CpdMappings mappings, FileSystem fs, Settings settings, SonarCpdBlockIndex index) { + this.mappings = mappings; + this.fs = fs; + this.settings = settings; + this.index = index; + } + + @Override + public boolean isLanguageSupported(String language) { + return true; + } + + @Override + public void index(String languageKey) { + CpdMapping mapping = mappings.getMapping(languageKey); + if (mapping == null) { + LOG.debug("No CpdMapping for language " + languageKey); + return; + } + + String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); + logExclusions(cpdExclusions, LOG); + FilePredicates p = fs.predicates(); + List sourceFiles = Lists.newArrayList(fs.inputFiles(p.and( + p.hasType(InputFile.Type.MAIN), + p.hasLanguage(languageKey), + p.doesNotMatchPathPatterns(cpdExclusions)))); + if (sourceFiles.isEmpty()) { + return; + } + + // Create index + populateIndex(languageKey, sourceFiles, mapping); + } + + private void populateIndex(String languageKey, List sourceFiles, CpdMapping mapping) { + TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fs.encoding().name(), getBlockSize(languageKey)); + for (InputFile inputFile : sourceFiles) { + if (!index.isIndexed(inputFile)) { + LOG.debug("Populating index from {}", inputFile.absolutePath()); + String resourceEffectiveKey = ((DefaultInputFile) inputFile).key(); + List blocks = bridge.chunk(resourceEffectiveKey, inputFile.file()); + index.insert(inputFile, blocks); + } + } + } + + @VisibleForTesting + int getBlockSize(String languageKey) { + int blockSize = settings.getInt("sonar.cpd." + languageKey + ".minimumLines"); + if (blockSize == 0) { + blockSize = getDefaultBlockSize(languageKey); + } + return blockSize; + } + + @VisibleForTesting + public static int getDefaultBlockSize(String languageKey) { + if ("cobol".equals(languageKey)) { + return 30; + } else if ("abap".equals(languageKey)) { + return 20; + } else { + return 10; + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/DeprecatedCpdBlockIndexerSensor.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/DeprecatedCpdBlockIndexerSensor.java new file mode 100644 index 00000000000..ad72d673777 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/DeprecatedCpdBlockIndexerSensor.java @@ -0,0 +1,106 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import com.google.common.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.CpdMapping; +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; + +/** + * Feed block index using deprecated {@link CpdMapping} extension point if not already + * fed by another Sensor using {@link SensorContext#newCpdTokens()}. Special case for Java + * that use a dedicated block indexer. + * Can be removed when {@link CpdMapping} extension is removed and Java specific part moved to Java plugin. + */ +@Phase(name = Phase.Name.POST) +public class DeprecatedCpdBlockIndexerSensor implements Sensor { + + private static final Logger LOG = LoggerFactory.getLogger(DeprecatedCpdBlockIndexerSensor.class); + + private CpdBlockIndexer javaCpdBlockIndexer; + private CpdBlockIndexer defaultCpdBlockIndexer; + private Settings settings; + private FileSystem fs; + + public DeprecatedCpdBlockIndexerSensor(JavaCpdBlockIndexer javaCpdBlockIndexer, DefaultCpdBlockIndexer defaultCpdBlockIndexer, Settings settings, FileSystem fs) { + this.javaCpdBlockIndexer = javaCpdBlockIndexer; + this.defaultCpdBlockIndexer = defaultCpdBlockIndexer; + this.settings = settings; + this.fs = fs; + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor.name("CPD Block Indexer"); + } + + @VisibleForTesting + CpdBlockIndexer getBlockIndexer(String language) { + if (javaCpdBlockIndexer.isLanguageSupported(language)) { + return javaCpdBlockIndexer; + } + return defaultCpdBlockIndexer; + } + + @VisibleForTesting + boolean isSkipped(String language) { + String key = "sonar.cpd." + language + ".skip"; + if (settings.hasKey(key)) { + return settings.getBoolean(key); + } + return settings.getBoolean(CoreProperties.CPD_SKIP_PROPERTY); + } + + @Override + public void execute(SensorContext context) { + if (settings.hasKey(CoreProperties.CPD_SKIP_PROPERTY)) { + LOG.warn("\"sonar.cpd.skip\" property is deprecated and will be removed. Please set \"sonar.cpd.exclusions=**\" instead to disable duplication mechanism."); + } + + for (String language : fs.languages()) { + if (settings.hasKey("sonar.cpd." + language + ".skip")) { + LOG + .warn("\"sonar.cpd." + language + ".skip\" property is deprecated and will be removed. Please set \"sonar.cpd.exclusions=**\" instead to disable duplication mechanism."); + } + + if (isSkipped(language)) { + LOG.info("Detection of duplicated code is skipped for {}", language); + continue; + } + + CpdBlockIndexer blockIndexer = getBlockIndexer(language); + if (!blockIndexer.isLanguageSupported(language)) { + LOG.debug("Detection of duplicated code is not supported for {}", language); + continue; + } + LOG.info("{} is used for {}", blockIndexer, language); + blockIndexer.index(language); + } + } + +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/JavaCpdBlockIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/JavaCpdBlockIndexer.java new file mode 100644 index 00000000000..7e0772408b7 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/JavaCpdBlockIndexer.java @@ -0,0 +1,105 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import com.google.common.collect.Lists; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.CoreProperties; +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.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.BlockChunker; +import org.sonar.duplications.java.JavaStatementBuilder; +import org.sonar.duplications.java.JavaTokenProducer; +import org.sonar.duplications.statement.Statement; +import org.sonar.duplications.statement.StatementChunker; +import org.sonar.duplications.token.TokenChunker; + +public class JavaCpdBlockIndexer extends CpdBlockIndexer { + + private static final Logger LOG = LoggerFactory.getLogger(JavaCpdBlockIndexer.class); + + private static final int BLOCK_SIZE = 10; + + private final FileSystem fs; + private final Settings settings; + private final SonarCpdBlockIndex index; + + public JavaCpdBlockIndexer(FileSystem fs, Settings settings, SonarCpdBlockIndex index) { + this.fs = fs; + this.settings = settings; + this.index = index; + } + + @Override + public boolean isLanguageSupported(String language) { + return "java".equals(language); + } + + @Override + public void index(String languageKey) { + String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); + logExclusions(cpdExclusions, LOG); + FilePredicates p = fs.predicates(); + List sourceFiles = Lists.newArrayList(fs.inputFiles(p.and( + p.hasType(InputFile.Type.MAIN), + p.hasLanguage(languageKey), + p.doesNotMatchPathPatterns(cpdExclusions)))); + if (sourceFiles.isEmpty()) { + return; + } + createIndex(sourceFiles); + } + + private void createIndex(Iterable sourceFiles) { + TokenChunker tokenChunker = JavaTokenProducer.build(); + StatementChunker statementChunker = JavaStatementBuilder.build(); + BlockChunker blockChunker = new BlockChunker(BLOCK_SIZE); + + for (InputFile inputFile : sourceFiles) { + LOG.debug("Populating index from {}", inputFile); + String resourceEffectiveKey = ((DefaultInputFile) inputFile).key(); + + List statements; + + try(Reader reader = new InputStreamReader(new FileInputStream(inputFile.file()), fs.encoding())) { + statements = statementChunker.chunk(tokenChunker.chunk(reader)); + } catch (FileNotFoundException e) { + throw new IllegalStateException("Cannot find file " + inputFile.file(), e); + } catch (IOException e ) { + throw new IllegalStateException("Exception hnadling file: " + inputFile.file(), e); + } + + List blocks = blockChunker.chunk(resourceEffectiveKey, statements); + index.insert(inputFile, blocks); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/package-info.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/package-info.java new file mode 100644 index 00000000000..8a035753659 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/cpd/deprecated/package-info.java @@ -0,0 +1,24 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.batch.cpd.deprecated; + +import javax.annotation.ParametersAreNonnullByDefault; + diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java index 1080dab084a..e0a61eb9117 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorContext.java @@ -109,7 +109,7 @@ public class DefaultSensorContext implements SensorContext { if (analysisMode.isIssues()) { return NO_OP_NEW_CPD_TOKENS; } - return new DefaultCpdTokens(sensorStorage); + return new DefaultCpdTokens(settings, sensorStorage); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java index 62e7d982d34..c8de752737b 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/batch/sensor/DefaultSensorStorage.java @@ -53,7 +53,7 @@ import org.sonar.api.resources.Resource; import org.sonar.api.source.Symbol; import org.sonar.api.utils.KeyValueFormat; import org.sonar.api.utils.SonarException; -import org.sonar.batch.cpd.DefaultCpdBlockIndexer; +import org.sonar.batch.cpd.deprecated.DefaultCpdBlockIndexer; import org.sonar.batch.cpd.index.SonarCpdBlockIndex; import org.sonar.batch.index.BatchComponent; import org.sonar.batch.index.BatchComponentCache; diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdSensorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdSensorTest.java deleted file mode 100644 index ce27f0776c4..00000000000 --- a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/CpdSensorTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import java.io.IOException; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -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 static org.assertj.core.api.Assertions.assertThat; - -public class CpdSensorTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - JavaCpdBlockIndexer sonarEngine; - DefaultCpdBlockIndexer sonarBridgeEngine; - CpdSensor sensor; - Settings settings; - - @Before - public void setUp() throws IOException { - sonarEngine = new JavaCpdBlockIndexer(null, null, null); - sonarBridgeEngine = new DefaultCpdBlockIndexer(new CpdMappings(), null, null, null); - settings = new Settings(new PropertyDefinitions(CpdComponents.class)); - - DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder().toPath()); - sensor = new CpdSensor(sonarEngine, sonarBridgeEngine, settings, fs); - } - - @Test - public void test_global_skip() { - settings.setProperty("sonar.cpd.skip", true); - assertThat(sensor.isSkipped(Java.KEY)).isTrue(); - } - - @Test - public void should_not_skip_by_default() { - assertThat(sensor.isSkipped(Java.KEY)).isFalse(); - } - - @Test - public void should_skip_by_language() { - settings.setProperty("sonar.cpd.skip", false); - settings.setProperty("sonar.cpd.php.skip", true); - - assertThat(sensor.isSkipped("php")).isTrue(); - assertThat(sensor.isSkipped(Java.KEY)).isFalse(); - } - - @Test - public void test_engine() { - assertThat(sensor.getEngine(Java.KEY)).isSameAs(sonarEngine); - assertThat(sensor.getEngine("PHP")).isSameAs(sonarBridgeEngine); - } - -} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DefaultCpdBlockIndexerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DefaultCpdBlockIndexerTest.java deleted file mode 100644 index 9fcd03ac940..00000000000 --- a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/DefaultCpdBlockIndexerTest.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import org.junit.Before; -import org.junit.Test; -import org.slf4j.Logger; -import org.sonar.api.config.Settings; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -public class DefaultCpdBlockIndexerTest { - - private DefaultCpdBlockIndexer engine; - private Settings settings; - - @Before - public void init() { - settings = new Settings(); - engine = new DefaultCpdBlockIndexer(null, null, settings, null); - } - - @Test - public void shouldLogExclusions() { - Logger logger = mock(Logger.class); - engine.logExclusions(new String[0], logger); - verify(logger, never()).info(anyString()); - - logger = mock(Logger.class); - engine.logExclusions(new String[] {"Foo*", "**/Bar*"}, logger); - - String message = "Copy-paste detection exclusions:" - + "\n Foo*" - + "\n **/Bar*"; - verify(logger, times(1)).info(message); - } - - @Test - public void shouldReturnDefaultBlockSize() { - assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("cobol")).isEqualTo(30); - assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("abap")).isEqualTo(20); - assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("other")).isEqualTo(10); - } - - @Test - public void defaultBlockSize() { - - assertThat(engine.getBlockSize("java")).isEqualTo(10); - } - - @Test - public void blockSizeForCobol() { - settings.setProperty("sonar.cpd.cobol.minimumLines", "42"); - - assertThat(engine.getBlockSize("cobol")).isEqualTo(42); - } - -} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/JavaCpdBlockIndexerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/JavaCpdBlockIndexerTest.java deleted file mode 100644 index 851fb523815..00000000000 --- a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/JavaCpdBlockIndexerTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program 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. - * - * This program 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.cpd; - -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.sonar.api.CoreProperties; -import org.sonar.api.batch.fs.FileSystem; -import org.sonar.api.batch.fs.internal.DefaultFileSystem; -import org.sonar.api.batch.fs.internal.DefaultInputFile; -import org.sonar.api.config.Settings; -import org.sonar.batch.cpd.index.SonarCpdBlockIndex; -import org.sonar.batch.index.BatchComponentCache; -import org.sonar.duplications.block.Block; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -public class JavaCpdBlockIndexerTest { - private static final String JAVA = "java"; - - @Mock - private SonarCpdBlockIndex index; - - @Captor - private ArgumentCaptor> blockCaptor; - - private Settings settings; - private JavaCpdBlockIndexer engine; - private DefaultInputFile file; - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - @Before - public void setUp() throws IOException { - MockitoAnnotations.initMocks(this); - - File baseDir = temp.newFolder(); - DefaultFileSystem fs = new DefaultFileSystem(baseDir); - file = new DefaultInputFile("foo", "src/ManyStatements.java").setLanguage(JAVA); - fs.add(file); - BatchComponentCache batchComponentCache = new BatchComponentCache(); - batchComponentCache.add(org.sonar.api.resources.File.create("src/Foo.java").setEffectiveKey("foo:src/ManyStatements.java"), null).setInputComponent(file); - File ioFile = file.file(); - FileUtils.copyURLToFile(this.getClass().getResource("ManyStatements.java"), ioFile); - - settings = new Settings(); - engine = new JavaCpdBlockIndexer(fs, settings, index); - } - - @Test - public void languageSupported() { - JavaCpdBlockIndexer engine = new JavaCpdBlockIndexer(mock(FileSystem.class), new Settings(), index); - assertThat(engine.isLanguageSupported(JAVA)).isTrue(); - assertThat(engine.isLanguageSupported("php")).isFalse(); - } - - @Test - public void testExclusions() { - settings.setProperty(CoreProperties.CPD_EXCLUSIONS, "**"); - engine.index(JAVA); - verifyZeroInteractions(index); - } - - @Test - public void testJavaIndexing() throws Exception { - engine.index(JAVA); - - verify(index).insert(eq(file), blockCaptor.capture()); - List blockList = blockCaptor.getValue(); - - assertThat(blockList).hasSize(26); - } -} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/DefaultCpdBlockIndexerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/DefaultCpdBlockIndexerTest.java new file mode 100644 index 00000000000..5dc28fde04c --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/DefaultCpdBlockIndexerTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.sonar.api.config.Settings; +import org.sonar.batch.cpd.deprecated.DefaultCpdBlockIndexer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class DefaultCpdBlockIndexerTest { + + private DefaultCpdBlockIndexer engine; + private Settings settings; + + @Before + public void init() { + settings = new Settings(); + engine = new DefaultCpdBlockIndexer(null, null, settings, null); + } + + @Test + public void shouldLogExclusions() { + Logger logger = mock(Logger.class); + engine.logExclusions(new String[0], logger); + verify(logger, never()).info(anyString()); + + logger = mock(Logger.class); + engine.logExclusions(new String[] {"Foo*", "**/Bar*"}, logger); + + String message = "Copy-paste detection exclusions:" + + "\n Foo*" + + "\n **/Bar*"; + verify(logger, times(1)).info(message); + } + + @Test + public void shouldReturnDefaultBlockSize() { + assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("cobol")).isEqualTo(30); + assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("abap")).isEqualTo(20); + assertThat(DefaultCpdBlockIndexer.getDefaultBlockSize("other")).isEqualTo(10); + } + + @Test + public void defaultBlockSize() { + + assertThat(engine.getBlockSize("java")).isEqualTo(10); + } + + @Test + public void blockSizeForCobol() { + settings.setProperty("sonar.cpd.cobol.minimumLines", "42"); + + assertThat(engine.getBlockSize("cobol")).isEqualTo(42); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/DeprecatedCpdBlockIndexerSensorTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/DeprecatedCpdBlockIndexerSensorTest.java new file mode 100644 index 00000000000..4adc630247e --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/DeprecatedCpdBlockIndexerSensorTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import java.io.IOException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +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.cpd.CpdComponents; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DeprecatedCpdBlockIndexerSensorTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + JavaCpdBlockIndexer sonarEngine; + DefaultCpdBlockIndexer sonarBridgeEngine; + DeprecatedCpdBlockIndexerSensor sensor; + Settings settings; + + @Before + public void setUp() throws IOException { + sonarEngine = new JavaCpdBlockIndexer(null, null, null); + sonarBridgeEngine = new DefaultCpdBlockIndexer(new CpdMappings(), null, null, null); + settings = new Settings(new PropertyDefinitions(CpdComponents.class)); + + DefaultFileSystem fs = new DefaultFileSystem(temp.newFolder().toPath()); + sensor = new DeprecatedCpdBlockIndexerSensor(sonarEngine, sonarBridgeEngine, settings, fs); + } + + @Test + public void test_global_skip() { + settings.setProperty("sonar.cpd.skip", true); + assertThat(sensor.isSkipped(Java.KEY)).isTrue(); + } + + @Test + public void should_not_skip_by_default() { + assertThat(sensor.isSkipped(Java.KEY)).isFalse(); + } + + @Test + public void should_skip_by_language() { + settings.setProperty("sonar.cpd.skip", false); + settings.setProperty("sonar.cpd.php.skip", true); + + assertThat(sensor.isSkipped("php")).isTrue(); + assertThat(sensor.isSkipped(Java.KEY)).isFalse(); + } + + @Test + public void test_engine() { + assertThat(sensor.getBlockIndexer(Java.KEY)).isSameAs(sonarEngine); + assertThat(sensor.getBlockIndexer("PHP")).isSameAs(sonarBridgeEngine); + } + +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/JavaCpdBlockIndexerTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/JavaCpdBlockIndexerTest.java new file mode 100644 index 00000000000..b42843d91aa --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/cpd/deprecated/JavaCpdBlockIndexerTest.java @@ -0,0 +1,107 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program 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. + * + * This program 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.cpd.deprecated; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.sonar.api.CoreProperties; +import org.sonar.api.batch.fs.FileSystem; +import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.config.Settings; +import org.sonar.batch.cpd.deprecated.JavaCpdBlockIndexer; +import org.sonar.batch.cpd.index.SonarCpdBlockIndex; +import org.sonar.batch.index.BatchComponentCache; +import org.sonar.duplications.block.Block; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class JavaCpdBlockIndexerTest { + private static final String JAVA = "java"; + + @Mock + private SonarCpdBlockIndex index; + + @Captor + private ArgumentCaptor> blockCaptor; + + private Settings settings; + private JavaCpdBlockIndexer engine; + private DefaultInputFile file; + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + + File baseDir = temp.newFolder(); + DefaultFileSystem fs = new DefaultFileSystem(baseDir); + file = new DefaultInputFile("foo", "src/ManyStatements.java").setLanguage(JAVA); + fs.add(file); + BatchComponentCache batchComponentCache = new BatchComponentCache(); + batchComponentCache.add(org.sonar.api.resources.File.create("src/Foo.java").setEffectiveKey("foo:src/ManyStatements.java"), null).setInputComponent(file); + File ioFile = file.file(); + FileUtils.copyURLToFile(this.getClass().getResource("ManyStatements.java"), ioFile); + + settings = new Settings(); + engine = new JavaCpdBlockIndexer(fs, settings, index); + } + + @Test + public void languageSupported() { + JavaCpdBlockIndexer engine = new JavaCpdBlockIndexer(mock(FileSystem.class), new Settings(), index); + assertThat(engine.isLanguageSupported(JAVA)).isTrue(); + assertThat(engine.isLanguageSupported("php")).isFalse(); + } + + @Test + public void testExclusions() { + settings.setProperty(CoreProperties.CPD_EXCLUSIONS, "**"); + engine.index(JAVA); + verifyZeroInteractions(index); + } + + @Test + public void testJavaIndexing() throws Exception { + engine.index(JAVA); + + verify(index).insert(eq(file), blockCaptor.capture()); + List blockList = blockCaptor.getValue(); + + assertThat(blockList).hasSize(26); + } +} diff --git a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java index 662ed7dbcfd..6613421a4da 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/batch/mediumtest/cpd/CpdMediumTest.java @@ -219,6 +219,43 @@ public class CpdMediumTest { assertThat(result.duplicationBlocksFor(inputFile1)).isEmpty(); } + @Test + public void testExclusions() throws IOException { + File srcDir = new File(baseDir, "src"); + srcDir.mkdir(); + + String duplicatedStuff = "Sample xoo\ncontent\n" + + "foo\nbar\ntoto\ntiti\n" + + "foo\nbar\ntoto\ntiti\n" + + "bar\ntoto\ntiti\n" + + "foo\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.cpd.exclusions", "src/sample1.xoo") + .build()) + .start(); + + assertThat(result.inputFiles()).hasSize(2); + + InputFile inputFile1 = result.inputFile("src/sample1.xoo"); + InputFile inputFile2 = result.inputFile("src/sample2.xoo"); + + List duplicationGroupsFile1 = result.duplicationsFor(inputFile1); + assertThat(duplicationGroupsFile1).isEmpty(); + + List duplicationGroupsFile2 = result.duplicationsFor(inputFile2); + assertThat(duplicationGroupsFile2).isEmpty(); + } + @Test public void enableCrossProjectDuplication() throws IOException { File srcDir = new File(baseDir, "src"); diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/ManyStatements.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/ManyStatements.java deleted file mode 100644 index ed2297068e4..00000000000 --- a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/ManyStatements.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.foo; - -public class ManyStatements { - - void foo() { - int A1 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; - int A2 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; - int A1 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; - } - -} \ No newline at end of file diff --git a/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/deprecated/ManyStatements.java b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/deprecated/ManyStatements.java new file mode 100644 index 00000000000..ed2297068e4 --- /dev/null +++ b/sonar-scanner-engine/src/test/resources/org/sonar/batch/cpd/deprecated/ManyStatements.java @@ -0,0 +1,11 @@ +package org.foo; + +public class ManyStatements { + + void foo() { + int A1 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; + int A2 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; + int A1 = 0; int B = 0; int C = 0; int D = 0; int E = 0; int F = 0; int G = 0; int H = 0; int I = 0; int J = 0; int K = 0; + } + +} \ No newline at end of file