All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.server.computation.step.LoadMeasureComputersStep Maven / Gradle / Ivy

There is a newer version: 7.0
Show newest version
/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact AT sonarsource DOT com
 *
 * This program 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.
 *
 * This program 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.Predicate;
import com.google.common.base.Predicates;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.sonar.api.ce.measure.MeasureComputer;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.measures.Metric;
import org.sonar.api.measures.Metrics;
import org.sonar.api.utils.dag.DirectAcyclicGraph;
import org.sonar.server.computation.measure.MutableMeasureComputersHolder;
import org.sonar.server.computation.measure.api.MeasureComputerDefinitionImpl;
import org.sonar.server.computation.measure.api.MeasureComputerWrapper;

import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.FluentIterable.from;
import static org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinition;
import static org.sonar.api.ce.measure.MeasureComputer.MeasureComputerDefinitionContext;

public class LoadMeasureComputersStep implements ComputationStep {

  private static final Set CORE_METRIC_KEYS = from(CoreMetrics.getMetrics()).transform(MetricToKey.INSTANCE).toSet();
  private Set pluginMetricKeys;

  private final MutableMeasureComputersHolder measureComputersHolder;
  private final MeasureComputer[] measureComputers;

  public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories, MeasureComputer[] measureComputers) {
    this.measureComputersHolder = measureComputersHolder;
    this.measureComputers = measureComputers;
    this.pluginMetricKeys = from(Arrays.asList(metricsRepositories))
      .transformAndConcat(MetricsToMetricList.INSTANCE)
      .transform(MetricToKey.INSTANCE)
      .toSet();
  }

  /**
   * Constructor override used by Pico to instantiate the class when no plugin is defining metrics
   */
  public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, MeasureComputer[] measureComputers) {
    this(measureComputersHolder, new Metrics[] {}, measureComputers);
  }

  /**
   * Constructor override used by Pico to instantiate the class when no plugin is defining measure computers
   */
  public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder, Metrics[] metricsRepositories) {
    this(measureComputersHolder, metricsRepositories, new MeasureComputer[] {});
  }

  /**
   * Constructor override used by Pico to instantiate the class when no plugin is defining metrics neither measure computers
   */
  public LoadMeasureComputersStep(MutableMeasureComputersHolder measureComputersHolder) {
    this(measureComputersHolder, new Metrics[] {}, new MeasureComputer[] {});
  }

  @Override
  public void execute() {
    List wrappers = from(Arrays.asList(measureComputers)).transform(ToMeasureWrapper.INSTANCE).toList();
    validateMetrics(wrappers);
    measureComputersHolder.setMeasureComputers(sortComputers(wrappers));
  }

  private static Iterable sortComputers(List wrappers) {
    Map computersByOutputMetric = new HashMap<>();
    Map computersByInputMetric = new HashMap<>();
    feedComputersByMetric(wrappers, computersByOutputMetric, computersByInputMetric);
    ToComputerByKey toComputerByOutputMetricKey = new ToComputerByKey(computersByOutputMetric);
    ToComputerByKey toComputerByInputMetricKey = new ToComputerByKey(computersByInputMetric);

    DirectAcyclicGraph dag = new DirectAcyclicGraph();
    for (MeasureComputerWrapper computer : wrappers) {
      dag.add(computer);
      for (MeasureComputerWrapper dependency : getDependencies(computer, toComputerByOutputMetricKey)) {
        dag.add(computer, dependency);
      }
      for (MeasureComputerWrapper generates : getDependents(computer, toComputerByInputMetricKey)) {
        dag.add(generates, computer);
      }
    }
    return dag.sort();
  }

  private static void feedComputersByMetric(List wrappers, Map computersByOutputMetric,
                                            Map computersByInputMetric) {
    for (MeasureComputerWrapper computer : wrappers) {
      for (String outputMetric : computer.getDefinition().getOutputMetrics()) {
        computersByOutputMetric.put(outputMetric, computer);
      }
      for (String inputMetric : computer.getDefinition().getInputMetrics()) {
        computersByInputMetric.put(inputMetric, computer);
      }
    }
  }

  private void validateMetrics(List wrappers) {
    from(wrappers).transformAndConcat(ToInputMetrics.INSTANCE).filter(new ValidateInputMetric()).size();
    from(wrappers).transformAndConcat(ToOutputMetrics.INSTANCE).filter(new ValidateOutputMetric()).size();
    from(wrappers).filter(new ValidateUniqueOutputMetric()).size();
  }

  private static Iterable getDependencies(MeasureComputerWrapper measureComputer, ToComputerByKey toComputerByOutputMetricKey) {
    // Remove null computer because a computer can depend on a metric that is only generated by a sensor or on a core metrics
    return from(measureComputer.getDefinition().getInputMetrics()).transform(toComputerByOutputMetricKey).filter(Predicates.notNull());
  }

  private static Iterable getDependents(MeasureComputerWrapper measureComputer, ToComputerByKey toComputerByInputMetricKey) {
    return from(measureComputer.getDefinition().getInputMetrics()).transform(toComputerByInputMetricKey);
  }

  private static class ToComputerByKey implements Function {
    private final Map computersByMetric;

    private ToComputerByKey(Map computersByMetric) {
      this.computersByMetric = computersByMetric;
    }

    @Override
    public MeasureComputerWrapper apply(@Nonnull String metricKey) {
      return computersByMetric.get(metricKey);
    }
  }

  private enum MetricToKey implements Function {
    INSTANCE;

    @Nullable
    @Override
    public String apply(@Nonnull Metric input) {
      return input.key();
    }
  }

  private enum MetricsToMetricList implements Function> {
    INSTANCE;

    @Override
    public List apply(@Nonnull Metrics input) {
      return input.getMetrics();
    }
  }

  private enum ToMeasureWrapper implements Function {
    INSTANCE;

    private final MeasureComputerDefinitionContextImpl context = new MeasureComputerDefinitionContextImpl();

    @Override
    public MeasureComputerWrapper apply(@Nonnull MeasureComputer measureComputer) {
      MeasureComputerDefinition def = measureComputer.define(context);
      return new MeasureComputerWrapper(measureComputer, validateDef(def));
    }

    private static MeasureComputerDefinition validateDef(MeasureComputerDefinition def) {
      if (def instanceof MeasureComputerDefinitionImpl) {
        return def;
      }
      // If the computer has not been created by the builder, we recreate it to make sure it's valid
      Set inputMetrics = def.getInputMetrics();
      Set outputMetrics = def.getOutputMetrics();
      return new MeasureComputerDefinitionImpl.BuilderImpl()
        .setInputMetrics(from(inputMetrics).toArray(String.class))
        .setOutputMetrics(from(outputMetrics).toArray(String.class))
        .build();
    }
  }

  private enum ToInputMetrics implements Function> {
    INSTANCE;

    @Override
    public Collection apply(@Nonnull MeasureComputerWrapper input) {
      return input.getDefinition().getInputMetrics();
    }
  }

  private class ValidateInputMetric implements Predicate {
    @Override
    public boolean apply(@Nonnull String metric) {
      checkState(pluginMetricKeys.contains(metric) || CORE_METRIC_KEYS.contains(metric),
        String.format("Metric '%s' cannot be used as an input metric as it's not a core metric and no plugin declare this metric", metric));
      return true;
    }
  }

  private enum ToOutputMetrics implements Function> {
    INSTANCE;

    @Override
    public Collection apply(@Nonnull MeasureComputerWrapper input) {
      return input.getDefinition().getOutputMetrics();
    }
  }

  private class ValidateOutputMetric implements Predicate {
    @Override
    public boolean apply(@Nonnull String metric) {
      checkState(!CORE_METRIC_KEYS.contains(metric), String.format("Metric '%s' cannot be used as an output metric as it's a core metric", metric));
      checkState(pluginMetricKeys.contains(metric), String.format("Metric '%s' cannot be used as an output metric as no plugin declare this metric", metric));
      return true;
    }
  }

  private static class ValidateUniqueOutputMetric implements Predicate {
    private Set allOutputMetrics = new HashSet<>();

    @Override
    public boolean apply(@Nonnull MeasureComputerWrapper wrapper ) {
      for (String outputMetric : wrapper.getDefinition().getOutputMetrics()) {
        checkState(!allOutputMetrics.contains(outputMetric),
          String.format("Output metric '%s' is already defined by another measure computer '%s'", outputMetric, wrapper.getComputer()));
        allOutputMetrics.add(outputMetric);
      }
      return true;
    }
  }

  private static class MeasureComputerDefinitionContextImpl implements MeasureComputerDefinitionContext {
    @Override
    public MeasureComputerDefinition.Builder newDefinitionBuilder() {
      return new MeasureComputerDefinitionImpl.BuilderImpl();
    }
  }

  @Override
  public String getDescription() {
    return "Load measure computers";
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy