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