org.joda.money.format.MoneyAmountStyle Maven / Gradle / Ivy
Show all versions of payment-retries-plugin Show documentation
/*
* Copyright 2009-present, Stephen Colebourne
*
* 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.joda.money.format;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Defines the style that the amount of a monetary value will be formatted with.
*
* The style contains a number of fields that may be configured based on the locale:
*
* - character used for zero, which defined all the numbers from zero to nine
*
- character used for positive and negative symbols
*
- character used for the decimal point
*
- whether and how to group the amount
*
- character used for grouping, such as grouping thousands
*
- size for each group, such as 3 for thousands
*
- whether to always use a decimal point
*
*
* The style can be used in three basic ways.
*
* - set all the fields manually, resulting in the same amount style for all locales
*
- call {@link #localize} manually and optionally adjust to set as required
*
- leave the localized fields as {@code null} and let the locale in the
* formatter to determine the style
*
*
* This class is immutable and thread-safe.
*/
public final class MoneyAmountStyle implements Serializable {
/**
* A style that uses ASCII digits/negative sign, the decimal point
* and groups large amounts in 3's using a comma.
* Forced decimal point is disabled.
*/
public static final MoneyAmountStyle ASCII_DECIMAL_POINT_GROUP3_COMMA =
new MoneyAmountStyle('0', '+', '-', '.', GroupingStyle.FULL, ',', 3, 0, false, false);
/**
* A style that uses ASCII digits/negative sign, the decimal point
* and groups large amounts in 3's using a space.
* Forced decimal point is disabled.
*/
public static final MoneyAmountStyle ASCII_DECIMAL_POINT_GROUP3_SPACE =
new MoneyAmountStyle('0', '+', '-', '.', GroupingStyle.FULL, ' ', 3, 0, false, false);
/**
* A style that uses ASCII digits/negative sign, the decimal point
* and no grouping of large amounts.
* Forced decimal point is disabled.
*/
public static final MoneyAmountStyle ASCII_DECIMAL_POINT_NO_GROUPING =
new MoneyAmountStyle('0', '+', '-', '.', GroupingStyle.NONE, ',', 3, 0, false, false);
/**
* A style that uses ASCII digits/negative sign, the decimal comma
* and groups large amounts in 3's using a dot.
* Forced decimal point is disabled.
*/
public static final MoneyAmountStyle ASCII_DECIMAL_COMMA_GROUP3_DOT =
new MoneyAmountStyle('0', '+', '-', ',', GroupingStyle.FULL, '.', 3, 0, false, false);
/**
* A style that uses ASCII digits/negative sign, the decimal comma
* and groups large amounts in 3's using a space.
* Forced decimal point is disabled.
*/
public static final MoneyAmountStyle ASCII_DECIMAL_COMMA_GROUP3_SPACE =
new MoneyAmountStyle('0', '+', '-', ',', GroupingStyle.FULL, ' ', 3, 0, false, false);
/**
* A style that uses ASCII digits/negative sign, the decimal point
* and no grouping of large amounts.
* Forced decimal point is disabled.
*/
public static final MoneyAmountStyle ASCII_DECIMAL_COMMA_NO_GROUPING =
new MoneyAmountStyle('0', '+', '-', ',', GroupingStyle.NONE, '.', 3, 0, false, false);
/**
* A style that will be filled in with localized values using the locale of the formatter.
* Grouping is enabled. Forced decimal point is disabled.
*/
public static final MoneyAmountStyle LOCALIZED_GROUPING =
new MoneyAmountStyle(-1, -1, -1, -1, GroupingStyle.FULL, -1, -1, -1, false, false);
/**
* A style that will be filled in with localized values using the locale of the formatter.
* Grouping is disabled. Forced decimal point is disabled.
*/
public static final MoneyAmountStyle LOCALIZED_NO_GROUPING =
new MoneyAmountStyle(-1, -1, -1, -1, GroupingStyle.NONE, -1, -1, -1, false, false);
/**
* Cache of localized styles.
*/
private static final ConcurrentMap LOCALIZED_CACHE = new ConcurrentHashMap();
/**
* Serialization version.
*/
private static final long serialVersionUID = 1L;
/**
* The character defining zero, and thus the numbers zero to nine.
*/
private final int zeroCharacter;
/**
* The character representing the positive sign.
*/
private final int positiveCharacter;
/**
* The prefix string when the amount is negative.
*/
private final int negativeCharacter;
/**
* The character used for the decimal point.
*/
private final int decimalPointCharacter;
/**
* Whether to group or not.
*/
private final GroupingStyle groupingStyle;
/**
* The character used for grouping.
*/
private final int groupingCharacter;
/**
* The size of each group.
*/
private final int groupingSize;
/**
* The size of each group.
*/
private final int extendedGroupingSize;
/**
* Whether to always require the decimal point to be visible.
*/
private final boolean forceDecimalPoint;
/**
* Whether to use the absolute value instead of the signed value.
*/
private final boolean absValue;
//-----------------------------------------------------------------------
/**
* Gets a localized style.
*
* This creates a localized style for the specified locale.
* Grouping will be enabled, forced decimal point will be disabled,
* absolute values will be disabled.
*
* @param locale the locale to use, not null
* @return the new instance, never null
*/
public static MoneyAmountStyle of(Locale locale) {
return getLocalizedStyle(locale);
}
//-----------------------------------------------------------------------
/**
* Constructor, creating a new monetary instance.
*
* @param zeroCharacter the zero character
* @param postiveCharacter the positive sign
* @param negativeCharacter the negative sign
* @param decimalPointCharacter the decimal point character
* @param groupingStyle the grouping style, not null
* @param groupingCharacter the grouping character
* @param groupingSize the grouping size
* @param forceDecimalPoint whether to always use the decimal point character
* @param absValue true to output the absolute value rather than the signed value
*/
private MoneyAmountStyle(
int zeroCharacter,
int positiveCharacter, int negativeCharacter,
int decimalPointCharacter, GroupingStyle groupingStyle,
int groupingCharacter, int groupingSize, int extendedGroupingSize,
boolean forceDecimalPoint, boolean absValue) {
this.zeroCharacter = zeroCharacter;
this.positiveCharacter = positiveCharacter;
this.negativeCharacter = negativeCharacter;
this.decimalPointCharacter = decimalPointCharacter;
this.groupingStyle = groupingStyle;
this.groupingCharacter = groupingCharacter;
this.groupingSize = groupingSize;
this.extendedGroupingSize = extendedGroupingSize;
this.forceDecimalPoint = forceDecimalPoint;
this.absValue = absValue;
}
//-----------------------------------------------------------------------
/**
* Returns a {@code MoneyAmountStyle} instance configured for the specified locale.
*
* This method will return a new instance where each field that was defined
* to be localized (by being set to {@code null}) is filled in.
* If this instance is fully defined (with all fields non-null), then this
* method has no effect. Once this method is called, no method will return null.
*
* The settings for the locale are pulled from {@link DecimalFormatSymbols} and
* {@link DecimalFormat}.
*
* @param locale the locale to use, not null
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle localize(Locale locale) {
MoneyFormatter.checkNotNull(locale, "Locale must not be null");
MoneyAmountStyle result = this;
MoneyAmountStyle protoStyle = null;
if (zeroCharacter < 0) {
protoStyle = getLocalizedStyle(locale);
result = result.withZeroCharacter(protoStyle.getZeroCharacter());
}
if (positiveCharacter < 0) {
protoStyle = getLocalizedStyle(locale);
result = result.withPositiveSignCharacter(protoStyle.getPositiveSignCharacter());
}
if (negativeCharacter < 0) {
protoStyle = getLocalizedStyle(locale);
result = result.withNegativeSignCharacter(protoStyle.getNegativeSignCharacter());
}
if (decimalPointCharacter < 0) {
protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
result = result.withDecimalPointCharacter(protoStyle.getDecimalPointCharacter());
}
if (groupingCharacter < 0) {
protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
result = result.withGroupingCharacter(protoStyle.getGroupingCharacter());
}
if (groupingSize < 0) {
protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
result = result.withGroupingSize(protoStyle.getGroupingSize());
}
if (extendedGroupingSize < 0) {
protoStyle = (protoStyle == null ? getLocalizedStyle(locale) : protoStyle);
result = result.withExtendedGroupingSize(protoStyle.getExtendedGroupingSize());
}
return result;
}
//-----------------------------------------------------------------------
/**
* Gets the prototype localized style for the given locale.
*
* This uses {@link DecimalFormatSymbols} and {@link NumberFormat}.
*
* If JDK 6 or newer is being used, {@code DecimalFormatSymbols.getInstance(locale)}
* will be used in order to allow the use of locales defined as extensions.
* Otherwise, {@code new DecimalFormatSymbols(locale)} will be used.
*
* @param locale the {@link Locale} used to get the correct {@link DecimalFormatSymbols}
* @return the symbols, never null
*/
private static MoneyAmountStyle getLocalizedStyle(Locale locale) {
MoneyAmountStyle protoStyle = LOCALIZED_CACHE.get(locale);
if (protoStyle == null) {
DecimalFormatSymbols symbols;
try {
Method method = DecimalFormatSymbols.class.getMethod("getInstance", new Class[] {Locale.class});
symbols = (DecimalFormatSymbols) method.invoke(null, new Object[] {locale}); // handle JDK 6
} catch (Exception ex) {
symbols = new DecimalFormatSymbols(locale); // handle JDK 5
}
NumberFormat format = NumberFormat.getCurrencyInstance(locale);
int size = (format instanceof DecimalFormat ? ((DecimalFormat) format).getGroupingSize() : 3);
protoStyle = new MoneyAmountStyle(
symbols.getZeroDigit(),
'+', symbols.getMinusSign(),
symbols.getMonetaryDecimalSeparator(),
GroupingStyle.FULL, symbols.getGroupingSeparator(), size, 0, false, false);
LOCALIZED_CACHE.putIfAbsent(locale, protoStyle);
}
return protoStyle;
}
//-----------------------------------------------------------------------
/**
* Gets the character used for zero, and defining the characters zero to nine.
*
* The UTF-8 standard supports a number of different numeric scripts.
* Each script has the characters in order from zero to nine.
* This method returns the zero character, which therefore also defines one to nine.
*
* @return the zero character, null if to be determined by locale
*/
public Character getZeroCharacter() {
return zeroCharacter < 0 ? null : (char) zeroCharacter;
}
/**
* Returns a copy of this style with the specified zero character.
*
* The UTF-8 standard supports a number of different numeric scripts.
* Each script has the characters in order from zero to nine.
* This method sets the zero character, which therefore also defines one to nine.
*
* For English, this is a '0'. Some other scripts use different characters
* for the numbers zero to nine.
*
* @param zeroCharacter the zero character, null if to be determined by locale
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withZeroCharacter(Character zeroCharacter) {
int zeroVal = (zeroCharacter == null ? -1 : zeroCharacter);
if (zeroVal == this.zeroCharacter) {
return this;
}
return new MoneyAmountStyle(
zeroVal,
positiveCharacter, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets the character used for the positive sign character.
*
* The standard ASCII symbol is '+'.
*
* @return the format for positive amounts, null if to be determined by locale
*/
public Character getPositiveSignCharacter() {
return positiveCharacter < 0 ? null : (char) positiveCharacter;
}
/**
* Returns a copy of this style with the specified positive sign character.
*
* The standard ASCII symbol is '+'.
*
* @param positiveCharacter the positive character, null if to be determined by locale
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withPositiveSignCharacter(Character positiveCharacter) {
int positiveVal = (positiveCharacter == null ? -1 : positiveCharacter);
if (positiveVal == this.positiveCharacter) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveVal, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets the character used for the negative sign character.
*
* The standard ASCII symbol is '-'.
*
* @return the format for negative amounts, null if to be determined by locale
*/
public Character getNegativeSignCharacter() {
return negativeCharacter < 0 ? null : (char) negativeCharacter;
}
/**
* Returns a copy of this style with the specified negative sign character.
*
* The standard ASCII symbol is '-'.
*
* @param negativeCharacter the negative character, null if to be determined by locale
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withNegativeSignCharacter(Character negativeCharacter) {
int negativeVal = (negativeCharacter == null ? -1 : negativeCharacter);
if (negativeVal == this.negativeCharacter) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeVal,
decimalPointCharacter, groupingStyle,
groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets the character used for the decimal point.
*
* @return the decimal point character, null if to be determined by locale
*/
public Character getDecimalPointCharacter() {
return decimalPointCharacter < 0 ? null : (char) decimalPointCharacter;
}
/**
* Returns a copy of this style with the specified decimal point character.
*
* For English, this is a dot.
*
* @param decimalPointCharacter the decimal point character, null if to be determined by locale
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withDecimalPointCharacter(Character decimalPointCharacter) {
int dpVal = (decimalPointCharacter == null ? -1 : decimalPointCharacter);
if (dpVal == this.decimalPointCharacter) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeCharacter,
dpVal, groupingStyle,
groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets the character used to separate groups, typically thousands.
*
* @return the grouping character, null if to be determined by locale
*/
public Character getGroupingCharacter() {
return groupingCharacter < 0 ? null : (char) groupingCharacter;
}
/**
* Returns a copy of this style with the specified grouping character.
*
* For English, this is a comma.
*
* @param groupingCharacter the grouping character, null if to be determined by locale
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withGroupingCharacter(Character groupingCharacter) {
int groupingVal = (groupingCharacter == null ? -1 : groupingCharacter);
if (groupingVal == this.groupingCharacter) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingVal, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets the size of each group, typically 3 for thousands.
*
* @return the size of each group, null if to be determined by locale
*/
public Integer getGroupingSize() {
return groupingSize < 0 ? null : groupingSize;
}
/**
* Returns a copy of this style with the specified grouping size.
*
* @param groupingSize the size of each group, such as 3 for thousands,
* not zero or negative, null if to be determined by locale
* @return the new instance for chaining, never null
* @throws IllegalArgumentException if the grouping size is zero or less
*/
public MoneyAmountStyle withGroupingSize(Integer groupingSize) {
int sizeVal = (groupingSize == null ? -1 : groupingSize);
if (groupingSize != null && sizeVal <= 0) {
throw new IllegalArgumentException("Grouping size must be greater than zero");
}
if (sizeVal == this.groupingSize) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingCharacter, sizeVal, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets the size of each group, not typically used.
*
* This is primarily used to enable the Indian Number System, where the group
* closest to the decimal point is of size 3 and other groups are of size 2.
* The extended grouping size is used for groups that are not next to the decimal point.
* The value zero is used to indicate that extended grouping is not needed.
*
* @return the size of each group, null if to be determined by locale
*/
public Integer getExtendedGroupingSize() {
return extendedGroupingSize < 0 ? null : extendedGroupingSize;
}
/**
* Returns a copy of this style with the specified extended grouping size.
*
* @param extendedGroupingSize the size of each group, such as 3 for thousands,
* not zero or negative, null if to be determined by locale
* @return the new instance for chaining, never null
* @throws IllegalArgumentException if the grouping size is zero or less
*/
public MoneyAmountStyle withExtendedGroupingSize(Integer extendedGroupingSize) {
int sizeVal = (extendedGroupingSize == null ? -1 : extendedGroupingSize);
if (extendedGroupingSize != null && sizeVal < 0) {
throw new IllegalArgumentException("Extended grouping size must not be negative");
}
if (sizeVal == this.extendedGroupingSize) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingCharacter, groupingSize, sizeVal, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets the style of grouping required.
*
* @return the grouping style, not null
*/
public GroupingStyle getGroupingStyle() {
return groupingStyle;
}
/**
* Returns a copy of this style with the specified grouping setting.
*
* @param groupingStyle the grouping style, not null
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withGroupingStyle(GroupingStyle groupingStyle) {
MoneyFormatter.checkNotNull(groupingStyle, "groupingStyle");
if (this.groupingStyle == groupingStyle) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Gets whether to always use the decimal point, even if there is no fraction.
*
* @return whether to force the decimal point on output
*/
public boolean isForcedDecimalPoint() {
return forceDecimalPoint;
}
/**
* Returns a copy of this style with the specified decimal point setting.
*
* @param forceDecimalPoint true to force the use of the decimal point, false to use it if required
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withForcedDecimalPoint(boolean forceDecimalPoint) {
if (this.forceDecimalPoint == forceDecimalPoint) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Returns true if the absolute value setting.
*
* If this is set to true, the absolute (unsigned) value will be output.
* If this is set to false, the signed value will be output.
* Note that when parsing, signs are accepted.
*
* @return true to output the absolute value, false for the signed value
*/
public boolean isAbsValue() {
return absValue;
}
/**
* Returns a copy of this style with the specified absolute value setting.
*
* If this is set to true, the absolute (unsigned) value will be output.
* If this is set to false, the signed value will be output.
* Note that when parsing, signs are accepted.
*
* @param absValue true to output the absolute value, false for the signed value
* @return the new instance for chaining, never null
*/
public MoneyAmountStyle withAbsValue(boolean absValue) {
if (this.absValue == absValue) {
return this;
}
return new MoneyAmountStyle(
zeroCharacter,
positiveCharacter, negativeCharacter,
decimalPointCharacter, groupingStyle,
groupingCharacter, groupingSize, extendedGroupingSize, forceDecimalPoint, absValue);
}
//-----------------------------------------------------------------------
/**
* Compares this style with another.
*
* @param other the other style, null returns false
* @return true if equal
*/
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if (other instanceof MoneyAmountStyle == false) {
return false;
}
MoneyAmountStyle otherStyle = (MoneyAmountStyle) other;
return (zeroCharacter == otherStyle.zeroCharacter) &&
(positiveCharacter == otherStyle.positiveCharacter) &&
(negativeCharacter == otherStyle.negativeCharacter) &&
(decimalPointCharacter == otherStyle.decimalPointCharacter) &&
(groupingStyle == otherStyle.groupingStyle) &&
(groupingCharacter == otherStyle.groupingCharacter) &&
(groupingSize == otherStyle.groupingSize) &&
(forceDecimalPoint == otherStyle.forceDecimalPoint) &&
(absValue == otherStyle.absValue);
}
/**
* A suitable hash code.
*
* @return a hash code
*/
@Override
public int hashCode() {
int hash = 13;
hash += zeroCharacter * 17;
hash += positiveCharacter * 17;
hash += negativeCharacter * 17;
hash += decimalPointCharacter * 17;
hash += groupingStyle.hashCode() * 17;
hash += groupingCharacter * 17;
hash += groupingSize * 17;
hash += (forceDecimalPoint ? 2 : 4);
hash += (absValue ? 3 : 9);
return hash;
}
//-----------------------------------------------------------------------
/**
* Gets a string summary of the style.
*
* @return a string summarising the style, never null
*/
@Override
public String toString() {
return "MoneyAmountStyle['" + getZeroCharacter() + "','" + getPositiveSignCharacter() + "','" +
getNegativeSignCharacter() + "','" + getDecimalPointCharacter() + "','" +
getGroupingStyle() + "," + getGroupingCharacter() + "','" + getGroupingSize() + "'," +
isForcedDecimalPoint() + "'," + isAbsValue() + "]";
}
}