org.sonar.server.computation.task.projectanalysis.step.LoadMeasureComputersStep.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.server.computation.task.projectanalysis.step.LoadMeasureComputersStep.java

Source

/*
 * SonarQube
 * Copyright (C) 2009-2017 SonarSource SA
 * mailto:info 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.task.projectanalysis.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.task.projectanalysis.api.measurecomputer.MeasureComputerDefinitionImpl;
import org.sonar.server.computation.task.projectanalysis.api.measurecomputer.MeasureComputerWrapper;
import org.sonar.server.computation.task.projectanalysis.measure.MutableMeasureComputersHolder;
import org.sonar.server.computation.task.step.ComputationStep;

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;

public class LoadMeasureComputersStep implements ComputationStep {

    private static final Set<String> CORE_METRIC_KEYS = from(CoreMetrics.getMetrics())
            .transform(MetricToKey.INSTANCE).toSet();
    private Set<String> 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<MeasureComputerWrapper> wrappers = from(Arrays.asList(measureComputers))
                .transform(ToMeasureWrapper.INSTANCE).toList();
        validateMetrics(wrappers);
        measureComputersHolder.setMeasureComputers(sortComputers(wrappers));
    }

    private static Iterable<MeasureComputerWrapper> sortComputers(List<MeasureComputerWrapper> wrappers) {
        Map<String, MeasureComputerWrapper> computersByOutputMetric = new HashMap<>();
        Map<String, MeasureComputerWrapper> 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<MeasureComputerWrapper> wrappers,
            Map<String, MeasureComputerWrapper> computersByOutputMetric,
            Map<String, MeasureComputerWrapper> 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<MeasureComputerWrapper> 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<MeasureComputerWrapper> 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<MeasureComputerWrapper> getDependents(MeasureComputerWrapper measureComputer,
            ToComputerByKey toComputerByInputMetricKey) {
        return from(measureComputer.getDefinition().getInputMetrics()).transform(toComputerByInputMetricKey);
    }

    private static class ToComputerByKey implements Function<String, MeasureComputerWrapper> {
        private final Map<String, MeasureComputerWrapper> computersByMetric;

        private ToComputerByKey(Map<String, MeasureComputerWrapper> computersByMetric) {
            this.computersByMetric = computersByMetric;
        }

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

    private enum MetricToKey implements Function<Metric, String> {
        INSTANCE;

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

    private enum MetricsToMetricList implements Function<Metrics, List<Metric>> {
        INSTANCE;

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

    private enum ToMeasureWrapper implements Function<MeasureComputer, MeasureComputerWrapper> {
        INSTANCE;

        @Override
        public MeasureComputerWrapper apply(@Nonnull MeasureComputer measureComputer) {
            MeasureComputerDefinition def = measureComputer.define(MeasureComputerDefinitionImpl.BuilderImpl::new);
            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<String> inputMetrics = def.getInputMetrics();
            Set<String> 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<MeasureComputerWrapper, Collection<String>> {
        INSTANCE;

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

    private class ValidateInputMetric implements Predicate<String> {
        @Override
        public boolean apply(@Nonnull String metric) {
            checkState(pluginMetricKeys.contains(metric) || CORE_METRIC_KEYS.contains(metric),
                    "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<MeasureComputerWrapper, Collection<String>> {
        INSTANCE;

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

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

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

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

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