001 /*
002 * SonarQube, open source software quality management tool.
003 * Copyright (C) 2008-2013 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * SonarQube 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 * SonarQube 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 License
017 * along with this program; if not, write to the Free Software Foundation,
018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
019 */
020 package org.sonar.api.measures;
021
022 import com.google.common.annotations.Beta;
023 import org.apache.commons.lang.builder.ReflectionToStringBuilder;
024 import org.apache.commons.lang.math.NumberUtils;
025 import org.sonar.api.qualitymodel.Characteristic;
026
027 import javax.annotation.Nullable;
028
029 import java.math.BigDecimal;
030 import java.math.RoundingMode;
031 import java.util.Date;
032
033 /**
034 * A class to handle measures.
035 *
036 * @since 1.10
037 */
038 public class Measure {
039 protected static final int MAX_TEXT_SIZE = 96;
040
041 /**
042 * Default precision when saving a float type metric
043 */
044 public static final int DEFAULT_PRECISION = 1;
045
046 private Long id; // for internal use
047 protected String metricKey;
048 protected Metric metric;
049 protected Double value;
050 protected String data;
051 protected String description;
052 protected Metric.Level alertStatus;
053 protected String alertText;
054 protected Integer tendency;
055 protected Date date;
056 protected Double variation1, variation2, variation3, variation4, variation5;
057 protected String url;
058 protected Characteristic characteristic;
059 protected Integer personId;
060 protected PersistenceMode persistenceMode = PersistenceMode.FULL;
061
062 public Measure(String metricKey) {
063 this.metricKey = metricKey;
064 }
065
066 /**
067 * Creates a measure with a metric
068 *
069 * @param metric the metric
070 */
071 public Measure(Metric metric) {
072 this.metric = metric;
073 this.metricKey = metric.getKey();
074 }
075
076 /**
077 * Creates a measure with a metric and a value
078 *
079 * @param metric the metric
080 * @param value its value
081 */
082 public Measure(Metric metric, Double value) {
083 this.metric = metric;
084 this.metricKey = metric.getKey();
085 setValue(value);
086 }
087
088 /**
089 * Creates a measure with a metric, a value and a precision for the value
090 *
091 * @param metric the metric
092 * @param value its value
093 * @param precision the value precision
094 */
095 public Measure(Metric metric, Double value, int precision) {
096 this.metric = metric;
097 this.metricKey = metric.getKey();
098 setValue(value, precision);
099 }
100
101 /**
102 * Creates a measure with a metric, a value and a data field
103 *
104 * @param metric the metric
105 * @param value the value
106 * @param data the data field
107 */
108 public Measure(Metric metric, Double value, String data) {
109 this.metric = metric;
110 this.metricKey = metric.getKey();
111 setValue(value);
112 setData(data);
113 }
114
115 /**
116 * * Creates a measure with a metric and a data field
117 *
118 * @param metric the metric
119 * @param data the data field
120 */
121 public Measure(Metric metric, String data) {
122 this.metric = metric;
123 this.metricKey = metric.getKey();
124 setData(data);
125 }
126
127 /**
128 * Creates a measure with a metric and an alert level
129 *
130 * @param metric the metric
131 * @param level the alert level
132 */
133 public Measure(Metric metric, Metric.Level level) {
134 this.metric = metric;
135 this.metricKey = metric.getKey();
136 if (level != null) {
137 this.data = level.toString();
138 }
139 }
140
141 /**
142 * Creates an empty measure
143 */
144 public Measure() {
145 }
146
147 /**
148 * Gets the persistence mode of the measure. Default persistence mode is FULL, except when instantiating the measure with a String
149 * parameter.
150 */
151 public PersistenceMode getPersistenceMode() {
152 return persistenceMode;
153 }
154
155 /**
156 * <p>
157 * Sets the persistence mode of a measure.
158 * </p>
159 * <p>
160 * <b>WARNING : </b>Being able to reuse measures saved in memory is only possible within the same tree. In a multi-module project for
161 * example, a measure save in memory at the module level will not be accessible by the root project. In that case, database should be
162 * used.
163 * </p>
164 *
165 * @param mode the mode
166 * @return the measure object instance
167 */
168 public Measure setPersistenceMode(PersistenceMode mode) {
169 this.persistenceMode = mode;
170 return this;
171 }
172
173 /**
174 * @return return the measures underlying metric
175 */
176 public Metric getMetric() {
177 return metric;
178 }
179
180 public String getMetricKey() {
181 return metricKey;
182 }
183
184 /**
185 * Set the underlying metric
186 *
187 * @param metric the metric
188 * @return the measure object instance
189 */
190 public Measure setMetric(Metric metric) {
191 this.metric = metric;
192 this.metricKey = metric.getKey();
193 return this;
194 }
195
196 /**
197 * @return transforms and returns the data fields as a level of alert
198 */
199 public Metric.Level getDataAsLevel() {
200 if (data != null) {
201 return Metric.Level.valueOf(data);
202 }
203 return null;
204 }
205
206 public boolean hasData() {
207 return data != null;
208 }
209
210 /**
211 * @return the date of the measure, i.e. the date the measure was taken. Used only in TimeMachine queries
212 */
213 public Date getDate() {
214 return date;
215 }
216
217 /**
218 * Sets the date of the measure - Used only in TimeMachine queries
219 *
220 * @param date the date
221 * @return the measure object instance
222 */
223 public Measure setDate(Date date) {
224 this.date = date;
225 return this;
226 }
227
228 /**
229 * @return the value of the measure as a double
230 */
231 public Double getValue() {
232 return value;
233 }
234
235 /**
236 * @return the value of the measure as an int
237 */
238 public Integer getIntValue() {
239 if (value == null) {
240 return null;
241 }
242 return value.intValue();
243 }
244
245 /**
246 * Sets the measure value with the default precision of 1
247 *
248 * @param v the measure value
249 * @return the measure object instance
250 */
251 public Measure setValue(@Nullable Double v) {
252 return setValue(v, DEFAULT_PRECISION);
253 }
254
255 /**
256 * Sets the measure value as an int
257 *
258 * @param i the value
259 * @return the measure object instance
260 */
261 public Measure setIntValue(Integer i) {
262 if (i == null) {
263 this.value = null;
264 } else {
265 this.value = Double.valueOf(i);
266 }
267 return this;
268 }
269
270 /**
271 * Sets the measure value with a given precision
272 *
273 * @param v the measure value
274 * @param precision the measure value precision
275 * @return the measure object instance
276 */
277 public Measure setValue(@Nullable Double v, int precision) {
278 if (v != null) {
279 if (Double.isNaN(v)) {
280 throw new IllegalArgumentException("Measure value can not be NaN");
281 }
282 this.value = scaleValue(v, precision);
283 } else {
284 this.value = null;
285 }
286 return this;
287 }
288
289 private double scaleValue(double value, int scale) {
290 BigDecimal bd = BigDecimal.valueOf(value);
291 return bd.setScale(scale, RoundingMode.HALF_UP).doubleValue();
292 }
293
294 /**
295 * @return the data field of the measure
296 */
297 public String getData() {
298 return data;
299 }
300
301 /**
302 * Sets the data field of the measure.
303 *
304 * @param s the data
305 * @return the measure object instance
306 */
307 public Measure setData(String s) {
308 this.data = s;
309 return this;
310 }
311
312 /**
313 * Sets an alert level as the data field
314 *
315 * @param level the alert level
316 * @return the measure object instance
317 */
318 public Measure setData(Metric.Level level) {
319 if (level == null) {
320 this.data = null;
321 } else {
322 this.data = level.toString();
323 }
324 return this;
325 }
326
327 /**
328 * @since 2.7
329 */
330 public Measure unsetData() {
331 this.data = null;
332 return this;
333 }
334
335 /**
336 * @return the description of the measure
337 */
338 public String getDescription() {
339 return description;
340 }
341
342 /**
343 * Sets the measure description
344 *
345 * @param description the description
346 * @return the measure object instance
347 */
348 public Measure setDescription(String description) {
349 this.description = description;
350 return this;
351 }
352
353 /**
354 * @return the alert status of the measure
355 */
356 public Metric.Level getAlertStatus() {
357 return alertStatus;
358 }
359
360 /**
361 * Set the alert status of the measure
362 *
363 * @param status the status
364 * @return the measure object instance
365 */
366 public Measure setAlertStatus(Metric.Level status) {
367 this.alertStatus = status;
368 return this;
369 }
370
371 /**
372 * @return the text associated to the alert on the measure
373 */
374 public String getAlertText() {
375 return alertText;
376 }
377
378 /**
379 * Sets the text associated to the alert on the measure
380 *
381 * @param alertText the text
382 * @return the measure object instance
383 */
384 public Measure setAlertText(String alertText) {
385 this.alertText = alertText;
386 return this;
387 }
388
389 /**
390 * Gets the measure tendency
391 *
392 * @return the tendency
393 */
394 public Integer getTendency() {
395 return tendency;
396 }
397
398 /**
399 * Sets the tendency for the measure - Internal use only
400 *
401 * @param tendency the tendency
402 * @return the measure object instance
403 */
404 public Measure setTendency(Integer tendency) {
405 this.tendency = tendency;
406 return this;
407 }
408
409 /**
410 * @return the measure id - Internal use only
411 */
412 public Long getId() {
413 return id;
414 }
415
416 /**
417 * Sets the measure id - Internal use only
418 *
419 * @param id the id
420 * @return the measure object instance
421 */
422 public Measure setId(Long id) {
423 this.id = id;
424 return this;
425 }
426
427 /**
428 * @return the first variation value
429 * @since 2.5
430 */
431 public Double getVariation1() {
432 return variation1;
433 }
434
435 /**
436 * Internal use only
437 *
438 * @since 2.5
439 */
440 public Measure setVariation1(Double d) {
441 this.variation1 = d;
442 return this;
443 }
444
445 /**
446 * @return the second variation value
447 * @since 2.5
448 */
449 public Double getVariation2() {
450 return variation2;
451 }
452
453 /**
454 * Internal use only
455 *
456 * @since 2.5
457 */
458 public Measure setVariation2(Double d) {
459 this.variation2 = d;
460 return this;
461 }
462
463 /**
464 * @return the third variation value
465 * @since 2.5
466 */
467 public Double getVariation3() {
468 return variation3;
469 }
470
471 /**
472 * Internal use only
473 *
474 * @since 2.5
475 */
476 public Measure setVariation3(Double d) {
477 this.variation3 = d;
478 return this;
479 }
480
481 /**
482 * @return the third variation value
483 * @since 2.5
484 */
485 public Double getVariation4() {
486 return variation4;
487 }
488
489 /**
490 * Internal use only
491 *
492 * @since 2.5
493 */
494 public Measure setVariation4(Double d) {
495 this.variation4 = d;
496 return this;
497 }
498
499 /**
500 * @return the third variation value
501 * @since 2.5
502 */
503 public Double getVariation5() {
504 return variation5;
505 }
506
507 /**
508 * Internal use only
509 *
510 * @since 2.5
511 */
512 public Measure setVariation5(Double d) {
513 this.variation5 = d;
514 return this;
515 }
516
517 /**
518 * @since 2.5
519 */
520 public Double getVariation(int index) {
521 switch (index) {
522 case 1:
523 return variation1;
524 case 2:
525 return variation2;
526 case 3:
527 return variation3;
528 case 4:
529 return variation4;
530 case 5:
531 return variation5;
532 default:
533 throw new IndexOutOfBoundsException("Index should be in range from 1 to 5");
534 }
535 }
536
537 /**
538 * Internal use only
539 *
540 * @since 2.5
541 */
542 public Measure setVariation(int index, Double d) {
543 switch (index) {
544 case 1:
545 variation1 = d;
546 break;
547 case 2:
548 variation2 = d;
549 break;
550 case 3:
551 variation3 = d;
552 break;
553 case 4:
554 variation4 = d;
555 break;
556 case 5:
557 variation5 = d;
558 break;
559 default:
560 throw new IndexOutOfBoundsException("Index should be in range from 1 to 5");
561 }
562 return this;
563 }
564
565 /**
566 * @return the url of the measure
567 */
568 public String getUrl() {
569 return url;
570 }
571
572 /**
573 * Sets the URL of the measure
574 *
575 * @param url the url
576 * @return the measure object instance
577 */
578 public Measure setUrl(String url) {
579 this.url = url;
580 return this;
581 }
582
583 /**
584 * @since 2.3
585 */
586 public final Characteristic getCharacteristic() {
587 return characteristic;
588 }
589
590 /**
591 * @since 2.3
592 */
593 public final Measure setCharacteristic(Characteristic characteristic) {
594 this.characteristic = characteristic;
595 return this;
596 }
597
598 /**
599 * @since 2.14
600 */
601 @Beta
602 public Integer getPersonId() {
603 return personId;
604 }
605
606 /**
607 * @since 2.14
608 */
609 @Beta
610 public Measure setPersonId(Integer i) {
611 this.personId = i;
612 return this;
613 }
614
615 /**
616 * @since 3.2
617 */
618 public boolean isBestValue() {
619 return metric.isOptimizedBestValue() == Boolean.TRUE
620 && metric.getBestValue() != null
621 && (value == null || NumberUtils.compare(metric.getBestValue(), value) == 0)
622 && allNull(id, alertStatus, description, tendency, url, data)
623 && isZeroVariation(variation1, variation2, variation3, variation4, variation5);
624 }
625
626 private static boolean isZeroVariation(Double... variations) {
627 for (Double variation : variations) {
628 if (!((variation == null) || NumberUtils.compare(variation.doubleValue(), 0.0) == 0)) {
629 return false;
630 }
631 }
632 return true;
633 }
634
635 private static boolean allNull(Object... values) {
636 for (Object value : values) {
637 if (null != value) {
638 return false;
639 }
640 }
641 return true;
642 }
643
644 @Override
645 public boolean equals(Object o) {
646 if (this == o) {
647 return true;
648 }
649 if (o == null || getClass() != o.getClass()) {
650 return false;
651 }
652
653 Measure measure = (Measure) o;
654 if (metricKey != null ? !metricKey.equals(measure.metricKey) : measure.metricKey != null) {
655 return false;
656 }
657 if (characteristic != null ? !characteristic.equals(measure.characteristic) : measure.characteristic != null) {
658 return false;
659 }
660 if (personId != null ? !personId.equals(measure.personId) : measure.personId != null) {
661 return false;
662 }
663 return true;
664 }
665
666 @Override
667 public int hashCode() {
668 int result = metricKey != null ? metricKey.hashCode() : 0;
669 result = 31 * result + (characteristic != null ? characteristic.hashCode() : 0);
670 result = 31 * result + (personId != null ? personId.hashCode() : 0);
671 return result;
672 }
673
674 @Override
675 public String toString() {
676 return ReflectionToStringBuilder.toString(this);
677 }
678 }