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.squid.bridges;
021
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024import org.sonar.api.batch.SensorContext;
025import org.sonar.api.design.Dependency;
026import org.sonar.api.measures.CoreMetrics;
027import org.sonar.api.measures.Measure;
028import org.sonar.api.measures.Metric;
029import org.sonar.api.measures.PersistenceMode;
030import org.sonar.api.resources.Project;
031import org.sonar.api.resources.Resource;
032import org.sonar.api.utils.TimeProfiler;
033import org.sonar.graph.*;
034import org.sonar.squid.Squid;
035import org.sonar.squid.api.SourceCode;
036import org.sonar.squid.api.SourceCodeEdge;
037import org.sonar.squid.api.SourcePackage;
038import org.sonar.squid.api.SourceProject;
039
040import java.util.Collection;
041import java.util.List;
042import java.util.Set;
043
044public class DesignBridge extends Bridge {
045
046  private static final Logger LOG = LoggerFactory.getLogger(DesignBridge.class);
047
048  /*
049   * This index is shared between onProject() and onPackage(). It works because onProject() is executed before onPackage().
050   */
051  private DependencyIndex dependencyIndex = new DependencyIndex();
052
053  protected DesignBridge() {
054    super(true);
055  }
056
057  @Override
058  public void onProject(SourceProject squidProject, Project sonarProject) {
059    Set<SourceCode> squidPackages = squidProject.getChildren();
060    if (squidPackages != null && !squidPackages.isEmpty()) {
061      TimeProfiler profiler = new TimeProfiler(LOG).start("Package design analysis");
062      LOG.debug("{} packages to analyze", squidPackages.size());
063
064      savePackageDependencies(squidPackages);
065
066      IncrementalCyclesAndFESSolver<SourceCode> cyclesAndFESSolver = new IncrementalCyclesAndFESSolver<SourceCode>(squid, squidPackages);
067      LOG.debug("{} cycles", cyclesAndFESSolver.getCycles().size());
068
069      Set<Edge> feedbackEdges = cyclesAndFESSolver.getFeedbackEdgeSet();
070      LOG.debug("{} feedback edges", feedbackEdges.size());
071      int tangles = cyclesAndFESSolver.getWeightOfFeedbackEdgeSet();
072
073      savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_CYCLES, cyclesAndFESSolver.getCycles().size());
074      savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_FEEDBACK_EDGES, feedbackEdges.size());
075      savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_TANGLES, tangles);
076      savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_EDGES_WEIGHT, getEdgesWeight(squidPackages));
077
078      String dsmJson = serializeDsm(squid, squidPackages, feedbackEdges);
079      Measure dsmMeasure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, dsmJson).setPersistenceMode(PersistenceMode.DATABASE);
080      context.saveMeasure(sonarProject, dsmMeasure);
081
082      profiler.stop();
083    }
084  }
085
086  private void savePositiveMeasure(Resource sonarResource, Metric metric, double value) {
087    if (value >= 0.0) {
088      context.saveMeasure(sonarResource, metric, value);
089    }
090  }
091
092  @Override
093  public void onPackage(SourcePackage squidPackage, Resource sonarPackage) {
094    Set<SourceCode> squidFiles = squidPackage.getChildren();
095    if (squidFiles != null && !squidFiles.isEmpty()) {
096
097      saveFileDependencies(squidFiles);
098
099      IncrementalCyclesAndFESSolver<SourceCode> cycleDetector = new IncrementalCyclesAndFESSolver<SourceCode>(squid, squidFiles);
100      Set<Cycle> cycles = cycleDetector.getCycles();
101
102      MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles);
103      Set<Edge> feedbackEdges = solver.getEdges();
104      int tangles = solver.getWeightOfFeedbackEdgeSet();
105
106      savePositiveMeasure(sonarPackage, CoreMetrics.FILE_CYCLES, cycles.size());
107      savePositiveMeasure(sonarPackage, CoreMetrics.FILE_FEEDBACK_EDGES, feedbackEdges.size());
108      savePositiveMeasure(sonarPackage, CoreMetrics.FILE_TANGLES, tangles);
109      savePositiveMeasure(sonarPackage, CoreMetrics.FILE_EDGES_WEIGHT, getEdgesWeight(squidFiles));
110
111      String dsmJson = serializeDsm(squid, squidFiles, feedbackEdges);
112      context.saveMeasure(sonarPackage, new Measure(CoreMetrics.DEPENDENCY_MATRIX, dsmJson));
113    }
114  }
115
116  private double getEdgesWeight(Collection<SourceCode> sourceCodes) {
117    List<SourceCodeEdge> edges = squid.getEdges(sourceCodes);
118    double total = 0.0;
119    for (SourceCodeEdge edge : edges) {
120      total += edge.getWeight();
121    }
122    return total;
123  }
124
125  private String serializeDsm(Squid squid, Set<SourceCode> squidSources, Set<Edge> feedbackEdges) {
126    Dsm<SourceCode> dsm = new Dsm<SourceCode>(squid, squidSources, feedbackEdges);
127    DsmTopologicalSorter.sort(dsm);
128    return DsmSerializer.serialize(dsm, dependencyIndex, resourceIndex);
129  }
130
131  /**
132   * Save package dependencies, including root file dependencies
133   */
134  public void savePackageDependencies(Set<SourceCode> squidPackages) {
135    for (SourceCode squidPackage : squidPackages) {
136      for (SourceCodeEdge edge : squid.getOutgoingEdges(squidPackage)) {
137        Dependency dependency = saveEdge(edge, context, null);
138        if (dependency != null) {
139          // save file dependencies
140          for (SourceCodeEdge subEdge : edge.getRootEdges()) {
141            saveEdge(subEdge, context, dependency);
142          }
143        }
144      }
145    }
146  }
147
148  /**
149   * Save file dependencies
150   */
151  public void saveFileDependencies(Set<SourceCode> squidFiles) {
152    for (SourceCode squidFile : squidFiles) {
153      for (SourceCodeEdge edge : squid.getOutgoingEdges(squidFile)) {
154        saveEdge(edge, context, null);
155      }
156    }
157  }
158
159  private Dependency saveEdge(SourceCodeEdge edge, SensorContext context, Dependency parentDependency) {
160    Dependency dependency = dependencyIndex.get(edge);
161    if (dependency == null) {
162      Resource from = resourceIndex.get(edge.getFrom());
163      Resource to = resourceIndex.get(edge.getTo());
164      if (from != null && to != null) {
165        dependency = new Dependency(from, to).setUsage(edge.getUsage().name()).setWeight(edge.getWeight()).setParent(parentDependency);
166        context.saveDependency(dependency);
167        dependencyIndex.put(edge, dependency);
168      } else {
169        if (from == null) {
170          LOG.warn("Unable to find resource '" + edge.getFrom() + "' to create a dependency with '" + edge.getTo() + "'");
171        }
172        if (to == null) {
173          LOG.warn("Unable to find resource '" + edge.getTo() + "' to create a dependency with '" + edge.getFrom() + "'");
174        }
175        return null;
176      }
177    }
178    return dependency;
179  }
180}