1 | /* |
2 | * Copyright (c) 2001-2009, Jean Tessier |
3 | * All rights reserved. |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * |
9 | * * Redistributions of source code must retain the above copyright |
10 | * notice, this list of conditions and the following disclaimer. |
11 | * |
12 | * * Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in the |
14 | * documentation and/or other materials provided with the distribution. |
15 | * |
16 | * * Neither the name of Jean Tessier nor the names of his contributors |
17 | * may be used to endorse or promote products derived from this software |
18 | * without specific prior written permission. |
19 | * |
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR |
24 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
25 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
26 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
27 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
28 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
29 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
30 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
31 | */ |
32 | |
33 | package com.jeantessier.metrics; |
34 | |
35 | import java.io.*; |
36 | import java.util.*; |
37 | |
38 | import org.apache.log4j.*; |
39 | |
40 | /** |
41 | * Counts the number of submetrics according to selection |
42 | * criteria. If there are no criteria, it matches all |
43 | * submetrics. Each criterion is a boolean expression |
44 | * with measurement names, numbers, and boolean operators |
45 | * (<, <=, >, >=, ==, and !=). If a submetric matches |
46 | * any one of the expressions in the criteria, it is |
47 | * included in the count. |
48 | * |
49 | * <p>This is the syntax for initializing this type of |
50 | * measurement:</p> |
51 | * |
52 | * <pre> |
53 | * <init> |
54 | * (number | measurement name [DISPOSE_x]) [operator [(number | measurement name [DISPOSE_x])]]* |
55 | * ... |
56 | * </init> |
57 | * </pre> |
58 | */ |
59 | public class NbSubMetricsMeasurement extends MeasurementBase { |
60 | private static final String LESSER_THAN = "<"; |
61 | private static final String LESSER_THAN_OR_EQUAL = "<="; |
62 | private static final String GREATER_THAN = ">"; |
63 | private static final String GREATER_THAN_OR_EQUAL = ">="; |
64 | private static final String EQUALS = "=="; |
65 | private static final String NOT_EQUALS = "!="; |
66 | |
67 | private static final String OPERATORS_REGULAR_EXPRESSION = |
68 | "/" + |
69 | "(" + LESSER_THAN_OR_EQUAL + ")|" + |
70 | "(" + LESSER_THAN + ")|" + |
71 | "(" + GREATER_THAN_OR_EQUAL + ")|" + |
72 | "(" + GREATER_THAN + ")|" + |
73 | "(" + EQUALS + ")|" + |
74 | "(" + NOT_EQUALS + ")" + |
75 | "/"; |
76 | |
77 | private static final double DELTA = 0.1; |
78 | |
79 | private List<String> terms = new LinkedList<String>(); |
80 | private int value = 0; |
81 | |
82 | public NbSubMetricsMeasurement(MeasurementDescriptor descriptor, Metrics context, String initText) { |
83 | super(descriptor, context, initText); |
84 | |
85 | try { |
86 | BufferedReader in = new BufferedReader(new StringReader(initText)); |
87 | String line; |
88 | |
89 | while ((line = in.readLine()) != null) { |
90 | terms.add(line.trim()); |
91 | } |
92 | |
93 | in.close(); |
94 | } catch (Exception ex) { |
95 | Logger.getLogger(getClass()).debug("Cannot initialize with \"" + initText + "\"", ex); |
96 | terms.clear(); |
97 | } |
98 | } |
99 | |
100 | public List<String> getTerms() { |
101 | return terms; |
102 | } |
103 | |
104 | public void accept(MeasurementVisitor visitor) { |
105 | visitor.visitNbSubMetricsMeasurement(this); |
106 | } |
107 | |
108 | public boolean isEmpty() { |
109 | if (!isCached()) { |
110 | compute(); |
111 | } |
112 | |
113 | return super.isEmpty(); |
114 | } |
115 | |
116 | protected double compute() { |
117 | if (!isCached()) { |
118 | synchronized (this) { |
119 | if (!isCached()) { |
120 | value = 0; |
121 | |
122 | if (getTerms().isEmpty()) { |
123 | value = getContext().getSubMetrics().size(); |
124 | } else { |
125 | for (Metrics metrics : getContext().getSubMetrics()) { |
126 | if (getSelectMetrics(metrics)) { |
127 | value++; |
128 | } |
129 | } |
130 | } |
131 | |
132 | setEmpty(value == 0); |
133 | |
134 | setCached(true); |
135 | } |
136 | } |
137 | } |
138 | |
139 | return value; |
140 | } |
141 | |
142 | private boolean getSelectMetrics(Metrics metrics) { |
143 | boolean result = getTerms().isEmpty(); |
144 | |
145 | Iterator<String> i = getTerms().iterator(); |
146 | while (!result && i.hasNext()) { |
147 | result = evaluateTerm(i.next(), metrics); |
148 | } |
149 | |
150 | return result; |
151 | } |
152 | |
153 | private boolean evaluateTerm(String term, Metrics metrics) { |
154 | boolean result; |
155 | |
156 | Logger.getLogger(getClass()).debug("EvaluateTerm(\"" + term + "\", " + metrics + ")"); |
157 | |
158 | List<String> elements = new ArrayList<String>(); |
159 | perl().split(elements, OPERATORS_REGULAR_EXPRESSION, term); |
160 | |
161 | result = (elements.size() > 0) && ((elements.size() % 2) == 1); |
162 | |
163 | if (elements.size() == 1) { |
164 | result = metrics.hasMeasurement(elements.remove(0)); |
165 | } else { |
166 | while (result && (elements.size() > 2) && ((elements.size() % 2) == 1)) { |
167 | String leftString = elements.remove(0); |
168 | String operator = elements.remove(0); |
169 | String rightString = elements.get(0); |
170 | |
171 | double leftOperand = 0; |
172 | try { |
173 | leftOperand = Double.parseDouble(leftString); |
174 | } catch (NumberFormatException ex) { |
175 | try { |
176 | leftOperand = resolveOperand(leftString, metrics); |
177 | } catch (NullPointerException ex2) { |
178 | result = false; |
179 | } |
180 | } |
181 | |
182 | double rightOperand = 0; |
183 | try { |
184 | rightOperand = Double.parseDouble(rightString); |
185 | } catch (NumberFormatException ex) { |
186 | try { |
187 | rightOperand = resolveOperand(rightString, metrics); |
188 | } catch (NullPointerException ex2) { |
189 | result = false; |
190 | } |
191 | } |
192 | |
193 | if (result) { |
194 | if (operator.equals(LESSER_THAN)) { |
195 | result = leftOperand < rightOperand; |
196 | } else if (operator.equals(LESSER_THAN_OR_EQUAL)) { |
197 | result = leftOperand <= rightOperand; |
198 | } else if (operator.equals(GREATER_THAN)) { |
199 | result = leftOperand > rightOperand; |
200 | } else if (operator.equals(GREATER_THAN_OR_EQUAL)) { |
201 | result = leftOperand >= rightOperand; |
202 | } else if (operator.equals(EQUALS)) { |
203 | result = Math.abs(leftOperand - rightOperand) <= DELTA; |
204 | } else if (operator.equals(NOT_EQUALS)) { |
205 | result = Math.abs(leftOperand - rightOperand) > DELTA; |
206 | } |
207 | } |
208 | } |
209 | } |
210 | |
211 | Logger.getLogger(getClass()).debug("EvaluateTerm(\"" + term + "\", " + metrics + "): " + result); |
212 | |
213 | return result; |
214 | } |
215 | |
216 | private double resolveOperand(String name, Metrics metrics) { |
217 | double result = 0; |
218 | |
219 | name = name.trim(); |
220 | |
221 | Logger.getLogger(getClass()).debug("ResolveOperand(\"" + name + "\", " + metrics + ")"); |
222 | |
223 | if (name.length() != 0) { |
224 | int dispose; |
225 | |
226 | synchronized (perl()) { |
227 | if (perl().match("/(.*)\\s+(dispose_\\w+)$/i", name)) { |
228 | name = perl().group(1); |
229 | |
230 | String disposeText = perl().group(2); |
231 | |
232 | if (disposeText.equalsIgnoreCase("DISPOSE_IGNORE")) { |
233 | dispose = StatisticalMeasurement.DISPOSE_IGNORE; |
234 | } else if (disposeText.equalsIgnoreCase("DISPOSE_MINIMUM")) { |
235 | dispose = StatisticalMeasurement.DISPOSE_MINIMUM; |
236 | } else if (disposeText.equalsIgnoreCase("DISPOSE_MEDIAN")) { |
237 | dispose = StatisticalMeasurement.DISPOSE_MEDIAN; |
238 | } else if (disposeText.equalsIgnoreCase("DISPOSE_AVERAGE")) { |
239 | dispose = StatisticalMeasurement.DISPOSE_AVERAGE; |
240 | } else if (disposeText.equalsIgnoreCase("DISPOSE_STANDARD_DEVIATION")) { |
241 | dispose = StatisticalMeasurement.DISPOSE_STANDARD_DEVIATION; |
242 | } else if (disposeText.equalsIgnoreCase("DISPOSE_MAXIMUM")) { |
243 | dispose = StatisticalMeasurement.DISPOSE_MAXIMUM; |
244 | } else if (disposeText.equalsIgnoreCase("DISPOSE_SUM")) { |
245 | dispose = StatisticalMeasurement.DISPOSE_SUM; |
246 | } else if (disposeText.equalsIgnoreCase("DISPOSE_NB_DATA_POINTS")) { |
247 | dispose = StatisticalMeasurement.DISPOSE_NB_DATA_POINTS; |
248 | } else { |
249 | dispose = StatisticalMeasurement.DISPOSE_IGNORE; |
250 | } |
251 | } else { |
252 | dispose = StatisticalMeasurement.DISPOSE_IGNORE; |
253 | } |
254 | } |
255 | |
256 | Measurement measurement = metrics.getMeasurement(name); |
257 | |
258 | if (measurement instanceof StatisticalMeasurement) { |
259 | StatisticalMeasurement stats = (StatisticalMeasurement) measurement; |
260 | |
261 | switch (dispose) { |
262 | case StatisticalMeasurement.DISPOSE_MINIMUM: |
263 | result = stats.getMinimum(); |
264 | break; |
265 | case StatisticalMeasurement.DISPOSE_MEDIAN: |
266 | result = stats.getMedian(); |
267 | break; |
268 | case StatisticalMeasurement.DISPOSE_AVERAGE: |
269 | result = stats.getAverage(); |
270 | break; |
271 | case StatisticalMeasurement.DISPOSE_STANDARD_DEVIATION: |
272 | result = stats.getStandardDeviation(); |
273 | break; |
274 | case StatisticalMeasurement.DISPOSE_MAXIMUM: |
275 | result = stats.getMaximum(); |
276 | break; |
277 | case StatisticalMeasurement.DISPOSE_SUM: |
278 | result = stats.getSum(); |
279 | break; |
280 | case StatisticalMeasurement.DISPOSE_NB_DATA_POINTS: |
281 | result = stats.getNbDataPoints(); |
282 | break; |
283 | case StatisticalMeasurement.DISPOSE_IGNORE: |
284 | default: |
285 | result = stats.getValue().doubleValue(); |
286 | break; |
287 | } |
288 | } else if (measurement instanceof NullMeasurement) { |
289 | throw new NullPointerException(); |
290 | } else { |
291 | result = measurement.getValue().doubleValue(); |
292 | } |
293 | } |
294 | |
295 | Logger.getLogger(getClass()).debug("ResolveOperand(\"" + name + "\", " + metrics + "): " + result); |
296 | |
297 | return result; |
298 | } |
299 | } |