Package org.sonar.plugins.cxx.squid

Source Code of org.sonar.plugins.cxx.squid.DependencyAnalyzer

/*
* Sonar C++ Plugin (Community)
* Copyright (C) 2010 Neticoa SAS France
* dev@sonar.codehaus.org
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
*/
package org.sonar.plugins.cxx.squid;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.sonar.api.batch.SensorContext;
import org.sonar.api.checks.CheckFactory;
import org.sonar.api.component.ResourcePerspectives;
import org.sonar.api.design.Dependency;
import org.sonar.api.issue.Issuable;
import org.sonar.api.issue.Issue;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Measure;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.PersistenceMode;
import org.sonar.api.resources.Directory;
import org.sonar.api.resources.File;
import org.sonar.api.resources.Project;
import org.sonar.api.resources.Resource;
import org.sonar.api.rules.ActiveRule;
import org.sonar.cxx.checks.CycleBetweenPackagesCheck;
import org.sonar.cxx.checks.DuplicatedIncludeCheck;
import org.sonar.cxx.preprocessor.CxxPreprocessor;
import org.sonar.graph.*;
import org.sonar.plugins.cxx.CxxMetrics;
import org.sonar.plugins.cxx.utils.CxxUtils;

import java.util.Collection;
import java.util.HashMap;
import java.util.Set;

public class DependencyAnalyzer {

  private Project project;
  private SensorContext context;
  private CheckFactory checkFactory;
  private ResourcePerspectives perspectives;
  private int violationsCount;
  private ActiveRule duplicateIncludeRule;

  private DirectedGraph<File, FileEdge> filesGraph = new DirectedGraph<File, FileEdge>();
  private DirectedGraph<Directory, DirectoryEdge> packagesGraph = new DirectedGraph<Directory, DirectoryEdge>();
  private HashMap<Edge, Dependency> dependencyIndex = new HashMap<Edge, Dependency>();
  private Multimap<Directory, File> directoryFiles = HashMultimap.create();

  public DependencyAnalyzer(ResourcePerspectives perspectives, Project project, SensorContext context, CheckFactory checkFactory) {
    this.project = project;
    this.context = context;
    this.checkFactory = checkFactory;
    this.perspectives = perspectives;

    this.violationsCount = 0;
    this.duplicateIncludeRule = DuplicatedIncludeCheck.getActiveRule(checkFactory);
  }

  public void addFile(File sonarFile, Collection<CxxPreprocessor.Include> includedFiles) {
    //Store the directory and file
    Directory sonarDir = sonarFile.getParent();
    packagesGraph.addVertex(sonarDir);
    directoryFiles.put(sonarDir, sonarFile);

    //Build the dependency graph
    for (CxxPreprocessor.Include include : includedFiles) {
      File includedFile = File.fromIOFile(new java.io.File(include.getPath()), project);
      if (includedFile == null) {
        CxxUtils.LOG.warn("Unable to find resource '" + include.getPath() + "' to create a dependency with '" + sonarFile.getKey() + "'");
      }
      else if (filesGraph.hasEdge(sonarFile, includedFile)) {
        FileEdge fileEdge = filesGraph.getEdge(sonarFile, includedFile);
        Issuable issuable = perspectives.as(Issuable.class, sonarFile);
        if ((issuable != null) && (duplicateIncludeRule != null)) {
          Issue issue = issuable.newIssueBuilder()
              .ruleKey(duplicateIncludeRule.getRule().ruleKey())
              .line(include.getLine())
              .message("Remove duplicated include, \"" + includedFile.getLongName() + "\" is already included at line " + fileEdge.getLine() + ".")
              .build();
          if (issuable.addIssue(issue))
            violationsCount++;
        }
        else {
          CxxUtils.LOG.warn("Already created edge from '" + sonarFile.getKey() + "' (line " + include.getLine() + ") to '" + includedFile.getKey() + "'" +
              ", previous edge from line " + fileEdge.getLine());
        }
      }
      else {
        //Add the dependency in the files graph
        FileEdge fileEdge = new FileEdge(sonarFile, includedFile, include.getLine());
        filesGraph.addEdge(fileEdge);

        //Add the dependency in the packages graph, if the directories are different
        Directory includedDir = includedFile.getParent();
        if (!sonarDir.equals(includedDir)) {
          DirectoryEdge edge = packagesGraph.getEdge(sonarDir, includedDir);
          if (edge == null) {
            edge = new DirectoryEdge(sonarDir, includedDir);
            packagesGraph.addEdge(edge);
          }
          edge.addRootEdge(fileEdge);
        }
      }
    }
  }

