/* * Copyright (c) 2012 Data Harmonisation Panel * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * HUMBOLDT EU Integrated Project #030962 * Data Harmonisation Panel <http://www.dhpanel.eu> */ package eu.esdihumboldt.util.validator; import java.math.BigDecimal; import org.springframework.core.convert.ConversionException; /** * Validator for digit counts. As in * (http://www.w3.org/TR/xmlschema-2/#rf-totalDigits and * http://www.w3.org/TR/xmlschema-2/#rf-fractionDigits) * * @author Kai Schwierczek */ public class DigitCountValidator extends AbstractValidator { private final Type type; private final int length; /** * Construct a validator that checks the digit count of the input to match * the given type and value. * * @param type the digits to check for * @param length the length to check for */ public DigitCountValidator(Type type, int length) { this.type = type; this.length = length; } /** * @see eu.esdihumboldt.util.validator.Validator#validate(Object) */ @Override public String validate(Object value) { // ignore null values, rely on the NillableFlagValidator for that. if (value == null) { return null; } BigDecimal decimal; try { decimal = getObjectAs(value, BigDecimal.class); if (decimal == null) return "Input must be a number."; } catch (ConversionException ce) { return "Input must be a number."; } switch (type) { case FRACTIONDIGITS: boolean ok = true; try { // try lowering the scale if it is too large without rounding // -> cut off ending zeros if possible if (decimal.scale() > length) { // throws exception if scaling is not possible /* * FIXME this does not change the original value, so what * does this achieve? */ decimal = decimal.setScale(length); } } catch (ArithmeticException ae) { ok = false; // scaling failed } if (ok) return null; else return "Input must have at most " + length + " fraction digits but has " + decimal.scale() + "."; case TOTALDIGITS: // single zero in front of decimal point and ending zeros don't // count // here BigDecimal doesn't help, do string work. String numberString = decimal.abs().toPlainString(); int indexOfDot = numberString.indexOf('.'); if (indexOfDot != -1) { StringBuilder buf = new StringBuilder(numberString); // remove ending zeros while (buf.charAt(buf.length() - 1) == '0') buf.deleteCharAt(buf.length() - 1); // remove dot and maybe single zero in front of dot if (indexOfDot == 1 && buf.charAt(0) == '0') buf.delete(0, 2); // delete leading zero and . else buf.deleteCharAt(indexOfDot); // only delete point numberString = buf.toString(); } else if (numberString.equals("0")) numberString = ""; if (numberString.length() <= length) return null; else return "Input must have at most " + length + " total digits but has " + numberString.length() + "."; default: return null; // all types checked, doesn't happen } } /** * @see eu.esdihumboldt.util.validator.Validator#getDescription() */ @Override public String getDescription() { switch (type) { case FRACTIONDIGITS: return "Input must be a number and must have at most " + length + " fraction digits."; case TOTALDIGITS: return "Input must be a number and must have at most " + length + " total digits."; default: return ""; // all types checked, doesn't happen } } /** * @return the type of the compare operation */ public Type getType() { return type; } /** * @return the length to compare against */ public int getLength() { return length; } /** * Type specifies what DigitCountValidator should check. */ public enum Type { /** * Check for fraction digit count. */ FRACTIONDIGITS, /** * Check for total digit count. */ TOTALDIGITS; } }