package fr.openwide.core.wicket.more.markup.html.basic;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParsePosition;
import java.util.Locale;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.util.convert.ConversionException;
import org.apache.wicket.util.convert.IConverter;
import fr.openwide.core.wicket.more.application.CoreWicketApplication;
/**
* Convertisseur entre BigDecimal et String pour les pourcentages
*
* Les pourcentages affichés entre 0 et 100 par exemple correspondent en BigDecimal à des valeurs :
* - entre 0 et 1 si modelIsRatio est true
* - entre 0 et 100 si modelIsRatio est false
*
* Le scale d'un BigDecimal correspond au nombre de chiffres après la virgule.
* La precision d'un BigDecimal correspond au nombre total de chiffre.
*
* Rmq : l'éventuel 0 devant la virgule ne compte pas dans la precision, au contraire des 0 à droite.
* Ainsi, 0.504400 aurait une precision de 6 (et un scale de 6).
*
* On attribue un scale fixe et une precision maximale autorisée aux instances de BigDecimal manipulées.
*/
public class PercentageBigDecimalConverter implements IConverter<BigDecimal> {
private static final long serialVersionUID = -4527255063711426051L;
/**
* Les # après la virgule sont importants car la valeur sera tronquée.
* Les # avant la virgule n'ont pas d'importance.
*/
private static final String PATTERN = "#.#########";
private int scale;
private RoundingMode roundingMode;
private int maxPrecision;
private boolean modelIsRatio;
private boolean displayPercentSymbol;
public PercentageBigDecimalConverter(int scale, RoundingMode roundingMode, int maxPrecision) {
this(scale, roundingMode, maxPrecision, true);
}
public PercentageBigDecimalConverter(int scale, RoundingMode roundingMode, int maxPrecision, boolean modelIsRatio) {
this(scale, roundingMode, maxPrecision, modelIsRatio, false);
}
public PercentageBigDecimalConverter(int scale, RoundingMode roundingMode, int maxPrecision,
boolean modelIsRatio, boolean displayPercentSymbol) {
super();
if (maxPrecision < scale) {
throw new IllegalArgumentException("The maximum precision must be equal or superior to the scale.");
}
this.scale = scale;
this.roundingMode = roundingMode;
this.maxPrecision = maxPrecision;
this.modelIsRatio = modelIsRatio;
this.displayPercentSymbol = displayPercentSymbol;
}
@Override
public BigDecimal convertToObject(String value, Locale locale) {
if (StringUtils.isEmpty(value)) {
return null;
}
if (displayPercentSymbol) {
value = value.replaceAll("%", "");
}
DecimalFormat decimalFormat = new DecimalFormat(PATTERN, getDecimalFormatSymbols());
decimalFormat.setParseBigDecimal(true);
getDecimalFormatSymbols().getDecimalSeparator();
/*
* La vérification suivante a pour but de s'assurer que le parser envoie une exception pour des données
* commençant par des chiffres suivis d'autres caractères non valides (ex: '54?' ou '54toto') plutôt
* que de les parser silencieusement.
*/
ParsePosition pos = new ParsePosition(0);
BigDecimal bigDecimal = (BigDecimal) decimalFormat.parse(value, pos);
if (pos.getIndex() < value.length()) {
String trailing = value.substring(pos.getIndex());
if (!StringUtils.isBlank(trailing)) {
ConversionException e = new ConversionException(
String.format("Failed parsing of '%1$s' at position %2$d", value, pos.getIndex()));
e.setConverter(this);
e.setVariable("value", value);
e.setResourceKey("common.validator.percentage.parsing.error");
throw e;
}
}
if (modelIsRatio) {
bigDecimal = bigDecimal.divide(new BigDecimal("100"));
}
bigDecimal = bigDecimal.setScale(scale, roundingMode);
if (bigDecimal.precision() > maxPrecision) {
ConversionException e = new ConversionException(
"The precision of the BigDecimal instance exceeds the maximum allowed precision.");
e.setConverter(this);
e.setVariable("maxBeforePoint", modelIsRatio ? (maxPrecision - scale + 2) : (maxPrecision - scale));
e.setResourceKey("common.validator.percentage.precision.error");
throw e;
} else {
return bigDecimal;
}
}
@Override
public String convertToString(BigDecimal value, Locale locale) {
if (value == null) {
return "";
}
value = value.setScale(scale, roundingMode);
if (modelIsRatio) {
value = value.multiply(new BigDecimal("100"));
}
if (value.precision() > maxPrecision) {
throw new IllegalStateException(String.format(
"The precision of the BigDecimal instance (%1$s) exceeds the maximum allowed precision, %2$s.",
value.precision(), maxPrecision));
} else {
String result = new DecimalFormat(PATTERN, getDecimalFormatSymbols()).format(value);
if (displayPercentSymbol) {
result = result + "%";
}
return result;
}
}
private DecimalFormatSymbols getDecimalFormatSymbols() {
return new DecimalFormatSymbols(CoreWicketApplication.get().getNumberFormatLocale());
}
}