/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * 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 org.arakhne.afc.vmutil.locale; import java.math.BigDecimal; import java.math.BigInteger; import java.math.RoundingMode; import java.text.DecimalFormat; import java.text.FieldPosition; import java.text.Format; import java.text.MessageFormat; import java.text.NumberFormat; import java.text.ParsePosition; import java.util.Locale; import org.eclipse.xtext.xbase.lib.Pure; /** * <code>LocaleMessageFormat</code> provides a means to produce concatenated * messages in a language-neutral way in the {@link Locale} * utility class. * * <p><code>LocaleMessageFormat</code> takes a set of objects, formats them, then * inserts the formatted strings into the pattern at the appropriate places. * * <p>In addition to the standard JDK {@link MessageFormat}, <code>LocaleMessageFormat</code> * provides the <code>FormatStyle</code> named "raw". This new style does not try * to format the given data according to the locale. It simply put the * not-formatted data in the result. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * @since 6.4 */ public class LocaleMessageFormat extends MessageFormat { /** String that corresponds to the raw format style. */ public static final String RAW_FORMAT_STYLE = "raw"; //$NON-NLS-1$ private static final long serialVersionUID = 6637824487735941754L; /** Construct a message format with the given pattern. * * @param pattern the pattern for this message format * @throws IllegalArgumentException if the pattern is invalid */ public LocaleMessageFormat(String pattern) { super(pattern); } /** Construct a message format with the given pattern and locale. * * @param pattern the pattern for this message format * @param locale the locale for this message format * @exception IllegalArgumentException if the pattern is invalid */ public LocaleMessageFormat(String pattern, Locale locale) { super(pattern, locale); } /** * Creates a LocaleMessageFormat with the given pattern and uses it * to format the given arguments. This is equivalent to * <blockquote> * <code>(new {@link #LocaleMessageFormat(String) MessageFormat}(pattern)).{@link #format(java.lang.Object[], * java.lang.StringBuffer, java.text.FieldPosition) format}(arguments, new StringBuffer(), null).toString()</code> * </blockquote> * * @param pattern the pattern of string. * @param arguments the dynamic arguments to put inside the string. * @return the formatted string. * @throws IllegalArgumentException if the pattern is invalid, * or if an argument in the <code>arguments</code> array * is not of the type expected by the format element(s) * that use it. */ @Pure public static String format(String pattern, Object... arguments) { final LocaleMessageFormat temp = new LocaleMessageFormat(pattern); return temp.format(arguments); } @Override public void applyPattern(String pattern) { super.applyPattern(pattern); final Format[] formats = getFormats(); boolean changed = false; for (int i = 0; i < formats.length; ++i) { try { final DecimalFormat df = (DecimalFormat) formats[i]; if (df != null && RAW_FORMAT_STYLE.equalsIgnoreCase(df.getPositivePrefix())) { formats[i] = new RawNumberFormat( pattern, df.getGroupingSize(), df.getMinimumIntegerDigits(), df.getMaximumIntegerDigits(), df.getMinimumFractionDigits(), df.getMaximumFractionDigits(), df.getRoundingMode()); changed = true; } } catch (ClassCastException exception) { // } } if (changed) { setFormats(formats); } } /** Format for generated a raw number. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private static class RawNumberFormat extends NumberFormat { private static final long serialVersionUID = 7091190928835741939L; private static final char RAW_NEGATIVE_SIGN = '-'; private static final char RAW_DECIMAL_SEPARATOR = '.'; private static final char RAW_ZERO_DIGIT = '0'; private final boolean isUnformatted; private final RoundingMode roundingMode; /** Construct the format. * * @param pattern the pattern for the number. * @param groupSize the number of groups in the pattern. * @param minInt the minimum number of integer digits. * @param maxInt the maximum number of integer digits. * @param minFrac the mininum number of fractional digits. * @param maxFrac the maxinum number of fractional digits. * @param roundingMode the mode for rounding the value. */ RawNumberFormat(String pattern, int groupSize, int minInt, int maxInt, int minFrac, int maxFrac, RoundingMode roundingMode) { super(); this.roundingMode = roundingMode; this.isUnformatted = (groupSize == 0) && (minInt == 0) && (maxInt == Integer.MAX_VALUE) && (minFrac == 0) && (maxFrac == 0); setMinimumIntegerDigits(minInt); setMaximumIntegerDigits(maxInt); setMinimumFractionDigits(minFrac); setMaximumFractionDigits(maxFrac); } @Override public StringBuffer format(Object number, StringBuffer toAppendTo, FieldPosition pos) { try { return format((BigInteger) number, toAppendTo, pos); } catch (ClassCastException exception1) { try { return format((BigDecimal) number, toAppendTo, pos); } catch (ClassCastException exception2) { return super.format(number, toAppendTo, pos); } } } /** * Specialization of format. * * @param number is the number to format. * @param toAppendTo is the string buffer into which the formatting result may be appended. * @param pos is on input: an alignment field, if desired. On output: the offsets of * the alignment field. * @return the value passed in as <code>toAppendTo</code> * @throws ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public StringBuffer format(BigInteger number, StringBuffer toAppendTo, FieldPosition pos) { if (this.isUnformatted) { toAppendTo.append(number.toString()); } else { formatInteger(number.signum() < 0, number.abs().toString(), toAppendTo); } return toAppendTo; } /** * Specialization of format. * * @param number is the number to format. * @param toAppendTo is the string buffer into which the formatting result may be appended. * @param pos is on input: an alignment field, if desired. On output: the offsets of * the alignment field. * @return the value passed in as <code>toAppendTo</code> * @throws ArithmeticException if rounding is needed with rounding * mode being set to RoundingMode.UNNECESSARY * @see java.text.Format#format */ public StringBuffer format(BigDecimal number, StringBuffer toAppendTo, FieldPosition pos) { if (this.isUnformatted) { toAppendTo.append(number.toPlainString()); } else { formatDecimal(number, toAppendTo); } return toAppendTo; } @Override public StringBuffer format(double number, StringBuffer toAppendTo, FieldPosition pos) { if (this.isUnformatted) { toAppendTo.append(Double.toString(number)); } else { formatDecimal(new BigDecimal(number), toAppendTo); } return toAppendTo; } @Override public StringBuffer format(long number, StringBuffer toAppendTo, FieldPosition pos) { if (this.isUnformatted) { toAppendTo.append(Long.toString(number)); } else { formatInteger(number < 0, Long.toString(Math.abs(number)), toAppendTo); } return toAppendTo; } private void formatInteger(boolean negative, String number, StringBuffer toAppendTo) { assert !this.isUnformatted; if (negative) { toAppendTo.append(RAW_NEGATIVE_SIGN); } for (int c = getMinimumIntegerDigits() - number.length(); c > 0; --c) { toAppendTo.append(RAW_ZERO_DIGIT); } toAppendTo.append(number); final int n = getMinimumFractionDigits(); if (n > 0) { toAppendTo.append(RAW_DECIMAL_SEPARATOR); for (int c = 0; c < n; ++c) { toAppendTo.append(RAW_ZERO_DIGIT); } } } private void formatDecimal(BigDecimal number, StringBuffer toAppendTo) { assert !this.isUnformatted; final boolean negative = number.compareTo(BigDecimal.ZERO) < 0; final int minInt = getMinimumIntegerDigits(); final int minFrac = getMinimumFractionDigits(); final int maxFrac = getMaximumFractionDigits(); final BigDecimal n = number.setScale(maxFrac, this.roundingMode); final String rawString = n.abs().toPlainString(); final int decimalPos = rawString.indexOf(RAW_DECIMAL_SEPARATOR); final String integer; final String decimal; if (decimalPos < 0) { integer = rawString; decimal = ""; //$NON-NLS-1$ } else { integer = rawString.substring(0, decimalPos); decimal = rawString.substring(decimalPos + 1); } if (negative) { toAppendTo.append(RAW_NEGATIVE_SIGN); } int c = minInt - integer.length(); while (c > 0) { toAppendTo.append(RAW_ZERO_DIGIT); --c; } toAppendTo.append(integer); if (minFrac > 0 || (maxFrac > 0 && decimal.length() > 0)) { toAppendTo.append(RAW_DECIMAL_SEPARATOR); toAppendTo.append(decimal); c = minFrac - decimal.length(); while (c > 0) { toAppendTo.append(RAW_ZERO_DIGIT); --c; } } } @Override public Number parse(String source, ParsePosition parsePosition) { throw new UnsupportedOperationException(); } } }