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 */ 020 package org.sonar.plugins.cpd; 021 022 import com.google.common.annotations.VisibleForTesting; 023 import com.google.common.base.Predicate; 024 import com.google.common.collect.Iterables; 025 import org.slf4j.Logger; 026 import org.slf4j.LoggerFactory; 027 import org.sonar.api.batch.CpdMapping; 028 import org.sonar.api.batch.SensorContext; 029 import org.sonar.api.resources.*; 030 import org.sonar.api.utils.SonarException; 031 import org.sonar.duplications.DuplicationPredicates; 032 import org.sonar.duplications.block.Block; 033 import org.sonar.duplications.index.CloneGroup; 034 import org.sonar.duplications.internal.pmd.TokenizerBridge; 035 import org.sonar.plugins.cpd.index.IndexFactory; 036 import org.sonar.plugins.cpd.index.SonarDuplicationsIndex; 037 038 import java.util.Collection; 039 import java.util.List; 040 import java.util.concurrent.*; 041 042 public class SonarBridgeEngine extends CpdEngine { 043 044 private static final Logger LOG = LoggerFactory.getLogger(SonarBridgeEngine.class); 045 046 /** 047 * Limit of time to analyse one file (in seconds). 048 */ 049 private static final int TIMEOUT = 5 * 60; 050 051 private final IndexFactory indexFactory; 052 private final CpdMapping[] mappings; 053 054 public SonarBridgeEngine(IndexFactory indexFactory) { 055 this.indexFactory = indexFactory; 056 this.mappings = null; 057 } 058 059 public SonarBridgeEngine(IndexFactory indexFactory, CpdMapping[] mappings) { 060 this.indexFactory = indexFactory; 061 this.mappings = mappings; 062 } 063 064 @Override 065 public boolean isLanguageSupported(Language language) { 066 return getMapping(language) != null; 067 } 068 069 @Override 070 public void analyse(Project project, SensorContext context) { 071 ProjectFileSystem fileSystem = project.getFileSystem(); 072 List<InputFile> inputFiles = fileSystem.mainFiles(project.getLanguageKey()); 073 if (inputFiles.isEmpty()) { 074 return; 075 } 076 077 CpdMapping mapping = getMapping(project.getLanguage()); 078 079 // Create index 080 SonarDuplicationsIndex index = indexFactory.create(project); 081 082 TokenizerBridge bridge = new TokenizerBridge(mapping.getTokenizer(), fileSystem.getSourceCharset().name(), getBlockSize(project)); 083 for (InputFile inputFile : inputFiles) { 084 LOG.debug("Populating index from {}", inputFile.getFile()); 085 Resource resource = mapping.createResource(inputFile.getFile(), fileSystem.getSourceDirs()); 086 String resourceId = SonarEngine.getFullKey(project, resource); 087 List<Block> blocks = bridge.chunk(resourceId, inputFile.getFile()); 088 index.insert(resource, blocks); 089 } 090 091 // Detect 092 Predicate<CloneGroup> minimumTokensPredicate = DuplicationPredicates.numberOfUnitsNotLessThan(PmdEngine.getMinimumTokens(project)); 093 094 ExecutorService executorService = Executors.newSingleThreadExecutor(); 095 try { 096 for (InputFile inputFile : inputFiles) { 097 LOG.debug("Detection of duplications for {}", inputFile.getFile()); 098 Resource resource = mapping.createResource(inputFile.getFile(), fileSystem.getSourceDirs()); 099 String resourceKey = SonarEngine.getFullKey(project, resource); 100 101 Collection<Block> fileBlocks = index.getByResource(resource, resourceKey); 102 103 Iterable<CloneGroup> filtered; 104 try { 105 List<CloneGroup> duplications = executorService.submit(new SonarEngine.Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS); 106 filtered = Iterables.filter(duplications, minimumTokensPredicate); 107 } catch (TimeoutException e) { 108 filtered = null; 109 LOG.warn("Timeout during detection of duplications for " + inputFile.getFile(), e); 110 } catch (InterruptedException e) { 111 throw new SonarException(e); 112 } catch (ExecutionException e) { 113 throw new SonarException(e); 114 } 115 116 SonarEngine.save(context, resource, filtered); 117 } 118 } finally { 119 executorService.shutdown(); 120 } 121 } 122 123 private static int getBlockSize(Project project) { 124 String languageKey = project.getLanguageKey(); 125 return project.getConfiguration() 126 .getInt("sonar.cpd." + languageKey + ".minimumLines", getDefaultBlockSize(languageKey)); 127 } 128 129 @VisibleForTesting 130 static int getDefaultBlockSize(String languageKey) { 131 if ("cobol".equals(languageKey)) { 132 return 30; 133 } else if ("abap".equals(languageKey) || "natur".equals(languageKey)) { 134 return 20; 135 } else { 136 return 10; 137 } 138 } 139 140 private CpdMapping getMapping(Language language) { 141 if (mappings != null) { 142 for (CpdMapping cpdMapping : mappings) { 143 if (cpdMapping.getLanguage().equals(language)) { 144 return cpdMapping; 145 } 146 } 147 } 148 return null; 149 } 150 151 }