001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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.squid.bridges;
021    
022    import org.slf4j.Logger;
023    import org.slf4j.LoggerFactory;
024    import org.sonar.api.batch.SensorContext;
025    import org.sonar.api.design.Dependency;
026    import org.sonar.api.measures.CoreMetrics;
027    import org.sonar.api.measures.Measure;
028    import org.sonar.api.measures.Metric;
029    import org.sonar.api.measures.PersistenceMode;
030    import org.sonar.api.resources.Project;
031    import org.sonar.api.resources.Resource;
032    import org.sonar.api.utils.TimeProfiler;
033    import org.sonar.graph.*;
034    import org.sonar.squid.Squid;
035    import org.sonar.squid.api.SourceCode;
036    import org.sonar.squid.api.SourceCodeEdge;
037    import org.sonar.squid.api.SourcePackage;
038    import org.sonar.squid.api.SourceProject;
039    
040    import java.util.Collection;
041    import java.util.List;
042    import java.util.Set;
043    
044    public 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, (double) cyclesAndFESSolver.getCycles().size(), true);
074          savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_FEEDBACK_EDGES, (double) feedbackEdges.size(), true);
075          savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_TANGLES, (double) tangles, true);
076          savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_EDGES_WEIGHT, getEdgesWeight(squidPackages), false);
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, boolean strict) {
087        if ((strict && value > 0.0) || ( !strict && 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, (double) cycles.size(), false);
107          savePositiveMeasure(sonarPackage, CoreMetrics.FILE_FEEDBACK_EDGES, (double) feedbackEdges.size(), false);
108          savePositiveMeasure(sonarPackage, CoreMetrics.FILE_TANGLES, (double) tangles, false);
109          savePositiveMeasure(sonarPackage, CoreMetrics.FILE_EDGES_WEIGHT, getEdgesWeight(squidFiles), false);
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    }