]> source.dussan.org Git - sonarqube.git/commitdiff
Create step to execute visitors and replace SqaleMeasureStep to SqaleMeasureVisitor
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Thu, 6 Aug 2015 16:16:52 +0000 (18:16 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 10 Aug 2015 12:18:19 +0000 (14:18 +0200)
16 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitorsCrawler.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainer.java
server/sonar-server/src/main/java/org/sonar/server/computation/container/ComputeEngineContainerImpl.java
server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleMeasuresVisitor.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/ComputationSteps.java
server/sonar-server/src/main/java/org/sonar/server/computation/step/ExecuteVisitorsStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/step/SqaleMeasuresStep.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/computation/component/DumbComponent.java
server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPathAwareVisitorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPostOrderTypeAwareVisitorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPreOrderTypeAwareVisitorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleMeasuresVisitorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/ExecuteVisitorsStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/step/SqaleMeasuresStepTest.java [deleted file]

diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitorsCrawler.java b/server/sonar-server/src/main/java/org/sonar/server/computation/component/VisitorsCrawler.java
new file mode 100644 (file)
index 0000000..134ff9f
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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.server.computation.component;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import java.util.List;
+import javax.annotation.Nonnull;
+
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.Iterables.concat;
+
+/**
+ * This crawler make any number of {@link TypeAwareVisitor} or {@link PathAwareVisitor} defined in a list visit a component tree, component per component, in the order of the list
+ */
+public class VisitorsCrawler implements ComponentCrawler {
+
+  private final List<VisitorWrapper> preOrderVisitorWrappers;
+  private final List<VisitorWrapper> postOrderVisitorWrappers;
+
+  public VisitorsCrawler(Iterable<Visitor> visitors) {
+    List<VisitorWrapper> visitorWrappers = from(visitors).transform(ToVisitorWrapper.INSTANCE).toList();
+    this.preOrderVisitorWrappers = from(visitorWrappers).filter(MathPreOrderVisitor.INSTANCE).toList();
+    this.postOrderVisitorWrappers = from(visitorWrappers).filter(MatchPostOrderVisitor.INSTANCE).toList();
+  }
+
+  @Override
+  public void visit(final Component component) {
+    List<VisitorWrapper> preOrderVisitorWrappersToExecute = from(preOrderVisitorWrappers).filter(new MatchVisitorMaxDepth(component)).toList();
+    List<VisitorWrapper> postOrderVisitorWrappersToExecute = from(postOrderVisitorWrappers).filter(new MatchVisitorMaxDepth(component)).toList();
+    if (preOrderVisitorWrappersToExecute.isEmpty() && postOrderVisitorWrappersToExecute.isEmpty()) {
+      return;
+    }
+
+    for (VisitorWrapper visitorWrapper : concat(preOrderVisitorWrappers, postOrderVisitorWrappers)) {
+      visitorWrapper.beforeComponent(component);
+    }
+
+    for (VisitorWrapper visitorWrapper : preOrderVisitorWrappersToExecute) {
+      visitNode(component, visitorWrapper);
+    }
+
+    visitChildren(component);
+
+    for (VisitorWrapper visitorWrapper : postOrderVisitorWrappersToExecute) {
+      visitNode(component, visitorWrapper);
+    }
+
+    for (VisitorWrapper visitorWrapper : concat(preOrderVisitorWrappersToExecute, postOrderVisitorWrappersToExecute)) {
+      visitorWrapper.afterComponent(component);
+    }
+  }
+
+  private void visitChildren(Component component) {
+    for (Component child : component.getChildren()) {
+      visit(child);
+    }
+  }
+
+  private void visitNode(Component component, VisitorWrapper visitor) {
+    visitor.visitAny(component);
+    switch (component.getType()) {
+      case PROJECT:
+        visitor.visitProject(component);
+        break;
+      case MODULE:
+        visitor.visitModule(component);
+        break;
+      case DIRECTORY:
+        visitor.visitDirectory(component);
+        break;
+      case FILE:
+        visitor.visitFile(component);
+        break;
+      default:
+        throw new IllegalStateException(String.format("Unknown type %s", component.getType().name()));
+    }
+  }
+
+  private enum ToVisitorWrapper implements Function<Visitor, VisitorWrapper> {
+    INSTANCE;
+
+    @Override
+    public VisitorWrapper apply(@Nonnull Visitor visitor) {
+      if (visitor instanceof TypeAwareVisitor) {
+        return new TypeAwareVisitorWrapper((TypeAwareVisitor) visitor);
+      } else if (visitor instanceof PathAwareVisitor) {
+        return new PathAwareVisitorWrapper((PathAwareVisitor) visitor);
+      } else {
+        throw new IllegalArgumentException("Only TypeAwareVisitor and PathAwareVisitor can be used");
+      }
+    }
+  }
+
+  private static class MatchVisitorMaxDepth implements Predicate<VisitorWrapper> {
+    private final Component component;
+
+    private MatchVisitorMaxDepth(Component component) {
+      this.component = component;
+    }
+
+    @Override
+    public boolean apply(@Nonnull VisitorWrapper visitorWrapper) {
+      return !component.getType().isDeeperThan(visitorWrapper.getMaxDepth());
+    }
+  }
+
+  private enum MathPreOrderVisitor implements Predicate<VisitorWrapper> {
+    INSTANCE;
+
+    @Override
+    public boolean apply(@Nonnull VisitorWrapper visitorWrapper) {
+      return visitorWrapper.getOrder() == Visitor.Order.PRE_ORDER;
+    }
+  }
+
+  private enum MatchPostOrderVisitor implements Predicate<VisitorWrapper> {
+    INSTANCE;
+
+    @Override
+    public boolean apply(@Nonnull VisitorWrapper visitorWrapper) {
+      return visitorWrapper.getOrder() == Visitor.Order.POST_ORDER;
+    }
+  }
+}
index 6a0aa739af6532a1f7affe09fbedce973d4d1542..742d898059d12ca2cb15f5fc0d7e4fd9a7bca21b 100644 (file)
@@ -21,6 +21,7 @@ package org.sonar.server.computation.container;
 
 import org.sonar.core.platform.ComponentContainer;
 import org.sonar.server.computation.ReportQueue.Item;
+import org.sonar.server.computation.component.Visitor;
 import org.sonar.server.computation.step.ComputationStep;
 
 /**
@@ -45,4 +46,7 @@ public interface ComputeEngineContainer {
    */
   <T extends ComputationStep> T getStep(Class<T> type);
 
+  <T extends Visitor> T getComponentVisitor(Class<T> type);
+
+
 }
index 0a0b20c2b20dbb0a8987ae143f02390de1334d09..add42ca7f7181167aeaf79badf03eb833e2bcdf9 100644 (file)
@@ -46,6 +46,7 @@ import org.sonar.server.computation.batch.BatchReportReaderImpl;
 import org.sonar.server.computation.component.DbIdsRepository;
 import org.sonar.server.computation.component.ProjectSettingsRepository;
 import org.sonar.server.computation.component.TreeRootHolderImpl;
+import org.sonar.server.computation.component.Visitor;
 import org.sonar.server.computation.debt.DebtModelHolderImpl;
 import org.sonar.server.computation.event.EventRepositoryImpl;
 import org.sonar.server.computation.issue.BaseIssuesLoader;
@@ -86,6 +87,7 @@ import org.sonar.server.computation.qualitygate.QualityGateHolderImpl;
 import org.sonar.server.computation.qualitygate.QualityGateServiceImpl;
 import org.sonar.server.computation.qualityprofile.ActiveRulesHolderImpl;
 import org.sonar.server.computation.sqale.SqaleRatingSettings;
+import org.sonar.server.computation.step.ComponentVisitors;
 import org.sonar.server.computation.step.ComputationStep;
 import org.sonar.server.computation.step.ComputationSteps;
 import org.sonar.server.view.index.ViewIndex;
@@ -101,12 +103,14 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
 
   private final ReportQueue.Item item;
   private final ComputationSteps steps;
+  private final ComponentVisitors visitors;
 
   public ComputeEngineContainerImpl(ComponentContainer parent, ReportQueue.Item item) {
     super(createContainer(requireNonNull(parent)));
 
     this.item = item;
     this.steps = new ComputationSteps(this);
+    this.visitors = new ComponentVisitors(this);
 
     populateContainer(requireNonNull(item));
     startComponents();
@@ -120,8 +124,10 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
   private void populateContainer(ReportQueue.Item item) {
     add(item);
     add(steps);
+    add(visitors);
     addSingletons(componentClasses());
     addSingletons(steps.orderedStepClasses());
+    addSingletons(visitors.orderedClasses());
     populateFromModules();
   }
 
@@ -267,6 +273,11 @@ public class ComputeEngineContainerImpl extends ComponentContainer implements Co
     return getComponentByType(type);
   }
 
