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    }