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.technicaldebt.batch.Characteristic;
026    import org.sonar.api.technicaldebt.batch.Requirement;
027    
028    import javax.annotation.CheckForNull;
029    import javax.annotation.Nullable;
030    
031    import java.math.BigDecimal;
032    import java.math.RoundingMode;
033    import java.util.Date;
034    
035    /**
036     * A class to handle measures.
037     *
038     * @since 1.10
039     */
040    public 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    }