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 }