All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.yadaframework.persistence.YadaMoney Maven / Gradle / Ivy

There is a newer version: 0.7.7.R4
Show newest version
package net.yadaframework.persistence;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

import org.springframework.context.i18n.LocaleContextHolder;

import com.fasterxml.jackson.annotation.JsonProperty;

import net.yadaframework.components.YadaUtil;

/**
 * An amount of money with a 1/10000 precision, stored as a long both in java and in the database.
 * The currency must be stored somewhere else.
 * Immutable value object: math operations create new instances.
 * "The rule of thumb for storage of fixed point decimal values is to store at least one more decimal
 * place than you actually require to allow for rounding."
 * https://stackoverflow.com/q/224462/587641
 * Some world currencies use 3 decimals: http://www.thefinancials.com/Default.aspx?SubSectionID=curformat
 *
 * This class can be stored in the database as a long when converted with YadaMoneyConverter:
 * 
 * @Convert(converter = YadaMoneyConverter.class)
 * private YadaMoney balance;
 * 
*/ public class YadaMoney implements Comparable { // Having this "final" makes everything more complicated private /*final*/ long internalValue; // Amount in 1/10000 of the currency private static final int multiplier = 10000; private YadaUtil yadaUtil = YadaUtil.INSTANCE; // Needed because YadaMoney is not autowired public static final YadaMoney ZERO = new YadaMoney(0); YadaMoney() { this.internalValue = 0; } /** * * @param amount an amount of money with no decimals */ public YadaMoney(int amount) { this((long) amount); } /** * Convert a string with a decimal value that uses the decimal separator of the specified locale * @param amount the decimal value like "12,87" * @param locale the locale to parse the comma separator, like Locale.ITALY * @throws ParseException if the amount contains invalid characters */ public YadaMoney(String amount, Locale locale) throws ParseException { double value = yadaUtil.stringToDouble(amount, locale); this.internalValue = (long)(value * multiplier); } /** * * @param integer amount */ public YadaMoney(long amount) { this.internalValue = amount * multiplier; } /** * * @param doubleValue */ public YadaMoney(double doubleValue) { this.internalValue = (long) (doubleValue * multiplier); } // /** // * Creates a YadaMoney by setting the internal value directly // * @param internalValue // */ // // Package accessibility because it is implementation-dependent. // // Need to pass a BigDecimal so that it can be different from the public long version. // YadaMoney(BigDecimal internalValue) { // this.internalValue = internalValue.longValue(); // } /** * Create a YadaMoney from the value stored in the database * @param dbValue * @return */ public static YadaMoney fromDatabaseColumn(Long dbValue) { YadaMoney result = new YadaMoney(); result.internalValue = dbValue==null?0:dbValue; return result; } /** * Returns true if the current value is equal to the amount specified or higher * @param amount a double with an optional decimal part * @param locale used for the decimal separator * @return * @throws ParseException */ public boolean isAtLeast(String amount, Locale locale) throws ParseException { double value = yadaUtil.stringToDouble(amount, locale); return this.internalValue >= value * multiplier; } /** * Returns true if the current value is equal to the amount specified or higher * @param amount * @return */ public boolean isAtLeast(int amount) { return this.internalValue >= amount * multiplier; } /** * Returns a new instance with the positive value of the current value * @return a positive value */ public YadaMoney getAbsolute() { YadaMoney result = new YadaMoney(); result.internalValue = Math.abs(this.internalValue); return result; } /** * Returns a new instance with the positive value of the current value. * Same as getAbsolute() * @return a positive value * @see YadaMoney#getAbsolute() */ public YadaMoney getPositive() { return getAbsolute(); } /** * Returns a new instance with the negative value of the current value. * If the current value was already negative, it stays negative. * @return a negative value */ public YadaMoney getNegative() { YadaMoney result = new YadaMoney(); result.internalValue = - Math.abs(this.internalValue); return result; } /** * Returns a new instance with the negated (inverted) value of the current value. * It will be positive if the current value is negative, it will be negative if the current value is positive. * @return a positive value when this was negative, a negative value when this was positive */ public YadaMoney getNegated() { YadaMoney result = new YadaMoney(); result.internalValue = - this.internalValue; return result; } /** * Returns true if the value is zero * @return */ public boolean isZero() { return this.internalValue == 0; } /** * Returns true if the value is lower than zero * @return */ public boolean isNegative() { return this.internalValue<0; } /** * Returns true if the value is greater than zero * @return */ public boolean isPositive() { return this.internalValue>0; } public YadaMoney getSum(long cents) { YadaMoney result = new YadaMoney(); result.internalValue = this.internalValue + (cents * multiplier / 100); return result; } /** * Return a new YadaMoney where the value is the sum of the current value and the argument. * The original object is not changed. * @param toAdd amount to add * @return a new instance of YadaMoney */ public YadaMoney getSum(YadaMoney toAdd) { YadaMoney result = new YadaMoney(); result.internalValue = this.internalValue + toAdd.internalValue; return result; } /** * Return a new YadaMoney where the value is the subtraction of the current value and the argument. * The original object is not changed. * @param toRemove * @return a new instance of YadaMoney */ public YadaMoney getSubtract(YadaMoney toRemove) { YadaMoney result = new YadaMoney(); result.internalValue = this.internalValue - toRemove.internalValue; return result; } /** * Return a new YadaMoney where the value is the division of the current value by the argument. * The original object is not changed. * @param factor * @return a new instance of YadaMoney */ public YadaMoney getDivide(double factor) { YadaMoney result = new YadaMoney(); result.internalValue = (long) (this.internalValue / factor); return result; } /** * Return a new YadaMoney where the value is the multiplication of the current value by the argument. * The original object is not changed. * @param factor * @return a new instance of YadaMoney */ public YadaMoney getMultiply(double factor) { YadaMoney result = new YadaMoney(); result.internalValue = (long) (this.internalValue * factor); return result; } /** * Returns the value with N decimal places * @param decimals the number of decimal places * @return */ public double getRoundValue(int decimals) { double toKeep = Math.pow(10, decimals); // 100 for 2 decimals long rounded = Math.round(internalValue*toKeep/multiplier); // Round to the nearest. TODO see https://en.wikipedia.org/wiki/Rounding#Round_half_away_from_zero return rounded/toKeep; } /** * Returns the value with 2 decimal places * @return */ public double getRoundValue() { return getRoundValue(2); } /** * Convert to a string with 2 decimal places */ @Override @JsonProperty("value") public String toString() { return toString(LocaleContextHolder.getLocale()); // // Decimal formats are generally not synchronized // Locale locale = LocaleContextHolder.getLocale(); // NumberFormat formatter = NumberFormat.getNumberInstance(locale); // if (formatter instanceof DecimalFormat) { // ((DecimalFormat) formatter).applyPattern("#0.00"); // } // return formatter.format(getRoundValue()); } /** * Convert to a string with no decimal places (truncated) */ public String toIntString() { return Long.toString(internalValue/multiplier); } /** * Convert to a string with 2 decimal places */ public String toString(Locale locale) { // Decimal formats are generally not synchronized DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance(locale); formatter.applyPattern("0.00"); return formatter.format(getRoundValue()); } @Override public int hashCode() { return Long.hashCode(internalValue); } @Override public boolean equals(Object obj) { return obj instanceof YadaMoney && ((YadaMoney)obj).internalValue == internalValue; } @Override protected Object clone() throws CloneNotSupportedException { YadaMoney yadaMoney = new YadaMoney(this.internalValue); return yadaMoney; } /** * Package visibility to be used by {@link YadaMoneyConverter} * @return */ Long getInternalValue() { return this.internalValue; } @Override public int compareTo(YadaMoney other) { return (this.internalValue < other.internalValue) ? -1 : ((this.internalValue == other.internalValue) ? 0 : 1); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy