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 }