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 * Utility to build a distribution based on defined ranges
034 *
035 * <p>An example of usage : you wish to record the percentage of lines of code that belong to method
036 * with pre-defined ranges of complexity.</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 * Each entry is initialize at zero
050 *
051 * @param metric the metric to record the measure against
052 * @param bottomLimits the bottom limits of ranges to be used
053 */
054 public RangeDistributionBuilder(Metric metric, Number[] bottomLimits) {
055 setMetric(metric);
056 this.bottomLimits = new Number[bottomLimits.length];
057 System.arraycopy(bottomLimits, 0, this.bottomLimits, 0, this.bottomLimits.length);
058 Arrays.sort(this.bottomLimits);
059 countBag = TransformedSortedBag.decorate(new TreeBag(), new RangeTransformer());
060 doClear();
061 }
062
063 /**
064 * Gives the bottom limits of ranges used
065 *
066 * @return the bottom limits of defined range for the distribution
067 */
068 public Number[] getBottomLimits() {
069 return bottomLimits;
070 }
071
072 /**
073 * Increments an entry by 1
074 *
075 * @param value the value to use to pick the entry to increment
076 * @return the current object
077 */
078 public RangeDistributionBuilder add(Number value) {
079 return add(value, 1);
080 }
081
082 /**
083 * Increments an entry
084 *
085 * @param value the value to use to pick the entry to increment
086 * @param count the number by which to increment
087 * @return the current object
088 */
089 public RangeDistributionBuilder add(Number value, int count) {
090 if (value != null && greaterOrEqualsThan(value, bottomLimits[0])) {
091 this.countBag.add(value, count);
092 isEmpty = false;
093 }
094 return this;
095 }
096
097 /**
098 * Adds an existing Distribution to the current one.
099 * It will create the entries if they don't exist.
100 * Can be used to add the values of children resources for example
101 *
102 * @param measure the measure to add to the current one
103 * @return the current object
104 */
105 public RangeDistributionBuilder add(Measure measure) {
106 if (measure != null && measure.getData() != null) {
107 Map<Double, Double> map = KeyValueFormat.parse(measure.getData(), new KeyValueFormat.DoubleNumbersPairTransformer());
108 for (Map.Entry<Double, Double> entry : map.entrySet()) {
109 add(entry.getKey(), entry.getValue().intValue());
110 }
111 }
112 return this;
113 }
114
115 /**
116 * Resets all entries to zero
117 *
118 * @return the current object
119 */
120 public RangeDistributionBuilder clear() {
121 doClear();
122 return this;
123 }
124
125 private void doClear() {
126 countBag.clear();
127 countBag.addAll(Arrays.asList(bottomLimits));
128 isEmpty = true;
129 }
130
131 /**
132 * @return whether the current object is empty or not
133 */
134 public boolean isEmpty() {
135 return isEmpty;
136 }
137
138 /**
139 * Shortcut for <code>build(true)</code>
140 *
141 * @return the built measure
142 */
143 public Measure build() {
144 return build(true);
145 }
146
147 /**
148 * Used to build a measure from the current object
149 *
150 * @param allowEmptyData should be built if current object is empty
151 * @return the built measure
152 */
153 public Measure build(boolean allowEmptyData) {
154 if (!isEmpty || allowEmptyData) {
155 return new Measure(metric, KeyValueFormat.format(countBag, -1));
156 }
157 return null;
158 }
159
160 private class RangeTransformer implements Transformer {
161 public Object transform(Object o) {
162 Number n = (Number) o;
163 for (int i = bottomLimits.length - 1; i >= 0; i--) {
164 if (greaterOrEqualsThan(n, bottomLimits[i])) {
165 return bottomLimits[i];
166 }
167 }
168 return null;
169 }
170 }
171
172 private static boolean greaterOrEqualsThan(Number n1, Number n2) {
173 return n1.doubleValue() >= n2.doubleValue();
174 }
175
176 private void setMetric(Metric metric) {
177 if (metric == null || !metric.isDataType()) {
178 throw new SonarException("Metric is null or has unvalid type");
179 }
180 this.metric = metric;
181 }
182 }