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