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.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());
074 savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_FEEDBACK_EDGES, (double) feedbackEdges.size());
075 savePositiveMeasure(sonarProject, CoreMetrics.PACKAGE_TANGLES, (double) 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, (double) cycles.size());
107 savePositiveMeasure(sonarPackage, CoreMetrics.FILE_FEEDBACK_EDGES, (double) feedbackEdges.size());
108 savePositiveMeasure(sonarPackage, CoreMetrics.FILE_TANGLES, (double) 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 }