All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertigo.dynamox.domain.formatter.FormatterNumber Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
/**
 * vertigo - simple java starter
 *
 * Copyright (C) 2013, KleeGroup, [email protected] (http://www.kleegroup.com)
 * KleeGroup, Centre d'affaire la Boursidiere - BP 159 - 92357 Le Plessis Robinson Cedex - France
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.vertigo.dynamox.domain.formatter;

import io.vertigo.dynamo.domain.metamodel.DataType;
import io.vertigo.dynamo.domain.metamodel.Formatter;
import io.vertigo.dynamo.domain.metamodel.FormatterException;
import io.vertigo.lang.Assertion;
import io.vertigo.lang.JsonExclude;
import io.vertigo.util.StringUtil;

import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;

/**
 * Gestion des formatages de nombres.
 * L'argument est obligatoire, il permet de préciser le format d'affichage des nombres.
 *
 * A l'affichage
 * - le séparateur de millier est un espace
 * - le séparateur décimal est une virgule
 * En saisie
 * - les séparateurs de milliers acceptés sont l'espace et l'espace insécable
 * - les séparateurs décimaux acceptés  sont la virgule et le point
*
 * Exemple d'argument : #,###,##0.00
  *
 * @author pchretien
 */
public class FormatterNumber implements Formatter {
	/**
	 * Format d'affichage des nombres.
	 */
	private String pattern;
	@JsonExclude
	private final DecimalFormatSymbols decFormatSymbols;

	/**
	 * Constructor.
	 * This formatter requires one arg that is a pattern.
	 * This pattern is used 
	 *  - to format a string into a number
	 *  - to format a number into a string 
	 *   
	 * @param args args used to initialize the formatter
	 */
	public FormatterNumber(final String args) {
		decFormatSymbols = new java.text.DecimalFormatSymbols();
		decFormatSymbols.setDecimalSeparator(','); //séparateur décimal
		decFormatSymbols.setGroupingSeparator(' '); //séparateur de milliers
		initParameters(args);
	}

	/**
	 * @return Pattern 
	 */
	public final String getPattern() {
		return pattern;
	}

	/**
	 * @param args args
	 */
	protected void initParameters(final String args) {
		Assertion.checkNotNull(args);
		//-----
		pattern = args;
		//-----
		//On vérifie la syntaxe de DecimalFormat
		Assertion.checkNotNull(new DecimalFormat(pattern));
	}

	/**
	 * @return Symboles decimaux utilisés
	 */
	protected DecimalFormatSymbols getDecimalFormatSymbols() {
		return decFormatSymbols;
	}

	/*
	 * Les formatters java ne sont pas threadSafe,
	 * on les recrée à chaque usage.
	 */
	private NumberFormat createNumberFormat() {
		// Si format non précisé on utilise le format par défaut
		return new DecimalFormat(pattern, getDecimalFormatSymbols());
	}

	private static void checkType(final DataType dataType) {
		Assertion.checkArgument(dataType == DataType.BigDecimal || dataType == DataType.Double || dataType == DataType.Integer || dataType == DataType.Long, "FormatterNumber ne s'applique qu'aux Nombres");
	}

	/** {@inheritDoc} */
	@Override
	public final Object stringToValue(final String strValue, final DataType dataType) throws FormatterException {
		checkType(dataType);
		//-----
		//Pour les nombres on "trim" à droite et à gauche
		String sValue = StringUtil.isEmpty(strValue) ? null : strValue.trim();

		if (sValue == null) {
			return null;
		}

		try {
			final DecimalFormatSymbols decimalFormatSymbols = getDecimalFormatSymbols();
			/**
			 * Puis on transforme la chaine pour revenir à l'ecriture la plus simple.
			 * Cela pour utiliser le Number.valueOf plutot que le parse de NumberFormat.
			 */
			sValue = cleanStringNumber(sValue, decimalFormatSymbols);

			switch (dataType) {
				case BigDecimal:
					return new BigDecimal(sValue);
				case Double:
					return Double.valueOf(sValue);
				case Integer:
					return toInteger(sValue);
				case Long:
					return Long.valueOf(sValue);
				case Boolean:
				case DataStream:
				case Date:
				case DtList:
				case DtObject:
				case String:
					throw new IllegalArgumentException("Type unsupported : " + dataType);
				default:
					throw new IllegalArgumentException("Type unknown : " + dataType);
			}
		} catch (final NumberFormatException e) {
			// cas des erreurs sur les formats de nombre
			throw new FormatterException(Resources.DYNAMOX_NUMBER_NOT_FORMATTED, e);
		}

	}

