aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api
diff options
context:
space:
mode:
authorJulien Lancelot <julien.lancelot@sonarsource.com>2015-08-27 16:05:18 +0200
committerJulien Lancelot <julien.lancelot@sonarsource.com>2015-08-31 09:52:02 +0200
commit48835a873353be8fdd48ca57313f6a74ddaf15f7 (patch)
tree4b6a16d3fd0b8822dc18d831b5ec027bf6962507 /sonar-plugin-api
parenta2a6a50c88c58a0af3daa52e39e76d362078f547 (diff)
downloadsonarqube-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')
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/ce/measure/RangeDistributionBuilder.java211
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/measures/RangeDistributionBuilder.java14
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/ce/measure/RangeDistributionBuilderTest.java173
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();
+ }
+
+}