001/* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020package org.sonar.plugins.cpd; 021 022import com.google.common.annotations.VisibleForTesting; 023import com.google.common.base.Predicate; 024import com.google.common.collect.Iterables; 025import org.apache.commons.configuration.Configuration; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028import org.sonar.api.CoreProperties; 029import org.sonar.api.batch.CpdMapping; 030import org.sonar.api.batch.SensorContext; 031import org.sonar.api.resources.*; 032import org.sonar.api.utils.SonarException; 033import org.sonar.duplications.DuplicationPredicates; 034import org.sonar.duplications.block.Block; 035import org.sonar.duplications.index.CloneGroup; 036import org.sonar.duplications.internal.pmd.TokenizerBridge; 037import org.sonar.plugins.cpd.index.IndexFactory; 038import org.sonar.plugins.cpd.index.SonarDuplicationsIndex; 039 040import java.util.Collection; 041import java.util.List; 042import java.util.concurrent.*; 043 044public class SonarBridgeEngine extends CpdEngine { 045 046 private static final Logger LOG = LoggerFactory.getLogger(SonarBridgeEngine.class); 047 048 /** 049 * Limit of time to analyse one file (in seconds). 050 */ 051 private static final int TIMEOUT = 5 * 60; 052 053 private final IndexFactory indexFactory; 054 private final CpdMapping[] mappings; 055 056 public SonarBridgeEngine(IndexFactory indexFactory) { 057 this.indexFactory = indexFactory; 058 this.mappings = null; 059 } 060 061 public SonarBridgeEngine(IndexFactory indexFactory, CpdMapping[] mappings) { 062 this.indexFactory = indexFactory; 063 this.mappings = mappings; 064 } 065 066 @Override 067 public boolean isLanguageSupported(Language language) { 068 return getMapping(language) != null; 069 } 070 071 @Override 072 public void analyse(Project project, SensorContext context) { 073 ProjectFileSystem fileSystem = project.getFileSystem(); 074 List<InputFile> inputFiles = fileSystem.mainFiles(project.getLanguageKey()); 075 if (inputFiles.isEmpty()) { 076 return; 077 } 078 079 CpdMapping mapping = getMapping(project.getLanguage()); 080 081 // Create index 082 SonarDuplicationsIndex index = indexFactory.create(project); 083 084 TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fileSystem.getSourceCharset().name(), getBlockSize(project)); 085 for (InputFile inputFile : inputFiles) { 086 LOG.debug("Populating index from {}", inputFile.getFile()); 087 Resource resource = mapping.createResource(inputFile.getFile(), fileSystem.getSourceDirs()); 088 String resourceId = SonarEngine.getFullKey(project, resource); 089 List<Block> blocks = bridge.chunk(resourceId, inputFile.getFile()); 090 index.insert(resource, blocks); 091 } 092 093 // Detect 094 Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(getMinimumTokens(project)); 095 096 ExecutorService executorService = Executors.newSingleThreadExecutor(); 097 try { 098 for (InputFile inputFile : inputFiles) { 099 LOG.debug("Detection of duplications for {}", inputFile.getFile()); 100 Resource resource = mapping.createResource(inputFile.getFile(), fileSystem.getSourceDirs()); 101 String resourceKey = SonarEngine.getFullKey(project, resource); 102 103 Collection<Block> fileBlocks = index.getByResource(resource, resourceKey); 104 105 Iterable<CloneGroup> filtered; 106 try { 107 List<CloneGroup> duplications = executorService.submit(new SonarEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS); 108 filtered = Iterables.filter(duplications, minimumTokensPredicate); 109 } catch (TimeoutException e) { 110 filtered = null; 111 LOG.warn("Timeout during detection of duplications for " + inputFile.getFile(), e); 112 } catch (InterruptedException e) { 113 throw new SonarException(e); 114 } catch (ExecutionException e) { 115 throw new SonarException(e); 116 } 117 118 SonarEngine.save(context, resource, filtered); 119 } 120 } finally { 121 executorService.shutdown(); 122 } 123 } 124 125 private static int getBlockSize(Project project) { 126 String languageKey = project.getLanguageKey(); 127 return project.getConfiguration() 128 .getInt("sonar.cpd." + languageKey + ".minimumLines", getDefaultBlockSize(languageKey)); 129 } 130 131 @VisibleForTesting 132 static int getDefaultBlockSize(String languageKey) { 133 if ("cobol".equals(languageKey)) { 134 return 30; 135 } else if ("abap".equals(languageKey) || "natur".equals(languageKey)) { 136 return 20; 137 } else { 138 return 10; 139 } 140 } 141 142 @VisibleForTesting 143 static int getMinimumTokens(Project project) { 144 Configuration conf = project.getConfiguration(); 145 return conf.getInt("sonar.cpd." + project.getLanguageKey() + ".minimumTokens", 146 conf.getInt("sonar.cpd.minimumTokens", CoreProperties.CPD_MINIMUM_TOKENS_DEFAULT_VALUE)); 147 } 148 149 private CpdMapping getMapping(Language language) { 150 if (mappings != null) { 151 for (CpdMapping cpdMapping : mappings) { 152 if (cpdMapping.getLanguage().equals(language)) { 153 return cpdMapping; 154 } 155 } 156 } 157 return null; 158 } 159 160}