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 */
020package org.sonar.api.measures;
021
022import com.google.common.annotations.Beta;
023import org.apache.commons.lang.builder.ReflectionToStringBuilder;
024import org.apache.commons.lang.math.NumberUtils;
025import org.sonar.api.qualitymodel.Characteristic;
026
027import javax.annotation.Nullable;
028
029import java.math.BigDecimal;
030import java.math.RoundingMode;
031import java.util.Date;
032
033/**
034 * A class to handle measures.
035 *
036 * @since 1.10
037 */
038public 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}