package org.jugile.util;
/*
Copyright (C) 2007-2011 Jukka Rahkonen email: jukka.rahkonen@iki.fi
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
import java.io.Serializable;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/**
* "No man can serve two masters: for either he will hate the one, and love the other;
* or else he will hold to the one, and despise the other.
* Ye cannot serve God and mammon." (Math. 6:24)
*
* ==========
*
* Immutable Money class for all money type handling consistently.
*
* Money should not be any kind of decimal number. No BigDecimal or any other
* floating point number. Money should be long integer, representing cents.
* Sometimes prices can have even fragments of cents and they can be presented
* as BigDecimal. Money is an accountable amount of money that can be part of
* money transaction. (RAHKOJUK)
*
* Btw, BigDecimal is buggy. (2009). Try how long does it take to run this simple loop:
* BigDecimal bd = new BigDecimal("1.0"); for (int i = 0; i < 1000; i++) { bd =
* bd.multiply(bd); } System.out.println("result: " + bd); (RAHKOJUK)
*
* @author jukka.rahkonen@iki.fi
*/
public final class Money extends Jugile implements Serializable, Comparable<Money> {
private static final long serialVersionUID = 1L;
private static final Locale LOCALE_FI = new Locale("fi", "FI");
public enum Currency {
EUR, FIM,
}
private final long cents; // immutable
private final Currency currency; // immutable
public Money(long cents) {
this.cents = cents;
this.currency = Currency.EUR;
}
public Money(long cents, Currency c) {
this.cents = cents;
this.currency = c;
}
public Money(double d) {
this.cents = toCents(d);
this.currency = Currency.EUR;
}
public Money(double d, Currency c) {
this.cents = toCents(d);
this.currency = c;
}
public Money(String str) {
cents = parse(str);
currency = Currency.EUR;
}
public Money(String str, Currency c) {
cents = parse(str);
currency = c;
}
public Money(BigDecimal bd) {
if (bd == null)
cents = 0;
else
cents = toCents(bd.doubleValue());
currency = Currency.EUR;
}
public Money(BigDecimal bd, Currency c) {
if (bd == null)
cents = 0;
else
cents = toCents(bd.doubleValue());
currency = c;
}
public long getCents() {
return cents;
}
public Currency getCurrency() {
return currency;
}
public BigDecimal getBigDecimal() {
BigDecimal c = new BigDecimal(cents);
return c.divide(new BigDecimal(100));
}
/**
* Get the integer value without cents. eg. 1,55 euros return 1.
*/
public int getInteger() {
return (int)cents/100;
}
/**
* Get rounded value to nearest integer without cents. eg. 1,49 euros return
* 1.
*/
public int getRoundedInteger() {
return (int) Math.round(nextUp(cents / 100.0));
}
public String toString() {
if (cents == 0)
return "0,00";
DecimalFormatSymbols symbols = new DecimalFormatSymbols(LOCALE_FI);
symbols.setGroupingSeparator(' ');
DecimalFormat f = new DecimalFormat("#,##0.00", symbols);
f.setDecimalSeparatorAlwaysShown(true);
f.setMaximumFractionDigits(2);
f.setMinimumFractionDigits(2);
f.setMinimumIntegerDigits(1);
return f.format(cents / 100.0);
}
public String toStringIntegerValueWithCurrency() {
String c = "€";
if (currency == Currency.FIM)
c = "mk";
return getInteger()+ " " + c;
}
public String toStringWithCurrency() {
String c = "€";
if (currency == Currency.FIM)
c = "mk";
return toString() + " " + c;
}
private long parse(String str) {
if (str == null || str.trim().length() == 0) return 0;
// remove all whites
str = str.trim();
str = str.replaceAll("\\s", "");
str = str.replaceAll(" ", ""); // some other white space (utf8?)
str = str.replaceAll(",", ".");
double d = Double.parseDouble(str);
return toCents(d);
}
/**
* Does the correct rounding for positive double values to cents.
*/
public static long toCents(double d) {
// nextUp is needed for getting 1.005 correctly rounded to 1.01
return Math.round(nextUp(d * 100.0));
}
public Money plus(Money m) {
assertSameCurrency(m);
return new Money(cents + m.cents, currency);
}
public Money minus(Money m) {
assertSameCurrency(m);
return new Money(cents - m.cents, currency);
}
public Money neg() {
return new Money(-this.cents, currency);
}
public int compareTo(Money m) {
assertSameCurrency(m);
return (new Long(cents)).compareTo(m.cents);
}
public Money mult(double d) {
return new Money(toCents((cents / 100.0) * d), currency);
}
public Money divBy(double d) {
if (d == 0)
throw new RuntimeException("divided by zero");
return new Money(toCents((cents / 100.0) / d), currency);
}
/**
* Common case for dividing Money to some parts where rounded amounts of
* parts must equals to original sum.
*
* @param numberOfParts
* number of parts to divide to.
* @return List of Money parts. Last one has rounding correction (modulo).
*/
public List<Money> divToParts(int numberOfParts) {
if (numberOfParts == 0)
throw new RuntimeException("divided by zero");
List<Money> res = new ArrayList<Money>();
Money a = divBy(numberOfParts);
long modulo = cents - numberOfParts * a.cents;
for (int i = 0; i < numberOfParts; i++) {
Money m = new Money(a.cents);
if (i == (numberOfParts - 1))
m = new Money(a.cents + modulo);
res.add(m);
}
return res;
}
/**
* @param d
* @return next double towards positive infinity
*/
private static double nextUp(double d) {
if (Double.isNaN(d) || d == (1.0D / 0.0D)) {
return d;
} else {
d += 0.0D;
return Double.longBitsToDouble(Double.doubleToRawLongBits(d)
+ (d < 0.0D ? -1L : 1L));
}
}
private void assertSameCurrency(Money m) {
if (m.currency != currency)
throw new RuntimeException("currency mismatch");
}
public boolean equals(Money m) {
if (m == null) return false;
return m.cents == cents;
}
}