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