]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-3139 Allow to use Sonar CPD with existing extension point CpdMapping
authorEvgeny Mandrikov <mandrikov@gmail.com>
Mon, 9 Jan 2012 07:39:03 +0000 (11:39 +0400)
committerEvgeny Mandrikov <mandrikov@gmail.com>
Mon, 9 Jan 2012 07:39:42 +0000 (11:39 +0400)
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdPlugin.java
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/CpdSensor.java
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java [new file with mode: 0644]
plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/TokenizerBridge.java [new file with mode: 0644]

index 429117653ab397037ac697299c3906a284261c33..bddce84afef6c4fec195e5ace2adb54e423d2efd 100644 (file)
@@ -19,9 +19,6 @@
  */
 package org.sonar.plugins.cpd;
 
-import java.util.Arrays;
-import java.util.List;
-
 import org.sonar.api.CoreProperties;
 import org.sonar.api.Properties;
 import org.sonar.api.Property;
@@ -29,6 +26,9 @@ import org.sonar.api.SonarPlugin;
 import org.sonar.plugins.cpd.decorators.DuplicationDensityDecorator;
 import org.sonar.plugins.cpd.decorators.SumDuplicationsDecorator;
 
+import java.util.Arrays;
+import java.util.List;
+
 @Properties({
     @Property(
         key = CoreProperties.CPD_ENGINE,
@@ -88,7 +88,10 @@ import org.sonar.plugins.cpd.decorators.SumDuplicationsDecorator;
 public class CpdPlugin extends SonarPlugin {
 
   public List getExtensions() {
-    return Arrays.asList(CpdSensor.class, SumDuplicationsDecorator.class, DuplicationDensityDecorator.class, SonarEngine.class, PmdEngine.class);
+    return Arrays.asList(CpdSensor.class, SumDuplicationsDecorator.class, DuplicationDensityDecorator.class,
+        SonarEngine.class,
+        PmdEngine.class,
+        SonarBridgeEngine.class);
   }
 
 }
index afc0ec21b71d6397580ce258e31264ac5138b666..91e02f34d5b941a9ec7fc154d5d99b7fc5496808 100644 (file)
@@ -32,12 +32,19 @@ public class CpdSensor implements Sensor {
 
   private CpdEngine sonarEngine;
   private CpdEngine pmdEngine;
+  private CpdEngine sonarBridgeEngine;
 
   public CpdSensor(SonarEngine sonarEngine, PmdEngine pmdEngine) {
     this.sonarEngine = sonarEngine;
     this.pmdEngine = pmdEngine;
   }
 
+  public CpdSensor(SonarEngine sonarEngine, PmdEngine pmdEngine, SonarBridgeEngine sonarBridgeEngine) {
+    this.sonarEngine = sonarEngine;
+    this.pmdEngine = pmdEngine;
+    this.sonarBridgeEngine = sonarBridgeEngine;
+  }
+
   public boolean shouldExecuteOnProject(Project project) {
     if (isSkipped(project)) {
       LoggerFactory.getLogger(getClass()).info("Detection of duplicated code is skipped");
@@ -53,21 +60,24 @@ public class CpdSensor implements Sensor {
   }
 
   private CpdEngine getEngine(Project project) {
-    if (isSonarEngineEnabled(project)) {
+    if (isEngineEnabled(project, "sonar")) {
       if (sonarEngine.isLanguageSupported(project.getLanguage())) {
         return sonarEngine;
-      } else {
-        // fallback to PMD
-        return pmdEngine;
       }
-    } else {
-      return pmdEngine;
+      // falback to PMD
+    } else if (isEngineEnabled(project, "bridge")) {
+      return sonarBridgeEngine;
     }
+    return pmdEngine;
   }
 
-  boolean isSonarEngineEnabled(Project project) {
+  boolean isEngineEnabled(Project project, String engineName) {
     Configuration conf = project.getConfiguration();
-    return StringUtils.equalsIgnoreCase(conf.getString(CoreProperties.CPD_ENGINE, CoreProperties.CPD_ENGINE_DEFAULT_VALUE), "sonar");
+    return StringUtils.equalsIgnoreCase(conf.getString(CoreProperties.CPD_ENGINE, CoreProperties.CPD_ENGINE_DEFAULT_VALUE), engineName);
+  }
+
+  boolean isSonarEngineEnabled(Project project) {
+    return isEngineEnabled(project, "sonar");
   }
 
   boolean isSkipped(Project project) {
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/SonarBridgeEngine.java
new file mode 100644 (file)
index 0000000..b9465be
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.cpd;
+
+import org.apache.commons.configuration.Configuration;
+import org.sonar.api.CoreProperties;
+import org.sonar.api.batch.CpdMapping;
+import org.sonar.api.batch.SensorContext;
+import org.sonar.api.database.model.ResourceModel;
+import org.sonar.api.resources.*;
+import org.sonar.duplications.block.Block;
+import org.sonar.duplications.block.BlockChunker;
+import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm;
+import org.sonar.duplications.index.CloneGroup;
+import org.sonar.duplications.index.CloneIndex;
+import org.sonar.duplications.index.PackedMemoryCloneIndex;
+
+import java.util.Collection;
+import java.util.List;
+
+public class SonarBridgeEngine extends CpdEngine {
+
+  private final CpdMapping[] mappings;
+
+  public SonarBridgeEngine() {
+    this.mappings = null;
+  }
+
+  public SonarBridgeEngine(CpdMapping[] mappings) {
+    this.mappings = mappings;
+  }
+
+  @Override
+  public boolean isLanguageSupported(Language language) {
+    return getMapping(language) != null;
+  }
+
+  @Override
+  public void analyse(Project project, SensorContext context) {
+    ProjectFileSystem fileSystem = project.getFileSystem();
+    List<InputFile> inputFiles = fileSystem.mainFiles(project.getLanguageKey());
+    if (inputFiles.isEmpty()) {
+      return;
+    }
+
+    CpdMapping mapping = getMapping(project.getLanguage());
+
+    // Create index
+    BlockChunker blockChunker = new BlockChunker(getMinimumTokens(project));
+    CloneIndex index = new PackedMemoryCloneIndex();
+    TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fileSystem.getSourceCharset().name());
+    for (InputFile inputFile : inputFiles) {
+      Resource resource = mapping.createResource(inputFile.getFile(), fileSystem.getSourceDirs());
+      String resourceId = getFullKey(project, resource);
+      List<Block> blocks = blockChunker.chunk(resourceId, bridge.tokenize(inputFile.getFile()));
+      for (Block block : blocks) {
+        index.insert(block);
+      }
+    }
+    bridge.clearCache();
+
+    // Detect
+    for (InputFile inputFile : inputFiles) {
+      Resource resource = mapping.createResource(inputFile.getFile(), fileSystem.getSourceDirs());
+      String resourceId = getFullKey(project, resource);
+      Collection<Block> fileBlocks = index.getByResourceId(resourceId);
+      List<CloneGroup> duplications = SuffixTreeCloneDetectionAlgorithm.detect(index, fileBlocks);
+      SonarEngine.save(context, resource, duplications);
+    }
+  }
+
+  private static String getFullKey(Project project, Resource resource) {
+    return new StringBuilder(ResourceModel.KEY_SIZE)
+        .append(project.getKey())
+        .append(':')
+        .append(resource.getKey())
+        .toString();
+  }
+
+  private int getMinimumTokens(Project project) {
+    Configuration conf = project.getConfiguration();
+    return conf.getInt("sonar.cpd." + project.getLanguageKey() + ".minimumTokens",
+        conf.getInt("sonar.cpd.minimumTokens", CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE));
+  }
+
+  private CpdMapping getMapping(Language language) {
+    if (mappings != null) {
+      for (CpdMapping cpdMapping : mappings) {
+        if (cpdMapping.getLanguage().equals(language)) {
+          return cpdMapping;
+        }
+      }
+    }
+    return null;
+  }
+
+}
diff --git a/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/TokenizerBridge.java b/plugins/sonar-cpd-plugin/src/main/java/org/sonar/plugins/cpd/TokenizerBridge.java
new file mode 100644 (file)
index 0000000..eab059e
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+package org.sonar.plugins.cpd;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import net.sourceforge.pmd.cpd.SourceCode;
+import net.sourceforge.pmd.cpd.TokenEntry;
+import net.sourceforge.pmd.cpd.Tokenizer;
+import net.sourceforge.pmd.cpd.Tokens;
+import org.sonar.duplications.cpd.FileCodeLoaderWithoutCache;
+import org.sonar.duplications.statement.Statement;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class TokenizerBridge {
+
+  private final Tokenizer tokenizer;
+  private final String encoding;
+
+  public TokenizerBridge(Tokenizer tokenizer, String encoding) {
+    this.tokenizer = tokenizer;
+    this.encoding = encoding;
+    clearCache();
+  }
+
+  public List<Statement> tokenize(File file) {
+    SourceCode sourceCode = new SourceCode(new FileCodeLoaderWithoutCache(file, encoding));
+    Tokens tokens = new Tokens();
+    try {
+      tokenizer.tokenize(sourceCode, tokens);
+    } catch (IOException e) {
+      throw Throwables.propagate(e);
+    }
+    return convert(tokens.getTokens());
+  }
+
+  /**
+   * We expect that implementation of {@link Tokenizer} is correct:
+   * tokens ordered by occurrence in source code and last token is EOF.
+   */
+  private List<Statement> convert(List<TokenEntry> tokens) {
+    ImmutableList.Builder<Statement> result = ImmutableList.builder();
+    for (TokenEntry token : tokens) {
+      if (token != TokenEntry.EOF) {
+        int line = token.getBeginLine();
+        int id = token.getIdentifier();
+        result.add(new Statement(line, line, Integer.toString(id)));
+      }
+    }
+    return result.build();
+  }
+
+  public void clearCache() {
+    TokenEntry.clearImages();
+  }
+
+}