From 07dee4c58e8be5e83c3c3c17ee2c47600ccf27e6 Mon Sep 17 00:00:00 2001 From: Julien HENRY Date: Fri, 7 Nov 2014 15:53:07 +0100 Subject: [PATCH] SONAR-5672 Don't save DSM when there is no dependency or more than 200 components --- .../batch/design/DirectoryDsmDecorator.java | 98 ++------ .../org/sonar/batch/design/DsmDecorator.java | 127 +++++++++++ .../batch/design/ProjectDsmDecorator.java | 67 ++---- .../batch/design/SubProjectDsmDecorator.java | 100 ++------ .../org/sonar/batch/index/DefaultIndex.java | 10 +- .../design/DirectoryDsmDecoratorTest.java | 214 ++++++++++++++++++ .../batch/design/ProjectDsmDecoratorTest.java | 149 ++++++++++++ .../design/SubProjectDsmDecoratorTest.java | 157 +++++++++++++ .../batch/scan/measure/MeasureCacheTest.java | 4 - .../java/org/sonar/graph/DirectedGraph.java | 10 +- .../src/main/java/org/sonar/graph/Dsm.java | 34 ++- .../main/java/org/sonar/graph/DsmScanner.java | 6 +- .../java/org/sonar/graph/FeedbackCycle.java | 12 +- .../graph/IncrementalCyclesAndFESSolver.java | 6 +- .../graph/MinimumFeedbackEdgeSetSolver.java | 14 +- 15 files changed, 766 insertions(+), 242 deletions(-) create mode 100644 sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java create mode 100644 sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryDsmDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryDsmDecorator.java index 0446d61fd38..95c3da4ae8c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryDsmDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryDsmDecorator.java @@ -19,107 +19,55 @@ */ package org.sonar.batch.design; -import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.batch.SonarIndex; -import org.sonar.api.design.Dependency; 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.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; import org.sonar.graph.Cycle; -import org.sonar.graph.Dsm; -import org.sonar.graph.DsmTopologicalSorter; import org.sonar.graph.Edge; import org.sonar.graph.IncrementalCyclesAndFESSolver; import org.sonar.graph.MinimumFeedbackEdgeSetSolver; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Set; -public class DirectoryDsmDecorator implements Decorator { - - private SonarIndex index; +public class DirectoryDsmDecorator extends DsmDecorator { public DirectoryDsmDecorator(SonarIndex index) { - this.index = index; + super(index); } @Override - public boolean shouldExecuteOnProject(Project project) { - return true; - } - - @Override - public void decorate(final Resource resource, DecoratorContext context) { - if (shouldDecorateResource(resource, context)) { - List fileContexts = context.getChildren(); - List files = new ArrayList(fileContexts.size()); - for (DecoratorContext decoratorContext : fileContexts) { - files.add(decoratorContext.getResource()); - } - - IncrementalCyclesAndFESSolver cycleDetector = new IncrementalCyclesAndFESSolver(index, files); - Set cycles = cycleDetector.getCycles(); - - MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); - Set feedbackEdges = solver.getEdges(); - int tangles = solver.getWeightOfFeedbackEdgeSet(); - - savePositiveMeasure(context, CoreMetrics.FILE_CYCLES, cycles.size()); - savePositiveMeasure(context, CoreMetrics.FILE_FEEDBACK_EDGES, feedbackEdges.size()); - savePositiveMeasure(context, CoreMetrics.FILE_TANGLES, tangles); - savePositiveMeasure(context, CoreMetrics.FILE_EDGES_WEIGHT, getEdgesWeight(files)); - - Dsm dsm = getDsm(files, feedbackEdges); - saveDsm(context, dsm); - } - } - - private void savePositiveMeasure(DecoratorContext context, Metric metric, double value) { - if (value >= 0.0) { - context.saveMeasure(new Measure(metric, value)); + protected List getChildren(Resource resource, DecoratorContext context) { + List fileContexts = context.getChildren(); + List files = new ArrayList(fileContexts.size()); + for (DecoratorContext decoratorContext : fileContexts) { + files.add(decoratorContext.getResource()); } + return files; } - private int getEdgesWeight(Collection sourceCodes) { - List edges = getEdges(sourceCodes); - int total = 0; - for (Dependency edge : edges) { - total += edge.getWeight(); - } - return total; - } + @Override + protected Set doProcess(List children, DecoratorContext context) { + IncrementalCyclesAndFESSolver cycleDetector = new IncrementalCyclesAndFESSolver(getIndex(), children); + Set cycles = cycleDetector.getCycles(); - public List getEdges(Collection vertices) { - List result = new ArrayList(); - for (Resource vertice : vertices) { - Collection outgoingEdges = index.getOutgoingEdges(vertice); - if (outgoingEdges != null) { - result.addAll(outgoingEdges); - } - } - return result; - } + MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); + Set feedbackEdges = solver.getEdges(); + int tangles = solver.getWeightOfFeedbackEdgeSet(); - private void saveDsm(DecoratorContext context, Dsm dsm) { - Measure measure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, DsmSerializer.serialize(dsm)); - measure.setPersistenceMode(PersistenceMode.DATABASE); - context.saveMeasure(measure); - } + savePositiveMeasure(context, CoreMetrics.FILE_CYCLES, cycles.size()); + savePositiveMeasure(context, CoreMetrics.FILE_FEEDBACK_EDGES, feedbackEdges.size()); + savePositiveMeasure(context, CoreMetrics.FILE_TANGLES, tangles); + savePositiveMeasure(context, CoreMetrics.FILE_EDGES_WEIGHT, getEdgesWeight(children)); - private Dsm getDsm(Collection files, Set feedbackEdges) { - Dsm dsm = new Dsm(index, files, feedbackEdges); - DsmTopologicalSorter.sort(dsm); - return dsm; + return feedbackEdges; } - private boolean shouldDecorateResource(Resource resource, DecoratorContext context) { - return ResourceUtils.isDirectory(resource) && context.getMeasure(CoreMetrics.DEPENDENCY_MATRIX) == null; + @Override + protected boolean shouldDecorateResource(Resource resource, DecoratorContext context) { + return ResourceUtils.isDirectory(resource); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java new file mode 100644 index 00000000000..3569ba9a913 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/DsmDecorator.java @@ -0,0 +1,127 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 02110-1301, USA. + */ +package org.sonar.batch.design; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.design.Dependency; +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.Project; +import org.sonar.api.resources.Resource; +import org.sonar.graph.Dsm; +import org.sonar.graph.DsmTopologicalSorter; +import org.sonar.graph.Edge; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; + +public abstract class DsmDecorator implements Decorator { + + private static final Logger LOG = LoggerFactory.getLogger(DsmDecorator.class); + + private static final int MAX_DSM_DIMENSION = 200; + private SonarIndex index; + + public DsmDecorator(SonarIndex index) { + this.index = index; + } + + public final SonarIndex getIndex() { + return index; + } + + @Override + public final boolean shouldExecuteOnProject(Project project) { + return true; + } + + @Override + public final void decorate(final Resource resource, DecoratorContext context) { + if (shouldDecorateResource(resource, context)) { + List children = getChildren(resource, context); + if (children.isEmpty()) { + return; + } + Set feedbackEdges = doProcess(children, context); + + if (children.size() > MAX_DSM_DIMENSION) { + LOG.warn("Too many components under resource '" + resource.getName() + "'. DSM will not be displayed."); + return; + } + Dsm dsm = getDsm(children, feedbackEdges); + // Optimization, don't save DSM if there is no dependency at all + if (dsm.hasAtLeastOneDependency()) { + saveDsm(context, dsm); + } + } + } + + protected abstract boolean shouldDecorateResource(Resource resource, DecoratorContext context); + + protected abstract List getChildren(Resource resource, DecoratorContext context); + + protected abstract Set doProcess(List children, DecoratorContext context); + + protected final void saveDsm(DecoratorContext context, Dsm dsm) { + Measure measure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, DsmSerializer.serialize(dsm)); + measure.setPersistenceMode(PersistenceMode.DATABASE); + context.saveMeasure(measure); + } + + protected final Dsm getDsm(Collection children, Set feedbackEdges) { + Dsm dsm = new Dsm(index, children, feedbackEdges); + DsmTopologicalSorter.sort(dsm); + return dsm; + } + + protected final void savePositiveMeasure(DecoratorContext context, Metric metric, double value) { + if (value >= 0.0) { + context.saveMeasure(new Measure(metric, value)); + } + } + + protected final int getEdgesWeight(Collection sourceCodes) { + List edges = getEdges(sourceCodes); + int total = 0; + for (Dependency edge : edges) { + total += edge.getWeight(); + } + return total; + } + + protected final List getEdges(Collection vertices) { + List result = new ArrayList(); + for (Resource vertice : vertices) { + Collection outgoingEdges = index.getOutgoingEdges(vertice); + if (outgoingEdges != null) { + result.addAll(outgoingEdges); + } + } + return result; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/ProjectDsmDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/ProjectDsmDecorator.java index f9ae4de6a50..66471955795 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/design/ProjectDsmDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/design/ProjectDsmDecorator.java @@ -20,81 +20,38 @@ package org.sonar.batch.design; import com.google.common.collect.Lists; -import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.batch.SonarIndex; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Measure; -import org.sonar.api.measures.PersistenceMode; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; import org.sonar.graph.Cycle; import org.sonar.graph.CycleDetector; -import org.sonar.graph.Dsm; -import org.sonar.graph.DsmTopologicalSorter; import org.sonar.graph.Edge; import org.sonar.graph.MinimumFeedbackEdgeSetSolver; -import java.util.Collection; import java.util.List; import java.util.Set; -public class ProjectDsmDecorator implements Decorator { - - private SonarIndex index; +public class ProjectDsmDecorator extends DsmDecorator { public ProjectDsmDecorator(SonarIndex index) { - this.index = index; - } - - @Override - public boolean shouldExecuteOnProject(Project project) { - return true; - } - - @Override - public void decorate(final Resource resource, DecoratorContext context) { - if (shouldDecorateResource(resource, context)) { - Collection subProjects = getSubProjects((Project) resource); - - if (!subProjects.isEmpty()) { - Dsm dsm = getDsm(subProjects); - saveDsm(context, dsm); - } - } - } - - private void saveDsm(DecoratorContext context, Dsm dsm) { - Measure measure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, DsmSerializer.serialize(dsm)); - measure.setPersistenceMode(PersistenceMode.DATABASE); - context.saveMeasure(measure); - } - - private Dsm getDsm(Collection subProjects) { - CycleDetector cycleDetector = new CycleDetector(index, subProjects); - Set cycles = cycleDetector.getCycles(); - - MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); - Set feedbackEdges = solver.getEdges(); - - Dsm dsm = new Dsm(index, subProjects, feedbackEdges); - DsmTopologicalSorter.sort(dsm); - return dsm; + super(index); } /** * sub-projects, including all descendants but not only direct children */ - private Collection getSubProjects(final Project project) { + @Override + protected List getChildren(Resource resource, DecoratorContext context) { List subProjects = Lists.newArrayList(); - addSubProjects(project, subProjects); + addSubProjects((Project) resource, subProjects); return subProjects; } private void addSubProjects(Project project, List subProjects) { for (Project subProject : project.getModules()) { - Project indexedSubProject = index.getResource(subProject); + Project indexedSubProject = getIndex().getResource(subProject); if (indexedSubProject != null) { subProjects.add(indexedSubProject); } @@ -102,7 +59,17 @@ public class ProjectDsmDecorator implements Decorator { } } - private boolean shouldDecorateResource(Resource resource, DecoratorContext context) { + @Override + protected Set doProcess(List children, DecoratorContext context) { + CycleDetector cycleDetector = new CycleDetector(getIndex(), children); + Set cycles = cycleDetector.getCycles(); + + MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); + return solver.getEdges(); + } + + @Override + protected boolean shouldDecorateResource(Resource resource, DecoratorContext context) { // Should not execute on views return (ResourceUtils.isRootProject(resource) || ResourceUtils.isModuleProject(resource)) && !((Project) resource).getModules().isEmpty(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/SubProjectDsmDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/SubProjectDsmDecorator.java index 3d850f3ab50..9e03a13c7df 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/design/SubProjectDsmDecorator.java +++ b/sonar-batch/src/main/java/org/sonar/batch/design/SubProjectDsmDecorator.java @@ -19,107 +19,55 @@ */ package org.sonar.batch.design; -import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.batch.SonarIndex; -import org.sonar.api.design.Dependency; 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.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; import org.sonar.graph.Cycle; -import org.sonar.graph.Dsm; -import org.sonar.graph.DsmTopologicalSorter; import org.sonar.graph.Edge; import org.sonar.graph.IncrementalCyclesAndFESSolver; import org.sonar.graph.MinimumFeedbackEdgeSetSolver; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Set; -public class SubProjectDsmDecorator implements Decorator { - - private SonarIndex index; +public class SubProjectDsmDecorator extends DsmDecorator { public SubProjectDsmDecorator(SonarIndex index) { - this.index = index; - } - - @Override - public boolean shouldExecuteOnProject(Project project) { - return true; + super(index); } @Override - public void decorate(final Resource resource, DecoratorContext context) { - if (shouldDecorateResource(resource, context)) { - List directoryContexts = context.getChildren(); - List directories = new ArrayList(directoryContexts.size()); - for (DecoratorContext decoratorContext : directoryContexts) { - directories.add(decoratorContext.getResource()); - } - - IncrementalCyclesAndFESSolver cycleDetector = new IncrementalCyclesAndFESSolver(index, directories); - Set cycles = cycleDetector.getCycles(); - - MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); - Set feedbackEdges = solver.getEdges(); - int tangles = solver.getWeightOfFeedbackEdgeSet(); - - savePositiveMeasure(context, CoreMetrics.DIRECTORY_CYCLES, cycles.size()); - savePositiveMeasure(context, CoreMetrics.DIRECTORY_FEEDBACK_EDGES, feedbackEdges.size()); - savePositiveMeasure(context, CoreMetrics.DIRECTORY_TANGLES, tangles); - savePositiveMeasure(context, CoreMetrics.DIRECTORY_EDGES_WEIGHT, getEdgesWeight(directories)); - - Dsm dsm = getDsm(directories, feedbackEdges); - saveDsm(context, dsm); - } - } - - private void savePositiveMeasure(DecoratorContext context, Metric metric, double value) { - if (value >= 0.0) { - context.saveMeasure(new Measure(metric, value)); + protected List getChildren(Resource resource, DecoratorContext context) { + List directoryContexts = context.getChildren(); + List directories = new ArrayList(directoryContexts.size()); + for (DecoratorContext decoratorContext : directoryContexts) { + directories.add(decoratorContext.getResource()); } + return directories; } - private int getEdgesWeight(Collection sourceCodes) { - List edges = getEdges(sourceCodes); - int total = 0; - for (Dependency edge : edges) { - total += edge.getWeight(); - } - return total; - } - - public List getEdges(Collection vertices) { - List result = new ArrayList(); - for (Resource vertice : vertices) { - Collection outgoingEdges = index.getOutgoingEdges(vertice); - if (outgoingEdges != null) { - result.addAll(outgoingEdges); - } - } - return result; - } - - private void saveDsm(DecoratorContext context, Dsm dsm) { - Measure measure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, DsmSerializer.serialize(dsm)); - measure.setPersistenceMode(PersistenceMode.DATABASE); - context.saveMeasure(measure); - } - - private Dsm getDsm(Collection directories, Set feedbackEdges) { - Dsm dsm = new Dsm(index, directories, feedbackEdges); - DsmTopologicalSorter.sort(dsm); - return dsm; + @Override + protected Set doProcess(List children, DecoratorContext context) { + IncrementalCyclesAndFESSolver cycleDetector = new IncrementalCyclesAndFESSolver(getIndex(), children); + Set cycles = cycleDetector.getCycles(); + + MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); + Set feedbackEdges = solver.getEdges(); + int tangles = solver.getWeightOfFeedbackEdgeSet(); + + savePositiveMeasure(context, CoreMetrics.DIRECTORY_CYCLES, cycles.size()); + savePositiveMeasure(context, CoreMetrics.DIRECTORY_FEEDBACK_EDGES, feedbackEdges.size()); + savePositiveMeasure(context, CoreMetrics.DIRECTORY_TANGLES, tangles); + savePositiveMeasure(context, CoreMetrics.DIRECTORY_EDGES_WEIGHT, getEdgesWeight(children)); + return feedbackEdges; } - private boolean shouldDecorateResource(Resource resource, DecoratorContext context) { + @Override + protected boolean shouldDecorateResource(Resource resource, DecoratorContext context) { // Should not execute on views return (ResourceUtils.isRootProject(resource) || ResourceUtils.isModuleProject(resource)) // Only on leaf projects diff --git a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java index 9cc85c83c2b..b1235f09964 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java +++ b/sonar-batch/src/main/java/org/sonar/batch/index/DefaultIndex.java @@ -80,11 +80,11 @@ public class DefaultIndex extends SonarIndex { // caches private Project currentProject; - private Map buckets = Maps.newHashMap(); - private Map bucketsByDeprecatedKey = Maps.newHashMap(); - private Set dependencies = Sets.newHashSet(); - private Map> outgoingDependenciesByResource = Maps.newHashMap(); - private Map> incomingDependenciesByResource = Maps.newHashMap(); + private Map buckets = Maps.newLinkedHashMap(); + private Map bucketsByDeprecatedKey = Maps.newLinkedHashMap(); + private Set dependencies = Sets.newLinkedHashSet(); + private Map> outgoingDependenciesByResource = Maps.newLinkedHashMap(); + private Map> incomingDependenciesByResource = Maps.newLinkedHashMap(); private ProjectTree projectTree; private final DeprecatedViolations deprecatedViolations; private ModuleIssues moduleIssues; diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java new file mode 100644 index 00000000000..48a7b814361 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/design/DirectoryDsmDecoratorTest.java @@ -0,0 +1,214 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 02110-1301, USA. + */ +package org.sonar.batch.design; + +import edu.emory.mathcs.backport.java.util.Collections; +import org.apache.commons.lang.ObjectUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.design.Dependency; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.File; +import org.sonar.api.resources.Project; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class DirectoryDsmDecoratorTest { + + private DirectoryDsmDecorator decorator; + private Directory dir; + private DecoratorContext dirContext; + private SonarIndex index; + private File file1; + private File file2; + private DecoratorContext file1Context; + private DecoratorContext file2Context; + + @Before + public void prepare() { + index = mock(SonarIndex.class); + decorator = new DirectoryDsmDecorator(index); + dir = Directory.create("src"); + dirContext = mock(DecoratorContext.class); + + file1 = File.create("src/Foo1.java", "Foo1.java", null, false); + file1.setId(1); + file2 = File.create("src/Foo2.java", "Foo2.java", null, false); + file2.setId(2); + + file1Context = mock(DecoratorContext.class); + when(file1Context.getResource()).thenReturn(file1); + file2Context = mock(DecoratorContext.class); + when(file2Context.getResource()).thenReturn(file2); + + when(dirContext.getChildren()).thenReturn(Arrays.asList(file1Context, file2Context)); + + } + + @Test + public void testDirectoryDsmDecoratorNoExecution() { + assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); + + // Should not execute on project + decorator.decorate(new Project("foo"), dirContext); + + // Should not do anything if dir has no files + when(dirContext.getChildren()).thenReturn(Collections.emptyList()); + decorator.decorate(dir, dirContext); + + verify(dirContext, never()).saveMeasure(any(Measure.class)); + } + + @Test + public void testDirectoryDsmDecoratorNoDependency() { + decorator.decorate(dir, dirContext); + + verify(dirContext, times(4)).saveMeasure(any(Measure.class)); + + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_CYCLES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_FEEDBACK_EDGES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_TANGLES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_EDGES_WEIGHT, 0.0)); + } + + @Test + public void testDirectoryDsmDecoratorDependency() { + Dependency dependency = new Dependency(file1, file2).setWeight(1).setId(51L); + when(index.getEdge(file1, file2)).thenReturn(dependency); + when(index.hasEdge(file1, file2)).thenReturn(true); + when(index.getOutgoingEdges(file1)).thenReturn(Arrays.asList(dependency)); + when(index.getIncomingEdges(file2)).thenReturn(Arrays.asList(dependency)); + + decorator.decorate(dir, dirContext); + + verify(dirContext, times(5)).saveMeasure(any(Measure.class)); + + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_CYCLES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_FEEDBACK_EDGES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_TANGLES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_EDGES_WEIGHT, 1.0)); + + verify(dirContext).saveMeasure( + isMeasureWithValue(CoreMetrics.DEPENDENCY_MATRIX, + "[{\"i\":1,\"n\":\"Foo1.java\",\"q\":\"FIL\",\"v\":[{},{}]},{\"i\":2,\"n\":\"Foo2.java\",\"q\":\"FIL\",\"v\":[{\"i\":51,\"w\":1},{}]}]")); + } + + @Test + public void testDirectoryDsmDecoratorNoDSMIfMoreThan200Components() { + Dependency dependency = new Dependency(file1, file2).setWeight(1).setId(51L); + when(index.getEdge(file1, file2)).thenReturn(dependency); + when(index.hasEdge(file1, file2)).thenReturn(true); + when(index.getOutgoingEdges(file1)).thenReturn(Arrays.asList(dependency)); + when(index.getIncomingEdges(file2)).thenReturn(Arrays.asList(dependency)); + + List contexts = new ArrayList(201); + contexts.add(file1Context); + contexts.add(file2Context); + for (int i = 0; i < 199; i++) { + DecoratorContext fileContext = mock(DecoratorContext.class); + when(fileContext.getResource()).thenReturn(File.create("file" + i)); + contexts.add(fileContext); + } + + when(dirContext.getChildren()).thenReturn(contexts); + + decorator.decorate(dir, dirContext); + + verify(dirContext, times(4)).saveMeasure(any(Measure.class)); + + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_CYCLES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_FEEDBACK_EDGES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_TANGLES, 0.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_EDGES_WEIGHT, 1.0)); + } + + @Test + public void testDirectoryDsmDecoratorCycleDependency() { + Dependency dependency1to2 = new Dependency(file1, file2).setWeight(1).setId(50L); + when(index.getEdge(file1, file2)).thenReturn(dependency1to2); + when(index.hasEdge(file1, file2)).thenReturn(true); + when(index.getOutgoingEdges(file1)).thenReturn(Arrays.asList(dependency1to2)); + when(index.getIncomingEdges(file2)).thenReturn(Arrays.asList(dependency1to2)); + Dependency dependency2to1 = new Dependency(file2, file1).setWeight(2).setId(51L); + when(index.getEdge(file2, file1)).thenReturn(dependency2to1); + when(index.hasEdge(file2, file1)).thenReturn(true); + when(index.getOutgoingEdges(file2)).thenReturn(Arrays.asList(dependency2to1)); + when(index.getIncomingEdges(file1)).thenReturn(Arrays.asList(dependency2to1)); + + decorator.decorate(dir, dirContext); + + verify(dirContext, times(5)).saveMeasure(any(Measure.class)); + + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_CYCLES, 1.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_FEEDBACK_EDGES, 1.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_TANGLES, 1.0)); + verify(dirContext).saveMeasure(isMeasureWithValue(CoreMetrics.FILE_EDGES_WEIGHT, 3.0)); + + verify(dirContext).saveMeasure( + isMeasureWithValue(CoreMetrics.DEPENDENCY_MATRIX, + "[{\"i\":2,\"n\":\"Foo2.java\",\"q\":\"FIL\",\"v\":[{},{\"i\":50,\"w\":1}]},{\"i\":1,\"n\":\"Foo1.java\",\"q\":\"FIL\",\"v\":[{\"i\":51,\"w\":2},{}]}]")); + } + + Measure isMeasureWithValue(Metric metric, Double value) { + return argThat(new IsMeasureWithValue(metric, value)); + } + + Measure isMeasureWithValue(Metric metric, String data) { + return argThat(new IsMeasureWithValue(metric, data)); + } + + class IsMeasureWithValue extends ArgumentMatcher { + + private Metric metric; + private Double value; + private String data; + + public IsMeasureWithValue(Metric metric, Double value) { + this.metric = metric; + this.value = value; + } + + public IsMeasureWithValue(Metric metric, String data) { + this.metric = metric; + this.data = data; + } + + public boolean matches(Object m) { + return ((Measure) m).getMetric().equals(metric) && ObjectUtils.equals(((Measure) m).getValue(), value) && ObjectUtils.equals(((Measure) m).getData(), data); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java new file mode 100644 index 00000000000..3850d032de8 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/design/ProjectDsmDecoratorTest.java @@ -0,0 +1,149 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 02110-1301, USA. + */ +package org.sonar.batch.design; + +import edu.emory.mathcs.backport.java.util.Collections; +import org.apache.commons.lang.ObjectUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.design.Dependency; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.Project; + +import java.util.Arrays; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class ProjectDsmDecoratorTest { + + private ProjectDsmDecorator decorator; + private Project root; + private DecoratorContext rootContext; + private SonarIndex index; + private Project module1; + private Project module2; + + @Before + public void prepare() { + index = mock(SonarIndex.class); + decorator = new ProjectDsmDecorator(index); + root = new Project("root"); + rootContext = mock(DecoratorContext.class); + + module1 = new Project("module1").setName("Module1").setParent(root); + module1.setId(1); + when(index.getResource(module1)).thenReturn(module1); + module2 = new Project("module2").setName("Module2").setParent(root); + module2.setId(2); + when(index.getResource(module2)).thenReturn(module2); + + DecoratorContext module1Context = mock(DecoratorContext.class); + when(module1Context.getResource()).thenReturn(module1); + DecoratorContext module2Context = mock(DecoratorContext.class); + when(module2Context.getResource()).thenReturn(module2); + + when(rootContext.getChildren()).thenReturn(Arrays.asList(module1Context, module2Context)); + + } + + @Test + public void testProjectDsmDecoratorNoExecution() { + assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); + + // Should not execute on directory + decorator.decorate(Directory.create("foo"), rootContext); + // Should not execute on aggregator projects + Project p = new Project("parent"); + Project child = new Project("child").setParent(p); + decorator.decorate(p, rootContext); + + // Should not do anything if module has no dir + when(rootContext.getChildren()).thenReturn(Collections.emptyList()); + decorator.decorate(root, rootContext); + + verify(rootContext, never()).saveMeasure(any(Measure.class)); + } + + @Test + public void testProjectDsmDecoratorNoDependency() { + decorator.decorate(root, rootContext); + + verify(rootContext, never()).saveMeasure(any(Measure.class)); + } + + @Test + public void testProjectDsmDecoratorDependency() { + Dependency dependency = new Dependency(module1, module2).setWeight(1).setId(51L); + when(index.getEdge(module1, module2)).thenReturn(dependency); + when(index.hasEdge(module1, module2)).thenReturn(true); + when(index.getOutgoingEdges(module1)).thenReturn(Arrays.asList(dependency)); + when(index.getIncomingEdges(module2)).thenReturn(Arrays.asList(dependency)); + + decorator.decorate(root, rootContext); + + verify(rootContext, times(1)).saveMeasure(any(Measure.class)); + + verify(rootContext).saveMeasure( + isMeasureWithValue(CoreMetrics.DEPENDENCY_MATRIX, + "[{\"i\":1,\"n\":\"Module1\",\"q\":\"BRC\",\"v\":[{},{}]},{\"i\":2,\"n\":\"Module2\",\"q\":\"BRC\",\"v\":[{\"i\":51,\"w\":1},{}]}]")); + } + + Measure isMeasureWithValue(Metric metric, Double value) { + return argThat(new IsMeasureWithValue(metric, value)); + } + + Measure isMeasureWithValue(Metric metric, String data) { + return argThat(new IsMeasureWithValue(metric, data)); + } + + class IsMeasureWithValue extends ArgumentMatcher { + + private Metric metric; + private Double value; + private String data; + + public IsMeasureWithValue(Metric metric, Double value) { + this.metric = metric; + this.value = value; + } + + public IsMeasureWithValue(Metric metric, String data) { + this.metric = metric; + this.data = data; + } + + public boolean matches(Object m) { + return ((Measure) m).getMetric().equals(metric) && ObjectUtils.equals(((Measure) m).getValue(), value) && ObjectUtils.equals(((Measure) m).getData(), data); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java new file mode 100644 index 00000000000..f4f2b4203a9 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/design/SubProjectDsmDecoratorTest.java @@ -0,0 +1,157 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube 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. + * + * SonarQube 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 02110-1301, USA. + */ +package org.sonar.batch.design; + +import edu.emory.mathcs.backport.java.util.Collections; +import org.apache.commons.lang.ObjectUtils; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.design.Dependency; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.Project; + +import java.util.Arrays; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SubProjectDsmDecoratorTest { + + private SubProjectDsmDecorator decorator; + private Project module; + private DecoratorContext moduleContext; + private SonarIndex index; + private Directory dir1; + private Directory dir2; + + @Before + public void prepare() { + index = mock(SonarIndex.class); + decorator = new SubProjectDsmDecorator(index); + module = new Project("foo"); + moduleContext = mock(DecoratorContext.class); + + dir1 = Directory.create("src/foo1", "foo1"); + dir1.setId(1); + dir2 = Directory.create("src/foo2", "foo2"); + dir2.setId(2); + + DecoratorContext dir1Context = mock(DecoratorContext.class); + when(dir1Context.getResource()).thenReturn(dir1); + DecoratorContext dir2Context = mock(DecoratorContext.class); + when(dir2Context.getResource()).thenReturn(dir2); + + when(moduleContext.getChildren()).thenReturn(Arrays.asList(dir1Context, dir2Context)); + + } + + @Test + public void testSubProjectDsmDecoratorNoExecution() { + assertThat(decorator.shouldExecuteOnProject(null)).isTrue(); + + // Should not execute on directory + decorator.decorate(Directory.create("foo"), moduleContext); + // Should not execute on aggregator projects + Project p = new Project("parent"); + Project child = new Project("child").setParent(p); + decorator.decorate(p, moduleContext); + + // Should not do anything if module has no dir + when(moduleContext.getChildren()).thenReturn(Collections.emptyList()); + decorator.decorate(module, moduleContext); + + verify(moduleContext, never()).saveMeasure(any(Measure.class)); + } + + @Test + public void testSubProjectDsmDecoratorNoDependency() { + decorator.decorate(module, moduleContext); + + verify(moduleContext, times(4)).saveMeasure(any(Measure.class)); + + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_CYCLES, 0.0)); + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_FEEDBACK_EDGES, 0.0)); + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_TANGLES, 0.0)); + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_EDGES_WEIGHT, 0.0)); + } + + @Test + public void testSubProjectDsmDecoratorDependency() { + Dependency dependency = new Dependency(dir1, dir2).setWeight(1).setId(51L); + when(index.getEdge(dir1, dir2)).thenReturn(dependency); + when(index.hasEdge(dir1, dir2)).thenReturn(true); + when(index.getOutgoingEdges(dir1)).thenReturn(Arrays.asList(dependency)); + when(index.getIncomingEdges(dir2)).thenReturn(Arrays.asList(dependency)); + + decorator.decorate(module, moduleContext); + + verify(moduleContext, times(5)).saveMeasure(any(Measure.class)); + + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_CYCLES, 0.0)); + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_FEEDBACK_EDGES, 0.0)); + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_TANGLES, 0.0)); + verify(moduleContext).saveMeasure(isMeasureWithValue(CoreMetrics.DIRECTORY_EDGES_WEIGHT, 1.0)); + + verify(moduleContext).saveMeasure( + isMeasureWithValue(CoreMetrics.DEPENDENCY_MATRIX, + "[{\"i\":1,\"n\":\"src/foo1\",\"q\":\"DIR\",\"v\":[{},{}]},{\"i\":2,\"n\":\"src/foo2\",\"q\":\"DIR\",\"v\":[{\"i\":51,\"w\":1},{}]}]")); + } + + Measure isMeasureWithValue(Metric metric, Double value) { + return argThat(new IsMeasureWithValue(metric, value)); + } + + Measure isMeasureWithValue(Metric metric, String data) { + return argThat(new IsMeasureWithValue(metric, data)); + } + + class IsMeasureWithValue extends ArgumentMatcher { + + private Metric metric; + private Double value; + private String data; + + public IsMeasureWithValue(Metric metric, Double value) { + this.metric = metric; + this.value = value; + } + + public IsMeasureWithValue(Metric metric, String data) { + this.metric = metric; + this.data = data; + } + + public boolean matches(Object m) { + return ((Measure) m).getMetric().equals(metric) && ObjectUtils.equals(((Measure) m).getValue(), value) && ObjectUtils.equals(((Measure) m).getData(), data); + } + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java index 82653d849fc..ba81aba16b1 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/measure/MeasureCacheTest.java @@ -111,10 +111,6 @@ public class MeasureCacheTest { assertThat(cache.byResource(p)).hasSize(2); } - /** - * This test fails when compression is not enabled for measures. PersistIt seems to be ok with - * put but fail when reading value. - */ @Test public void should_add_measure_with_big_data() throws Exception { MeasureCache cache = new MeasureCache(caches, metricFinder, techDebtModel); diff --git a/sonar-graph/src/main/java/org/sonar/graph/DirectedGraph.java b/sonar-graph/src/main/java/org/sonar/graph/DirectedGraph.java index ecbb0fb6d72..c0d63887b89 100644 --- a/sonar-graph/src/main/java/org/sonar/graph/DirectedGraph.java +++ b/sonar-graph/src/main/java/org/sonar/graph/DirectedGraph.java @@ -22,7 +22,7 @@ package org.sonar.graph; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -32,7 +32,7 @@ public class DirectedGraph> implements DirectedGraphAccesso private EdgeFactory edgeFactory; private Map> outgoingEdgesByVertex = new HashMap>(); private Map> incomingEdgesByVertex = new HashMap>(); - private Set vertices = new HashSet(); + private Set vertices = new LinkedHashSet(); public DirectedGraph() { } @@ -60,7 +60,7 @@ public class DirectedGraph> implements DirectedGraphAccesso private void checkEdgeFacory() { if (edgeFactory == null) { throw new IllegalStateException( - "EdgeFactory has not been defined. Please use the 'public E addEdge(V from, V to, E edge)' method."); + "EdgeFactory has not been defined. Please use the 'public E addEdge(V from, V to, E edge)' method."); } } @@ -133,7 +133,7 @@ public class DirectedGraph> implements DirectedGraphAccesso public Collection getOutgoingEdges(V from) { Map outgoingEdges = outgoingEdgesByVertex.get(from); if (outgoingEdges == null) { - return new HashSet(); + return new LinkedHashSet(); } return outgoingEdges.values(); } @@ -142,7 +142,7 @@ public class DirectedGraph> implements DirectedGraphAccesso public Collection getIncomingEdges(V to) { Map incomingEdges = incomingEdgesByVertex.get(to); if (incomingEdges == null) { - return new HashSet(); + return new LinkedHashSet(); } return incomingEdges.values(); } diff --git a/sonar-graph/src/main/java/org/sonar/graph/Dsm.java b/sonar-graph/src/main/java/org/sonar/graph/Dsm.java index fc9f386fff2..69e85c251da 100644 --- a/sonar-graph/src/main/java/org/sonar/graph/Dsm.java +++ b/sonar-graph/src/main/java/org/sonar/graph/Dsm.java @@ -27,17 +27,17 @@ import java.util.Set; public class Dsm { - private V[] vertices; - private DsmCell[][] cells; - private int dimension; - private DirectedGraphAccessor> graph; + private final V[] vertices; + private final DsmCell[][] cells; + private final int dimension; + private final DirectedGraphAccessor> graph; + private boolean atLeastOneDependency = false; public Dsm(DirectedGraphAccessor> graph, Collection vertices, Set feedbackEdges) { this.graph = graph; this.dimension = vertices.size(); - this.cells = new DsmCell[dimension][dimension]; - initVertices(vertices); - initCells(feedbackEdges); + this.vertices = initVertices(vertices); + this.cells = initCells(feedbackEdges); } public Dsm(DirectedGraphAccessor> acyclicGraph, Set feedbackEdges) { @@ -48,7 +48,8 @@ public class Dsm { this(acyclicGraph, acyclicGraph.getVertices(), Collections.emptySet()); } - private void initCells(Set feedbackEdges) { + private DsmCell[][] initCells(Set feedbackEdges) { + DsmCell[][] cells = new DsmCell[dimension][dimension]; for (int x = 0; x < dimension; x++) { for (int y = 0; y < dimension; y++) { V from = vertices[x]; @@ -56,20 +57,23 @@ public class Dsm { Edge edge = graph.getEdge(from, to); if (edge != null) { + atLeastOneDependency = true; boolean isFeedbackEdge = feedbackEdges.contains(edge); cells[x][y] = new DsmCell(edge, isFeedbackEdge); } } } + return cells; } - private void initVertices(Collection verticesCol) { - this.vertices = (V[]) new Object[dimension]; + private V[] initVertices(Collection verticesCol) { + V[] vertices = (V[]) new Object[dimension]; int i = 0; for (V vertex : verticesCol) { vertices[i] = vertex; i++; } + return vertices; } public V getVertex(int rowIndex) { @@ -159,11 +163,21 @@ public class Dsm { return cell != null ? cell : new DsmCell(null, false); } + /** + * @since 5.0 + */ @CheckForNull public DsmCell cell(int x, int y) { return cells[x][y]; } + /** + * @since 5.0 + */ + public boolean hasAtLeastOneDependency() { + return atLeastOneDependency; + } + public V[] getVertices() { V[] verticesCopy = (V[]) new Object[vertices.length]; System.arraycopy(vertices, 0, verticesCopy, 0, vertices.length); diff --git a/sonar-graph/src/main/java/org/sonar/graph/DsmScanner.java b/sonar-graph/src/main/java/org/sonar/graph/DsmScanner.java index 4057748c2c6..beb5318f74b 100644 --- a/sonar-graph/src/main/java/org/sonar/graph/DsmScanner.java +++ b/sonar-graph/src/main/java/org/sonar/graph/DsmScanner.java @@ -26,7 +26,7 @@ import java.io.LineNumberReader; import java.io.Reader; import java.io.StringReader; import java.util.Arrays; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Set; public final class DsmScanner { @@ -36,7 +36,7 @@ public final class DsmScanner { private static final char FEEDBACK_EDGE_FLAG = '*'; private final DirectedGraph graph = DirectedGraph.createStringDirectedGraph(); private String[] vertices; - private Set feedbackEdges = new HashSet(); + private Set feedbackEdges = new LinkedHashSet(); private DsmScanner(Reader reader) { this.reader = new LineNumberReader(reader); @@ -49,7 +49,7 @@ public final class DsmScanner { readRow(i); } } catch (IOException e) { - throw new RuntimeException("Unable to read DSM content.", e); //NOSONAR + throw new RuntimeException("Unable to read DSM content.", e); // NOSONAR } Dsm dsm = new Dsm(graph, graph.getVertices(), feedbackEdges); DsmManualSorter.sort(dsm, Arrays.asList(vertices)); diff --git a/sonar-graph/src/main/java/org/sonar/graph/FeedbackCycle.java b/sonar-graph/src/main/java/org/sonar/graph/FeedbackCycle.java index 81de9669e43..7a22fcd0c0d 100644 --- a/sonar-graph/src/main/java/org/sonar/graph/FeedbackCycle.java +++ b/sonar-graph/src/main/java/org/sonar/graph/FeedbackCycle.java @@ -20,15 +20,15 @@ package org.sonar.graph; +import com.google.common.collect.LinkedHashMultiset; +import com.google.common.collect.Multiset; + import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Set; -import com.google.common.collect.HashMultiset; -import com.google.common.collect.Multiset; - /** * Note: this class has a natural ordering that is inconsistent with equals */ @@ -70,7 +70,7 @@ public final class FeedbackCycle implements Iterable, Comparable createBagWithAllEdgesOfCycles(Set cycles) { - Multiset edgesBag = HashMultiset.create(); + Multiset edgesBag = LinkedHashMultiset.create(); for (Cycle cycle : cycles) { for (Edge edge : cycle.getEdges()) { edgesBag.add(edge); @@ -94,7 +94,9 @@ public final class FeedbackCycle implements Iterable, Comparable { - private Set cycles = new HashSet(); + private Set cycles = new LinkedHashSet(); private long searchCyclesCalls = 0; private static final int DEFAULT_MAX_SEARCH_DEPTH_AT_FIRST = 3; private static final int DEFAULT_MAX_CYCLES_TO_FOUND_BY_ITERATION = 100; @@ -37,7 +37,7 @@ public class IncrementalCyclesAndFESSolver { } public IncrementalCyclesAndFESSolver(DirectedGraphAccessor graph, Collection vertices, int maxSearchDepthAtFirst, - int maxCyclesToFoundByIteration) { + int maxCyclesToFoundByIteration) { iterations++; CycleDetector cycleDetector = new CycleDetector(graph, vertices); diff --git a/sonar-graph/src/main/java/org/sonar/graph/MinimumFeedbackEdgeSetSolver.java b/sonar-graph/src/main/java/org/sonar/graph/MinimumFeedbackEdgeSetSolver.java index 39a5c374b9f..aaebdced22c 100644 --- a/sonar-graph/src/main/java/org/sonar/graph/MinimumFeedbackEdgeSetSolver.java +++ b/sonar-graph/src/main/java/org/sonar/graph/MinimumFeedbackEdgeSetSolver.java @@ -19,7 +19,9 @@ */ package org.sonar.graph; -import java.util.*; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; public class MinimumFeedbackEdgeSetSolver { @@ -62,7 +64,7 @@ public class MinimumFeedbackEdgeSetSolver { * Get edges tagged as feedback. */ public Set getEdges() { - Set edges = new HashSet(); + Set edges = new LinkedHashSet(); for (FeedbackEdge fe : feedbackEdges) { edges.add(fe.getEdge()); } @@ -70,7 +72,7 @@ public class MinimumFeedbackEdgeSetSolver { } private void run() { - Set pendingFeedbackEdges = new HashSet(); + Set pendingFeedbackEdges = new LinkedHashSet(); if (cyclesNumber < maxNumberCyclesForSearchingMinimumFeedback) { searchFeedbackEdges(0, 0, pendingFeedbackEdges); } else { @@ -79,7 +81,7 @@ public class MinimumFeedbackEdgeSetSolver { } private void lightResearchForFeedbackEdges() { - feedbackEdges = new HashSet(); + feedbackEdges = new LinkedHashSet(); for (FeedbackCycle cycle : feedbackCycles) { for (FeedbackEdge edge : cycle) { feedbackEdges.add(edge); @@ -87,7 +89,7 @@ public class MinimumFeedbackEdgeSetSolver { } } minimumFeedbackEdgesWeight = 0; - for(FeedbackEdge edge : feedbackEdges) { + for (FeedbackEdge edge : feedbackEdges) { minimumFeedbackEdgesWeight += edge.getWeight(); } } @@ -103,7 +105,7 @@ public class MinimumFeedbackEdgeSetSolver { if (level == cyclesNumber) { minimumFeedbackEdgesWeight = pendingWeight; - feedbackEdges = new HashSet(pendingFeedbackEdges); + feedbackEdges = new LinkedHashSet(pendingFeedbackEdges); return; } -- 2.39.5