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.technicaldebt.batch.Characteristic;
026import org.sonar.api.technicaldebt.batch.Requirement;
027
028import javax.annotation.CheckForNull;
029import javax.annotation.Nullable;
030
031import java.math.BigDecimal;
032import java.math.RoundingMode;
033import java.util.Date;
034
035/**
036 * A class to handle measures.
037 *
038 * @since 1.10
039 */
040public class Measure {
041  private static final String INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5 = "Index should be in range from 1 to 5";
042
043  protected static final int MAX_TEXT_SIZE = 96;
044
045  /**
046   * Default precision when saving a float type metric
047   */
048  public static final int DEFAULT_PRECISION = 1;
049
050  // for internal use
051  private Long id;
052  protected String metricKey;
053  protected Metric metric;
054  protected Double value;
055  protected String data;
056  protected String description;
057  protected Metric.Level alertStatus;
058  protected String alertText;
059  protected Integer tendency;
060  protected Date date;
061  protected Double variation1, variation2, variation3, variation4, variation5;
062  protected String url;
063  protected Characteristic characteristic;
064  protected Requirement requirement;
065  protected Integer personId;
066  protected PersistenceMode persistenceMode = PersistenceMode.FULL;
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(PersistenceMode mode) {
175    this.persistenceMode = mode;
176    return this;
177  }
178
179  /**
180   * @return return the measures underlying metric
181   */
182  public Metric getMetric() {
183    return metric;
184  }
185
186  public String getMetricKey() {
187    return metricKey;
188  }
189
190  /**
191   * Set the underlying metric
192   *
193   * @param metric the metric
194   * @return the measure object instance
195   */
196  public Measure setMetric(Metric metric) {
197    this.metric = metric;
198    this.metricKey = metric.getKey();
199    return this;
200  }
201
202  /**
203   * @return transforms and returns the data fields as a level of alert
204   */
205  public Metric.Level getDataAsLevel() {
206    if (data != null) {
207      return Metric.Level.valueOf(data);
208    }
209    return null;
210  }
211
212  public boolean hasData() {
213    return data != null;
214  }
215
216  /**
217   * @return the date of the measure, i.e. the date the measure was taken. Used only in TimeMachine queries
218   */
219  public Date getDate() {
220    return date;
221  }
222
223  /**
224   * Sets the date of the measure - Used only in TimeMachine queries
225   *
226   * @param date the date
227   * @return the measure object instance
228   */
229  public Measure setDate(Date date) {
230    this.date = date;
231    return this;
232  }
233
234  /**
235   * @return the value of the measure as a double
236   */
237  public Double getValue() {
238    return value;
239  }
240
241  /**
242   * @return the value of the measure as an int
243   */
244  public Integer getIntValue() {
245    if (value == null) {
246      return null;
247    }
248    return value.intValue();
249  }
250
251  /**
252   * Sets the measure value with the default precision of 1
253   *
254   * @param v the measure value
255   * @return the measure object instance
256   */
257  public Measure setValue(@Nullable Double v) {
258    return setValue(v, DEFAULT_PRECISION);
259  }
260
261  /**
262   * Sets the measure value as an int
263   *
264   * @param i the value
265   * @return the measure object instance
266   */
267  public Measure setIntValue(Integer i) {
268    if (i == null) {
269      this.value = null;
270    } else {
271      this.value = Double.valueOf(i);
272    }
273    return this;
274  }
275
276  /**
277   * Sets the measure value with a given precision
278   *
279   * @param v         the measure value
280   * @param precision the measure value precision
281   * @return the measure object instance
282   */
283  public Measure setValue(@Nullable Double v, int precision) {
284    if (v != null) {
285      if (Double.isNaN(v)) {
286        throw new IllegalArgumentException("Measure value can not be NaN");
287      }
288      this.value = scaleValue(v, precision);
289    } else {
290      this.value = null;
291    }
292    return this;
293  }
294
295  private double scaleValue(double value, int scale) {
296    BigDecimal bd = BigDecimal.valueOf(value);
297    return bd.setScale(scale, RoundingMode.HALF_UP).doubleValue();
298  }
299
300  /**
301   * @return the data field of the measure
302   */
303  public String getData() {
304    return data;
305  }
306
307  /**
308   * Sets the data field of the measure.
309   *
310   * @param s the data
311   * @return the measure object instance
312   */
313  public Measure setData(String s) {
314    this.data = s;
315    return this;
316  }
317
318  /**
319   * Sets an alert level as the data field
320   *
321   * @param level the alert level
322   * @return the measure object instance
323   */
324  public Measure setData(Metric.Level level) {
325    if (level == null) {
326      this.data = null;
327    } else {
328      this.data = level.toString();
329    }
330    return this;
331  }
332
333  /**
334   * @since 2.7
335   */
336  public Measure unsetData() {
337    this.data = null;
338    return this;
339  }
340
341  /**
342   * @return the description of the measure
343   */
344  public String getDescription() {
345    return description;
346  }
347
348  /**
349   * Sets the measure description
350   *
351   * @param description the description
352   * @return the measure object instance
353   */
354  public Measure setDescription(String description) {
355    this.description = description;
356    return this;
357  }
358
359  /**
360   * @return the alert status of the measure
361   */
362  public Metric.Level getAlertStatus() {
363    return alertStatus;
364  }
365
366  /**
367   * Set the alert status of the measure
368   *
369   * @param status the status
370   * @return the measure object instance
371   */
372  public Measure setAlertStatus(Metric.Level status) {
373    this.alertStatus = status;
374    return this;
375  }
376
377  /**
378   * @return the text associated to the alert on the measure
379   */
380  public String getAlertText() {
381    return alertText;
382  }
383
384  /**
385   * Sets the text associated to the alert on the measure
386   *
387   * @param alertText the text
388   * @return the measure object instance
389   */
390  public Measure setAlertText(String alertText) {
391    this.alertText = alertText;
392    return this;
393  }
394
395  /**
396   * Gets the measure tendency
397   *
398   * @return the tendency
399   */
400  public Integer getTendency() {
401    return tendency;
402  }
403
404  /**
405   * Sets the tendency for the measure - Internal use only
406   *
407   * @param tendency the tendency
408   * @return the measure object instance
409   */
410  public Measure setTendency(Integer tendency) {
411    this.tendency = tendency;
412    return this;
413  }
414
415  /**
416   * @return the measure id - Internal use only
417   */
418  public Long getId() {
419    return id;
420  }
421
422  /**
423   * Sets the measure id - Internal use only
424   *
425   * @param id the id
426   * @return the measure object instance
427   */
428  public Measure setId(Long id) {
429    this.id = id;
430    return this;
431  }
432
433  /**
434   * @return the first variation value
435   * @since 2.5
436   */
437  public Double getVariation1() {
438    return variation1;
439  }
440
441  /**
442   * Internal use only
443   *
444   * @since 2.5
445   */
446  public Measure setVariation1(Double d) {
447    this.variation1 = d;
448    return this;
449  }
450
451  /**
452   * @return the second variation value
453   * @since 2.5
454   */
455  public Double getVariation2() {
456    return variation2;
457  }
458
459  /**
460   * Internal use only
461   *
462   * @since 2.5
463   */
464  public Measure setVariation2(Double d) {
465    this.variation2 = d;
466    return this;
467  }
468
469  /**
470   * @return the third variation value
471   * @since 2.5
472   */
473  public Double getVariation3() {
474    return variation3;
475  }
476
477  /**
478   * Internal use only
479   *
480   * @since 2.5
481   */
482  public Measure setVariation3(Double d) {
483    this.variation3 = d;
484    return this;
485  }
486
487  /**
488   * @return the third variation value
489   * @since 2.5
490   */
491  public Double getVariation4() {
492    return variation4;
493  }
494
495  /**
496   * Internal use only
497   *
498   * @since 2.5
499   */
500  public Measure setVariation4(Double d) {
501    this.variation4 = d;
502    return this;
503  }
504
505  /**
506   * @return the third variation value
507   * @since 2.5
508   */
509  public Double getVariation5() {
510    return variation5;
511  }
512
513  /**
514   * Internal use only
515   *
516   * @since 2.5
517   */
518  public Measure setVariation5(Double d) {
519    this.variation5 = d;
520    return this;
521  }
522
523  /**
524   * @since 2.5
525   */
526  public Double getVariation(int index) {
527    switch (index) {
528      case 1:
529        return variation1;
530      case 2:
531        return variation2;
532      case 3:
533        return variation3;
534      case 4:
535        return variation4;
536      case 5:
537        return variation5;
538      default:
539        throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
540    }
541  }
542
543  /**
544   * Internal use only
545   *
546   * @since 2.5
547   */
548  public Measure setVariation(int index, Double d) {
549    switch (index) {
550      case 1:
551        variation1 = d;
552        break;
553      case 2:
554        variation2 = d;
555        break;
556      case 3:
557        variation3 = d;
558        break;
559      case 4:
560        variation4 = d;
561        break;
562      case 5:
563        variation5 = d;
564        break;
565      default:
566        throw new IndexOutOfBoundsException(INDEX_SHOULD_BE_IN_RANGE_FROM_1_TO_5);
567    }
568    return this;
569  }
570
571  /**
572   * @return the url of the measure
573   */
574  public String getUrl() {
575    return url;
576  }
577
578  /**
579   * Sets the URL of the measure
580   *
581   * @param url the url
582   * @return the measure object instance
583   */
584  public Measure setUrl(String url) {
585    this.url = url;
586    return this;
587  }
588
589  /**
590   * @since 4.1
591   */
592  @CheckForNull
593  public final Characteristic getCharacteristic() {
594    return characteristic;
595  }
596
597  /**
598   * @since 4.1
599   */
600  public final Measure setCharacteristic(@Nullable Characteristic characteristic) {
601    this.characteristic = characteristic;
602    return this;
603  }
604
605  /**
606   * @since 4.1
607   */
608  @CheckForNull
609  public final Requirement getRequirement() {
610    return requirement;
611  }
612
613  /**
614   * @since 4.1
615   */
616  public final Measure setRequirement(@Nullable Requirement requirement) {
617    this.requirement = requirement;
618    return this;
619  }
620
621  /**
622   * @since 2.14
623   */
624  @Beta
625  public Integer getPersonId() {
626    return personId;
627  }
628
629  /**
630   * @since 2.14
631   */
632  @Beta
633  public Measure setPersonId(Integer i) {
634    this.personId = i;
635    return this;
636  }
637
638  /**
639   * @since 3.2
640   */
641  public boolean isBestValue() {
642    return metric.isOptimizedBestValue() == Boolean.TRUE
643      && metric.getBestValue() != null
644      && (value == null || NumberUtils.compare(metric.getBestValue(), value) == 0)
645      && allNull(id, alertStatus, description, tendency, url, data)
646      && isZeroVariation(variation1, variation2, variation3, variation4, variation5);
647  }
648
649  private static boolean isZeroVariation(Double... variations) {
650    for (Double variation : variations) {
651      if (!((variation == null) || NumberUtils.compare(variation.doubleValue(), 0.0) == 0)) {
652        return false;
653      }
654    }
655    return true;
656  }
657
658  private static boolean allNull(Object... values) {
659    for (Object value : values) {
660      if (null != value) {
661        return false;
662      }
663    }
664    return true;
665  }
666
667  @Override
668  public boolean equals(Object o) {
669    if (this == o) {
670      return true;
671    }
672    if (o == null || getClass() != o.getClass()) {
673      return false;
674    }
675
676    Measure measure = (Measure) o;
677    if (metricKey != null ? !metricKey.equals(measure.metricKey) : measure.metricKey != null) {
678      return false;
679    }
680    if (characteristic != null ? !characteristic.equals(measure.characteristic) : measure.characteristic != null) {
681      return false;
682    }
683    if (requirement != null ? !requirement.equals(measure.requirement) : measure.requirement != null) {
684      return false;
685    }
686    if (personId != null ? !personId.equals(measure.personId) : measure.personId != null) {
687      return false;
688    }
689    return true;
690  }
691
692  @Override
693  public int hashCode() {
694    int result = metricKey != null ? metricKey.hashCode() : 0;
695    result = 31 * result + (characteristic != null ? characteristic.hashCode() : 0);
696    result = 31 * result + (requirement != null ? requirement.hashCode() : 0);
697    result = 31 * result + (personId != null ? personId.hashCode() : 0);
698    return result;
699  }
700
701  @Override
702  public String toString() {
703    return ReflectionToStringBuilder.toString(this);
704  }
705}