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 }