Java tutorial
/* * Sonar, open source software quality management tool. * Copyright (C) 2008-2012 SonarSource * mailto:contact AT sonarsource DOT com * * Sonar is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * Sonar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with Sonar; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 */ package org.sonar.plugins.cpd; import com.google.common.collect.Iterables; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringEscapeUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.CoreProperties; import org.sonar.api.batch.SensorContext; import org.sonar.api.config.Settings; import org.sonar.api.database.model.ResourceModel; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Measure; import org.sonar.api.measures.PersistenceMode; import org.sonar.api.resources.Java; import org.sonar.api.resources.JavaFile; import org.sonar.api.resources.Language; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.scan.filesystem.FileQuery; import org.sonar.api.scan.filesystem.ModuleFileSystem; import org.sonar.api.scan.filesystem.PathResolver; import org.sonar.api.utils.SonarException; import org.sonar.duplications.block.Block; import org.sonar.duplications.block.BlockChunker; import org.sonar.duplications.detector.suffixtree.SuffixTreeCloneDetectionAlgorithm; import org.sonar.duplications.index.CloneGroup; import org.sonar.duplications.index.CloneIndex; import org.sonar.duplications.index.ClonePart; import org.sonar.duplications.java.JavaStatementBuilder; import org.sonar.duplications.java.JavaTokenProducer; import org.sonar.duplications.statement.Statement; import org.sonar.duplications.statement.StatementChunker; import org.sonar.duplications.token.TokenChunker; import org.sonar.plugins.cpd.index.IndexFactory; import org.sonar.plugins.cpd.index.SonarDuplicationsIndex; import javax.annotation.Nullable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStreamReader; import java.io.Reader; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class SonarEngine extends CpdEngine { private static final Logger LOG = LoggerFactory.getLogger(SonarEngine.class); private static final int BLOCK_SIZE = 10; /** * Limit of time to analyse one file (in seconds). */ private static final int TIMEOUT = 5 * 60; private final IndexFactory indexFactory; private final ModuleFileSystem fileSystem; private final PathResolver pathResolver; private final Settings settings; public SonarEngine(IndexFactory indexFactory, ModuleFileSystem moduleFileSystem, PathResolver pathResolver, Settings settings) { this.indexFactory = indexFactory; this.fileSystem = moduleFileSystem; this.pathResolver = pathResolver; this.settings = settings; } @Override public boolean isLanguageSupported(Language language) { return Java.INSTANCE.equals(language); } static String getFullKey(Project project, Resource<?> resource) { return new StringBuilder(ResourceModel.KEY_SIZE).append(project.getKey()).append(':') .append(resource.getKey()).toString(); } @Override public void analyse(Project project, SensorContext context) { String[] cpdExclusions = settings.getStringArray(CoreProperties.CPD_EXCLUSIONS); logExclusions(cpdExclusions, LOG); List<File> sourceFiles = fileSystem .files(FileQuery.onSource().onLanguage(project.getLanguageKey()).withExclusions(cpdExclusions)); if (sourceFiles.isEmpty()) { return; } SonarDuplicationsIndex index = createIndex(project, sourceFiles); detect(index, context, project, sourceFiles); } private SonarDuplicationsIndex createIndex(Project project, List<File> sourceFiles) { final SonarDuplicationsIndex index = indexFactory.create(project); TokenChunker tokenChunker = JavaTokenProducer.build(); StatementChunker statementChunker = JavaStatementBuilder.build(); BlockChunker blockChunker = new BlockChunker(BLOCK_SIZE); for (File file : sourceFiles) { LOG.debug("Populating index from {}", file); Resource<?> resource = getResource(file); String resourceKey = getFullKey(project, resource); List<Statement> statements; Reader reader = null; try { reader = new InputStreamReader(new FileInputStream(file), fileSystem.sourceCharset()); statements = statementChunker.chunk(tokenChunker.chunk(reader)); } catch (FileNotFoundException e) { throw new SonarException(e); } finally { IOUtils.closeQuietly(reader); } List<Block> blocks = blockChunker.chunk(resourceKey, statements); index.insert(resource, blocks); } return index; } private void detect(SonarDuplicationsIndex index, SensorContext context, Project project, List<File> sourceFiles) { ExecutorService executorService = Executors.newSingleThreadExecutor(); try { for (File file : sourceFiles) { LOG.debug("Detection of duplications for {}", file); Resource<?> resource = getResource(file); String resourceKey = getFullKey(project, resource); Collection<Block> fileBlocks = index.getByResource(resource, resourceKey); List<CloneGroup> clones; try { clones = executorService.submit(new Task(index, fileBlocks)).get(TIMEOUT, TimeUnit.SECONDS); } catch (TimeoutException e) { clones = null; LOG.warn("Timeout during detection of duplications for " + file, e); } catch (InterruptedException e) { throw new SonarException(e); } catch (ExecutionException e) { throw new SonarException(e); } save(context, resource, clones); } } finally { executorService.shutdown(); } } static class Task implements Callable<List<CloneGroup>> { private final CloneIndex index; private final Collection<Block> fileBlocks; public Task(CloneIndex index, Collection<Block> fileBlocks) { this.index = index; this.fileBlocks = fileBlocks; } public List<CloneGroup> call() { return SuffixTreeCloneDetectionAlgorithm.detect(index, fileBlocks); } } protected Resource<?> getResource(File file) { String relativePath = pathResolver.relativePath(fileSystem.sourceDirs(), file).path(); return JavaFile.fromRelativePath(relativePath, false); } static void save(SensorContext context, Resource<?> resource, @Nullable Iterable<CloneGroup> duplications) { if (duplications == null || Iterables.isEmpty(duplications)) { return; } // Calculate number of lines and blocks Set<Integer> duplicatedLines = new HashSet<Integer>(); double duplicatedBlocks = 0; for (CloneGroup clone : duplications) { ClonePart origin = clone.getOriginPart(); for (ClonePart part : clone.getCloneParts()) { if (part.getResourceId().equals(origin.getResourceId())) { duplicatedBlocks++; for (int duplicatedLine = part.getStartLine(); duplicatedLine < part.getStartLine() + part.getLines(); duplicatedLine++) { duplicatedLines.add(duplicatedLine); } } } } // Save context.saveMeasure(resource, CoreMetrics.DUPLICATED_FILES, 1.0); context.saveMeasure(resource, CoreMetrics.DUPLICATED_LINES, (double) duplicatedLines.size()); context.saveMeasure(resource, CoreMetrics.DUPLICATED_BLOCKS, duplicatedBlocks); Measure data = new Measure(CoreMetrics.DUPLICATIONS_DATA, toXml(duplications)) .setPersistenceMode(PersistenceMode.DATABASE); context.saveMeasure(resource, data); } private static String toXml(Iterable<CloneGroup> duplications) { StringBuilder xml = new StringBuilder(); xml.append("<duplications>"); for (CloneGroup duplication : duplications) { xml.append("<g>"); for (ClonePart part : duplication.getCloneParts()) { xml.append("<b s=\"").append(part.getStartLine()).append("\" l=\"").append(part.getLines()) .append("\" r=\"").append(StringEscapeUtils.escapeXml(part.getResourceId())).append("\"/>"); } xml.append("</g>"); } xml.append("</duplications>"); return xml.toString(); } }