  /** Perform the analysis and save the results.
   */
  public void save() {
    final Collection<Directory> packages = packagesGraph.getVertices();
    for (Directory dir : packages) {
      //Save dependencies (cross-directories, including cross-directory file dependencies)
      for (DirectoryEdge edge : packagesGraph.getOutgoingEdges(dir)) {
        Dependency dependency = new Dependency(dir, edge.getTo())
            .setUsage("references")
            .setWeight(edge.getWeight())
            .setParent(null);
        context.saveDependency(dependency);
        dependencyIndex.put(edge, dependency);

        for(FileEdge subEdge : edge.getRootEdges()) {
          saveFileEdge(subEdge, dependency);
        }
      }

      //Save file dependencies (inside directory) & directory metrics
      saveDirectory(dir);
    }

    IncrementalCyclesAndFESSolver<Directory> cycleDetector = new IncrementalCyclesAndFESSolver<Directory>(packagesGraph, packages);
    Set<Cycle> cycles = cycleDetector.getCycles();
    Set<Edge> feedbackEdges = cycleDetector.getFeedbackEdgeSet();
    int tangles = cycleDetector.getWeightOfFeedbackEdgeSet();

    CxxUtils.LOG.info("Project '" + project.getKey() + "'"
        + " Cycles:" + cycles.size()
        + " Feedback cycles:" + feedbackEdges.size()
        + " Tangles:" + tangles
        + " Weight:" + getEdgesWeight(packagesGraph.getEdges(packages)));

    saveViolations(feedbackEdges, packagesGraph);
    savePositiveMeasure(project, CoreMetrics.PACKAGE_CYCLES, cycles.size());
    savePositiveMeasure(project, CoreMetrics.PACKAGE_FEEDBACK_EDGES, feedbackEdges.size());
    savePositiveMeasure(project, CoreMetrics.PACKAGE_TANGLES, tangles);
    savePositiveMeasure(project, CoreMetrics.PACKAGE_EDGES_WEIGHT, getEdgesWeight(packagesGraph.getEdges(packages)));

    String dsmJson = serializeDsm(packages, feedbackEdges);
    Measure dsmMeasure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, dsmJson)
        .setPersistenceMode(PersistenceMode.DATABASE);
    context.saveMeasure(project, dsmMeasure);
  }

  private void saveDirectory(Directory dir) {
    final Collection<File> files = directoryFiles.get(dir);
    for(File file: files) {
      for (FileEdge edge : filesGraph.getOutgoingEdges(file)) {
        saveFileEdge(edge, null);
      }
    }

    IncrementalCyclesAndFESSolver<File> cycleDetector = new IncrementalCyclesAndFESSolver<File>(filesGraph, files);
    Set<Cycle> cycles = cycleDetector.getCycles();
    MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles);
    Set<Edge> feedbackEdges = solver.getEdges();
    int tangles = solver.getWeightOfFeedbackEdgeSet();

    CxxUtils.LOG.info("Directory: '" + dir.getKey() + "'"
        + " Cycles:" + cycles.size()
        + " Feedback cycles:" + feedbackEdges.size()
        + " Tangles:" + tangles
        + " Weight:" + getEdgesWeight(filesGraph.getEdges(files)));

    savePositiveMeasure(dir, CoreMetrics.FILE_CYCLES, cycles.size());
    savePositiveMeasure(dir, CoreMetrics.FILE_FEEDBACK_EDGES, feedbackEdges.size());
    savePositiveMeasure(dir, CoreMetrics.FILE_TANGLES, tangles);
    savePositiveMeasure(dir, CoreMetrics.FILE_EDGES_WEIGHT, getEdgesWeight(filesGraph.getEdges(files)));

    String dsmJson = serializeDsm(files, feedbackEdges);
    context.saveMeasure(dir, new Measure(CoreMetrics.DEPENDENCY_MATRIX, dsmJson));
  }

  private void saveViolations(Set<Edge> feedbackEdges, DirectedGraph<Directory, DirectoryEdge> packagesGraph) {
    ActiveRule cycleBetweenPackagesRule = CycleBetweenPackagesCheck.getActiveRule(checkFactory);
    if (cycleBetweenPackagesRule != null) {
      for (Edge feedbackEdge : feedbackEdges) {
        Directory fromPackage = (Directory) feedbackEdge.getFrom();
        Directory toPackage = (Directory) feedbackEdge.getTo();
        DirectoryEdge edge = packagesGraph.getEdge(fromPackage, toPackage);
        for (FileEdge subEdge : edge.getRootEdges()) {
          Resource fromFile = subEdge.getFrom();
          Resource toFile = subEdge.getTo();
          Issuable issuable = perspectives.as(Issuable.class, fromFile);
          // If resource cannot be obtained, then silently ignore, because anyway warning will be printed by method addFile
          if ((issuable != null) && (fromFile != null) && (toFile != null)) {
            Issue issue = issuable.newIssueBuilder()
                .ruleKey(cycleBetweenPackagesRule.getRule().ruleKey())
                .line(subEdge.getLine())
                .message("Remove the dependency from file \"" + fromFile.getLongName()
                    + "\" to file \"" + toFile.getLongName() + "\" to break a package cycle.")
                .effortToFix((double) subEdge.getWeight())
                .build();
            if (issuable.addIssue(issue))
              violationsCount++;
          }
        }
      }
    }
    if (cycleBetweenPackagesRule != null || duplicateIncludeRule != null) {
      Measure measure = new Measure(CxxMetrics.DEPENDENCIES);
      measure.setIntValue(violationsCount);
      context.saveMeasure(measure);
    }
  }

  private void saveFileEdge(FileEdge edge, Dependency parent) {
    if (!dependencyIndex.containsKey(edge)) {
      Dependency dependency = new Dependency(edge.getFrom(), edge.getTo())
          .setUsage("includes")
          .setWeight(edge.getWeight())
          .setParent(parent);
      context.saveDependency(dependency);
      dependencyIndex.put(edge, dependency);
    }
  }

  private double getEdgesWeight(Collection<? extends Edge> edges) {
    double total = 0.0;
    for (Edge edge : edges) {
      total += edge.getWeight();
    }
    return total;
  }

  private String serializeDsm(Collection<? extends Resource> vertices, Set<Edge> feedbackEdges) {
    Dsm<? extends Resource> dsm = new Dsm(packagesGraph, vertices, feedbackEdges);
    DsmTopologicalSorter.sort(dsm);
    return DsmSerializer.serialize(dsm, dependencyIndex);
  }

  private void savePositiveMeasure(Resource sonarResource, Metric metric, double value) {
    if (value >= 0.0) {
      context.saveMeasure(sonarResource, metric, value);
    }
  }

}
TOP

Related Classes of org.sonar.plugins.cxx.squid.DependencyAnalyzer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.