001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2009 SonarSource SA
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.api.measures;
021
022 import org.apache.commons.collections.SortedBag;
023 import org.apache.commons.collections.Transformer;
024 import org.apache.commons.collections.bag.TransformedSortedBag;
025 import org.apache.commons.collections.bag.TreeBag;
026 import org.sonar.api.utils.KeyValueFormat;
027 import org.sonar.api.utils.SonarException;
028
029 import java.util.Arrays;
030 import java.util.Map;
031
032 /**
033 * An utility to build a range distribution based on 2 criteria: one is measure, the other one is recorded.
034 *
035 * <p>An example of usage : you wish to save the number of lines that belong to methods that have complexity
036 * belonging to certain ranges.</p>
037 *
038 * @since 1.10
039 */
040 public class RangeDistributionBuilder implements MeasureBuilder {
041
042 private Metric metric;
043 private SortedBag countBag;
044 private boolean isEmpty = true;
045 private final Number[] bottomLimits;
046
047 /**
048 * RangeDistributionBuilder for a metric and a defined range
049 *
050 * @param metric the metric for the measure being built
051 * @param bottomLimits the bottom limits for lookup
052 */
053 public RangeDistributionBuilder(Metric metric, Number[] bottomLimits) {
054 setMetric(metric);
055 this.bottomLimits = new Number[bottomLimits.length];
056 System.arraycopy(bottomLimits, 0, this.bottomLimits, 0, this.bottomLimits.length);
057 Arrays.sort(this.bottomLimits);
058 countBag = TransformedSortedBag.decorate(new TreeBag(), new RangeTransformer());
059 doClear();
060 }
061
062 /**
063 * Gets the defined bottom limits
064 *
065 * @return the bottom limits of defined range for the distribution
066 */
067 public Number[] getBottomLimits() {
068 return bottomLimits;
069 }
070
071 /**
072 * Add 1 to the range object belongs to
073 *
074 * @param object the Number to use to pick the range
075 * @return this
076 */
077 public RangeDistributionBuilder add(Number object) {
078 return add(object, 1);
079 }
080
081 public RangeDistributionBuilder add(Number object, int count) {
082 if (object != null && greaterOrEqualsThan(object, bottomLimits[0])) {
083 this.countBag.add(object, count);
084 isEmpty = false;
085 }
086 return this;
087 }
088
089 public RangeDistributionBuilder add(Measure measure) {
090 if (measure != null && measure.getData() != null) {
091 Map<Double, Double> map = KeyValueFormat.parse(measure.getData(), new KeyValueFormat.DoubleNumbersPairTransformer());
092 for (Map.Entry<Double, Double> entry : map.entrySet()) {
093 add(entry.getKey(), entry.getValue().intValue());
094 }
095 }
096 return this;
097 }
098
099 public RangeDistributionBuilder clear() {
100 doClear();
101 return this;
102 }
103
104 private void doClear() {
105 countBag.clear();
106 countBag.addAll(Arrays.asList(bottomLimits));
107 isEmpty = true;
108 }
109
110 public boolean isEmpty() {
111 return isEmpty;
112 }
113
114 public Measure build() {
115 return build(true);
116 }
117
118 public Measure build(boolean allowEmptyData) {
119 if (!isEmpty || allowEmptyData) {
120 return new Measure(metric, KeyValueFormat.format(countBag, -1));
121 }
122 return null;
123 }
124
125 private class RangeTransformer implements Transformer {
126 public Object transform(Object o) {
127 Number n = (Number) o;
128 for (int i = bottomLimits.length - 1; i >= 0; i--) {
129 if (greaterOrEqualsThan(n, bottomLimits[i])) {
130 return bottomLimits[i];
131 }
132 }
133 return null;
134 }
135 }
136
137 private static boolean greaterOrEqualsThan(Number n1, Number n2) {
138 return n1.doubleValue() >= n2.doubleValue();
139 }
140
141 private void setMetric(Metric metric) {
142 if (metric == null || !metric.isDataType()) {
143 throw new SonarException("Metric is null or has unvalid type");
144 }
145 this.metric = metric;
146 }
147 }