diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2015-08-27 16:05:18 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2015-08-31 09:52:02 +0200 |
commit | 48835a873353be8fdd48ca57313f6a74ddaf15f7 (patch) | |
tree | 4b6a16d3fd0b8822dc18d831b5ec027bf6962507 /sonar-plugin-api | |
parent | a2a6a50c88c58a0af3daa52e39e76d362078f547 (diff) | |
download | sonarqube-48835a873353be8fdd48ca57313f6a74ddaf15f7.tar.gz sonarqube-48835a873353be8fdd48ca57313f6a74ddaf15f7.zip |
SONAR-6730 Move RangeDistrubtionBuilder to API and allow constructor to take bottom limits
Diffstat (limited to 'sonar-plugin-api')
3 files changed, 391 insertions, 7 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/RangeDistributionBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/RangeDistributionBuilder.java new file mode 100644 index 00000000000..06408466db6 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/RangeDistributionBuilder.java @@ -0,0 +1,211 @@ +/* + * 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.api.ce.measure; + +import com.google.common.collect.Multiset; +import com.google.common.collect.TreeMultiset; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import javax.annotation.CheckForNull; +import org.sonar.api.utils.KeyValueFormat; + +/** + * Utility to build a distribution based on defined ranges + * <p/> + * <p>An example of usage : you wish to record the percentage of lines of code that belong to method + * with pre-defined ranges of complexity.</p> + * + */ +public class RangeDistributionBuilder { + + private Multiset<Number> distributionSet; + private boolean isEmpty = true; + private Number[] bottomLimits; + private boolean isValid = true; + + public RangeDistributionBuilder() { + // Nothing to be done here, bottom limits will be automatically calculated when adding the first value + } + + /** + * RangeDistributionBuilder for a defined range + * Each entry is initialized at zero + * + * @param bottomLimits the bottom limits of ranges to be used + */ + public RangeDistributionBuilder(Number[] bottomLimits) { + init(bottomLimits); + } + + /** + * Increments an entry by 1 + * + * @param value the value to use to pick the entry to increment + */ + public RangeDistributionBuilder add(Number value) { + return add(value, 1); + } + + /** + * Increments an entry + * + * @param value the value to use to pick the entry to increment + * @param count the number by which to increment + */ + public RangeDistributionBuilder add(Number value, int count) { + if (greaterOrEqualsThan(value, bottomLimits[0])) { + addValue(value, count); + isEmpty = false; + } + return this; + } + + /** + * Adds an existing Distribution to the current one. + * It will create the entries if they don't exist. + * Can be used to add the values of children resources for example + * <p/> + * The returned distribution will be invalidated in case the given value does not use the same bottom limits + * + * @param data the data to add to the current one + */ + public RangeDistributionBuilder add(String data) { + Map<Double, Double> map = KeyValueFormat.parse(data, KeyValueFormat.newDoubleConverter(), KeyValueFormat.newDoubleConverter()); + Number[] limits = map.keySet().toArray(new Number[map.size()]); + if (bottomLimits == null) { + init(limits); + + } else if (!areSameLimits(bottomLimits, map.keySet())) { + isValid = false; + } + + if (isValid) { + for (Map.Entry<Double, Double> entry : map.entrySet()) { + addLimitCount(entry.getKey(), entry.getValue().intValue()); + } + } + return this; + } + + private void init(Number[] bottomLimits) { + this.bottomLimits = new Number[bottomLimits.length]; + System.arraycopy(bottomLimits, 0, this.bottomLimits, 0, this.bottomLimits.length); + Arrays.sort(this.bottomLimits); + changeDoublesToInts(); + distributionSet = TreeMultiset.create(NumberComparator.INSTANCE); + } + + private void changeDoublesToInts() { + boolean onlyInts = true; + for (Number bottomLimit : bottomLimits) { + if (NumberComparator.INSTANCE.compare(bottomLimit.intValue(), bottomLimit.doubleValue()) != 0) { + onlyInts = false; + } + } + if (onlyInts) { + for (int i = 0; i < bottomLimits.length; i++) { + bottomLimits[i] = bottomLimits[i].intValue(); + } + } + } + + private static boolean areSameLimits(Number[] bottomLimits, Set<Double> limits) { + if (limits.size() == bottomLimits.length) { + for (Number l : bottomLimits) { + if (!limits.contains(l.doubleValue())) { + return false; + } + } + return true; + } + return false; + } + + private RangeDistributionBuilder addLimitCount(Number limit, int count) { + for (Number bottomLimit : bottomLimits) { + if (NumberComparator.INSTANCE.compare(bottomLimit.doubleValue(), limit.doubleValue()) == 0) { + addValue(limit, count); + isEmpty = false; + return this; + } + } + isValid = false; + return this; + } + + private void addValue(Number value, int count) { + for (int i = bottomLimits.length - 1; i >= 0; i--) { + if (greaterOrEqualsThan(value, bottomLimits[i])) { + this.distributionSet.add(bottomLimits[i], count); + return; + } + } + } + + /** + * @return whether the current object is empty or not + */ + public boolean isEmpty() { + return isEmpty; + } + + /** + * Used to build a measure from the current object + * + * @return the built measure + */ + @CheckForNull + public String build() { + if (isValid) { + return KeyValueFormat.format(toMap()); + } + return null; + } + + private Map<Number, Integer> toMap() { + if (bottomLimits == null || bottomLimits.length == 0) { + return Collections.emptyMap(); + } + Map<Number, Integer> map = new TreeMap<>(); + for (Number value : bottomLimits) { + map.put(value, distributionSet.count(value)); + } + return map; + } + + private static boolean greaterOrEqualsThan(Number n1, Number n2) { + return NumberComparator.INSTANCE.compare(n1, n2) >= 0; + } + + private enum NumberComparator implements Comparator<Number> { + INSTANCE; + + @Override + public int compare(Number n1, Number n2) { + return ((Double) n1.doubleValue()).compareTo(n2.doubleValue()); + } + } + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/measures/RangeDistributionBuilder.java b/sonar-plugin-api/src/main/java/org/sonar/api/measures/RangeDistributionBuilder.java index 26d82c1fdcf..6e2fd0049b7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/measures/RangeDistributionBuilder.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/measures/RangeDistributionBuilder.java @@ -19,6 +19,11 @@ */ package org.sonar.api.measures; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; import org.apache.commons.collections.SortedBag; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.bag.TransformedSortedBag; @@ -27,13 +32,6 @@ import org.apache.commons.lang.NumberUtils; import org.sonar.api.utils.KeyValueFormat; import org.sonar.api.utils.SonarException; -import javax.annotation.Nullable; - -import java.util.Arrays; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - /** * Utility to build a distribution based on defined ranges * <p/> @@ -41,7 +39,9 @@ import java.util.Set; * with pre-defined ranges of complexity.</p> * * @since 1.10 + * @deprecated since 5.2 use {@link org.sonar.api.ce.measure.RangeDistributionBuilder instead} */ +@Deprecated public class RangeDistributionBuilder implements MeasureBuilder { private Metric<String> metric; diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/ce/measure/RangeDistributionBuilderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/ce/measure/RangeDistributionBuilderTest.java new file mode 100644 index 00000000000..f59ee7b963e --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/ce/measure/RangeDistributionBuilderTest.java @@ -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.api.ce.measure; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RangeDistributionBuilderTest { + + @Test + public void work_on_an_limits_array_copy() { + Integer[] limits = new Integer[] {4, 2, 0}; + RangeDistributionBuilder builder = new RangeDistributionBuilder(limits); + builder.add(3.2).add(2.0).add(6.2).build(); + + assertThat(limits[0]).isEqualTo(4); + assertThat(limits[1]).isEqualTo(2); + assertThat(limits[2]).isEqualTo(0); + } + + @Test + public void build_integer_distribution() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(new Integer[] {0, 2, 4}); + String data = builder + .add(3.2) + .add(2.0) + .add(6.2) + .build(); + + assertThat(data).isEqualTo("0=0;2=2;4=1"); + } + + @Test + public void build_double_distribution() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(new Double[] {0.0, 2.0, 4.0}); + String data = builder + .add(3.2) + .add(2.0) + .add(6.2) + .build(); + + assertThat(data).isEqualTo("0=0;2=2;4=1"); + } + + @Test + public void value_lesser_than_minimum_is_ignored() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(new Integer[] {0, 2, 4}); + String data = builder + .add(3.2) + .add(2.0) + .add(-3.0) + .build(); + + assertThat(data).isEqualTo("0=0;2=2;4=0"); + } + + @Test + public void add_existing_integer_distribution() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + String data = builder + .add("0=0;2=2;4=1") + .add("0=1;2=2;4=2") + .build(); + + assertThat(data).isEqualTo("0=1;2=4;4=3"); + } + + @Test + public void add_existing_double_distribution() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + String data = builder + .add("0.5=0;1.9=2;4.5=1") + .add("0.5=1;1.9=3;4.5=1") + .build(); + + assertThat(data).isEqualTo("0.5=1;1.9=5;4.5=2"); + } + + @Test + public void add_distribution_with_identical_limits() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + String data = builder + .add("0=1;2=0") + .add("0=3;2=5") + .build(); + + assertThat(data).isEqualTo("0=4;2=5"); + } + + @Test + public void add_distribution_with_different_int_limits() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + + assertThat(builder + .add("0=1") + .add("0=3;2=5") + .build()).isNull(); + } + + @Test + public void add_distribution_with_different_double_limits() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + + assertThat(builder + .add("0.0=3;3.0=5") + .add("0.0=3;3.0=5;6.0=9") + .build()).isNull(); + } + + @Test + public void init_limits_at_the_first_add() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + String data = builder + .add("0.5=3;3.5=5;6.5=9") + .add("0.5=0;3.5=2;6.5=1") + .build(); + + assertThat(data).isEqualTo("0.5=3;3.5=7;6.5=10"); + } + + @Test + public void keep_int_ranges_when_merging_distributions() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + String data = builder + .add("0=3;3=5;6=9") + .add("0=0;3=2;6=1") + .build(); + + assertThat(data).isEqualTo("0=3;3=7;6=10"); + } + + + @Test + public void is_empty_is_true_when_no_data() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + + assertThat(builder.isEmpty()).isTrue(); + } + + @Test + public void is_empty_is_true_when_no_data_on_distribution_with_limits() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(new Integer[] {4, 2, 0}); + + assertThat(builder.isEmpty()).isTrue(); + } + + @Test + public void aggregate_empty_distribution() { + RangeDistributionBuilder builder = new RangeDistributionBuilder(); + String distribution = builder.build(); + assertThat(distribution).isEmpty(); + } + +} |