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