OceanusDecimalFormatter.java
/*******************************************************************************
* Oceanus: Java Utilities
* Copyright 2012,2025 Tony Washer
*
* 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 net.sourceforge.joceanus.oceanus.decimal;
import java.util.Currency;
import java.util.Locale;
/**
* Presentation methods for decimals in a particular locale.
* @author Tony Washer
*/
public class OceanusDecimalFormatter {
/**
* The Buffer length for building decimal strings.
*/
protected static final int INITIAL_BUFLEN = 20;
/**
* The Blank character.
*/
public static final char CHAR_BLANK = ' ';
/**
* The Zero character.
*/
public static final char CHAR_ZERO = '0';
/**
* The Minus character.
*/
public static final char CHAR_MINUS = '-';
/**
* The Group character.
*/
public static final char CHAR_GROUP = ',';
/**
* The Decimal character.
*/
public static final String STR_DEC = ".";
/**
* The Currency separator.
*/
protected static final String STR_CURRSEP = ":";
/**
* The locale.
*/
protected static final OceanusDecimalLocale LOCALE_DEFAULT = new OceanusDecimalLocale();
/**
* The locale.
*/
private OceanusDecimalLocale theLocale;
/**
* Do we use accounting format for monetary values?
*/
private boolean useAccounting;
/**
* Width for accounting format.
*/
private int theAccountingWidth;
/**
* Constructor.
*/
public OceanusDecimalFormatter() {
/* Use default locale */
this(Locale.getDefault());
}
/**
* Constructor.
* @param pLocale the locale
*/
public OceanusDecimalFormatter(final Locale pLocale) {
/* Set the locale */
setLocale(pLocale);
}
/**
* Set the locale.
* @param pLocale the locale
*/
public final void setLocale(final Locale pLocale) {
/* Store the locale */
theLocale = new OceanusDecimalLocale(pLocale);
}
/**
* Set accounting width.
* @param pWidth the accounting width to use
*/
public void setAccountingWidth(final int pWidth) {
/* Set accounting mode on and set the width */
useAccounting = true;
theAccountingWidth = pWidth;
}
/**
* Clear accounting mode.
*/
public void clearAccounting() {
/* Clear the accounting mode flag */
useAccounting = false;
}
/**
* Format a decimal value without reference to locale.
* @param pValue the value to format
* @return the formatted value
*/
protected static String toString(final OceanusDecimal pValue) {
/* Access the value and scale */
long myValue = pValue.unscaledValue();
final int myScale = pValue.scale();
/* handle negative values */
final boolean isNegative = myValue < 0;
if (isNegative) {
myValue = -myValue;
}
/* Format the string */
final StringBuilder myString = new StringBuilder(INITIAL_BUFLEN);
myString.append(myValue);
/* Add leading zeroes */
int myLen = myString.length();
while (myLen < (myScale + 1)) {
myString.insert(0, CHAR_ZERO);
myLen++;
}
/* Insert the decimal into correct position if needed */
if (myScale > 0) {
myString.insert(myLen
- myScale, STR_DEC);
}
/* Add minus sign if required */
if (isNegative) {
myString.insert(0, CHAR_MINUS);
}
/* Return the string */
return myString.toString();
}
/**
* Format a money value with currency code, into a locale independent format.
* @param pValue the value to format
* @return the formatted value
*/
public String toCurrencyString(final OceanusMoney pValue) {
/* Format the basic value */
final StringBuilder myWork = new StringBuilder(toString(pValue));
/* Add the currency symbol */
final Currency myCurrency = pValue.getCurrency();
myWork.insert(0, STR_CURRSEP);
myWork.insert(0, myCurrency.getCurrencyCode());
/* Return the string */
return myWork.toString();
}
/**
* Format a numeric decimal value.
* @param pValue the value to format
* @param pScale the scale of the decimal
* @param pDecSeparator the decimal separator
* @return the formatted value.
*/
private StringBuilder formatDecimal(final long pValue,
final int pScale,
final String pDecSeparator) {
/* Access the value */
long myValue = pValue;
/* Reject negative scales */
if (pScale < 0) {
throw new IllegalArgumentException("Decimals cannot be negative");
}
/* handle negative values */
final boolean isNegative = myValue < 0;
if (isNegative) {
myValue = -myValue;
}
/* Format the string */
final StringBuilder myString = new StringBuilder(INITIAL_BUFLEN);
myString.append(Long.toString(myValue));
/* Add leading zeroes */
int myLen = myString.length();
while (myLen < (pScale + 1)) {
myString.insert(0, CHAR_ZERO);
myLen++;
}
/* If we have decimals */
if (pScale > 0) {
/* Insert decimal point and remove decimals from length */
myString.insert(myLen
- pScale, pDecSeparator);
myLen -= pScale;
}
/* Loop while we need to add grouping */
final int myGroupingSize = theLocale.getGroupingSize();
final String myGrouping = theLocale.getGrouping();
while (myLen > myGroupingSize) {
/* Insert grouping character and remove grouping size from length */
myString.insert(myLen
- myGroupingSize, myGrouping);
myLen -= myGroupingSize;
}
/* Add minus sign if required */
if (isNegative) {
myString.insert(0, theLocale.getMinusSign());
}
/* Return the string */
return myString;
}
/**
* Format a long value.
* @param pValue the value to format
* @return the formatted value.
*/
private StringBuilder formatLong(final long pValue) {
/* Access the value */
long myValue = pValue;
/* handle negative values */
final boolean isNegative = myValue < 0;
if (isNegative) {
myValue = -myValue;
}
/* Format the string */
final StringBuilder myString = new StringBuilder(INITIAL_BUFLEN);
myString.append(Long.toString(myValue));
/* Loop while we need to add grouping */
int myLen = myString.length();
final int myGroupingSize = theLocale.getGroupingSize();
final String myGrouping = theLocale.getGrouping();
while (myLen > myGroupingSize) {
/* Insert grouping character and remove grouping size from length */
myString.insert(myLen
- myGroupingSize, myGrouping);
myLen -= myGroupingSize;
}
/* Add minus sign if required */
if (isNegative) {
myString.insert(0, theLocale.getMinusSign());
}
/* Return the string */
return myString;
}
/**
* Format Money value.
* @param pMoney the value to format
* @return the formatted value
*/
public String formatMoney(final OceanusMoney pMoney) {
/* If we are using accounting and have zero */
if (useAccounting
&& pMoney.isZero()) {
/* Format the zero */
return formatZeroAccounting(pMoney.getCurrency());
}
/* Format the basic value */
final StringBuilder myWork = formatDecimal(pMoney.unscaledValue(), pMoney.scale(), theLocale.getMoneyDecimal());
/* If we have a leading minus sign */
final char myMinus = theLocale.getMinusSign();
final boolean isNegative = myWork.charAt(0) == myMinus;
if (isNegative) {
/* Remove the minus sign */
myWork.deleteCharAt(0);
}
/* If we are using accounting mode */
if (useAccounting) {
/* Format for accounting */
formatForAccounting(myWork);
}
/* Add the currency symbol */
final Currency myCurrency = pMoney.getCurrency();
myWork.insert(0, theLocale.getSymbol(myCurrency));
/* Re-Add the minus sign */
if (isNegative) {
myWork.insert(0, myMinus);
}
/* return the formatted value */
return myWork.toString();
}
/**
* Format Price value.
* @param pPrice the value to format
* @return the formatted value
*/
public String formatPrice(final OceanusPrice pPrice) {
/* return the formatted value */
return formatMoney(pPrice);
}
/**
* Format Rate value.
* @param pRate the value to format
* @return the formatted value
*/
public String formatRate(final OceanusRate pRate) {
/* Format the basic value */
final StringBuilder myWork = formatDecimal(pRate.unscaledValue(), pRate.scale()
- OceanusDecimalParser.ADJUST_PERCENT, theLocale.getDecimal());
/* Append the perCent sign */
myWork.append(theLocale.getPerCent());
/* return the formatted value */
return myWork.toString();
}
/**
* Format Rate value.
* @param pRate the value to format
* @return the formatted value
*/
public String formatRatePerMille(final OceanusRate pRate) {
/* Format the basic value */
final StringBuilder myWork = formatDecimal(pRate.unscaledValue(), pRate.scale()
- OceanusDecimalParser.ADJUST_PERMILLE, theLocale.getDecimal());
/* Append the perMille sign */
myWork.append(theLocale.getPerMille());
/* return the formatted value */
return myWork.toString();
}
/**
* Format Units value.
* @param pUnits the value to format
* @return the formatted value
*/
public String formatUnits(final OceanusUnits pUnits) {
/* Format the basic value */
return formatBasicDecimal(pUnits);
}
/**
* Format Ratio value.
* @param pRatio the value to format
* @return the formatted value
*/
public String formatRatio(final OceanusRatio pRatio) {
/* Format the basic value */
return formatBasicDecimal(pRatio);
}
/**
* Format Decimal value.
* @param pDecimal the value to format
* @return the formatted value
*/
public String formatDecimal(final OceanusDecimal pDecimal) {
/* Split out special cases */
if (pDecimal instanceof OceanusMoney) {
return formatMoney((OceanusMoney) pDecimal);
} else if (pDecimal instanceof OceanusRate) {
return formatRate((OceanusRate) pDecimal);
}
/* return the formatted value */
return formatBasicDecimal(pDecimal);
}
/**
* Format Decimal value.
* @param pDecimal the value to format
* @return the formatted value
*/
private String formatBasicDecimal(final OceanusDecimal pDecimal) {
/* Format the basic value */
final StringBuilder myWork = formatDecimal(pDecimal.unscaledValue(), pDecimal.scale(), theLocale.getDecimal());
/* return the formatted value */
return myWork.toString();
}
/**
* Format for accounting.
* @param pWork the working buffer
*/
private void formatForAccounting(final StringBuilder pWork) {
/* If we are short of the width */
int myLen = pWork.length();
while (myLen < theAccountingWidth) {
/* Prefix with blank */
pWork.insert(0, CHAR_BLANK);
myLen++;
}
}
/**
* Format a Zero for accounting.
* @param pCurrency the currency
* @return the formatted string
*/
private String formatZeroAccounting(final Currency pCurrency) {
/* Determine the scale */
final int myScale = pCurrency.getDefaultFractionDigits();
/* Create a buffer build */
final StringBuilder myWork = new StringBuilder(Character.toString(CHAR_MINUS));
/* If we have decimals */
for (int i = 0; i < myScale; i++) {
/* Add a blank in place of the decimal digit */
myWork.append(CHAR_BLANK);
}
/* If we are short of the width */
int myLen = myWork.length();
while (myLen < theAccountingWidth) {
/* Prefix with blank */
myWork.insert(0, CHAR_BLANK);
myLen++;
}
/* Add the currency symbol */
myWork.insert(0, theLocale.getSymbol(pCurrency));
/* Return the string */
return myWork.toString();
}
/**
* Format Long value.
* @param pValue the value to format
* @return the formatted value
*/
public String formatLong(final Long pValue) {
/* Format the basic value */
final StringBuilder myWork = formatLong(pValue.longValue());
/* return the formatted value */
return myWork.toString();
}
/**
* Format Integer value.
* @param pValue the value to format
* @return the formatted value
*/
public String formatInteger(final Integer pValue) {
/* Format the basic value */
final StringBuilder myWork = formatLong(pValue.longValue());
/* return the formatted value */
return myWork.toString();
}
/**
* Format Short value.
* @param pValue the value to format
* @return the formatted value
*/
public String formatShort(final Short pValue) {
/* Format the basic value */
final StringBuilder myWork = formatLong(pValue.longValue());
/* return the formatted value */
return myWork.toString();
}
}