Limit.java
/*******************************************************************************
* Copyright (c) 2009, 2024 Mountainminds GmbH & Co. KG and Contributors
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Marc R. Hoffmann - initial API and implementation
*
*******************************************************************************/
package org.jacoco.report.check;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.jacoco.core.analysis.ICounter.CounterValue;
import org.jacoco.core.analysis.ICoverageNode;
import org.jacoco.core.analysis.ICoverageNode.CounterEntity;
/**
* Descriptor for a limit which is given by a {@link Rule}.
*/
public class Limit {
private static final Map<CounterValue, String> VALUE_NAMES;
private static final Map<CounterEntity, String> ENTITY_NAMES;
static {
final Map<CounterValue, String> values = new HashMap<CounterValue, String>();
values.put(CounterValue.TOTALCOUNT, "total count");
values.put(CounterValue.MISSEDCOUNT, "missed count");
values.put(CounterValue.COVEREDCOUNT, "covered count");
values.put(CounterValue.MISSEDRATIO, "missed ratio");
values.put(CounterValue.COVEREDRATIO, "covered ratio");
VALUE_NAMES = Collections.unmodifiableMap(values);
final Map<CounterEntity, String> entities = new HashMap<CounterEntity, String>();
entities.put(CounterEntity.INSTRUCTION, "instructions");
entities.put(CounterEntity.BRANCH, "branches");
entities.put(CounterEntity.COMPLEXITY, "complexity");
entities.put(CounterEntity.LINE, "lines");
entities.put(CounterEntity.METHOD, "methods");
entities.put(CounterEntity.CLASS, "classes");
ENTITY_NAMES = Collections.unmodifiableMap(entities);
}
private CounterEntity entity;
private CounterValue value;
private BigDecimal minimum;
private BigDecimal maximum;
/**
* Creates a new instance with the following defaults:
* <ul>
* <li>counter entity: {@link CounterEntity#INSTRUCTION}
* <li>counter value: {@link CounterValue#COVEREDRATIO}
* <li>minimum: no limit
* <li>maximum: no limit
* </ul>
*/
public Limit() {
this.entity = CounterEntity.INSTRUCTION;
this.value = CounterValue.COVEREDRATIO;
}
/**
* @return the configured counter entity to check
*/
public CounterEntity getEntity() {
return entity;
}
/**
* Sets the counter entity to check.
*
* @param entity
* counter entity to check
*/
// TODO: use CounterEntity directly once Maven 3 is required.
public void setCounter(final String entity) {
this.entity = CounterEntity.valueOf(entity);
}
/**
* @return the configured value to check
*/
public CounterValue getValue() {
return value;
}
/**
* Sets the value to check.
*
* @param value
* value to check
*/
// TODO: use CounterValue directly once Maven 3 is required.
public void setValue(final String value) {
this.value = CounterValue.valueOf(value);
}
/**
* @return configured minimum value, or <code>null</code> if no minimum is
* given
*/
public String getMinimum() {
return minimum == null ? null : minimum.toPlainString();
}
/**
* Sets the expected minimum value. If the minimum refers to a ratio it must
* be in the range from 0.0 to 1.0 where the number of decimal places will
* also determine the precision in error messages. A limit ratio may
* optionally be declared as a percentage where 0.80 and 80% represent the
* same value.
*
* @param minimum
* allowed minimum or <code>null</code>, if no minimum should be
* checked
*/
public void setMinimum(final String minimum) {
this.minimum = parseValue(minimum);
}
/**
* @return configured maximum value, or <code>null</code> if no maximum is
* given
*/
public String getMaximum() {
return maximum == null ? null : maximum.toPlainString();
}
/**
* Sets the expected maximum value. If the maximum refers to a ratio it must
* be in the range from 0.0 to 1.0 where the number of decimal places will
* also determine the precision in error messages. A limit ratio may
* optionally be declared as a percentage where 0.80 and 80% represent the
* same value.
*
* @param maximum
* allowed maximum or <code>null</code>, if no maximum should be
* checked
*/
public void setMaximum(final String maximum) {
this.maximum = parseValue(maximum);
}
private static BigDecimal parseValue(final String value) {
if (value == null) {
return null;
}
final String trimmedValue = value.trim();
if (trimmedValue.endsWith("%")) {
final String percent = trimmedValue.substring(0,
trimmedValue.length() - 1);
return new BigDecimal(percent).movePointLeft(2);
}
return new BigDecimal(trimmedValue);
}
String check(final ICoverageNode node) {
final String msg = checkRatioLimit();
if (msg != null) {
return msg;
}
final double d = node.getCounter(entity).getValue(value);
if (Double.isNaN(d)) {
return null;
}
final BigDecimal bd = BigDecimal.valueOf(d);
if (minimum != null && minimum.compareTo(bd) > 0) {
return message("minimum", bd, minimum, RoundingMode.FLOOR);
}
if (maximum != null && maximum.compareTo(bd) < 0) {
return message("maximum", bd, maximum, RoundingMode.CEILING);
}
return null;
}
private String message(final String minmax, final BigDecimal v,
final BigDecimal ref, final RoundingMode mode) {
final BigDecimal rounded = v.setScale(ref.scale(), mode);
return String.format("%s %s is %s, but expected %s is %s",
ENTITY_NAMES.get(entity), VALUE_NAMES.get(value),
rounded.toPlainString(), minmax, ref.toPlainString());
}
private String checkRatioLimit() {
if (CounterValue.MISSEDRATIO.equals(value)
|| CounterValue.COVEREDRATIO.equals(value)) {
final String minmsg = checkRatioLimit("minimum", minimum);
if (minmsg != null) {
return minmsg;
}
final String maxmsg = checkRatioLimit("maximum", maximum);
if (maxmsg != null) {
return maxmsg;
}
}
return null;
}
private String checkRatioLimit(final String minmax, final BigDecimal v) {
if (v != null && (v.compareTo(BigDecimal.ZERO) < 0
|| v.compareTo(BigDecimal.ONE) > 0)) {
return String.format(
"given %s ratio is %s, but must be between 0.0 and 1.0",
minmax, v);
}
return null;
}
}