package co.smartreceipts.android.model; import android.support.annotation.NonNull; import android.support.annotation.VisibleForTesting; import com.google.common.base.Preconditions; import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.Currency; import java.util.Locale; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import co.smartreceipts.android.model.utils.ModelUtils; import co.smartreceipts.android.utils.log.Logger; public final class PriceCurrency { @Deprecated public static final PriceCurrency MISSING_CURRENCY = new PriceCurrency("NUL"); @Deprecated public static final PriceCurrency MIXED_CURRENCY = new PriceCurrency("MIXED"); private static final Map<String, PriceCurrency> sCurrencyMap = new ConcurrentHashMap<>(); private final String mCurrencyCode; private Currency mCurrency; // Saved to reduce Memory Allocs for heavy calls private final Map<Integer, NumberFormat> numberFormatCache = new ConcurrentHashMap<>(); @NonNull public static PriceCurrency getInstance(@NonNull String currencyCode) { // Note: I'm not concerned if we have a few duplicate entries (ie this isn't fully thread safe) as the objects are all equal PriceCurrency priceCurrency = sCurrencyMap.get(currencyCode); if (priceCurrency != null) { return priceCurrency; } else { priceCurrency = new PriceCurrency(currencyCode); sCurrencyMap.put(currencyCode, priceCurrency); return priceCurrency; } } @NonNull public static PriceCurrency getDefaultCurrency() { try { return PriceCurrency.getInstance(Currency.getInstance(Locale.getDefault()).getCurrencyCode()); } catch (IllegalArgumentException e) { Logger.warn(PriceCurrency.class, "Unable to find a default currency, since the device has an unsupported ISO 3166 locale. Returning USD instead"); return PriceCurrency.getInstance("USD"); } } private PriceCurrency(@NonNull String currencyCode) { this.mCurrencyCode = Preconditions.checkNotNull(currencyCode); try { mCurrency = Currency.getInstance(currencyCode); } catch (IllegalArgumentException e) { Logger.warn(this, "Unknown system currency code requested: {}. Handling this internally", currencyCode); } } @NonNull public final String getCurrencyCode() { if (mCurrency != null) { return mCurrency.getCurrencyCode(); } else { return mCurrencyCode; } } @NonNull public final String format(@NonNull BigDecimal price, int decimalPrecision) { try { if (mCurrency != null) { NumberFormat numberFormat = numberFormatCache.get(decimalPrecision); if (numberFormat == null) { numberFormat = NumberFormat.getCurrencyInstance(Locale.getDefault()); numberFormat.setCurrency(mCurrency); numberFormat.setMaximumFractionDigits(decimalPrecision); numberFormat.setMinimumFractionDigits(decimalPrecision); numberFormat.setGroupingUsed(false); numberFormatCache.put(decimalPrecision, numberFormat); } return numberFormat.format(price.doubleValue()); } else { return mCurrencyCode + ModelUtils.getDecimalFormattedValue(price); } } catch (NumberFormatException e) { return "$0.00"; } } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PriceCurrency)) return false; PriceCurrency that = (PriceCurrency) o; return mCurrencyCode.equals(that.mCurrencyCode); } @Override public int hashCode() { return mCurrencyCode.hashCode(); } @VisibleForTesting public static void clearStaticCachesForTesting() { sCurrencyMap.clear(); } }