diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2014-09-17 10:47:32 +0200 |
---|---|---|
committer | Julien HENRY <julien.henry@sonarsource.com> | 2014-09-17 10:48:31 +0200 |
commit | 000576a34e031bc58398d85116f1c7bb1f0a200e (patch) | |
tree | 7094aeb186a4a9d5566e8e5a367aff2157c561bd /sonar-batch | |
parent | 9513bee576bda233b91e6703bf0e40a6c102752c (diff) | |
download | sonarqube-000576a34e031bc58398d85116f1c7bb1f0a200e.tar.gz sonarqube-000576a34e031bc58398d85116f1c7bb1f0a200e.zip |
SONAR-5389 New dependency API
Diffstat (limited to 'sonar-batch')
21 files changed, 1113 insertions, 30 deletions
diff --git a/sonar-batch/pom.xml b/sonar-batch/pom.xml index 8a44063c151..d63d11bea9a 100644 --- a/sonar-batch/pom.xml +++ b/sonar-batch/pom.xml @@ -79,6 +79,11 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>org.apache.maven.shared</groupId> + <artifactId>maven-dependency-tree</artifactId> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> <scope>provided</scope> diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java index f7acd618a3a..8fcf38baafc 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchComponents.java @@ -20,6 +20,12 @@ package org.sonar.batch.bootstrap; import com.google.common.collect.Lists; +import org.sonar.batch.design.DirectoryDsmDecorator; +import org.sonar.batch.design.DirectoryTangleIndexDecorator; +import org.sonar.batch.design.FileTangleIndexDecorator; +import org.sonar.batch.design.MavenDependenciesSensor; +import org.sonar.batch.design.ProjectDsmDecorator; +import org.sonar.batch.design.SubProjectDsmDecorator; import org.sonar.batch.maven.DefaultMavenPluginExecutor; import org.sonar.batch.maven.MavenProjectBootstrapper; import org.sonar.batch.maven.MavenProjectBuilder; @@ -37,7 +43,15 @@ public class BatchComponents { public static Collection all() { List components = Lists.newArrayList( // Maven - MavenProjectBootstrapper.class, DefaultMavenPluginExecutor.class, MavenProjectConverter.class, MavenProjectBuilder.class + MavenProjectBootstrapper.class, DefaultMavenPluginExecutor.class, MavenProjectConverter.class, MavenProjectBuilder.class, + + // Design + MavenDependenciesSensor.class, + ProjectDsmDecorator.class, + SubProjectDsmDecorator.class, + DirectoryDsmDecorator.class, + DirectoryTangleIndexDecorator.class, + FileTangleIndexDecorator.class ); components.addAll(CorePropertyDefinitions.all()); return components; diff --git a/sonar-batch/src/main/java/org/sonar/batch/dependency/OutgoingDependency.java b/sonar-batch/src/main/java/org/sonar/batch/dependency/OutgoingDependency.java index b768687b040..0869ee6cde6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/dependency/OutgoingDependency.java +++ b/sonar-batch/src/main/java/org/sonar/batch/dependency/OutgoingDependency.java @@ -24,19 +24,19 @@ import org.sonar.api.batch.fs.InputFile; public class OutgoingDependency { private final InputFile to; - private final String usage; + private final int weight; - public OutgoingDependency(InputFile to, String usage) { + public OutgoingDependency(InputFile to, int weight) { this.to = to; - this.usage = usage; + this.weight = weight; } public InputFile to() { return to; } - public String usage() { - return usage; + public int weight() { + return weight; } } 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 new file mode 100644 index 00000000000..0aaa2aa854d --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryDsmDecorator.java @@ -0,0 +1,123 @@ +/* + * 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.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 DirectoryDsmDecorator(SonarIndex index) { + this.index = index; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + public void decorate(final Resource resource, DecoratorContext context) { + if (shouldDecorateResource(resource, context)) { + List<DecoratorContext> fileContexts = context.getChildren(); + List<Resource> files = new ArrayList<Resource>(fileContexts.size()); + for (DecoratorContext decoratorContext : fileContexts) { + files.add(decoratorContext.getResource()); + } + + IncrementalCyclesAndFESSolver<Resource> cycleDetector = new IncrementalCyclesAndFESSolver<Resource>(index, files); + Set<Cycle> cycles = cycleDetector.getCycles(); + + MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); + Set<Edge> 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<Resource> dsm = getDsm(files, feedbackEdges); + saveDsm(context, dsm); + } + } + + private void savePositiveMeasure(DecoratorContext context, Metric<Integer> metric, double value) { + if (value >= 0.0) { + context.saveMeasure(new Measure(metric, value)); + } + } + + private int getEdgesWeight(Collection<Resource> sourceCodes) { + List<Dependency> edges = getEdges(sourceCodes); + int total = 0; + for (Dependency edge : edges) { + total += edge.getWeight(); + } + return total; + } + + public List<Dependency> getEdges(Collection<Resource> vertices) { + List<Dependency> result = new ArrayList<Dependency>(); + for (Resource vertice : vertices) { + Collection<Dependency> outgoingEdges = index.getOutgoingEdges(vertice); + if (outgoingEdges != null) { + result.addAll(outgoingEdges); + } + } + return result; + } + + private void saveDsm(DecoratorContext context, Dsm<Resource> dsm) { + Measure measure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, DsmSerializer.serialize(dsm)); + measure.setPersistenceMode(PersistenceMode.DATABASE); + context.saveMeasure(measure); + } + + private Dsm<Resource> getDsm(Collection<Resource> files, Set<Edge> feedbackEdges) { + Dsm<Resource> dsm = new Dsm<Resource>(index, files, feedbackEdges); + DsmTopologicalSorter.sort(dsm); + return dsm; + } + + private boolean shouldDecorateResource(Resource resource, DecoratorContext context) { + return ResourceUtils.isDirectory(resource) && context.getMeasure(CoreMetrics.DEPENDENCY_MATRIX) == null; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryTangleIndexDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryTangleIndexDecorator.java new file mode 100644 index 00000000000..b1fc52c9625 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/DirectoryTangleIndexDecorator.java @@ -0,0 +1,34 @@ +/* + * 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.sonar.api.measures.CoreMetrics; + +public class DirectoryTangleIndexDecorator extends TangleIndexDecorator { + + public DirectoryTangleIndexDecorator() { + super(CoreMetrics.PACKAGE_TANGLES, CoreMetrics.PACKAGE_EDGES_WEIGHT, CoreMetrics.PACKAGE_TANGLE_INDEX); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/DsmSerializer.java b/sonar-batch/src/main/java/org/sonar/batch/design/DsmSerializer.java new file mode 100644 index 00000000000..48da21bd0c2 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/DsmSerializer.java @@ -0,0 +1,92 @@ +/* + * 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.sonar.api.design.Dependency; +import org.sonar.api.resources.Resource; +import org.sonar.graph.Dsm; +import org.sonar.graph.DsmCell; + +public final class DsmSerializer { + + private Dsm dsm; + private StringBuilder json; + + private DsmSerializer(Dsm<Resource> dsm) { + this.dsm = dsm; + this.json = new StringBuilder(); + } + + private String serialize() { + json.append('['); + serializeRows(); + json.append(']'); + return json.toString(); + } + + private void serializeRows() { + for (int y = 0; y < dsm.getDimension(); y++) { + if (y > 0) { + json.append(','); + } + serializeRow(y); + } + } + + private void serializeRow(int y) { + Resource resource = (Resource) dsm.getVertex(y); + + json.append("{"); + if (resource != null) { + json.append("\"i\":"); + json.append(resource.getId()); + json.append(",\"n\":\""); + json.append(resource.getName()); + json.append("\",\"q\":\""); + json.append(resource.getQualifier()); + json.append("\",\"v\":["); + for (int x = 0; x < dsm.getDimension(); x++) { + if (x > 0) { + json.append(','); + } + serializeCell(y, x); + } + json.append("]"); + } + json.append("}"); + } + + private void serializeCell(int y, int x) { + DsmCell cell = dsm.getCell(x, y); + json.append('{'); + if (cell.getEdge() != null && cell.getWeight() > 0) { + Dependency dep = (Dependency) cell.getEdge(); + json.append("\"i\":"); + json.append(dep.getId()); + json.append(",\"w\":"); + json.append(cell.getWeight()); + } + json.append('}'); + } + + public static String serialize(Dsm<Resource> dsm) { + return new DsmSerializer(dsm).serialize(); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/FileTangleIndexDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/FileTangleIndexDecorator.java new file mode 100644 index 00000000000..079e3774ebd --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/FileTangleIndexDecorator.java @@ -0,0 +1,34 @@ +/* + * 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.sonar.api.measures.CoreMetrics; + +public class FileTangleIndexDecorator extends TangleIndexDecorator { + + public FileTangleIndexDecorator() { + super(CoreMetrics.FILE_TANGLES, CoreMetrics.FILE_EDGES_WEIGHT, CoreMetrics.FILE_TANGLE_INDEX); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/MavenDependenciesSensor.java b/sonar-batch/src/main/java/org/sonar/batch/design/MavenDependenciesSensor.java new file mode 100644 index 00000000000..0f2e7157b22 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/MavenDependenciesSensor.java @@ -0,0 +1,265 @@ +/* + * 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 com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.factory.ArtifactFactory; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactCollector; +import org.apache.maven.shared.dependency.tree.DependencyNode; +import org.apache.maven.shared.dependency.tree.DependencyTreeBuilder; +import org.apache.maven.shared.dependency.tree.DependencyTreeBuilderException; +import org.apache.maven.shared.dependency.tree.filter.AncestorOrSelfDependencyNodeFilter; +import org.apache.maven.shared.dependency.tree.filter.DependencyNodeFilter; +import org.apache.maven.shared.dependency.tree.filter.StateDependencyNodeFilter; +import org.apache.maven.shared.dependency.tree.traversal.BuildingDependencyNodeVisitor; +import org.apache.maven.shared.dependency.tree.traversal.CollectingDependencyNodeVisitor; +import org.apache.maven.shared.dependency.tree.traversal.DependencyNodeVisitor; +import org.apache.maven.shared.dependency.tree.traversal.FilteringDependencyNodeVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.batch.SupportedEnvironment; +import org.sonar.api.config.Settings; +import org.sonar.api.design.Dependency; +import org.sonar.api.resources.Library; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; +import org.sonar.api.utils.SonarException; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@SupportedEnvironment("maven") +public class MavenDependenciesSensor implements Sensor { + + private static final String SONAR_MAVEN_PROJECT_DEPENDENCY = "sonar.maven.projectDependencies"; + + private static final Logger LOG = LoggerFactory.getLogger(MavenDependenciesSensor.class); + + private ArtifactRepository localRepository; + private ArtifactFactory artifactFactory; + private ArtifactMetadataSource artifactMetadataSource; + private ArtifactCollector artifactCollector; + private DependencyTreeBuilder treeBuilder; + private SonarIndex index; + private Settings settings; + + public MavenDependenciesSensor(Settings settings, ArtifactRepository localRepository, ArtifactFactory artifactFactory, ArtifactMetadataSource artifactMetadataSource, + ArtifactCollector artifactCollector, DependencyTreeBuilder treeBuilder, SonarIndex index) { + this.settings = settings; + this.localRepository = localRepository; + this.artifactFactory = artifactFactory; + this.artifactMetadataSource = artifactMetadataSource; + this.artifactCollector = artifactCollector; + this.index = index; + this.treeBuilder = treeBuilder; + } + + /** + * Used with SQ Maven plugin 2.5+ + */ + public MavenDependenciesSensor(Settings settings, SonarIndex index) { + this.settings = settings; + this.index = index; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + private static class InputDependency { + + private final String key; + + private final String version; + + private String scope; + + List<InputDependency> dependencies = new ArrayList<InputDependency>(); + + public InputDependency(String key, String version) { + this.key = key; + this.version = version; + } + + public String key() { + return key; + } + + public String version() { + return version; + } + + public String scope() { + return scope; + } + + public InputDependency setScope(String scope) { + this.scope = scope; + return this; + } + + public List<InputDependency> dependencies() { + return dependencies; + } + } + + private static class DependencyDeserializer implements JsonDeserializer<InputDependency> { + + @Override + public InputDependency deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) { + + JsonObject dep = json.getAsJsonObject(); + String key = dep.get("k").getAsString(); + String version = dep.get("v").getAsString(); + InputDependency result = new InputDependency(key, version); + result.setScope(dep.get("s").getAsString()); + JsonElement subDeps = dep.get("d"); + if (subDeps != null) { + JsonArray arrayOfSubDeps = subDeps.getAsJsonArray(); + for (JsonElement e : arrayOfSubDeps) { + result.dependencies().add(deserialize(e, typeOfT, context)); + } + } + return result; + } + + } + + public void analyse(final Project project, final SensorContext context) { + if (settings.hasKey(SONAR_MAVEN_PROJECT_DEPENDENCY)) { + LOG.debug("Using dependency provided by property " + SONAR_MAVEN_PROJECT_DEPENDENCY); + String depsAsJson = settings.getString(SONAR_MAVEN_PROJECT_DEPENDENCY); + Collection<InputDependency> deps; + try { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(InputDependency.class, new DependencyDeserializer()); + Gson gson = gsonBuilder.create(); + + Type collectionType = new TypeToken<Collection<InputDependency>>() { + }.getType(); + deps = gson.fromJson(depsAsJson, collectionType); + saveDependencies(project, deps, context); + } catch (Exception e) { + throw new IllegalStateException("Unable to deserialize dependency information: " + depsAsJson, e); + } + } else if (treeBuilder != null) { + computeDependencyTree(project, context); + } + } + + private void computeDependencyTree(final Project project, final SensorContext context) { + LOG.warn("Computation of Maven dependencies by SonarQube is deprecated. Please update the version of SonarQube Maven plugin to 2.5+"); + try { + DependencyNode root = treeBuilder.buildDependencyTree(project.getPom(), localRepository, artifactFactory, artifactMetadataSource, null, artifactCollector); + + DependencyNodeVisitor visitor = new BuildingDependencyNodeVisitor(new DependencyNodeVisitor() { + public boolean visit(DependencyNode node) { + return true; + } + + public boolean endVisit(DependencyNode node) { + if (node.getParent() != null && node.getParent() != node) { + saveDependency(node, context); + } + return true; + } + }); + + // mode verbose OFF : do not show the same lib many times + DependencyNodeFilter filter = StateDependencyNodeFilter.INCLUDED; + + CollectingDependencyNodeVisitor collectingVisitor = new CollectingDependencyNodeVisitor(); + DependencyNodeVisitor firstPassVisitor = new FilteringDependencyNodeVisitor(collectingVisitor, filter); + root.accept(firstPassVisitor); + + DependencyNodeFilter secondPassFilter = new AncestorOrSelfDependencyNodeFilter(collectingVisitor.getNodes()); + visitor = new FilteringDependencyNodeVisitor(visitor, secondPassFilter); + + root.accept(visitor); + + } catch (DependencyTreeBuilderException e) { + throw new SonarException("Can not load the graph of dependencies of the project " + project.getKey(), e); + } + } + + private void saveDependencies(Resource from, Collection<InputDependency> deps, SensorContext context) { + for (InputDependency inputDep : deps) { + Resource to = toResource(inputDep, context); + Dependency dependency = new Dependency(from, to); + dependency.setUsage(inputDep.scope()); + dependency.setWeight(1); + context.saveDependency(dependency); + if (!inputDep.dependencies().isEmpty()) { + saveDependencies(to, inputDep.dependencies(), context); + } + } + } + + private Resource toResource(InputDependency dependency, SensorContext context) { + Project project = new Project(dependency.key()); + Resource result = context.getResource(project); + if (result == null || !((Project) result).getAnalysisVersion().equals(dependency.version())) { + Library lib = new Library(project.getKey(), dependency.version()); + context.saveResource(lib); + result = context.getResource(lib); + } + return result; + } + + protected void saveDependency(DependencyNode node, SensorContext context) { + Resource from = (node.getParent().getParent() == null) ? index.getProject() : toResource(node.getParent().getArtifact(), context); + Resource to = toResource(node.getArtifact(), context); + Dependency dependency = new Dependency(from, to); + dependency.setUsage(node.getArtifact().getScope()); + dependency.setWeight(1); + context.saveDependency(dependency); + } + + protected static Resource toResource(Artifact artifact, SensorContext context) { + Project project = Project.createFromMavenIds(artifact.getGroupId(), artifact.getArtifactId()); + Resource result = context.getResource(project); + if (result == null || !((Project) result).getAnalysisVersion().equals(artifact.getBaseVersion())) { + Library lib = new Library(project.getKey(), artifact.getBaseVersion()); + context.saveResource(lib); + result = context.getResource(lib); + } + return result; + } + + @Override + public String toString() { + return "Maven dependencies"; + } +} 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 new file mode 100644 index 00000000000..03a74c830af --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/ProjectDsmDecorator.java @@ -0,0 +1,106 @@ +/* + * 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 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 ProjectDsmDecorator(SonarIndex index) { + this.index = index; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + public void decorate(final Resource resource, DecoratorContext context) { + if (shouldDecorateResource(resource, context)) { + Collection<Resource> subProjects = getSubProjects((Project) resource); + + if (!subProjects.isEmpty()) { + Dsm<Resource> dsm = getDsm(subProjects); + saveDsm(context, dsm); + } + } + } + + private void saveDsm(DecoratorContext context, Dsm<Resource> dsm) { + Measure measure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, DsmSerializer.serialize(dsm)); + measure.setPersistenceMode(PersistenceMode.DATABASE); + context.saveMeasure(measure); + } + + private Dsm<Resource> getDsm(Collection<Resource> subProjects) { + CycleDetector<Resource> cycleDetector = new CycleDetector<Resource>(index, subProjects); + Set<Cycle> cycles = cycleDetector.getCycles(); + + MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); + Set<Edge> feedbackEdges = solver.getEdges(); + + Dsm<Resource> dsm = new Dsm<Resource>(index, subProjects, feedbackEdges); + DsmTopologicalSorter.sort(dsm); + return dsm; + } + + /** + * sub-projects, including all descendants but not only direct children + */ + private Collection<Resource> getSubProjects(final Project project) { + List<Resource> subProjects = Lists.newArrayList(); + addSubProjects(project, subProjects); + return subProjects; + } + + private void addSubProjects(Project project, List<Resource> subProjects) { + for (Project subProject : project.getModules()) { + Project indexedSubProject = index.getResource(subProject); + if (indexedSubProject != null) { + subProjects.add(indexedSubProject); + } + addSubProjects(subProject, subProjects); + } + } + + private boolean shouldDecorateResource(Resource resource, DecoratorContext context) { + return ResourceUtils.isProject(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 new file mode 100644 index 00000000000..b0556819c1d --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/SubProjectDsmDecorator.java @@ -0,0 +1,123 @@ +/* + * 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.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 SubProjectDsmDecorator(SonarIndex index) { + this.index = index; + } + + public boolean shouldExecuteOnProject(Project project) { + return true; + } + + public void decorate(final Resource resource, DecoratorContext context) { + if (shouldDecorateResource(resource, context)) { + List<DecoratorContext> directoryContexts = context.getChildren(); + List<Resource> directories = new ArrayList<Resource>(directoryContexts.size()); + for (DecoratorContext decoratorContext : directoryContexts) { + directories.add(decoratorContext.getResource()); + } + + IncrementalCyclesAndFESSolver<Resource> cycleDetector = new IncrementalCyclesAndFESSolver<Resource>(index, directories); + Set<Cycle> cycles = cycleDetector.getCycles(); + + MinimumFeedbackEdgeSetSolver solver = new MinimumFeedbackEdgeSetSolver(cycles); + Set<Edge> 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<Resource> dsm = getDsm(directories, feedbackEdges); + saveDsm(context, dsm); + } + } + + private void savePositiveMeasure(DecoratorContext context, Metric<Integer> metric, double value) { + if (value >= 0.0) { + context.saveMeasure(new Measure(metric, value)); + } + } + + private int getEdgesWeight(Collection<Resource> sourceCodes) { + List<Dependency> edges = getEdges(sourceCodes); + int total = 0; + for (Dependency edge : edges) { + total += edge.getWeight(); + } + return total; + } + + public List<Dependency> getEdges(Collection<Resource> vertices) { + List<Dependency> result = new ArrayList<Dependency>(); + for (Resource vertice : vertices) { + Collection<Dependency> outgoingEdges = index.getOutgoingEdges(vertice); + if (outgoingEdges != null) { + result.addAll(outgoingEdges); + } + } + return result; + } + + private void saveDsm(DecoratorContext context, Dsm<Resource> dsm) { + Measure measure = new Measure(CoreMetrics.DEPENDENCY_MATRIX, DsmSerializer.serialize(dsm)); + measure.setPersistenceMode(PersistenceMode.DATABASE); + context.saveMeasure(measure); + } + + private Dsm<Resource> getDsm(Collection<Resource> directories, Set<Edge> feedbackEdges) { + Dsm<Resource> dsm = new Dsm<Resource>(index, directories, feedbackEdges); + DsmTopologicalSorter.sort(dsm); + return dsm; + } + + private boolean shouldDecorateResource(Resource resource, DecoratorContext context) { + return ResourceUtils.isProject(resource) && ((Project) resource).getModules().isEmpty(); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/design/TangleIndexDecorator.java b/sonar-batch/src/main/java/org/sonar/batch/design/TangleIndexDecorator.java new file mode 100644 index 00000000000..c4c1cc3a241 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/design/TangleIndexDecorator.java @@ -0,0 +1,91 @@ +/* + * 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.sonar.api.batch.Decorator; +import org.sonar.api.batch.DecoratorContext; +import org.sonar.api.batch.DependedUpon; +import org.sonar.api.batch.DependsUpon; +import org.sonar.api.measures.Measure; +import org.sonar.api.measures.MeasureUtils; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Project; +import org.sonar.api.resources.Resource; + +import java.util.Arrays; +import java.util.List; + +public abstract class TangleIndexDecorator implements Decorator { + + private Metric tanglesMetric; + private Metric edgesWeightMetric; + private Metric tangleIndexMetric; + + protected TangleIndexDecorator(Metric tanglesMetric, Metric edgesWeightMetric, Metric tangleIndexMetric) { + this.tanglesMetric = tanglesMetric; + this.edgesWeightMetric = edgesWeightMetric; + this.tangleIndexMetric = tangleIndexMetric; + } + + @DependsUpon + public final List<Metric> dependsUponMetrics() { + return Arrays.asList(tanglesMetric, edgesWeightMetric); + } + + /** + * Used to define downstream dependencies + */ + @DependedUpon + public final Metric generatesMetric() { + return tangleIndexMetric; + } + + public final boolean shouldExecuteOnProject(Project project) { + return true; + } + + /** + * {@inheritDoc} + */ + public final void decorate(Resource resource, DecoratorContext context) { + if (!shouldDecorateResource(context)) { + return; + } + Measure tangles = context.getMeasure(tanglesMetric); + Measure totalweight = context.getMeasure(edgesWeightMetric); + + if (MeasureUtils.hasValue(totalweight)) { + context.saveMeasure(new Measure(tangleIndexMetric, compute(MeasureUtils.getValue(tangles, 0.0), totalweight.getValue()))); + } + } + + private boolean shouldDecorateResource(DecoratorContext context) { + return context.getMeasure(tangleIndexMetric) == null; + } + + + private double compute(double tangles, double totalWeight) { + if (totalWeight==0.0) { + return 0.0; + } + double result = 2 * tangles / totalWeight; + return result * 100; + } +} 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 17d935699cd..d73f1fcab1a 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 @@ -53,6 +53,7 @@ import org.sonar.batch.ProjectTree; import org.sonar.batch.issue.DeprecatedViolations; import org.sonar.batch.issue.ModuleIssues; import org.sonar.batch.scan.measure.MeasureCache; +import org.sonar.batch.scan2.DefaultSensorContext; import org.sonar.core.component.ComponentKeys; import org.sonar.core.component.ScanGraph; @@ -214,6 +215,10 @@ public class DefaultIndex extends SonarIndex { if (metric == null) { throw new SonarException("Unknown metric: " + measure.getMetricKey()); } + if (DefaultSensorContext.INTERNAL_METRICS.contains(metric)) { + LOG.warn("Metric " + metric + " is an internal metric computed by SonarQube. Please update your plugin."); + return measure; + } measure.setMetric(metric); if (measureCache.contains(resource, measure)) { throw new SonarException("Can not add the same measure twice on " + resource + ": " + measure); diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java index e17c5a087d5..001fc1ad72d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -20,7 +20,6 @@ package org.sonar.batch.mediumtest; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.SonarPlugin; @@ -235,7 +234,7 @@ public class BatchMediumTester { private Map<InputFile, SymbolData> symbolTablePerFile = new HashMap<InputFile, SymbolData>(); private Map<String, Map<String, TestCase>> testCasesPerFile = new HashMap<String, Map<String, TestCase>>(); private Map<String, Map<String, Map<String, List<Integer>>>> coveragePerTest = new HashMap<String, Map<String, Map<String, List<Integer>>>>(); - private Map<String, Map<String, String>> dependencies = new HashMap<String, Map<String, String>>(); + private Map<String, Map<String, Integer>> dependencies = new HashMap<String, Map<String, Integer>>(); @Override public void scanTaskCompleted(ProjectScanContainer container) { @@ -322,9 +321,9 @@ public class BatchMediumTester { String fromKey = entry.key()[1].toString(); String toKey = entry.key()[2].toString(); if (!dependencies.containsKey(fromKey)) { - dependencies.put(fromKey, new HashMap<String, String>()); + dependencies.put(fromKey, new HashMap<String, Integer>()); } - dependencies.get(fromKey).put(toKey, StringUtils.trimToEmpty(entry.value().usage())); + dependencies.get(fromKey).put(toKey, entry.value().weight()); } } @@ -409,10 +408,10 @@ public class BatchMediumTester { } /** - * @return null if no dependency else return dependency usage. + * @return null if no dependency else return dependency weight. */ @CheckForNull - public String dependencyUsage(InputFile from, InputFile to) { + public Integer dependencyWeight(InputFile from, InputFile to) { String fromKey = ((DefaultInputFile) from).key(); String toKey = ((DefaultInputFile) to).key(); return dependencies.containsKey(fromKey) ? dependencies.get(fromKey).get(toKey) : null; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java index 64ef5fb2ddc..ae6b1aa4ee6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -147,7 +147,7 @@ public class ModuleScanContainer extends ComponentContainer { TimeMachineConfiguration.class, DefaultSensorContext.class, - SensorContextAdaptor.class, + SensorContextAdapter.class, BatchExtensionDictionnary.class, DefaultTimeMachine.class, ViolationFilters.class, diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdapter.java index 59375015b11..121a106a9be 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdaptor.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/SensorContextAdapter.java @@ -20,7 +20,10 @@ package org.sonar.batch.scan; import com.google.common.base.Preconditions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.batch.Sensor; +import org.sonar.api.batch.SonarIndex; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputDir; import org.sonar.api.batch.fs.InputFile; @@ -67,21 +70,25 @@ import java.util.List; * Will be dropped once old {@link Sensor} API is dropped. * */ -public class SensorContextAdaptor extends BaseSensorContext { +public class SensorContextAdapter extends BaseSensorContext { + + private static final Logger LOG = LoggerFactory.getLogger(SensorContextAdapter.class); private final org.sonar.api.batch.SensorContext sensorContext; private final MetricFinder metricFinder; private final Project project; private final ResourcePerspectives perspectives; + private final SonarIndex sonarIndex; - public SensorContextAdaptor(org.sonar.api.batch.SensorContext sensorContext, MetricFinder metricFinder, Project project, ResourcePerspectives perspectives, + public SensorContextAdapter(org.sonar.api.batch.SensorContext sensorContext, MetricFinder metricFinder, Project project, ResourcePerspectives perspectives, Settings settings, FileSystem fs, ActiveRules activeRules, ComponentDataCache componentDataCache, BlockCache blockCache, - DuplicationCache duplicationCache) { + DuplicationCache duplicationCache, SonarIndex sonarIndex) { super(settings, fs, activeRules, componentDataCache, blockCache, duplicationCache); this.sensorContext = sensorContext; this.metricFinder = metricFinder; this.project = project; this.perspectives = perspectives; + this.sonarIndex = sonarIndex; } @Override @@ -139,7 +146,6 @@ public class SensorContextAdaptor extends BaseSensorContext { if (m == null) { throw new IllegalStateException("Unknow metric with key: " + measure.metric().key()); } - org.sonar.api.measures.Measure measureToSave = new org.sonar.api.measures.Measure(m); setValueAccordingToMetricType(measure, m, measureToSave); if (measure.inputFile() != null) { @@ -297,18 +303,27 @@ public class SensorContextAdaptor extends BaseSensorContext { } @Override - public void saveDependency(InputFile from, InputFile to, String usage) { + public void saveDependency(InputFile from, InputFile to, int weight) { File fromResource = getFile(from); File toResource = getFile(to); + if (sonarIndex.getEdge(fromResource, toResource) != null) { + throw new IllegalStateException("Dependency between " + from + " and " + to + " was already saved."); + } Directory fromParent = fromResource.getParent(); Directory toParent = toResource.getParent(); Dependency parentDep = null; if (!fromParent.equals(toParent)) { - parentDep = new Dependency(fromParent, toParent).setUsage("USES"); - parentDep = sensorContext.saveDependency(parentDep); + parentDep = sonarIndex.getEdge(fromParent, toParent); + if (parentDep != null) { + parentDep.setWeight(parentDep.getWeight() + 1); + } else { + parentDep = new Dependency(fromParent, toParent).setUsage("USES").setWeight(1); + parentDep = sensorContext.saveDependency(parentDep); + } } sensorContext.saveDependency(new Dependency(fromResource, toResource) - .setUsage(usage) + .setUsage("USES") + .setWeight(weight) .setParent(parentDep)); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java index 48a74cccd81..ddbfd0c5d3f 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/BaseSensorContext.java @@ -43,7 +43,7 @@ import org.sonar.batch.duplication.DefaultTokenBuilder; import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.highlighting.DefaultHighlightingBuilder; import org.sonar.batch.index.ComponentDataCache; -import org.sonar.batch.scan.SensorContextAdaptor; +import org.sonar.batch.scan.SensorContextAdapter; import org.sonar.batch.symbol.DefaultSymbolTableBuilder; import org.sonar.duplications.internal.pmd.PmdBlockChunker; @@ -51,7 +51,7 @@ import java.io.Serializable; import java.util.List; /** - * Common bits between {@link DefaultSensorContext} and {@link SensorContextAdaptor} + * Common bits between {@link DefaultSensorContext} and {@link SensorContextAdapter} * @author julien * */ diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java index 1ceee76c6d7..57d362c699d 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan2/DefaultSensorContext.java @@ -21,6 +21,8 @@ package org.sonar.batch.scan2; import com.google.common.base.Preconditions; import com.google.common.base.Strings; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.FileSystem; import org.sonar.api.batch.fs.InputFile; @@ -35,6 +37,7 @@ import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; import org.sonar.api.batch.sensor.test.TestCase; import org.sonar.api.batch.sensor.test.internal.DefaultTestCase; import org.sonar.api.config.Settings; +import org.sonar.api.measures.CoreMetrics; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.MessageException; import org.sonar.batch.dependency.DependencyCache; @@ -43,16 +46,31 @@ import org.sonar.batch.duplication.BlockCache; import org.sonar.batch.duplication.DuplicationCache; import org.sonar.batch.index.ComponentDataCache; import org.sonar.batch.issue.IssueFilters; -import org.sonar.batch.scan.SensorContextAdaptor; +import org.sonar.batch.scan.SensorContextAdapter; import org.sonar.batch.test.CoveragePerTestCache; import org.sonar.batch.test.TestCaseCache; import org.sonar.core.component.ComponentKeys; import java.io.Serializable; +import java.util.Arrays; import java.util.List; public class DefaultSensorContext extends BaseSensorContext { + private static final Logger LOG = LoggerFactory.getLogger(DefaultSensorContext.class); + + public static final List<Metric> INTERNAL_METRICS = Arrays.<Metric>asList(CoreMetrics.DEPENDENCY_MATRIX, + CoreMetrics.DIRECTORY_CYCLES, + CoreMetrics.DIRECTORY_EDGES_WEIGHT, + CoreMetrics.DIRECTORY_FEEDBACK_EDGES, + CoreMetrics.DIRECTORY_TANGLE_INDEX, + CoreMetrics.DIRECTORY_TANGLES, + CoreMetrics.FILE_CYCLES, + CoreMetrics.FILE_EDGES_WEIGHT, + CoreMetrics.FILE_FEEDBACK_EDGES, + CoreMetrics.FILE_TANGLE_INDEX, + CoreMetrics.FILE_TANGLES + ); private final AnalyzerMeasureCache measureCache; private final AnalyzerIssueCache issueCache; private final ProjectDefinition def; @@ -98,6 +116,10 @@ public class DefaultSensorContext extends BaseSensorContext { @Override public void addMeasure(Measure<?> measure) { + if (INTERNAL_METRICS.contains(measure.metric())) { + LOG.warn("Metric " + measure.metric() + " is an internal metric computed by SonarQube. Please update your plugin."); + return; + } InputFile inputFile = measure.inputFile(); if (inputFile != null) { measureCache.put(def.getKey(), ComponentKeys.createEffectiveKey(def.getKey(), inputFile), (DefaultMeasure) measure); @@ -126,7 +148,7 @@ public class DefaultSensorContext extends BaseSensorContext { updateIssue((DefaultIssue) issue, activeRule); - if (issueFilters.accept(SensorContextAdaptor.toDefaultIssue(def.getKey(), resourceKey, issue), null)) { + if (issueFilters.accept(SensorContextAdapter.toDefaultIssue(def.getKey(), resourceKey, issue), null)) { issueCache.put(def.getKey(), resourceKey, (DefaultIssue) issue); return true; } @@ -165,10 +187,10 @@ public class DefaultSensorContext extends BaseSensorContext { } @Override - public void saveDependency(InputFile from, InputFile to, String usage) { + public void saveDependency(InputFile from, InputFile to, int weight) { Preconditions.checkNotNull(from); Preconditions.checkNotNull(to); - OutgoingDependency dep = new OutgoingDependency(to, usage); + OutgoingDependency dep = new OutgoingDependency(to, weight); dependencyCache.put(def.getKey(), from, dep); } diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/DsmSerializerTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/DsmSerializerTest.java new file mode 100644 index 00000000000..24d949d000c --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/design/DsmSerializerTest.java @@ -0,0 +1,60 @@ +/* + * 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.apache.commons.io.IOUtils; +import org.junit.Test; +import org.sonar.api.design.Dependency; +import org.sonar.api.resources.Directory; +import org.sonar.api.resources.Resource; +import org.sonar.graph.DirectedGraph; +import org.sonar.graph.Dsm; +import org.sonar.graph.DsmManualSorter; +import org.sonar.graph.Edge; + +import java.io.IOException; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +public class DsmSerializerTest { + @Test + public void serializeEmptyDsm() { + Dsm<Resource> dsm = new Dsm<Resource>(new DirectedGraph<Resource, Edge<Resource>>()); + assertThat(DsmSerializer.serialize(dsm), is("[]")); + } + + @Test + public void serialize() throws IOException { + Resource foo = Directory.create("src/org/foo", "org/foo").setId(7); + Resource bar = Directory.create("src/org/bar", "org/bar").setId(8); + Dependency dep = new Dependency(foo, bar).setId(30l).setWeight(1); + + DirectedGraph<Resource, Dependency> graph = new DirectedGraph<Resource, Dependency>(); + graph.addVertex(foo); + graph.addVertex(bar); + graph.addEdge(dep); + + Dsm<Resource> dsm = new Dsm<Resource>(graph); + DsmManualSorter.sort(dsm, bar, foo); // for test reproductibility + String json = IOUtils.toString(getClass().getResourceAsStream("/org/sonar/batch/design/DsmSerializerTest/dsm.json")).trim(); + assertThat(DsmSerializer.serialize(dsm), is(json)); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/design/MavenDependenciesSensorTest.java b/sonar-batch/src/test/java/org/sonar/batch/design/MavenDependenciesSensorTest.java new file mode 100644 index 00000000000..504b82981a0 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/design/MavenDependenciesSensorTest.java @@ -0,0 +1,93 @@ +/* + * 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.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.SonarIndex; +import org.sonar.api.config.Settings; +import org.sonar.api.design.Dependency; +import org.sonar.api.resources.Library; +import org.sonar.api.resources.Project; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class MavenDependenciesSensorTest { + + private MavenDependenciesSensor sensor; + private Settings settings; + private SonarIndex sonarIndex; + private SensorContext sensorContext; + + @Before + public void prepare() { + settings = new Settings(); + sonarIndex = mock(SonarIndex.class); + sensor = new MavenDependenciesSensor(settings, sonarIndex); + sensorContext = mock(SensorContext.class); + when(sensorContext.getResource(any(Library.class))).thenAnswer(new Answer<Library>() { + public Library answer(InvocationOnMock invocation) throws Throwable { + return (invocation.getArguments()[0] instanceof Library) ? (Library) invocation.getArguments()[0] : null; + } + }); + + } + + @Test + public void testDependenciesProvidedAsProperties() { + settings + .setProperty( + "sonar.maven.projectDependencies", + "[{\"k\":\"antlr:antlr\",\"v\":\"2.7.2\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-beanutils:commons-beanutils\",\"v\":\"1.7.0\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-chain:commons-chain\",\"v\":\"1.1\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-digester:commons-digester\",\"v\":\"1.8\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-fileupload:commons-fileupload\",\"v\":\"1.1.1\",\"s\":\"compile\",\"d\":[{\"k\":\"commons-io:commons-io\",\"v\":\"1.1\",\"s\":\"compile\",\"d\":[]}]}," + + "{\"k\":\"commons-logging:commons-logging\",\"v\":\"1.0.4\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"commons-validator:commons-validator\",\"v\":\"1.3.1\",\"s\":\"compile\",\"d\":[]}," + + "{\"k\":\"javax.servlet:servlet-api\",\"v\":\"2.3\",\"s\":\"provided\",\"d\":[]}," + + "{\"k\":\"junit:junit\",\"v\":\"3.8.1\",\"s\":\"test\",\"d\":[]}," + + "{\"k\":\"oro:oro\",\"v\":\"2.0.8\",\"s\":\"compile\",\"d\":[]}]"); + + Project project = new Project("foo"); + sensor.analyse(project, sensorContext); + + Library antlr = new Library("antlr:antlr", "2.7.2"); + verify(sensorContext).saveResource(eq(antlr)); + Library commonsFU = new Library("commons-fileupload:commons-fileupload", "1.1.1"); + verify(sensorContext).saveResource(eq(commonsFU)); + Library commonsIo = new Library("commons-io:commons-io", "1.1"); + verify(sensorContext).saveResource(eq(commonsIo)); + Library junit = new Library("junit:junit", "3.8.1"); + verify(sensorContext).saveResource(eq(junit)); + + verify(sensorContext).saveDependency(new Dependency(project, antlr).setUsage("compile").setWeight(1)); + verify(sensorContext).saveDependency(new Dependency(commonsFU, commonsIo).setUsage("compile").setWeight(1)); + verify(sensorContext).saveDependency(new Dependency(project, junit).setUsage("test").setWeight(1)); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java index addf1f8bb0c..f6fd70f4a5c 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/SensorContextAdapterTest.java @@ -25,6 +25,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.sonar.api.batch.SensorContext; +import org.sonar.api.batch.SonarIndex; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.DefaultInputFile; @@ -58,7 +59,7 @@ public class SensorContextAdapterTest { private ActiveRules activeRules; private DefaultFileSystem fs; - private SensorContextAdaptor adaptor; + private SensorContextAdapter adaptor; private SensorContext sensorContext; private Settings settings; private ResourcePerspectives resourcePerspectives; @@ -74,8 +75,8 @@ public class SensorContextAdapterTest { resourcePerspectives = mock(ResourcePerspectives.class); ComponentDataCache componentDataCache = mock(ComponentDataCache.class); BlockCache blockCache = mock(BlockCache.class); - adaptor = new SensorContextAdaptor(sensorContext, metricFinder, new Project("myProject"), - resourcePerspectives, settings, fs, activeRules, componentDataCache, blockCache, mock(DuplicationCache.class)); + adaptor = new SensorContextAdapter(sensorContext, metricFinder, new Project("myProject"), + resourcePerspectives, settings, fs, activeRules, componentDataCache, blockCache, mock(DuplicationCache.class), mock(SonarIndex.class)); } @Test diff --git a/sonar-batch/src/test/resources/org/sonar/batch/design/DsmSerializerTest/dsm.json b/sonar-batch/src/test/resources/org/sonar/batch/design/DsmSerializerTest/dsm.json new file mode 100644 index 00000000000..57996b089e7 --- /dev/null +++ b/sonar-batch/src/test/resources/org/sonar/batch/design/DsmSerializerTest/dsm.json @@ -0,0 +1 @@ +[{"i":8,"n":"src/org/bar","q":"DIR","v":[{},{"i":30,"w":1}]},{"i":7,"n":"src/org/foo","q":"DIR","v":[{},{}]}] |