OceanusDecimalLocale.java

/*
 * Oceanus: Java Utilities
 * Copyright 2012-2026. 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 io.github.tonywasher.joceanus.oceanus.decimal;

import io.github.tonywasher.joceanus.oceanus.base.OceanusLocale;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Currency;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * Locale constants.
 */
public class OceanusDecimalLocale {
    /**
     * The dollar.
     */
    private static final String DOLLAR = "$";

    /**
     * The pound.
     */
    private static final String POUND = "£";

    /**
     * The locale.
     */
    private final Locale theLocale;

    /**
     * The currencies map.
     */
    private final Map<String, Currency> theCurrencyMap;

    /**
     * The currency symbols map.
     */
    private final Map<String, String> theSymbolMap;

    /**
     * The grouping size.
     */
    private final int theGroupingSize;

    /**
     * The grouping separator.
     */
    private final String theGrouping;

    /**
     * The minus sign.
     */
    private final char theMinusSign;

    /**
     * The perCent symbol.
     */
    private final char thePerCent;

    /**
     * The perMille symbol.
     */
    private final char thePerMille;

    /**
     * The decimal separator.
     */
    private final String theDecimal;

    /**
     * The money decimal separator.
     */
    private final String theMoneyDecimal;

    /**
     * The default currency.
     */
    private final Currency theCurrency;

    /**
     * Constructor.
     */
    protected OceanusDecimalLocale() {
        /* Use default locale */
        this(OceanusLocale.getDefaultLocale());
    }

    /**
     * Constructor.
     *
     * @param pLocale the locale
     */
    protected OceanusDecimalLocale(final Locale pLocale) {
        /* Default to UK locale if the locale only has a pseudo-currency */
        DecimalFormatSymbols mySymbols = DecimalFormatSymbols.getInstance(pLocale);
        final Currency myCurrency = mySymbols.getCurrency();
        theLocale = myCurrency.getDefaultFractionDigits() == -1
                ? Locale.UK
                : pLocale;

        /* Create currency maps */
        theCurrencyMap = new HashMap<>();
        theSymbolMap = new HashMap<>();

        /* Access decimal formats */
        mySymbols = DecimalFormatSymbols.getInstance(theLocale);
        final DecimalFormat myFormat = (DecimalFormat) NumberFormat.getInstance(theLocale);
        theGroupingSize = myFormat.getGroupingSize();

        /* Access various interesting formats */
        theMinusSign = mySymbols.getMinusSign();
        thePerCent = mySymbols.getPercent();
        thePerMille = mySymbols.getPerMill();
        theGrouping = Character.toString(mySymbols.getGroupingSeparator());
        theDecimal = Character.toString(mySymbols.getDecimalSeparator());
        theMoneyDecimal = Character.toString(mySymbols.getMonetaryDecimalSeparator());

        /* Access the default currency */
        theCurrency = mySymbols.getCurrency();
        final String myCurrSymbol = theCurrency.getSymbol(theLocale);
        declareSymbol(myCurrSymbol, theCurrency);

        /* Declare simplified USD/GBP if possible */
        if (!DOLLAR.equals(myCurrSymbol)) {
            declareSymbol(DOLLAR, Currency.getInstance(Locale.US));
        }
        if (!POUND.equals(myCurrSymbol)) {
            declareSymbol(POUND, Currency.getInstance(Locale.UK));
        }
    }

    /**
     * Obtain the grouping size.
     *
     * @return the size
     */
    protected int getGroupingSize() {
        return theGroupingSize;
    }

    /**
     * Obtain the grouping string.
     *
     * @return the string
     */
    protected String getGrouping() {
        return theGrouping;
    }

    /**
     * Obtain the minus sign.
     *
     * @return the sign
     */
    protected char getMinusSign() {
        return theMinusSign;
    }

    /**
     * Obtain the perCent sign.
     *
     * @return the sign
     */
    protected char getPerCent() {
        return thePerCent;
    }

    /**
     * Obtain the perMille sign.
     *
     * @return the sign
     */
    protected char getPerMille() {
        return thePerMille;
    }

    /**
     * Obtain the decimal string.
     *
     * @return the string
     */
    protected String getDecimal() {
        return theDecimal;
    }

    /**
     * Obtain the grouping string.
     *
     * @return the string
     */
    protected String getMoneyDecimal() {
        return theMoneyDecimal;
    }

    /**
     * Obtain the default currency.
     *
     * @return the currency
     */
    protected Currency getDefaultCurrency() {
        return theCurrency;
    }

    /**
     * Parse currency symbol.
     *
     * @param pSymbol the symbol
     * @return the currency
     * @throws IllegalArgumentException on invalid currency
     */
    protected Currency parseCurrencySymbol(final String pSymbol) {
        /* Look for the currency in the map */
        Currency myCurrency = theCurrencyMap.get(pSymbol);

        /* If this is a new currency */
        if (myCurrency == null) {
            /* Loop through all the currencies */
            for (Currency myCurr : Currency.getAvailableCurrencies()) {
                /* If the symbol matches */
                if (pSymbol.equals(myCurr.getSymbol(theLocale))) {
                    /* Record currency and break the loop */
                    myCurrency = myCurr;
                    declareSymbol(pSymbol, myCurrency);
                    break;
                }
            }

            /* If we did not find a currency */
            if (myCurrency == null) {
                /* Reject the currency */
                throw new IllegalArgumentException("Invalid currency: "
                        + pSymbol);
            }
        }

        /* Return the currency */
        return myCurrency;
    }

    /**
     * Declare symbol.
     *
     * @param pSymbol   the symbol
     * @param pCurrency the currency
     */
    private void declareSymbol(final String pSymbol,
                               final Currency pCurrency) {
        /* Store in currency map */
        theCurrencyMap.put(pSymbol, pCurrency);

        /* Store symbol if not already declared */
        final String myCode = pCurrency.getCurrencyCode();
        theSymbolMap.computeIfAbsent(myCode, c -> pSymbol);
    }

    /**
     * Get currency symbol.
     *
     * @param pCurrency the currency
     * @return the symbol
     */
    protected String getSymbol(final Currency pCurrency) {
        /* Look for the currency in the map */
        final String mySymbol = theSymbolMap.get(pCurrency.getCurrencyCode());
        return mySymbol == null
                ? pCurrency.getSymbol(theLocale)
                : mySymbol;
    }
}