+  @Override
+  public <T extends Visitor> T getComponentVisitor(Class<T> type) {
+    return getComponentByType(type);
+  }
+
   @Override
   public String toString() {
     return "ComputeEngineContainerImpl";
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleMeasuresVisitor.java b/server/sonar-server/src/main/java/org/sonar/server/computation/sqale/SqaleMeasuresVisitor.java
new file mode 100644 (file)
index 0000000..8a9170a
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * 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.server.computation.sqale;
+
+import com.google.common.base.Optional;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.PathAwareVisitorAdapter;
+import org.sonar.server.computation.component.Visitor;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepository;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricRepository;
+
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+
+public class SqaleMeasuresVisitor extends PathAwareVisitorAdapter<SqaleMeasuresVisitor.DevelopmentCost> {
+
+  private final MetricRepository metricRepository;
+  private final MeasureRepository measureRepository;
+  private final SqaleRatingSettings sqaleRatingSettings;
+
+  private final Metric developmentCostMetric;
+  private final Metric technicalDebtMetric;
+  private final Metric debtRatioMetric;
+  private final Metric sqaleRatingMetric;
+
+  public SqaleMeasuresVisitor(MetricRepository metricRepository, MeasureRepository measureRepository, SqaleRatingSettings sqaleRatingSettings) {
+    super(Component.Type.FILE, Visitor.Order.POST_ORDER, new SimpleStackElementFactory<DevelopmentCost>() {
+      @Override
+      public DevelopmentCost createForAny(Component component) {
+        return new DevelopmentCost();
+      }
+    });
+    this.metricRepository = metricRepository;
+    this.measureRepository = measureRepository;
+    this.sqaleRatingSettings = sqaleRatingSettings;
+
+    this.developmentCostMetric = this.metricRepository.getByKey(CoreMetrics.DEVELOPMENT_COST_KEY);
+    this.technicalDebtMetric = this.metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY);
+    this.debtRatioMetric = this.metricRepository.getByKey(CoreMetrics.SQALE_DEBT_RATIO_KEY);
+    this.sqaleRatingMetric = this.metricRepository.getByKey(CoreMetrics.SQALE_RATING_KEY);
+  }
+
+  @Override
+  public void visitProject(Component project, Path<DevelopmentCost> path) {
+    computeAndSaveMeasures(project, path);
+  }
+
+  @Override
+  public void visitDirectory(Component directory, Path<DevelopmentCost> path) {
+    computeAndSaveMeasures(directory, path);
+  }
+
+  @Override
+  public void visitModule(Component module, Path<DevelopmentCost> path) {
+    computeAndSaveMeasures(module, path);
+  }
+
+  @Override
+  public void visitFile(Component file, Path<DevelopmentCost> path) {
+    if (!file.getFileAttributes().isUnitTest()) {
+      long developmentCosts = computeDevelopmentCost(file);
+      path.current().add(developmentCosts);
+      computeAndSaveMeasures(file, path);
+    }
+  }
+
+  private void computeAndSaveMeasures(Component component, Path<DevelopmentCost> path) {
+    saveDevelopmentCostMeasure(component, path.current());
+
+    double density = computeDensity(component, path.current());
+    saveDebtRatioMeasure(component, density);
+    saveSqaleRatingMeasure(component, density);
+
+    increaseParentDevelopmentCost(path);
+  }
+
+  private void saveDevelopmentCostMeasure(Component component, DevelopmentCost developmentCost) {
+    // the value of this measure is stored as a string because it can exceed the size limit of number storage on some DB
+    measureRepository.add(component, developmentCostMetric, newMeasureBuilder().create(Long.toString(developmentCost.getValue())));
+  }
+
+  private double computeDensity(Component component, DevelopmentCost developmentCost) {
+    double debt = getLongValue(measureRepository.getRawMeasure(component, technicalDebtMetric));
+    if (Double.doubleToRawLongBits(developmentCost.getValue()) != 0L) {
+      return debt / (double) developmentCost.getValue();
+    }
+    return 0d;
+  }
+
+  private void saveDebtRatioMeasure(Component component, double density) {
+    measureRepository.add(component, debtRatioMetric, newMeasureBuilder().create(100.0 * density));
+  }
+
+  private void saveSqaleRatingMeasure(Component component, double density) {
+    SqaleRatingGrid ratingGrid = new SqaleRatingGrid(sqaleRatingSettings.getRatingGrid());
+    int rating = ratingGrid.getRatingForDensity(density);
+    String ratingLetter = toRatingLetter(rating);
+    measureRepository.add(component, sqaleRatingMetric, newMeasureBuilder().create(rating, ratingLetter));
+  }
+
+  private void increaseParentDevelopmentCost(Path<DevelopmentCost> path) {
+    if (!path.isRoot()) {
+      // increase parent's developmentCost with our own
+      path.parent().add(path.current().getValue());
+    }
+  }
+
+  private long computeDevelopmentCost(Component file) {
+    String languageKey = file.getFileAttributes().getLanguageKey();
+    String sizeMetricKey = sqaleRatingSettings.getSizeMetricKey(languageKey);
+    Metric sizeMetric = metricRepository.getByKey(sizeMetricKey);
+    return getLongValue(measureRepository.getRawMeasure(file, sizeMetric)) * sqaleRatingSettings.getDevCost(languageKey);
+  }
+
+  private static long getLongValue(Optional<Measure> measure) {
+    if (!measure.isPresent()) {
+      return 0L;
+    }
+    return getLongValue(measure.get());
+  }
+
+  private static long getLongValue(Measure measure) {
+    switch (measure.getValueType()) {
+      case INT:
+        return measure.getIntValue();
+      case LONG:
+        return measure.getLongValue();
+      case DOUBLE:
+        return (long) measure.getDoubleValue();
+      default:
+        return 0L;
+    }
+  }
+
+  private static String toRatingLetter(int rating) {
+    return SqaleRatingGrid.SqaleRating.createForIndex(rating).name();
+  }
+
+  /**
+   * A wrapper class around a long which can be increased and represents the development cost of a Component
+   */
+  public static final class DevelopmentCost {
+    private long value = 0;
+
+    public void add(long developmentCosts) {
+      this.value += developmentCosts;
+    }
+
+    public long getValue() {
+      return value;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ComponentVisitors.java
new file mode 100644 (file)
index 0000000..35673b6
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.server.computation.step;
+
+import com.google.common.base.Function;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.util.List;
+import javax.annotation.Nonnull;
+import org.sonar.server.computation.component.Visitor;
+import org.sonar.server.computation.container.ComputeEngineContainer;
+import org.sonar.server.computation.sqale.SqaleMeasuresVisitor;
+
+/**
+ * Ordered list of component visitors to be executed by {@link ExecuteVisitorsStep}
+ */
+public class ComponentVisitors {
+
+  private static final List<Class<? extends Visitor>> ORDERED_VISITOR_CLASSES = ImmutableList.<Visitor>of(
+    SqaleMeasuresVisitor.class
+  );
+
+  /**
+   * List of all {@link Visitor}, ordered by execution sequence.
+   */
+  public List<Class<? extends Visitor>> orderedClasses() {
+    return ORDERED_VISITOR_CLASSES;
+  }
+
+  private final ComputeEngineContainer computeEngineContainer;
+
+  public ComponentVisitors(ComputeEngineContainer computeEngineContainer) {
+    this.computeEngineContainer = computeEngineContainer;
+  }
+
+  public Iterable<Visitor> instances() {
+    return Iterables.transform(orderedClasses(), new Function<Class<? extends Visitor>, Visitor>() {
+      @Override
+      public Visitor apply(@Nonnull Class<? extends Visitor> input) {
+        Visitor visitor = computeEngineContainer.getComponentVisitor(input);
+        Preconditions.checkState(visitor != null, String.format("Visitor not found: %s", input));
+        return visitor;
+      }
+    });
+  }
+
+}
index ed0d26b02868879edaa2b7447c8c7c4d1d6114f6..982942e0517347c1c9a1a72d59ea9bf6cd551c27 100644 (file)
@@ -58,14 +58,14 @@ public class ComputationSteps {
       CommentMeasuresStep.class,
       CustomMeasuresCopyStep.class,
       DuplicationMeasuresStep.class,
-      // must be executed after the measures required for common rules (coverage, comment density, duplications)
-      IntegrateIssuesStep.class,
       LanguageDistributionMeasuresStep.class,
       UnitTestMeasuresStep.class,
       ComplexityMeasuresStep.class,
 
-      // SQALE measures depend on issues
-      SqaleMeasuresStep.class,
+      // must be executed after the measures required for common rules (coverage, comment density, duplications)
+      IntegrateIssuesStep.class,
+
+      ExecuteVisitorsStep.class,
 
       FeedMeasureComputers.class,
       ComputePluginMeasuresStep.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/ExecuteVisitorsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/ExecuteVisitorsStep.java
new file mode 100644 (file)
index 0000000..7d93a3e
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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.server.computation.step;
+
+import org.sonar.server.computation.component.TreeRootHolder;
+import org.sonar.server.computation.component.Visitor;
+import org.sonar.server.computation.component.VisitorsCrawler;
+
+public class ExecuteVisitorsStep implements ComputationStep {
+  private final TreeRootHolder treeRootHolder;
+  private final Iterable<Visitor> visitors;
+
+  public ExecuteVisitorsStep(TreeRootHolder treeRootHolder, ComponentVisitors visitors) {
+    this.treeRootHolder = treeRootHolder;
+    this.visitors = visitors.instances();
+  }
+
+  @Override
+  public void execute() {
+    new VisitorsCrawler(visitors).visit(treeRootHolder.getRoot());
+  }
+
+  @Override
+  public String getDescription() {
+    return "Execute Visitors";
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/step/SqaleMeasuresStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/step/SqaleMeasuresStep.java
deleted file mode 100644 (file)
index 441d097..0000000
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * 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.server.computation.step;
-
-import com.google.common.base.Optional;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.server.computation.component.Component;
-import org.sonar.server.computation.component.PathAwareCrawler;
-import org.sonar.server.computation.component.TreeRootHolder;
-import org.sonar.server.computation.component.Visitor;
-import org.sonar.server.computation.measure.Measure;
-import org.sonar.server.computation.measure.MeasureRepository;
-import org.sonar.server.computation.metric.Metric;
-import org.sonar.server.computation.metric.MetricRepository;
-import org.sonar.server.computation.sqale.SqaleRatingGrid;
-import org.sonar.server.computation.sqale.SqaleRatingSettings;
-
-import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
-
-public class SqaleMeasuresStep implements ComputationStep {
-  private final TreeRootHolder treeRootHolder;
-  private final MetricRepository metricRepository;
-  private final MeasureRepository measureRepository;
-  private final SqaleRatingSettings sqaleRatingSettings;
-
-  public SqaleMeasuresStep(TreeRootHolder treeRootHolder, MetricRepository metricRepository, MeasureRepository measureRepository,
-    SqaleRatingSettings sqaleRatingSettings) {
-    this.treeRootHolder = treeRootHolder;
-    this.metricRepository = metricRepository;
-    this.measureRepository = measureRepository;
-    this.sqaleRatingSettings = sqaleRatingSettings;
-  }
-
-  @Override
-  public void execute() {
-    new SqaleMeasuresCrawler().visit(treeRootHolder.getRoot());
-  }
-
-  @Override
-  public String getDescription() {
-    return "Compute Sqale related measures";
-  }
-
-  private class SqaleMeasuresCrawler extends PathAwareCrawler<DevelopmentCost> {
-    private final Metric developmentCostMetric;
-    private final Metric technicalDebtMetric;
-    private final Metric debtRatioMetric;
-    private final Metric sqaleRatingMetric;
-
-    public SqaleMeasuresCrawler() {
-      super(Component.Type.FILE, Visitor.Order.POST_ORDER, new SimpleStackElementFactory<DevelopmentCost>() {
-        @Override
-        public DevelopmentCost createForAny(Component component) {
-          return new DevelopmentCost();
-        }
-      });
-
-      this.developmentCostMetric = metricRepository.getByKey(CoreMetrics.DEVELOPMENT_COST_KEY);
-      this.technicalDebtMetric = metricRepository.getByKey(CoreMetrics.TECHNICAL_DEBT_KEY);
-      this.debtRatioMetric = metricRepository.getByKey(CoreMetrics.SQALE_DEBT_RATIO_KEY);
-      this.sqaleRatingMetric = metricRepository.getByKey(CoreMetrics.SQALE_RATING_KEY);
-    }
-
-    @Override
-    public void visitProject(Component project, Path<DevelopmentCost> path) {
-      computeAndSaveMeasures(project, path);
-    }
-
-    @Override
-    public void visitDirectory(Component directory, Path<DevelopmentCost> path) {
-      computeAndSaveMeasures(directory, path);
-    }
-
-    @Override
-    public void visitModule(Component module, Path<DevelopmentCost> path) {
-      computeAndSaveMeasures(module, path);
-    }
-
-    @Override
-    public void visitFile(Component file, Path<DevelopmentCost> path) {
-      if (!file.getFileAttributes().isUnitTest()) {
-        long developmentCosts = computeDevelopmentCost(file);
-        path.current().add(developmentCosts);
-        computeAndSaveMeasures(file, path);
-      }
-    }
-
-    private void computeAndSaveMeasures(Component component, Path<DevelopmentCost> path) {
-      saveDevelopmentCostMeasure(component, path.current());
-
-      double density = computeDensity(component, path.current());
-      saveDebtRatioMeasure(component, density);
-      saveSqaleRatingMeasure(component, density);
-
-      increaseParentDevelopmentCost(path);
-    }
-
-    private void saveDevelopmentCostMeasure(Component component, DevelopmentCost developmentCost) {
-      // the value of this measure is stored as a string because it can exceed the size limit of number storage on some DB
-      measureRepository.add(component, developmentCostMetric, newMeasureBuilder().create(Long.toString(developmentCost.getValue())));
-    }
-
-    private double computeDensity(Component component, DevelopmentCost developmentCost) {
-      double debt = getLongValue(measureRepository.getRawMeasure(component, technicalDebtMetric));
-      if (Double.doubleToRawLongBits(developmentCost.getValue()) != 0L) {
-        return debt / (double) developmentCost.getValue();
-      }
-      return 0d;
-    }
-
-    private void saveDebtRatioMeasure(Component component, double density) {
-      measureRepository.add(component, debtRatioMetric, newMeasureBuilder().create(100.0 * density));
-    }
-
-    private void saveSqaleRatingMeasure(Component component, double density) {
-      SqaleRatingGrid ratingGrid = new SqaleRatingGrid(sqaleRatingSettings.getRatingGrid());
-      int rating = ratingGrid.getRatingForDensity(density);
-      String ratingLetter = toRatingLetter(rating);
-      measureRepository.add(component, sqaleRatingMetric, newMeasureBuilder().create(rating, ratingLetter));
-    }
-
-    private void increaseParentDevelopmentCost(Path<DevelopmentCost> path) {
-      if (!path.isRoot()) {
-        // increase parent's developmentCost with our own
-        path.parent().add(path.current().getValue());
-      }
-    }
-  }
-
-  private long computeDevelopmentCost(Component file) {
-    String languageKey = file.getFileAttributes().getLanguageKey();
-    String sizeMetricKey = sqaleRatingSettings.getSizeMetricKey(languageKey);
-    Metric sizeMetric = metricRepository.getByKey(sizeMetricKey);
-    return getLongValue(measureRepository.getRawMeasure(file, sizeMetric)) * sqaleRatingSettings.getDevCost(languageKey);
-  }
-
-  private static long getLongValue(Optional<Measure> measure) {
-    if (!measure.isPresent()) {
-      return 0L;
-    }
-    return getLongValue(measure.get());
-  }
-
-  private static long getLongValue(Measure measure) {
-    switch (measure.getValueType()) {
-      case INT:
-        return measure.getIntValue();
-      case LONG:
-        return measure.getLongValue();
-      case DOUBLE:
-        return (long) measure.getDoubleValue();
-      default:
-        return 0L;
-    }
-  }
-
-  private static String toRatingLetter(int rating) {
-    return SqaleRatingGrid.SqaleRating.createForIndex(rating).name();
-  }
-
-  /**
-   * A wrapper class around a long which can be increased and represents the development cost of a Component
-   */
-  private static final class DevelopmentCost {
-    private long value = 0;
-
-    public void add(long developmentCosts) {
-      this.value += developmentCosts;
-    }
-
-    public long getValue() {
-      return value;
-    }
-  }
-}
index 46a1e09ac611a48787f7ac8264302a97d36fd25b..ef9f04ec00a2ba55e3def40288f68097eaa68402 100644 (file)
@@ -123,6 +123,15 @@ public class DumbComponent implements Component {
     return ref;
   }
 
+  @Override
+  public String toString() {
+    return "DumbComponent{" +
+      "ref=" + ref +
+      ", key='" + key + '\'' +
+      ", type=" + type +
+      '}';
+  }
+
   public static Builder builder(Type type, int ref) {
     return new Builder(type, ref);
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerTest.java
new file mode 100644 (file)
index 0000000..a381ed2
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * 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.server.computation.component;
+
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.InOrder;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.component.Visitor.Order.POST_ORDER;
+import static org.sonar.server.computation.component.Visitor.Order.PRE_ORDER;
+
+public class VisitorsCrawlerTest {
+
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+
+  private static final Component FILE_5 = component(FILE, 5);
+  private static final Component DIRECTORY_4 = component(DIRECTORY, 4, FILE_5);
+  private static final Component MODULE_3 = component(MODULE, 3, DIRECTORY_4);
+  private static final Component MODULE_2 = component(MODULE, 2, MODULE_3);
+  private static final Component COMPONENT_TREE = component(PROJECT, 1, MODULE_2);
+
+  private final TypeAwareVisitor spyPreOrderTypeAwareVisitor = spy(new TestTypeAwareVisitor(FILE, PRE_ORDER));
+  private final TypeAwareVisitor spyPostOrderTypeAwareVisitor = spy(new TestTypeAwareVisitor(FILE, POST_ORDER));
+  private final TestPathAwareVisitor spyPathAwareVisitor = spy(new TestPathAwareVisitor(FILE, POST_ORDER));
+
+  @Test
+  public void execute_each_visitor_on_each_level() throws Exception {
+    InOrder inOrder = inOrder(spyPostOrderTypeAwareVisitor, spyPathAwareVisitor);
+    VisitorsCrawler underTest = new VisitorsCrawler(Arrays.asList(spyPostOrderTypeAwareVisitor, spyPathAwareVisitor));
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitAny(FILE_5);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitFile(FILE_5);
+    inOrder.verify(spyPathAwareVisitor).visitAny(eq(FILE_5), any(PathAwareVisitor.Path.class));
+    inOrder.verify(spyPathAwareVisitor).visitFile(eq(FILE_5), any(PathAwareVisitor.Path.class));
+
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitAny(DIRECTORY_4);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitDirectory(DIRECTORY_4);
+    inOrder.verify(spyPathAwareVisitor).visitAny(eq(DIRECTORY_4), any(PathAwareVisitor.Path.class));
+    inOrder.verify(spyPathAwareVisitor).visitDirectory(eq(DIRECTORY_4), any(PathAwareVisitor.Path.class));
+
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitAny(MODULE_3);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyPathAwareVisitor).visitAny(eq(MODULE_3), any(PathAwareVisitor.Path.class));
+    inOrder.verify(spyPathAwareVisitor).visitModule(eq(MODULE_3), any(PathAwareVisitor.Path.class));
+
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitAny(MODULE_2);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyPathAwareVisitor).visitAny(eq(MODULE_2), any(PathAwareVisitor.Path.class));
+    inOrder.verify(spyPathAwareVisitor).visitModule(eq(MODULE_2), any(PathAwareVisitor.Path.class));
+
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verify(spyPathAwareVisitor).visitAny(eq(COMPONENT_TREE), any(PathAwareVisitor.Path.class));
+    inOrder.verify(spyPathAwareVisitor).visitProject(eq(COMPONENT_TREE), any(PathAwareVisitor.Path.class));
+  }
+
+  @Test
+  public void execute_pre_visitor_before_post_visitor() throws Exception {
+    InOrder inOrder = inOrder(spyPreOrderTypeAwareVisitor, spyPostOrderTypeAwareVisitor);
+    VisitorsCrawler underTest = new VisitorsCrawler(Arrays.<Visitor>asList(spyPreOrderTypeAwareVisitor, spyPostOrderTypeAwareVisitor));
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyPreOrderTypeAwareVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verify(spyPreOrderTypeAwareVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyPreOrderTypeAwareVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyPreOrderTypeAwareVisitor).visitDirectory(DIRECTORY_4);
+    inOrder.verify(spyPreOrderTypeAwareVisitor).visitFile(FILE_5);
+
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitFile(FILE_5);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitDirectory(DIRECTORY_4);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyPostOrderTypeAwareVisitor).visitProject(COMPONENT_TREE);
+  }
+
+  @Test
+  public void fail_with_IAE_when_visitor_is_not_path_aware_or_type_aware() throws Exception {
+    thrown.expect(IllegalArgumentException.class);
+    thrown.expectMessage("Only TypeAwareVisitor and PathAwareVisitor can be used");
+
+    Visitor visitor = new Visitor() {
+      @Override
+      public Order getOrder() {
+        return PRE_ORDER;
+      }
+
+      @Override
+      public Component.Type getMaxDepth() {
+        return FILE;
+      }
+    };
+    new VisitorsCrawler(Arrays.asList(visitor));
+  }
+
+  private static Component component(final Component.Type type, final int ref, final Component... children) {
+    return DumbComponent.builder(type, ref).addChildren(children).build();
+  }
+
+  private static class TestTypeAwareVisitor extends TypeAwareVisitorAdapter {
+
+    public TestTypeAwareVisitor(Component.Type maxDepth, Visitor.Order order) {
+      super(maxDepth, order);
+    }
+  }
+
+  private static class TestPathAwareVisitor extends PathAwareVisitorAdapter<Integer> {
+
+    public TestPathAwareVisitor(Component.Type maxDepth, Visitor.Order order) {
+      super(maxDepth, order, new SimpleStackElementFactory<Integer>() {
+        @Override
+        public Integer createForAny(Component component) {
+          return component.getRef();
+        }
+      });
+    }
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPathAwareVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPathAwareVisitorTest.java
new file mode 100644 (file)
index 0000000..fb7741e
--- /dev/null
@@ -0,0 +1,392 @@
+/*
+ * 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.server.computation.component;
+
+import com.google.common.base.Function;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.junit.Test;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.collect.FluentIterable.from;
+import static com.google.common.collect.ImmutableList.of;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.component.Visitor.Order.POST_ORDER;
+import static org.sonar.server.computation.component.Visitor.Order.PRE_ORDER;
+
+public class VisitorsCrawlerWithPathAwareVisitorTest {
+
+  private static final int ROOT_REF = 1;
+  private static final DumbComponent SOME_TREE_ROOT = DumbComponent.builder(PROJECT, ROOT_REF)
+    .addChildren(
+      DumbComponent.builder(MODULE, 11)
+        .addChildren(
+          DumbComponent.builder(DIRECTORY, 111)
+            .addChildren(
+              DumbComponent.builder(FILE, 1111).build(),
+              DumbComponent.builder(FILE, 1112).build()
+            )
+            .build(),
+          DumbComponent.builder(DIRECTORY, 112)
+            .addChildren(
+              DumbComponent.builder(FILE, 1121).build()
+            )
+            .build())
+        .build(),
+      DumbComponent.builder(MODULE, 12)
+        .addChildren(
+          DumbComponent.builder(MODULE, 121)
+            .addChildren(
+              DumbComponent.builder(DIRECTORY, 1211)
+                .addChildren(
+                  DumbComponent.builder(FILE, 12111).build()
+                )
+                .build()
+            ).build()
+        ).build()
+    ).build();
+
+  @Test
+  public void verify_preOrder_visit_call_when_visit_tree_with_depth_FILE() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(FILE, PRE_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1)),
+      newCallRecord("visitAny", 11, 1, of(11, 1)),
+      newCallRecord("visitModule", 11, 1, of(11, 1)),
+      newCallRecord("visitAny", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitDirectory", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitAny", 1111, 111, of(1111, 111, 11, 1)),
+      newCallRecord("visitFile", 1111, 111, of(1111, 111, 11, 1)),
+      newCallRecord("visitAny", 1112, 111, of(1112, 111, 11, 1)),
+      newCallRecord("visitFile", 1112, 111, of(1112, 111, 11, 1)),
+      newCallRecord("visitAny", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitDirectory", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitAny", 1121, 112, of(1121, 112, 11, 1)),
+      newCallRecord("visitFile", 1121, 112, of(1121, 112, 11, 1)),
+      newCallRecord("visitAny", 12, 1, of(12, 1)),
+      newCallRecord("visitModule", 12, 1, of(12, 1)),
+      newCallRecord("visitAny", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitModule", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitAny", 1211, 121, of(1211, 121, 12, 1)),
+      newCallRecord("visitDirectory", 1211, 121, of(1211, 121, 12, 1)),
+      newCallRecord("visitAny", 12111, 1211, of(12111, 1211, 121, 12, 1)),
+      newCallRecord("visitFile", 12111, 1211, of(12111, 1211, 121, 12, 1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  @Test
+  public void verify_preOrder_visit_call_when_visit_tree_with_depth_DIRECTORY() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(DIRECTORY, PRE_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1)),
+      newCallRecord("visitAny", 11, 1, of(11, 1)),
+      newCallRecord("visitModule", 11, 1, of(11, 1)),
+      newCallRecord("visitAny", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitDirectory", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitAny", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitDirectory", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitAny", 12, 1, of(12, 1)),
+      newCallRecord("visitModule", 12, 1, of(12, 1)),
+      newCallRecord("visitAny", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitModule", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitAny", 1211, 121, of(1211, 121, 12, 1)),
+      newCallRecord("visitDirectory", 1211, 121, of(1211, 121, 12, 1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  @Test
+  public void verify_preOrder_visit_call_when_visit_tree_with_depth_MODULE() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(MODULE, PRE_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1)),
+      newCallRecord("visitAny", 11, 1, of(11, 1)),
+      newCallRecord("visitModule", 11, 1, of(11, 1)),
+      newCallRecord("visitAny", 12, 1, of(12, 1)),
+      newCallRecord("visitModule", 12, 1, of(12, 1)),
+      newCallRecord("visitAny", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitModule", 121, 12, of(121, 12, 1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  @Test
+  public void verify_preOrder_visit_call_when_visit_tree_with_depth_PROJECT() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(PROJECT, PRE_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  @Test
+  public void verify_postOrder_visit_call_when_visit_tree_with_depth_FILE() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(FILE, POST_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 1111, 111, of(1111, 111, 11, 1)),
+      newCallRecord("visitFile", 1111, 111, of(1111, 111, 11, 1)),
+      newCallRecord("visitAny", 1112, 111, of(1112, 111, 11, 1)),
+      newCallRecord("visitFile", 1112, 111, of(1112, 111, 11, 1)),
+      newCallRecord("visitAny", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitDirectory", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitAny", 1121, 112, of(1121, 112, 11, 1)),
+      newCallRecord("visitFile", 1121, 112, of(1121, 112, 11, 1)),
+      newCallRecord("visitAny", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitDirectory", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitAny", 11, 1, of(11, 1)),
+      newCallRecord("visitModule", 11, 1, of(11, 1)),
+      newCallRecord("visitAny", 12111, 1211, of(12111, 1211, 121, 12, 1)),
+      newCallRecord("visitFile", 12111, 1211, of(12111, 1211, 121, 12, 1)),
+      newCallRecord("visitAny", 1211, 121, of(1211, 121, 12, 1)),
+      newCallRecord("visitDirectory", 1211, 121, of(1211, 121, 12, 1)),
+      newCallRecord("visitAny", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitModule", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitAny", 12, 1, of(12, 1)),
+      newCallRecord("visitModule", 12, 1, of(12, 1)),
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  @Test
+  public void verify_postOrder_visit_call_when_visit_tree_with_depth_DIRECTORY() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(DIRECTORY, POST_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitDirectory", 111, 11, of(111, 11, 1)),
+      newCallRecord("visitAny", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitDirectory", 112, 11, of(112, 11, 1)),
+      newCallRecord("visitAny", 11, 1, of(11, 1)),
+      newCallRecord("visitModule", 11, 1, of(11, 1)),
+      newCallRecord("visitAny", 1211, 121, of(1211, 121, 12, 1)),
+      newCallRecord("visitDirectory", 1211, 121, of(1211, 121, 12, 1)),
+      newCallRecord("visitAny", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitModule", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitAny", 12, 1, of(12, 1)),
+      newCallRecord("visitModule", 12, 1, of(12, 1)),
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  @Test
+  public void verify_postOrder_visit_call_when_visit_tree_with_depth_MODULE() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(MODULE, POST_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 11, 1, of(11, 1)),
+      newCallRecord("visitModule", 11, 1, of(11, 1)),
+      newCallRecord("visitAny", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitModule", 121, 12, of(121, 12, 1)),
+      newCallRecord("visitAny", 12, 1, of(12, 1)),
+      newCallRecord("visitModule", 12, 1, of(12, 1)),
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  @Test
+  public void verify_postOrder_visit_call_when_visit_tree_with_depth_PROJECT() {
+    TestPathAwareVisitor visitor = new TestPathAwareVisitor(PROJECT, POST_ORDER);
+    VisitorsCrawler underTest = newVisitorsCrawler(visitor);
+    underTest.visit(SOME_TREE_ROOT);
+
+    Iterator<CallRecord> expected = of(
+      newCallRecord("visitAny", 1, null, of(1)),
+      newCallRecord("visitProject", 1, null, of(1))
+      ).iterator();
+    verifyCallRecords(expected, visitor.callsRecords.iterator());
+  }
+
+  private static void verifyCallRecords(Iterator<CallRecord> expected, Iterator<CallRecord> actual) {
+    while (expected.hasNext()) {
+      assertThat(actual.next()).isEqualTo(expected.next());
+    }
+  }
+
+  private static CallRecord newCallRecord(String method, int currentRef, @Nullable Integer parentRef, List<Integer> path) {
+    return new CallRecord(method, currentRef, currentRef, parentRef, ROOT_REF, path);
+  }
+
+  private static VisitorsCrawler newVisitorsCrawler(Visitor visitor) {
+    return new VisitorsCrawler(Arrays.asList(visitor));
+  }
+
+  private static class TestPathAwareVisitor extends PathAwareVisitorAdapter<Integer> {
+    private final List<CallRecord> callsRecords = new ArrayList<>();
+
+    public TestPathAwareVisitor(Component.Type maxDepth, Visitor.Order order) {
+      super(maxDepth, order, new SimpleStackElementFactory<Integer>() {
+        @Override
+        public Integer createForAny(Component component) {
+          return component.getRef();
+        }
+      });
+    }
+
+    @Override
+    public void visitProject(Component project, Path<Integer> path) {
+      callsRecords.add(newCallRecord(project, path, "visitProject"));
+    }
+
+    @Override
+    public void visitModule(Component module, Path<Integer> path) {
+      callsRecords.add(newCallRecord(module, path, "visitModule"));
+    }
+
+    @Override
+    public void visitDirectory(Component directory, Path<Integer> path) {
+      callsRecords.add(newCallRecord(directory, path, "visitDirectory"));
+    }
+
+    @Override
+    public void visitFile(Component file, Path<Integer> path) {
+      callsRecords.add(newCallRecord(file, path, "visitFile"));
+    }
+
+    @Override
+    public void visitUnknown(Component unknownComponent, Path<Integer> path) {
+      callsRecords.add(newCallRecord(unknownComponent, path, "visitUnknown"));
+    }
+
+    @Override
+    public void visitAny(Component component, Path<Integer> path) {
+      callsRecords.add(newCallRecord(component, path, "visitAny"));
+    }
+
+    private static CallRecord newCallRecord(Component project, Path<Integer> path, String method) {
+      return new CallRecord(method, project.getRef(), path.current(), getParent(path), path.root(),
+        toValueList(path));
+    }
+
+    private static List<Integer> toValueList(Path<Integer> path) {
+      return from(path.getCurrentPath()).transform(new Function<PathElement<Integer>, Integer>() {
+        @Nonnull
+        @Override
+        public Integer apply(@Nonnull PathElement<Integer> input) {
+          return input.getElement();
+        }
+      }).toList();
+    }
+
+    private static Integer getParent(Path<Integer> path) {
+      try {
+        Integer parent = path.parent();
+        checkArgument(parent != null, "Path.parent returned a null value!");
+        return parent;
+      } catch (NoSuchElementException e) {
+        return null;
+      }
+    }
+  }
+
+  private static class CallRecord {
+    private final String method;
+    private final int ref;
+    private final int current;
+    @CheckForNull
+    private final Integer parent;
+    private final int root;
+    private final List<Integer> path;
+
+    private CallRecord(String method, int ref, int current, @Nullable Integer parent, int root, List<Integer> path) {
+      this.method = method;
+      this.ref = ref;
+      this.current = current;
+      this.parent = parent;
+      this.root = root;
+      this.path = path;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      CallRecord that = (CallRecord) o;
+      return Objects.equals(ref, that.ref) &&
+        Objects.equals(current, that.current) &&
+        Objects.equals(root, that.root) &&
+        Objects.equals(method, that.method) &&
+        Objects.equals(parent, that.parent) &&
+        Objects.equals(path, that.path);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(method, ref, current, parent, root, path);
+    }
+
+    @Override
+    public String toString() {
+      return "{" +
+        "method='" + method + '\'' +
+        ", ref=" + ref +
+        ", current=" + current +
+        ", parent=" + parent +
+        ", root=" + root +
+        ", path=" + path +
+        '}';
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPostOrderTypeAwareVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPostOrderTypeAwareVisitorTest.java
new file mode 100644 (file)
index 0000000..ceddd9b
--- /dev/null
@@ -0,0 +1,303 @@
+/*
+ * 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.server.computation.component;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.component.Visitor.Order.POST_ORDER;
+
+public class VisitorsCrawlerWithPostOrderTypeAwareVisitorTest {
+
+  private static final Component FILE_5 = component(FILE, 5);
+  private static final Component FILE_6 = component(FILE, 6);
+  private static final Component DIRECTORY_4 = component(DIRECTORY, 4, FILE_5, FILE_6);
+  private static final Component MODULE_3 = component(MODULE, 3, DIRECTORY_4);
+  private static final Component MODULE_2 = component(MODULE, 2, MODULE_3);
+  private static final Component COMPONENT_TREE = component(PROJECT, 1, MODULE_2);
+
+  private final TypeAwareVisitor spyProjectVisitor = spy(new TypeAwareVisitorAdapter(PROJECT, POST_ORDER) {
+  });
+  private final TypeAwareVisitor spyModuleVisitor = spy(new TypeAwareVisitorAdapter(MODULE, POST_ORDER) {
+  });
+  private final TypeAwareVisitor spyDirectoryVisitor = spy(new TypeAwareVisitorAdapter(DIRECTORY, POST_ORDER) {
+  });
+  private final TypeAwareVisitor spyFileVisitor = spy(new TypeAwareVisitorAdapter(FILE, POST_ORDER) {
+  });
+  private final InOrder inOrder = inOrder(spyProjectVisitor, spyModuleVisitor, spyDirectoryVisitor, spyFileVisitor);
+
+  @Test(expected = NullPointerException.class)
+  public void visit_null_Component_throws_NPE() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(null);
+  }
+
+  @Test
+  public void visit_file_with_depth_FILE_calls_visit_file() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitAny(component);
+    inOrder.verify(spyFileVisitor).visitFile(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_module_with_depth_FILE_calls_visit_module() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitAny(component);
+    inOrder.verify(spyFileVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_directory_with_depth_FILE_calls_visit_directory() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitAny(component);
+    inOrder.verify(spyFileVisitor).visitDirectory(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_project_with_depth_FILE_calls_visit_project() {
+    Component component = component(PROJECT, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitAny(component);
+    inOrder.verify(spyFileVisitor).visitProject(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_file_with_depth_DIRECTORY_does_not_call_visit_file_nor_visitAny() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor, never()).visitFile(component);
+    inOrder.verify(spyDirectoryVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_directory_with_depth_DIRECTORY_calls_visit_directory() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor).visitAny(component);
+    inOrder.verify(spyDirectoryVisitor).visitDirectory(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_module_with_depth_DIRECTORY_calls_visit_module() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor).visitAny(component);
+    inOrder.verify(spyDirectoryVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_project_with_depth_DIRECTORY_calls_visit_project() {
+    Component component = component(PROJECT, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor).visitAny(component);
+    inOrder.verify(spyDirectoryVisitor).visitProject(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_file_with_depth_MODULE_does_not_call_visit_file_nor_visitAny() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor, never()).visitFile(component);
+    inOrder.verify(spyModuleVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_directory_with_depth_MODULE_does_not_call_visit_directory_nor_visitAny() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor, never()).visitDirectory(component);
+    inOrder.verify(spyModuleVisitor, never()).visitFile(component);
+    inOrder.verify(spyModuleVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_module_with_depth_MODULE_calls_visit_module() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor).visitAny(component);
+    inOrder.verify(spyModuleVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_project_with_depth_MODULE_calls_visit_project() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor).visitAny(component);
+    inOrder.verify(spyModuleVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_file_with_depth_PROJECT_does_not_call_visit_file_nor_visitAny() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor, never()).visitFile(component);
+    inOrder.verify(spyProjectVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_directory_with_depth_PROJECT_does_not_call_visit_directory_nor_visitAny() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor, never()).visitDirectory(component);
+    inOrder.verify(spyProjectVisitor, never()).visitFile(component);
+    inOrder.verify(spyProjectVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_module_with_depth_PROJECT_does_not_call_visit_module_nor_visitAny() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor, never()).visitModule(component);
+    inOrder.verify(spyProjectVisitor, never()).visitDirectory(component);
+    inOrder.verify(spyProjectVisitor, never()).visitFile(component);
+    inOrder.verify(spyProjectVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_project_with_depth_PROJECT_calls_visit_project() {
+    Component component = component(PROJECT, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor).visitAny(component);
+    inOrder.verify(spyProjectVisitor).visitProject(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_FILE() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyFileVisitor).visitAny(FILE_5);
+    inOrder.verify(spyFileVisitor).visitFile(FILE_5);
+    inOrder.verify(spyFileVisitor).visitAny(FILE_6);
+    inOrder.verify(spyFileVisitor).visitFile(FILE_6);
+    inOrder.verify(spyFileVisitor).visitAny(DIRECTORY_4);
+    inOrder.verify(spyFileVisitor).visitDirectory(DIRECTORY_4);
+    inOrder.verify(spyFileVisitor).visitAny(MODULE_3);
+    inOrder.verify(spyFileVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyFileVisitor).visitAny(MODULE_2);
+    inOrder.verify(spyFileVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyFileVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyFileVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_DIRECTORY() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyDirectoryVisitor).visitAny(DIRECTORY_4);
+    inOrder.verify(spyDirectoryVisitor).visitDirectory(DIRECTORY_4);
+    inOrder.verify(spyDirectoryVisitor).visitAny(MODULE_3);
+    inOrder.verify(spyDirectoryVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyDirectoryVisitor).visitAny(MODULE_2);
+    inOrder.verify(spyDirectoryVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyDirectoryVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyDirectoryVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_MODULE() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyModuleVisitor).visitAny(MODULE_3);
+    inOrder.verify(spyModuleVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyModuleVisitor).visitAny(MODULE_2);
+    inOrder.verify(spyModuleVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyModuleVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyModuleVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_PROJECT() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyProjectVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyProjectVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  private static Component component(final Component.Type type, final int ref, final Component... children) {
+    return DumbComponent.builder(type, ref).addChildren(children).build();
+  }
+
+  private static VisitorsCrawler newVisitorsCrawler(Visitor visitor) {
+    return new VisitorsCrawler(Arrays.asList(visitor));
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPreOrderTypeAwareVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/component/VisitorsCrawlerWithPreOrderTypeAwareVisitorTest.java
new file mode 100644 (file)
index 0000000..5ceabfc
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ * 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.server.computation.component;
+
+import java.util.Arrays;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.component.Visitor.Order.PRE_ORDER;
+
+public class VisitorsCrawlerWithPreOrderTypeAwareVisitorTest {
+
+  private static final Component FILE_5 = component(FILE, 5);
+  private static final Component FILE_6 = component(FILE, 6);
+  private static final Component DIRECTORY_4 = component(DIRECTORY, 4, FILE_5, FILE_6);
+  private static final Component MODULE_3 = component(MODULE, 3, DIRECTORY_4);
+  private static final Component MODULE_2 = component(MODULE, 2, MODULE_3);
+  private static final Component COMPONENT_TREE = component(PROJECT, 1, MODULE_2);
+
+  private final TypeAwareVisitor spyProjectVisitor = spy(new TypeAwareVisitorAdapter(PROJECT, PRE_ORDER) {
+  });
+  private final TypeAwareVisitor spyModuleVisitor = spy(new TypeAwareVisitorAdapter(MODULE, PRE_ORDER) {
+  });
+  private final TypeAwareVisitor spyDirectoryVisitor = spy(new TypeAwareVisitorAdapter(DIRECTORY, PRE_ORDER) {
+  });
+  private final TypeAwareVisitor spyFileVisitor = spy(new TypeAwareVisitorAdapter(FILE, PRE_ORDER) {
+  });
+  private final InOrder inOrder = inOrder(spyProjectVisitor, spyModuleVisitor, spyDirectoryVisitor, spyFileVisitor);
+
+  @Test(expected = NullPointerException.class)
+  public void visit_null_Component_throws_NPE() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(null);
+  }
+
+  @Test
+  public void visit_file_with_depth_FILE_calls_visit_file() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitAny(component);
+    inOrder.verify(spyFileVisitor).visitFile(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_module_with_depth_FILE_calls_visit_module() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitAny(component);
+    inOrder.verify(spyFileVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_directory_with_depth_FILE_calls_visit_directory() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitAny(component);
+    inOrder.verify(spyFileVisitor).visitDirectory(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_project_with_depth_FILE_calls_visit_project() {
+    Component component = component(PROJECT, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyFileVisitor).visitProject(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_file_with_depth_DIRECTORY_does_not_call_visit_file_nor_visitAny() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor, never()).visitFile(component);
+    inOrder.verify(spyDirectoryVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_directory_with_depth_DIRECTORY_calls_visit_directory() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor).visitAny(component);
+    inOrder.verify(spyDirectoryVisitor).visitDirectory(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_module_with_depth_DIRECTORY_calls_visit_module() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor).visitAny(component);
+    inOrder.verify(spyDirectoryVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_project_with_depth_DIRECTORY_calls_visit_project() {
+    Component component = component(PROJECT, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyDirectoryVisitor).visitAny(component);
+    inOrder.verify(spyDirectoryVisitor).visitProject(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_file_with_depth_MODULE_does_not_call_visit_file_nor_visit_any() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor, never()).visitFile(component);
+    inOrder.verify(spyModuleVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_directory_with_depth_MODULE_does_not_call_visit_directory_not_visit_any() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor, never()).visitFile(component);
+    inOrder.verify(spyModuleVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_module_with_depth_MODULE_calls_visit_module() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor).visitAny(component);
+    inOrder.verify(spyModuleVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_project_with_depth_MODULE_calls_visit_project() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyModuleVisitor).visitAny(component);
+    inOrder.verify(spyModuleVisitor).visitModule(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void visit_file_with_depth_PROJECT_does_not_call_visit_file_nor_visitAny() {
+    Component component = component(FILE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor, never()).visitFile(component);
+    inOrder.verify(spyProjectVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_directory_with_depth_PROJECT_does_not_call_visit_directory_nor_visitAny() {
+    Component component = component(DIRECTORY, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor, never()).visitFile(component);
+    inOrder.verify(spyProjectVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_module_with_depth_PROJECT_does_not_call_visit_module_nor_visitAny() {
+    Component component = component(MODULE, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor, never()).visitFile(component);
+    inOrder.verify(spyProjectVisitor, never()).visitAny(component);
+  }
+
+  @Test
+  public void visit_project_with_depth_PROJECT_calls_visit_project_nor_visitAny() {
+    Component component = component(PROJECT, 1);
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(component);
+
+    inOrder.verify(spyProjectVisitor).visitAny(component);
+    inOrder.verify(spyProjectVisitor).visitProject(component);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_FILE() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyFileVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyFileVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyFileVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verify(spyFileVisitor).visitAny(MODULE_2);
+    inOrder.verify(spyFileVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyFileVisitor).visitAny(MODULE_3);
+    inOrder.verify(spyFileVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyFileVisitor).visitAny(DIRECTORY_4);
+    inOrder.verify(spyFileVisitor).visitDirectory(DIRECTORY_4);
+    inOrder.verify(spyFileVisitor).visitAny(FILE_5);
+    inOrder.verify(spyFileVisitor).visitFile(FILE_5);
+    inOrder.verify(spyFileVisitor).visitAny(FILE_6);
+    inOrder.verify(spyFileVisitor).visitFile(FILE_6);
+    inOrder.verifyNoMoreInteractions();
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_DIRECTORY() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyDirectoryVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyDirectoryVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verify(spyDirectoryVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyDirectoryVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyDirectoryVisitor).visitDirectory(DIRECTORY_4);
+    inOrder.verify(spyProjectVisitor, never()).visitFile(FILE_5);
+    inOrder.verify(spyProjectVisitor, never()).visitFile(FILE_6);
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_MODULE() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyModuleVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyModuleVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyModuleVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verify(spyModuleVisitor).visitAny(MODULE_2);
+    inOrder.verify(spyModuleVisitor).visitModule(MODULE_2);
+    inOrder.verify(spyModuleVisitor).visitAny(MODULE_3);
+    inOrder.verify(spyModuleVisitor).visitModule(MODULE_3);
+    inOrder.verify(spyProjectVisitor, never()).visitDirectory(DIRECTORY_4);
+  }
+
+  @Test
+  public void verify_visit_call_when_visit_tree_with_depth_PROJECT() {
+    VisitorsCrawler underTest = newVisitorsCrawler(spyProjectVisitor);
+    underTest.visit(COMPONENT_TREE);
+
+    inOrder.verify(spyProjectVisitor).visitAny(COMPONENT_TREE);
+    inOrder.verify(spyProjectVisitor).visitProject(COMPONENT_TREE);
+    inOrder.verify(spyProjectVisitor, never()).visitModule(MODULE_2);
+    inOrder.verify(spyProjectVisitor, never()).visitModule(MODULE_3);
+  }
+
+  private static Component component(final Component.Type type, final int ref, final Component... children) {
+    return DumbComponent.builder(type, ref).addChildren(children).build();
+  }
+
+  private static VisitorsCrawler newVisitorsCrawler(Visitor visitor) {
+    return new VisitorsCrawler(Arrays.asList(visitor));
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleMeasuresVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/sqale/SqaleMeasuresVisitorTest.java
new file mode 100644 (file)
index 0000000..e0f846f
--- /dev/null
@@ -0,0 +1,253 @@
+/*
+ * 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.server.computation.sqale;
+
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.measures.CoreMetrics;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.DumbComponent;
+import org.sonar.server.computation.component.FileAttributes;
+import org.sonar.server.computation.component.Visitor;
+import org.sonar.server.computation.component.VisitorsCrawler;
+import org.sonar.server.computation.measure.Measure;
+import org.sonar.server.computation.measure.MeasureRepoEntry;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricImpl;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
+import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO_KEY;
+import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
+import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+import static org.sonar.server.computation.measure.MeasureRepoEntry.toEntries;
+import static org.sonar.server.computation.sqale.SqaleRatingGrid.SqaleRating.A;
+import static org.sonar.server.computation.sqale.SqaleRatingGrid.SqaleRating.C;
+
+public class SqaleMeasuresVisitorTest {
+
+  private static final String METRIC_KEY_1 = "mKey1";
+  private static final String METRIC_KEY_2 = "mKey2";
+  private static final Metric METRIC_1 = new MetricImpl(1, METRIC_KEY_1, "metric1", Metric.MetricType.FLOAT);
+  private static final Metric METRIC_2 = new MetricImpl(2, METRIC_KEY_2, "metric2", Metric.MetricType.WORK_DUR);
+  private static final String LANGUAGE_KEY_1 = "lKey1";
+  private static final String LANGUAGE_KEY_2 = "lKey2";
+  private static final double[] RATING_GRID = new double[] {34, 50, 362, 900, 36258};
+  private static final long DEV_COST_LANGUAGE_1 = 33;
+  private static final long DEV_COST_LANGUAGE_2 = 42;
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
+    .add(METRIC_1)
+    .add(METRIC_2)
+    .add(CoreMetrics.DEVELOPMENT_COST)
+    .add(CoreMetrics.TECHNICAL_DEBT)
+    .add(CoreMetrics.SQALE_DEBT_RATIO)
+    .add(CoreMetrics.SQALE_RATING);
+
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+
+  private SqaleRatingSettings sqaleRatingSettings = mock(SqaleRatingSettings.class);
+
+  private VisitorsCrawler underTest = new VisitorsCrawler(Arrays.<Visitor>asList(new SqaleMeasuresVisitor(metricRepository, measureRepository, sqaleRatingSettings)));
+
+  @Before
+  public void setUp() {
+    // assumes SQALE rating configuration is consistent
+    when(sqaleRatingSettings.getRatingGrid()).thenReturn(RATING_GRID);
+    when(sqaleRatingSettings.getSizeMetricKey(LANGUAGE_KEY_1)).thenReturn(METRIC_KEY_1);
+    when(sqaleRatingSettings.getSizeMetricKey(LANGUAGE_KEY_2)).thenReturn(METRIC_KEY_2);
+    when(sqaleRatingSettings.getDevCost(LANGUAGE_KEY_1)).thenReturn(DEV_COST_LANGUAGE_1);
+    when(sqaleRatingSettings.getDevCost(LANGUAGE_KEY_2)).thenReturn(DEV_COST_LANGUAGE_2);
+  }
+
+  @Test
+  public void measures_created_for_project_are_all_zero_when_they_have_no_FILE_child() {
+    DumbComponent root = DumbComponent.builder(PROJECT, 1).build();
+    treeRootHolder.setRoot(root);
+
+    underTest.visit(root);
+
+    assertThat(toEntries(measureRepository.getRawMeasures(root))).containsOnly(
+      MeasureRepoEntry.entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create("0")),
+      MeasureRepoEntry.entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(0d)),
+      MeasureRepoEntry.entryOf(SQALE_RATING_KEY, createSqaleRatingMeasure(A))
+      );
+  }
+
+  private Measure createSqaleRatingMeasure(SqaleRatingGrid.SqaleRating sqaleRating) {
+    return newMeasureBuilder().create(sqaleRating.getIndex(), sqaleRating.name());
+  }
+
+  @Test
+  public void verify_computation_of_measures_for_file_depending_upon_language_1() {
+    verify_computation_of_measure_for_file(33000l, DEV_COST_LANGUAGE_1, METRIC_KEY_1, LANGUAGE_KEY_1, C);
+  }
+
+  @Test
+  public void verify_computation_of_measures_for_file_depending_upon_language_2() {
+    verify_computation_of_measure_for_file(4200l, DEV_COST_LANGUAGE_2, METRIC_KEY_2, LANGUAGE_KEY_2, A);
+  }
+
+  /**
+   * Verify the computation of measures values depending upon which language is associated to the file by
+   * processing a tree of a single Component of type FILE.
+   */
+  private void verify_computation_of_measure_for_file(long debt, long languageCost, String metricKey, String languageKey,
+    SqaleRatingGrid.SqaleRating expectedRating) {
+    long measureValue = 10;
+
+    DumbComponent fileComponent = createFileComponent(languageKey, 1);
+    treeRootHolder.setRoot(fileComponent);
+    measureRepository.addRawMeasure(fileComponent.getRef(), metricKey, newMeasureBuilder().create(measureValue));
+    measureRepository.addRawMeasure(fileComponent.getRef(), TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt));
+
+    underTest.visit(fileComponent);
+
+    verifyFileMeasures(fileComponent.getRef(), measureValue, debt, languageCost, expectedRating);
+  }
+
+  @Test
+  public void verify_aggregation_of_developmentCost_and_value_of_measures_computed_from_that() {
+    DumbComponent root = DumbComponent.builder(PROJECT, 1)
+      .addChildren(
+        DumbComponent.builder(MODULE, 11)
+          .addChildren(
+            DumbComponent.builder(DIRECTORY, 111)
+              .addChildren(
+                createFileComponent(LANGUAGE_KEY_1, 1111),
+                createFileComponent(LANGUAGE_KEY_2, 1112)
+              ).build(),
+            DumbComponent.builder(DIRECTORY, 112)
+              .addChildren(
+                createFileComponent(LANGUAGE_KEY_2, 1121)
+              ).build()
+          ).build(),
+        DumbComponent.builder(MODULE, 12)
+          .addChildren(
+            DumbComponent.builder(DIRECTORY, 121)
+              .addChildren(
+                createFileComponent(LANGUAGE_KEY_1, 1211)
+              ).build(),
+            DumbComponent.builder(DIRECTORY, 122).build()
+          ).build(),
+        DumbComponent.builder(MODULE, 13).build()
+      ).build();
+
+    treeRootHolder.setRoot(root);
+
+    long measureValue1111 = 10;
+    long debt1111 = 66000l;
+    measureRepository.addRawMeasure(1111, METRIC_KEY_1, newMeasureBuilder().create(measureValue1111));
+    measureRepository.addRawMeasure(1111, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1111));
+
+    long measureValue1112 = 10;
+    long debt1112 = 4200l;
+    measureRepository.addRawMeasure(1112, METRIC_KEY_2, newMeasureBuilder().create(measureValue1112));
+    measureRepository.addRawMeasure(1112, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1112));
+
+    long debt111 = 96325l;
+    measureRepository.addRawMeasure(111, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt111));
+
+    long measureValue1121 = 30;
+    long debt1121 = 25200l;
+    measureRepository.addRawMeasure(1121, METRIC_KEY_2, newMeasureBuilder().create(measureValue1121));
+    measureRepository.addRawMeasure(1121, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1121));
+
+    long debt112 = 99633l;
+    measureRepository.addRawMeasure(112, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt112));
+
+    long measureValue1211 = 20;
+    long debt1211 = 33000l;
+    measureRepository.addRawMeasure(1211, METRIC_KEY_1, newMeasureBuilder().create(measureValue1211));
+    measureRepository.addRawMeasure(1211, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1211));
+
+    long debt121 = 7524l;
+    measureRepository.addRawMeasure(121, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt121));
+
+    long debt1 = 9999l;
+    measureRepository.addRawMeasure(1, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1));
+
+    underTest.visit(root);
+
+    // verify measures on files
+    verifyFileMeasures(1111, measureValue1111, debt1111, DEV_COST_LANGUAGE_1, C);
+    verifyFileMeasures(1112, measureValue1112, debt1112, DEV_COST_LANGUAGE_2, A);
+    verifyFileMeasures(1121, measureValue1121, debt1121, DEV_COST_LANGUAGE_2, A);
+    verifyFileMeasures(1211, measureValue1211, debt1211, DEV_COST_LANGUAGE_1, C);
+    // directory has no children => no file => 0 everywhere and A rating
+    verifyComponentMeasures(122, 0, 0, A);
+    // directory has children => dev cost is aggregated
+    long devCost111 = measureValue1111 * DEV_COST_LANGUAGE_1 + measureValue1112 * DEV_COST_LANGUAGE_2;
+    verifyComponentMeasures(111, devCost111, debt111 / (double) devCost111, C);
+    long devCost112 = measureValue1121 * DEV_COST_LANGUAGE_2;
+    verifyComponentMeasures(112, devCost112, debt112 / (double) devCost112, C);
+    long devCost121 = measureValue1211 * DEV_COST_LANGUAGE_1;
+    verifyComponentMeasures(121, devCost121, debt121 / (double) devCost121, A);
+    // just for fun, we didn't define any debt on module => they must all have rating A
+    long devCost11 = devCost111 + devCost112;
+    verifyComponentMeasures(11, devCost11, 0, A);
+    long devCost12 = devCost121;
+    verifyComponentMeasures(12, devCost12, 0, A);
+    long devCost13 = 0;
+    verifyComponentMeasures(13, devCost13, 0, A);
+    // project has aggregated dev cost of all files
+    long devCost1 = devCost11 + devCost12 + devCost13;
+    verifyComponentMeasures(1, devCost1, debt1 / (double) devCost1, A);
+  }
+
+  private DumbComponent createFileComponent(String languageKey1, int fileRef) {
+    return DumbComponent.builder(FILE, fileRef).setFileAttributes(new FileAttributes(false, languageKey1)).build();
+  }
+
+  private void verifyNoMeasure(int componentRef) {
+    assertThat(measureRepository.getRawMeasures(componentRef).isEmpty()).isTrue();
+  }
+
+  private void verifyFileMeasures(int componentRef, long measureValue, long debt, long languageCost, SqaleRatingGrid.SqaleRating expectedRating) {
+    long developmentCost = measureValue * languageCost;
+    verifyComponentMeasures(componentRef, developmentCost, debt / developmentCost, expectedRating);
+  }
+
+  private void verifyComponentMeasures(int componentRef, long expectedDevCost, double expectedDebtRatio, SqaleRatingGrid.SqaleRating expectedRating) {
+    assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).containsOnly(
+      MeasureRepoEntry.entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create(Long.toString(expectedDevCost))),
+      MeasureRepoEntry.entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(expectedDebtRatio * 100.0)),
+      MeasureRepoEntry.entryOf(SQALE_RATING_KEY, createSqaleRatingMeasure(expectedRating))
+      );
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/ExecuteVisitorsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/ExecuteVisitorsStepTest.java
new file mode 100644 (file)
index 0000000..137fdd8
--- /dev/null
@@ -0,0 +1,194 @@
+/*
+ * 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.server.computation.step;
+
+import java.util.Arrays;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.server.computation.batch.TreeRootHolderRule;
+import org.sonar.server.computation.component.Component;
+import org.sonar.server.computation.component.PathAwareVisitorAdapter;
+import org.sonar.server.computation.component.TypeAwareVisitorAdapter;
+import org.sonar.server.computation.component.Visitor;
+import org.sonar.server.computation.measure.MeasureRepositoryRule;
+import org.sonar.server.computation.metric.Metric;
+import org.sonar.server.computation.metric.MetricImpl;
+import org.sonar.server.computation.metric.MetricRepositoryRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.api.measures.CoreMetrics.NCLOC;
+import static org.sonar.api.measures.CoreMetrics.NCLOC_KEY;
+import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
+import static org.sonar.server.computation.component.Component.Type.FILE;
+import static org.sonar.server.computation.component.Component.Type.MODULE;
+import static org.sonar.server.computation.component.Component.Type.PROJECT;
+import static org.sonar.server.computation.component.DumbComponent.builder;
+import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
+
+public class ExecuteVisitorsStepTest {
+
+  private static final String TEST_METRIC_KEY = "test";
+
+  private static final int ROOT_REF = 1;
+  private static final int MODULE_REF = 12;
+  private static final int DIRECTORY_REF = 123;
+  private static final int FILE_1_REF = 1231;
+  private static final int FILE_2_REF = 1232;
+
+  @Rule
+  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
+
+  @Rule
+  public MetricRepositoryRule metricRepository = new MetricRepositoryRule()
+    .add(1, NCLOC)
+    .add(new MetricImpl(2, TEST_METRIC_KEY, "name", Metric.MetricType.INT));
+
+  @Rule
+  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
+
+  @Before
+  public void setUp() throws Exception {
+    treeRootHolder.setRoot(
+      builder(PROJECT, ROOT_REF).setKey("project")
+        .addChildren(
+          builder(MODULE, MODULE_REF).setKey("module")
+            .addChildren(
+              builder(DIRECTORY, DIRECTORY_REF).setKey("directory")
+                .addChildren(
+                  builder(FILE, FILE_1_REF).setKey("file1").build(),
+                  builder(FILE, FILE_2_REF).setKey("file2").build()
+                ).build()
+            ).build()
+        ).build());
+  }
+
+  ComponentVisitors visitors = mock(ComponentVisitors.class);
+
+  @Test
+  public void execute_with_type_aware_visitor() throws Exception {
+    when(visitors.instances()).thenReturn(Arrays.<Visitor>asList(new TestTypeAwareVisitor()));
+    ExecuteVisitorsStep underStep = new ExecuteVisitorsStep(treeRootHolder, visitors);
+
+    measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(1));
+    measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(2));
+    measureRepository.addRawMeasure(DIRECTORY_REF, NCLOC_KEY, newMeasureBuilder().create(3));
+    measureRepository.addRawMeasure(MODULE_REF, NCLOC_KEY, newMeasureBuilder().create(3));
+    measureRepository.addRawMeasure(ROOT_REF, NCLOC_KEY, newMeasureBuilder().create(3));
+
+    underStep.execute();
+
+    assertThat(measureRepository.getAddedRawMeasure(FILE_1_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getAddedRawMeasure(FILE_2_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(3);
+    assertThat(measureRepository.getAddedRawMeasure(DIRECTORY_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(4);
+    assertThat(measureRepository.getAddedRawMeasure(MODULE_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(4);
+    assertThat(measureRepository.getAddedRawMeasure(ROOT_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(4);
+  }
+
+  @Test
+  public void execute_with_path_aware_visitor() throws Exception {
+    when(visitors.instances()).thenReturn(Arrays.<Visitor>asList(new TestPathAwareVisitor()));
+    ExecuteVisitorsStep underStep = new ExecuteVisitorsStep(treeRootHolder, visitors);
+
+    measureRepository.addRawMeasure(FILE_1_REF, NCLOC_KEY, newMeasureBuilder().create(1));
+    measureRepository.addRawMeasure(FILE_2_REF, NCLOC_KEY, newMeasureBuilder().create(1));
+
+    underStep.execute();
+
+    assertThat(measureRepository.getAddedRawMeasure(FILE_1_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getAddedRawMeasure(FILE_2_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(1);
+    assertThat(measureRepository.getAddedRawMeasure(DIRECTORY_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getAddedRawMeasure(MODULE_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(2);
+    assertThat(measureRepository.getAddedRawMeasure(ROOT_REF, TEST_METRIC_KEY).get().getIntValue()).isEqualTo(2);
+  }
+
+  private class TestTypeAwareVisitor extends TypeAwareVisitorAdapter {
+
+    public TestTypeAwareVisitor() {
+      super(Component.Type.FILE, Visitor.Order.POST_ORDER);
+    }
+
+    @Override
+    public void visitAny(Component any) {
+      int ncloc = measureRepository.getRawMeasure(any, metricRepository.getByKey(NCLOC_KEY)).get().getIntValue();
+      measureRepository.add(any, metricRepository.getByKey(TEST_METRIC_KEY), newMeasureBuilder().create(ncloc + 1));
+    }
+  }
+
+  private class TestPathAwareVisitor extends PathAwareVisitorAdapter<Counter> {
+
+    public TestPathAwareVisitor() {
+      super(Component.Type.FILE, Visitor.Order.POST_ORDER, new SimpleStackElementFactory<Counter>() {
+        @Override
+        public Counter createForAny(Component component) {
+          return new Counter();
+        }
+      });
+    }
+
+    @Override
+    public void visitProject(Component project, Path<Counter> path) {
+      computeAndSaveMeasures(project, path);
+    }
+
+    @Override
+    public void visitModule(Component module, Path<Counter> path) {
+      computeAndSaveMeasures(module, path);
+    }
+
+    @Override
+    public void visitDirectory(Component directory, Path<Counter> path) {
+      computeAndSaveMeasures(directory, path);
+    }
+
+    @Override
+    public void visitFile(Component file, Path<Counter> path) {
+      int ncloc = measureRepository.getRawMeasure(file, metricRepository.getByKey(NCLOC_KEY)).get().getIntValue();
+      path.current().add(ncloc);
+      computeAndSaveMeasures(file, path);
+    }
+
+    private void computeAndSaveMeasures(Component component, Path<Counter> path) {
+      measureRepository.add(component, metricRepository.getByKey(TEST_METRIC_KEY), newMeasureBuilder().create(path.current().getValue()));
+      increaseParentValue(path);
+    }
+
+    private void increaseParentValue(Path<Counter> path) {
+      if (!path.isRoot()) {
+        path.parent().add(path.current().getValue());
+      }
+    }
+  }
+
+  public class Counter {
+    private int value = 0;
+
+    public void add(int value) {
+      this.value += value;
+    }
+
+    public int getValue() {
+      return value;
+    }
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/step/SqaleMeasuresStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/step/SqaleMeasuresStepTest.java
deleted file mode 100644 (file)
index 4031ad6..0000000
+++ /dev/null
@@ -1,251 +0,0 @@
-/*
- * 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.server.computation.step;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.sonar.api.measures.CoreMetrics;
-import org.sonar.server.computation.batch.TreeRootHolderRule;
-import org.sonar.server.computation.component.DumbComponent;
-import org.sonar.server.computation.component.FileAttributes;
-import org.sonar.server.computation.measure.Measure;
-import org.sonar.server.computation.measure.MeasureRepoEntry;
-import org.sonar.server.computation.measure.MeasureRepositoryRule;
-import org.sonar.server.computation.metric.Metric;
-import org.sonar.server.computation.metric.MetricImpl;
-import org.sonar.server.computation.metric.MetricRepositoryRule;
-import org.sonar.server.computation.sqale.SqaleRatingGrid;
-import org.sonar.server.computation.sqale.SqaleRatingSettings;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.api.measures.CoreMetrics.DEVELOPMENT_COST_KEY;
-import static org.sonar.api.measures.CoreMetrics.SQALE_DEBT_RATIO_KEY;
-import static org.sonar.api.measures.CoreMetrics.SQALE_RATING_KEY;
-import static org.sonar.api.measures.CoreMetrics.TECHNICAL_DEBT_KEY;
-import static org.sonar.server.computation.component.Component.Type.DIRECTORY;
-import static org.sonar.server.computation.component.Component.Type.FILE;
-import static org.sonar.server.computation.component.Component.Type.MODULE;
-import static org.sonar.server.computation.component.Component.Type.PROJECT;
-import static org.sonar.server.computation.measure.Measure.newMeasureBuilder;
-import static org.sonar.server.computation.measure.MeasureRepoEntry.toEntries;
-import static org.sonar.server.computation.sqale.SqaleRatingGrid.SqaleRating.A;
-import static org.sonar.server.computation.sqale.SqaleRatingGrid.SqaleRating.C;
-
-public class SqaleMeasuresStepTest {
-
-  private static final String METRIC_KEY_1 = "mKey1";
-  private static final String METRIC_KEY_2 = "mKey2";
-  private static final Metric METRIC_1 = new MetricImpl(1, METRIC_KEY_1, "metric1", Metric.MetricType.FLOAT);
-  private static final Metric METRIC_2 = new MetricImpl(2, METRIC_KEY_2, "metric2", Metric.MetricType.WORK_DUR);
-  private static final String LANGUAGE_KEY_1 = "lKey1";
-  private static final String LANGUAGE_KEY_2 = "lKey2";
-  private static final double[] RATING_GRID = new double[]{34, 50, 362, 900, 36258};
-  private static final long DEV_COST_LANGUAGE_1 = 33;
-  private static final long DEV_COST_LANGUAGE_2 = 42;
-
-  @Rule
-  public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule();
-
-  @Rule
-  public MetricRepositoryRule metricRepository = new MetricRepositoryRule().add(METRIC_1).add(METRIC_2);
-
-  @Rule
-  public MeasureRepositoryRule measureRepository = MeasureRepositoryRule.create(treeRootHolder, metricRepository);
-
-  private SqaleRatingSettings sqaleRatingSettings = mock(SqaleRatingSettings.class);
-
-  private SqaleMeasuresStep underTest = new SqaleMeasuresStep(treeRootHolder, metricRepository, measureRepository, sqaleRatingSettings);
-
-  @Before
-  public void setUp() {
-    // assumes SQALE rating configuration is consistent
-    when(sqaleRatingSettings.getRatingGrid()).thenReturn(RATING_GRID);
-    when(sqaleRatingSettings.getSizeMetricKey(LANGUAGE_KEY_1)).thenReturn(METRIC_KEY_1);
-    when(sqaleRatingSettings.getSizeMetricKey(LANGUAGE_KEY_2)).thenReturn(METRIC_KEY_2);
-    when(sqaleRatingSettings.getDevCost(LANGUAGE_KEY_1)).thenReturn(DEV_COST_LANGUAGE_1);
-    when(sqaleRatingSettings.getDevCost(LANGUAGE_KEY_2)).thenReturn(DEV_COST_LANGUAGE_2);
-
-    // this measures are always retrieved by the step
-    metricRepository.add(CoreMetrics.DEVELOPMENT_COST);
-    metricRepository.add(CoreMetrics.TECHNICAL_DEBT);
-    metricRepository.add(CoreMetrics.SQALE_DEBT_RATIO);
-    metricRepository.add(CoreMetrics.SQALE_RATING);
-  }
-
-  @Test
-  public void measures_created_for_project_are_all_zero_when_they_have_no_FILE_child() {
-    DumbComponent root = DumbComponent.builder(PROJECT, 1).build();
-    treeRootHolder.setRoot(root);
-
-    underTest.execute();
-
-    assertThat(toEntries(measureRepository.getRawMeasures(root))).containsOnly(
-      MeasureRepoEntry.entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create("0")),
-      MeasureRepoEntry.entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(0d)),
-      MeasureRepoEntry.entryOf(SQALE_RATING_KEY, createSqaleRatingMeasure(A))
-    );
-  }
-
-  private Measure createSqaleRatingMeasure(SqaleRatingGrid.SqaleRating sqaleRating) {
-    return newMeasureBuilder().create(sqaleRating.getIndex(), sqaleRating.name());
-  }
-
-  @Test
-  public void verify_computation_of_measures_for_file_depending_upon_language_1() {
-    verify_computation_of_measure_for_file(33000l, DEV_COST_LANGUAGE_1, METRIC_KEY_1, LANGUAGE_KEY_1, C);
-  }
-
-  @Test
-  public void verify_computation_of_measures_for_file_depending_upon_language_2() {
-    verify_computation_of_measure_for_file(4200l, DEV_COST_LANGUAGE_2, METRIC_KEY_2, LANGUAGE_KEY_2, A);
-  }
-
-  /**
-   * Verify the computation of measures values depending upon which language is associated to the file by
-   * processing a tree of a single Component of type FILE.
-   */
-  private void verify_computation_of_measure_for_file(long debt, long languageCost, String metricKey, String languageKey,
-                                                      SqaleRatingGrid.SqaleRating expectedRating) {
-    long measureValue = 10;
-
-    DumbComponent fileComponent = createFileComponent(languageKey, 1);
-    treeRootHolder.setRoot(fileComponent);
-    measureRepository.addRawMeasure(fileComponent.getRef(), metricKey, newMeasureBuilder().create(measureValue));
-    measureRepository.addRawMeasure(fileComponent.getRef(), TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt));
-
-    underTest.execute();
-
-    verifyFileMeasures(fileComponent.getRef(), measureValue, debt, languageCost, expectedRating);
-  }
-
-  @Test
-  public void verify_aggregation_of_developmentCost_and_value_of_measures_computed_from_that() {
-    DumbComponent root = DumbComponent.builder(PROJECT, 1)
-      .addChildren(
-        DumbComponent.builder(MODULE, 11)
-          .addChildren(
-            DumbComponent.builder(DIRECTORY, 111)
-              .addChildren(
-                createFileComponent(LANGUAGE_KEY_1, 1111),
-                createFileComponent(LANGUAGE_KEY_2, 1112)
-              ).build(),
-            DumbComponent.builder(DIRECTORY, 112)
-              .addChildren(
-                createFileComponent(LANGUAGE_KEY_2, 1121)
-              ).build()
-          ).build(),
-        DumbComponent.builder(MODULE, 12)
-          .addChildren(
-            DumbComponent.builder(DIRECTORY, 121)
-              .addChildren(
-                createFileComponent(LANGUAGE_KEY_1, 1211)
-              ).build(),
-            DumbComponent.builder(DIRECTORY, 122).build()
-          ).build(),
-        DumbComponent.builder(MODULE, 13).build()
-      ).build();
-
-    treeRootHolder.setRoot(root);
-
-    long measureValue1111 = 10;
-    long debt1111 = 66000l;
-    measureRepository.addRawMeasure(1111, METRIC_KEY_1, newMeasureBuilder().create(measureValue1111));
-    measureRepository.addRawMeasure(1111, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1111));
-
-    long measureValue1112 = 10;
-    long debt1112 = 4200l;
-    measureRepository.addRawMeasure(1112, METRIC_KEY_2, newMeasureBuilder().create(measureValue1112));
-    measureRepository.addRawMeasure(1112, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1112));
-
-    long debt111 = 96325l;
-    measureRepository.addRawMeasure(111, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt111));
-
-    long measureValue1121 = 30;
-    long debt1121 = 25200l;
-    measureRepository.addRawMeasure(1121, METRIC_KEY_2, newMeasureBuilder().create(measureValue1121));
-    measureRepository.addRawMeasure(1121, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1121));
-
-    long debt112 = 99633l;
-    measureRepository.addRawMeasure(112, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt112));
-
-    long measureValue1211 = 20;
-    long debt1211 = 33000l;
-    measureRepository.addRawMeasure(1211, METRIC_KEY_1, newMeasureBuilder().create(measureValue1211));
-    measureRepository.addRawMeasure(1211, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1211));
-
-    long debt121 = 7524l;
-    measureRepository.addRawMeasure(121, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt121));
-
-    long debt1 = 9999l;
-    measureRepository.addRawMeasure(1, TECHNICAL_DEBT_KEY, newMeasureBuilder().create(debt1));
-
-    underTest.execute();
-
-    // verify measures on files
-    verifyFileMeasures(1111, measureValue1111, debt1111, DEV_COST_LANGUAGE_1, C);
-    verifyFileMeasures(1112, measureValue1112, debt1112, DEV_COST_LANGUAGE_2, A);
-    verifyFileMeasures(1121, measureValue1121, debt1121, DEV_COST_LANGUAGE_2, A);
-    verifyFileMeasures(1211, measureValue1211, debt1211, DEV_COST_LANGUAGE_1, C);
-    // directory has no children => no file => 0 everywhere and A rating
-    verifyComponentMeasures(122, 0, 0, A);
-    // directory has children => dev cost is aggregated
-    long devCost111 = measureValue1111 * DEV_COST_LANGUAGE_1 + measureValue1112 * DEV_COST_LANGUAGE_2;
-    verifyComponentMeasures(111, devCost111, debt111 / (double) devCost111, C);
-    long devCost112 = measureValue1121 * DEV_COST_LANGUAGE_2;
-    verifyComponentMeasures(112, devCost112, debt112 / (double) devCost112, C);
-    long devCost121 = measureValue1211 * DEV_COST_LANGUAGE_1;
-    verifyComponentMeasures(121, devCost121, debt121 / (double) devCost121, A);
-    // just for fun, we didn't define any debt on module => they must all have rating A
-    long devCost11 = devCost111 + devCost112;
-    verifyComponentMeasures(11, devCost11, 0, A);
-    long devCost12 = devCost121;
-    verifyComponentMeasures(12, devCost12, 0, A);
-    long devCost13 = 0;
-    verifyComponentMeasures(13, devCost13, 0, A);
-    // project has aggregated dev cost of all files
-    long devCost1 = devCost11 + devCost12 + devCost13;
-    verifyComponentMeasures(1, devCost1, debt1 / (double) devCost1, A);
-  }
-
-  private DumbComponent createFileComponent(String languageKey1, int fileRef) {
-    return DumbComponent.builder(FILE, fileRef).setFileAttributes(new FileAttributes(false, languageKey1)).build();
-  }
-
-  private void verifyNoMeasure(int componentRef) {
-    assertThat(measureRepository.getRawMeasures(componentRef).isEmpty()).isTrue();
-  }
-
-  private void verifyFileMeasures(int componentRef, long measureValue, long debt, long languageCost, SqaleRatingGrid.SqaleRating expectedRating) {
-    long developmentCost = measureValue * languageCost;
-    verifyComponentMeasures(componentRef, developmentCost, debt / developmentCost, expectedRating);
-  }
-
-  private void verifyComponentMeasures(int componentRef, long expectedDevCost, double expectedDebtRatio, SqaleRatingGrid.SqaleRating expectedRating) {
-    assertThat(toEntries(measureRepository.getAddedRawMeasures(componentRef))).containsOnly(
-      MeasureRepoEntry.entryOf(DEVELOPMENT_COST_KEY, newMeasureBuilder().create(Long.toString(expectedDevCost))),
-      MeasureRepoEntry.entryOf(SQALE_DEBT_RATIO_KEY, newMeasureBuilder().create(expectedDebtRatio * 100.0)),
-      MeasureRepoEntry.entryOf(SQALE_RATING_KEY, createSqaleRatingMeasure(expectedRating))
-    );
-  }
-
-}