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}