001/*
002 * SonarQube
003 * Copyright (C) 2009-2016 SonarSource SA
004 * mailto:contact AT sonarsource DOT com
005 *
006 * This program 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 * This program 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.base.Function;
023import com.google.common.collect.Lists;
024import java.io.Serializable;
025import java.util.Arrays;
026import java.util.List;
027import javax.annotation.CheckForNull;
028import javax.annotation.Nonnull;
029import javax.annotation.Nullable;
030import org.apache.commons.lang.builder.ReflectionToStringBuilder;
031import org.apache.commons.lang.builder.ToStringStyle;
032import org.sonar.api.batch.ScannerSide;
033import org.sonar.api.batch.InstantiationStrategy;
034import org.sonar.api.ce.ComputeEngineSide;
035import org.sonar.api.server.ServerSide;
036
037import static com.google.common.base.MoreObjects.firstNonNull;
038import static com.google.common.base.Preconditions.checkArgument;
039import static org.apache.commons.lang.StringUtils.isNotBlank;
040
041/**
042 * Used to define a metric in a plugin. Should be used with {@link Metrics} extension point.
043 * Should no more be used on scanner side. Use {@link org.sonar.api.batch.measure.Metric} instead. 
044 */
045@ScannerSide
046@InstantiationStrategy(InstantiationStrategy.PER_BATCH)
047@ServerSide
048@ComputeEngineSide
049public class Metric<G extends Serializable> implements Serializable, org.sonar.api.batch.measure.Metric<G> {
050
051  /**
052   * @since 5.3
053   */
054  public static final int DEFAULT_DECIMAL_SCALE = 1;
055
056  /**
057   * The maximum supported value of scale for decimal metrics
058   * @since 5.3
059   */
060  public static final int MAX_DECIMAL_SCALE = 5;
061
062  /**
063   * A metric bigger value means a degradation
064   */
065  public static final int DIRECTION_WORST = -1;
066  /**
067   * A metric bigger value means an improvement
068   */
069  public static final int DIRECTION_BETTER = 1;
070  /**
071   * The metric direction has no meaning
072   */
073  public static final int DIRECTION_NONE = 0;
074
075  public enum ValueType {
076    INT(Integer.class),
077    FLOAT(Double.class),
078    PERCENT(Double.class),
079    BOOL(Boolean.class),
080    STRING(String.class),
081    MILLISEC(Long.class),
082    DATA(String.class),
083    LEVEL(Metric.Level.class),
084    DISTRIB(String.class),
085    RATING(Integer.class),
086    WORK_DUR(Long.class);
087
088    private final Class valueClass;
089
090    ValueType(Class valueClass) {
091      this.valueClass = valueClass;
092    }
093
094    private Class valueType() {
095      return valueClass;
096    }
097
098    public static String[] names() {
099      ValueType[] values = values();
100      String[] names = new String[values.length];
101      for (int i = 0; i < values.length; i += 1) {
102        names[i] = values[i].name();
103      }
104
105      return names;
106    }
107  }
108
109  public enum Level {
110    OK("Green"), WARN("Orange"), ERROR("Red");
111
112    private static final List<String> NAMES = Lists.transform(Arrays.asList(values()), new Function<Level, String>() {
113      @Nonnull
114      @Override
115      public String apply(@Nonnull Level level) {
116        return level.name();
117      }
118    });
119
120    private String colorName;
121
122    Level(String colorName) {
123      this.colorName = colorName;
124    }
125
126    public String getColorName() {
127      return colorName;
128    }
129
130    public static List<String> names() {
131      return NAMES;
132    }
133  }
134
135  private Integer id;
136  private transient Formula formula;
137  private String key;
138  private String description;
139  private ValueType type;
140  private Integer direction;
141  private String domain;
142  private String name;
143  private Boolean qualitative = Boolean.FALSE;
144  private Boolean userManaged = Boolean.FALSE;
145  private Boolean enabled = Boolean.TRUE;
146  private Double worstValue;
147  private Double bestValue;
148  private Boolean optimizedBestValue;
149  private Boolean hidden = Boolean.FALSE;
150  private Boolean deleteHistoricalData;
151  private Integer decimalScale;
152
153  private Metric(Builder builder) {
154    this.key = builder.key;
155    this.name = builder.name;
156    this.description = builder.description;
157    this.type = builder.type;
158    this.direction = builder.direction;
159    this.domain = builder.domain;
160    this.qualitative = builder.qualitative;
161    this.enabled = Boolean.TRUE;
162    this.worstValue = builder.worstValue;
163    this.optimizedBestValue = builder.optimizedBestValue;
164    this.bestValue = builder.bestValue;
165    this.hidden = builder.hidden;
166    this.formula = builder.formula;
167    this.userManaged = builder.userManaged;
168    this.deleteHistoricalData = builder.deleteHistoricalData;
169    this.decimalScale = builder.decimalScale;
170  }
171
172  /**
173   * Creates an empty metric
174   *
175   * @deprecated in 1.12. Use the {@link Builder} factory.
176   */
177  @Deprecated
178  public Metric() {
179  }
180
181  /**
182   * Creates a metric based on its key. Shortcut to Metric(key, ValueType.INT)
183   *
184   * @param key the metric key
185   * @deprecated since 2.7 use the {@link Builder} factory.
186   */
187  @Deprecated
188  public Metric(String key) {
189    this(key, ValueType.INT);
190  }
191
192  /**
193   * Creates a metric based on a key and a type. Shortcut to
194   * Metric(key, key, key, type, -1, Boolean.FALSE, null, false)
195   *
196   * @param key  the key
197   * @param type the type
198   * @deprecated since 2.7 use the {@link Builder} factory.
199   */
200  @Deprecated
201  public Metric(String key, ValueType type) {
202    this(key, key, key, type, -1, Boolean.FALSE, null, false);
203  }
204
205  /**
206   * @deprecated since 2.7 use the {@link Builder} factory.
207   */
208  @Deprecated
209  public Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, String domain) {
210    this(key, name, description, type, direction, qualitative, domain, false);
211  }
212
213  /**
214   * Creates a fully qualified metric.
215   *
216   * @param key         the metric key
217   * @param name        the metric name
218   * @param description the metric description
219   * @param type        the metric type
220   * @param direction   the metric direction
221   * @param qualitative whether the metric is qualitative
222   * @param domain      the metric domain
223   * @param userManaged whether the metric is user managed
224   */
225  private Metric(String key, String name, String description, ValueType type, Integer direction, Boolean qualitative, @Nullable String domain,
226    boolean userManaged) {
227    this.key = key;
228    this.description = description;
229    this.type = type;
230    this.direction = direction;
231    this.domain = domain;
232    this.name = name;
233    this.qualitative = qualitative;
234    this.userManaged = userManaged;
235    if (ValueType.PERCENT == this.type) {
236      this.bestValue = (direction == DIRECTION_BETTER) ? 100.0 : 0.0;
237      this.worstValue = (direction == DIRECTION_BETTER) ? 0.0 : 100.0;
238      this.decimalScale = DEFAULT_DECIMAL_SCALE;
239    } else if (ValueType.FLOAT == this.type) {
240      this.decimalScale = DEFAULT_DECIMAL_SCALE;
241    }
242  }
243
244  /**
245   * For internal use only
246   */
247  public Integer getId() {
248    return id;
249  }
250
251  /**
252   * For internal use only
253   */
254  public Metric<G> setId(@Nullable Integer id) {
255    this.id = id;
256    return this;
257  }
258
259  /**
260   * @return the metric formula
261   * @deprecated since 5.2 there's no more decorator on batch side, please use {@link org.sonar.api.ce.measure.MeasureComputer} instead
262   */
263  @Deprecated
264  public Formula getFormula() {
265    return formula;
266  }
267
268  /**
269   * Sets the metric formula
270   *
271   * @param formula the formula
272   * @return this
273   * @deprecated since 5.2 there's no more decorator on batch side, please use {@link org.sonar.api.ce.measure.MeasureComputer} instead
274   */
275  @Deprecated
276  public Metric<G> setFormula(Formula formula) {
277    this.formula = formula;
278    return this;
279  }
280
281  /**
282   * @return wether the metric is qualitative
283   */
284  public Boolean getQualitative() {
285    return qualitative;
286  }
287
288  /**
289   * Sets whether the metric is qualitative
290   *
291   * @param qualitative whether the metric is qualitative
292   * @return this
293   */
294  public Metric<G> setQualitative(Boolean qualitative) {
295    this.qualitative = qualitative;
296    return this;
297  }
298
299  /**
300   * @return the metric key
301   */
302  public String getKey() {
303    return key;
304  }
305
306  /**
307   * Sets the metric key
308   *
309   * @param key the key
310   * @return this
311   */
312  public Metric<G> setKey(String key) {
313    this.key = key;
314    return this;
315  }
316
317  /**
318   * @return the metric type
319   */
320  public ValueType getType() {
321    return type;
322  }
323
324  /**
325   * Sets the metric type
326   *
327   * @param type the type
328   * @return this
329   */
330  public Metric<G> setType(ValueType type) {
331    this.type = type;
332    return this;
333  }
334
335  /**
336   * @return the metric description
337   */
338  @CheckForNull
339  public String getDescription() {
340    return description;
341  }
342
343  /**
344   * Sets the metric description
345   *
346   * @param description the description
347   * @return this
348   */
349  public Metric<G> setDescription(@Nullable String description) {
350    this.description = description;
351    return this;
352  }
353
354  /**
355   * @return whether the metric is a managed by the users ("manual metric")
356   */
357  public Boolean getUserManaged() {
358    return userManaged;
359  }
360
361  /**
362   * Sets whether the metric is managed by users ("manual metric")
363   *
364   * @param userManaged whether the metric is user managed
365   * @return this
366   */
367  public Metric<G> setUserManaged(Boolean userManaged) {
368    this.userManaged = userManaged;
369    return this;
370  }
371
372  /**
373   * @return whether the metric is enabled
374   */
375  public Boolean getEnabled() {
376    return enabled;
377  }
378
379  /**
380   * Sets whether the metric is enabled
381   *
382   * @param enabled whether the metric is enabled
383   * @return this
384   */
385  public Metric<G> setEnabled(Boolean enabled) {
386    this.enabled = enabled;
387    return this;
388  }
389
390  /**
391   * @return the metric direction
392   */
393  public Integer getDirection() {
394    return direction;
395  }
396
397  /**
398   * Sets the metric direction.
399   *
400   * @param direction the direction
401   */
402  public Metric<G> setDirection(Integer direction) {
403    this.direction = direction;
404    return this;
405  }
406
407  /**
408   * @return the domain of the metric
409   */
410  public String getDomain() {
411    return domain;
412  }
413
414  /**
415   * Sets the domain for the metric (General, Complexity...)
416   *
417   * @param domain the domain
418   * @return this
419   */
420  public Metric<G> setDomain(String domain) {
421    this.domain = domain;
422    return this;
423  }
424
425  /**
426   * @return the metric name
427   */
428  public String getName() {
429    return name;
430  }
431
432  /**
433   * Sets the metric name
434   *
435   * @param name the name
436   * @return this
437   */
438  public Metric<G> setName(String name) {
439    this.name = name;
440    return this;
441  }
442
443  public Double getWorstValue() {
444    return worstValue;
445  }
446
447  @CheckForNull
448  public Double getBestValue() {
449    return bestValue;
450  }
451
452  /**
453   * @return this
454   */
455  public Metric<G> setWorstValue(@Nullable Double d) {
456    this.worstValue = d;
457    return this;
458  }
459
460  /**
461   * @param bestValue the best value. It can be null.
462   * @return this
463   */
464  public Metric<G> setBestValue(@Nullable Double bestValue) {
465    this.bestValue = bestValue;
466    return this;
467  }
468
469  /**
470   * @return whether the metric is of a numeric type (int, percentage...)
471   */
472  public boolean isNumericType() {
473    return ValueType.INT.equals(type)
474      || ValueType.FLOAT.equals(type)
475      || ValueType.PERCENT.equals(type)
476      || ValueType.BOOL.equals(type)
477      || ValueType.MILLISEC.equals(type)
478      || ValueType.RATING.equals(type)
479      || ValueType.WORK_DUR.equals(type);
480  }
481
482  /**
483   * @return whether the metric is of type data
484   */
485  public boolean isDataType() {
486    return ValueType.DATA.equals(type) || ValueType.DISTRIB.equals(type);
487  }
488
489  /**
490   * @return whether the metric is of type percentage
491   */
492  public boolean isPercentageType() {
493    return ValueType.PERCENT.equals(type);
494  }
495
496  public Metric<G> setOptimizedBestValue(@Nullable Boolean b) {
497    this.optimizedBestValue = b;
498    return this;
499  }
500
501  /**
502   * @return null for manual metrics
503   */
504  @CheckForNull
505  public Boolean isOptimizedBestValue() {
506    return optimizedBestValue;
507  }
508
509  public Boolean isHidden() {
510    return hidden;
511  }
512
513  public Metric<G> setHidden(Boolean hidden) {
514    this.hidden = hidden;
515    return this;
516  }
517
518  public Boolean getDeleteHistoricalData() {
519    return deleteHistoricalData;
520  }
521
522  /**
523   * Return the number scale if metric type is {@link ValueType#FLOAT}, else {@code null}
524   * @since 5.3
525   */
526  @CheckForNull
527  public Integer getDecimalScale() {
528    return decimalScale;
529  }
530
531  @Override
532  public int hashCode() {
533    return key.hashCode();
534  }
535
536  @Override
537  public boolean equals(Object obj) {
538    if (!(obj instanceof Metric)) {
539      return false;
540    }
541    if (this == obj) {
542      return true;
543    }
544    Metric other = (Metric) obj;
545    return key.equals(other.getKey());
546  }
547
548  @Override
549  public String toString() {
550    return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString();
551  }
552
553  /**
554   * Merge with fields from other metric. All fields are copied, except the id.
555   *
556   * @return this
557   */
558  public Metric<G> merge(final Metric with) {
559    this.description = with.description;
560    this.domain = with.domain;
561    this.enabled = with.enabled;
562    this.qualitative = with.qualitative;
563    this.worstValue = with.worstValue;
564    this.bestValue = with.bestValue;
565    this.optimizedBestValue = with.optimizedBestValue;
566    this.direction = with.direction;
567    this.key = with.key;
568    this.type = with.type;
569    this.name = with.name;
570    this.userManaged = with.userManaged;
571    this.hidden = with.hidden;
572    this.deleteHistoricalData = with.deleteHistoricalData;
573    return this;
574  }
575
576  /**
577   * Metric.Builder is used to create metric definitions. It must be preferred to creating new instances of the Metric class directly.
578   *
579   * @since 2.7
580   */
581  public static final class Builder {
582    private String key;
583    private Metric.ValueType type;
584    private String name;
585    private String description;
586    private Integer direction = DIRECTION_NONE;
587    private Boolean qualitative = Boolean.FALSE;
588    private String domain = null;
589    private Formula formula;
590    private Double worstValue;
591    private Double bestValue;
592    private boolean optimizedBestValue = false;
593    private boolean hidden = false;
594    private boolean userManaged = false;
595    private boolean deleteHistoricalData = false;
596    private Integer decimalScale = null;
597
598    /**
599     * Creates a new {@link Builder} object.
600     *
601     * @param key  the metric key, should be unique among all metrics
602     * @param name the metric name
603     * @param type the metric type
604     */
605    public Builder(String key, String name, ValueType type) {
606      checkArgument(isNotBlank(key), "Metric key can not be blank");
607      checkArgument(isNotBlank(name), "Name of metric %s must be set", key);
608      checkArgument(type != null, "Type of metric %s must be set", key);
609      this.key = key;
610      this.name = name;
611      this.type = type;
612    }
613
614    /**
615     * Sets the metric description.
616     *
617     * @param d the description
618     * @return the builder
619     */
620    public Builder setDescription(String d) {
621      this.description = d;
622      return this;
623    }
624
625    /**
626     * Sets the metric direction (used for numeric values only), which is used in the Web UI to show if the trend of a metric is good or not.
627     * <ul>
628     * <li>Metric.DIRECTION_WORST: indicates that an increase of the metric value is not a good thing (example: the complexity of a function)</li>
629     * <li>Metric.DIRECTION_BETTER: indicates that an increase of the metric value is a good thing (example: the code coverage of a function)</li>
630     * <li>Metric.DIRECTION_NONE: indicates that the variation of the metric value is neither good nor bad (example: number of files).</li>
631     * </ul>
632     * Metric.DIRECTION_NONE is the default value.
633     *
634     * @see Metric#DIRECTION_WORST
635     * @see Metric#DIRECTION_BETTER
636     * @see Metric#DIRECTION_NONE
637     *
638     * @param d the direction
639     * @return the builder
640     */
641    public Builder setDirection(Integer d) {
642      this.direction = d;
643      return this;
644    }
645
646    /**
647     * Sets whether the metric is qualitative or not. Default value is false.
648     * <br>
649     * If set to true, then variations of this metric will be highlighted in the Web UI (for instance, trend icons will be red or green instead of default grey).
650     *
651     * @param b Boolean.TRUE if the metric is qualitative
652     * @return the builder
653     */
654    public Builder setQualitative(Boolean b) {
655      this.qualitative = b;
656      return this;
657    }
658
659    /**
660     * Sets the domain for the metric (General, Complexity...). This is used to group metrics in the Web UI.
661     * <br>
662     * By default, the metric belongs to no specific domain.
663     *
664     * @param d the domain
665     * @return the builder
666     */
667    public Builder setDomain(String d) {
668      this.domain = d;
669      return this;
670    }
671
672    /**
673     * Specifies the formula used by Sonar to automatically aggregate measures stored on files up to the project level.
674     * <br>
675     * <br>
676     * By default, no formula is defined, which means that it's up to a sensor/decorator to compute measures on appropriate levels.
677     * <br>
678     * When a formula is set, sensors/decorators just need to store measures at a specific level and let Sonar run the formula to store
679     * measures on the remaining levels.
680     *
681     * @param f the formula
682     * @return the builder
683     *
684     * @deprecated since 5.2, it's no more possible to define a formula on a metric, please use {@link org.sonar.api.ce.measure.MeasureComputer} instead
685     */
686    @Deprecated
687    public Builder setFormula(Formula f) {
688      this.formula = f;
689      return this;
690    }
691
692    /**
693     * Sets the worst value that the metric can get (example: 0.0 for code coverage). No worst value is set by default.
694     *
695     * @param d the worst value
696     * @return the builder
697     */
698    public Builder setWorstValue(Double d) {
699      this.worstValue = d;
700      return this;
701    }
702
703    /**
704     * Sets the best value that the metric can get (example: 100.0 for code coverage). No best value is set by default.
705     * <br>
706     * Resources would be hidden on drilldown page, if the value of measure equals to best value.
707     *
708     * @param d the best value
709     * @return the builder
710     */
711    public Builder setBestValue(Double d) {
712      this.bestValue = d;
713      return this;
714    }
715
716    /**
717     * Specifies whether file-level measures that equal to the defined best value are stored or not. Default is false.
718     * <br>
719     * Example with the metric that stores the number of violation ({@link CoreMetrics#VIOLATIONS}):
720     * if a file has no violation, then the value '0' won't be stored in the database.
721     *
722     * @param b true if the measures must not be stored when they equal to the best value
723     * @return the builder
724     */
725    public Builder setOptimizedBestValue(boolean b) {
726      this.optimizedBestValue = b;
727      return this;
728    }
729
730    /**
731     * Sets whether the metric should be hidden in Web UI (e.g. in Time Machine). Default is false.
732     *
733     * @param b true if the metric should be hidden.
734     * @return the builder
735     */
736    public Builder setHidden(boolean b) {
737      this.hidden = b;
738      return this;
739    }
740
741    /**
742     * Specifies whether this metric can be edited online in the "Manual measures" page. Default is false.
743     *
744     * @since 2.10
745     *
746     * @param b true if the metric can be edited online.
747     * @return the builder
748     */
749    public Builder setUserManaged(boolean b) {
750      this.userManaged = b;
751      return this;
752    }
753
754    /**
755     * Specifies whether measures from the past can be automatically deleted to minimize database volume.
756     * <br>
757     * By default, historical data are kept.
758     *
759     * @since 2.14
760     *
761     * @param b true if measures from the past can be deleted automatically.
762     * @return the builder
763     */
764    public Builder setDeleteHistoricalData(boolean b) {
765      this.deleteHistoricalData = b;
766      return this;
767    }
768
769    /**
770     * Scale to be used if the metric has decimal type ({@link ValueType#FLOAT} or {@link ValueType#PERCENT}).
771     * Default is 1. It is not set (({@code null}) on non-decimal metrics.
772     * @since 5.3
773     */
774    public Builder setDecimalScale(int scale) {
775      checkArgument(scale >= 0, "Scale of decimal metric %s must be positive: %d", key, scale);
776      checkArgument(scale <= MAX_DECIMAL_SCALE, "Scale of decimal metric [%s] must be less than or equal %s: %s", key, MAX_DECIMAL_SCALE, scale);
777      this.decimalScale = scale;
778      return this;
779    }
780
781    /**
782     * Creates a new metric definition based on the properties set on this metric builder.
783     *
784     * @return a new {@link Metric} object
785     */
786    public <G extends Serializable> Metric<G> create() {
787      if (ValueType.PERCENT == this.type) {
788        this.bestValue = (direction == DIRECTION_BETTER) ? 100.0 : 0.0;
789        this.worstValue = (direction == DIRECTION_BETTER) ? 0.0 : 100.0;
790        this.decimalScale = firstNonNull(decimalScale, DEFAULT_DECIMAL_SCALE);
791
792      } else if (ValueType.FLOAT == this.type) {
793        this.decimalScale = firstNonNull(decimalScale, DEFAULT_DECIMAL_SCALE);
794      }
795      return new Metric<>(this);
796    }
797  }
798
799  @Override
800  public String key() {
801    return getKey();
802  }
803
804  @Override
805  public Class<G> valueType() {
806    return getType().valueType();
807  }
808}