	private static Integer toInteger(final String sValue) throws FormatterException {
		// on commence par vérifier que c'est bien un entier (Integer ou Long)
		Long.valueOf(sValue);
		try {
			// c'est bien un entier. On va vérifier qu'il s'agit bien d'un Integer
			return Integer.valueOf(sValue);
		} catch (final NumberFormatException e) {
			// C'est un entier trop grand
			final FormatterException formatterException = new FormatterException(Resources.DYNAMOX_NUMBER_TOO_BIG);
			formatterException.initCause(e);
			throw formatterException;
		}
	}

	/**
	 * Simplifie une chaine réprésentant un nombre.
	 * Utilisé en préprocessing avant le parsing.
	 * @param value Chaine saisie
	 * @param decimalFormatSymbols symboles décimaux utilisées
	 * @return Chaine simplifiée
	 */
	protected String cleanStringNumber(final String value, final DecimalFormatSymbols decimalFormatSymbols) {
		return cleanStringNumber(value, decimalFormatSymbols.getDecimalSeparator(), decimalFormatSymbols.getGroupingSeparator());
	}

	/**
	 * Simplifie une chaine réprésentant un nombre.
	 * Utilisé en préprocessing avant le parsing.
	 * @param sValue Chaine saisie
	 * @param decimalCharUsed caractère décimal utilisé
	 * @param groupCharUsed caractère de millier utilisé
	 * @return Chaine simplifiée
	 */
	protected static final String cleanStringNumber(final String sValue, final char decimalCharUsed, final char groupCharUsed) {
		String result = sValue;
		// 1 >> On supprime les blancs. (simples et insécables)
		if (groupCharUsed == ' ' || groupCharUsed == (char) 160) {
			result = result.replace((char) 160, ' '); //aussi rapide que l'indexOf si absend
			result = StringUtil.replace(result, " ", "");
		} else if (result.indexOf(groupCharUsed) != -1) {
			// 2 >> On supprime les séparateurs de milliers.
			result = StringUtil.replace(result, String.valueOf(groupCharUsed), "");
		}

		// 3 >> On remplace le séparateur décimal par des '.'
		result = result.replace(decimalCharUsed, '.');
		return result;
	}

	/** {@inheritDoc} */
	@Override
	public final String valueToString(final Object objValue, final DataType dataType) {
		checkType(dataType);
		//-----
		String decimalString = null;
		if (objValue == null) {
			decimalString = "";
		} else {
			switch (dataType) {
				case BigDecimal:
				case Double:
					decimalString = createNumberFormat().format(objValue);
					break;
				case Integer:
				case Long:
					if (pattern == null) {
						// on ne passe surtout pas pas un formatter interne java
						// pour les perfs et conserver des identifiants en un seul morceau
						decimalString = objValue.toString();
					} else {
						decimalString = createNumberFormat().format(objValue);
					}
					break;
				case Boolean:
				case DataStream:
				case Date:
				case DtList:
				case DtObject:
				case String:
					throw new IllegalArgumentException("Type unsupported" + dataType);
				default:
					throw new IllegalArgumentException("Type unknown : " + dataType);
			}
		}
		return decimalString;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy