| 68 | 1 /* | 
|  | 2 Copyright (c) 2008  Franklin Schmidt <fschmidt@gmail.com> | 
|  | 3 | 
|  | 4 Permission is hereby granted, free of charge, to any person obtaining a copy | 
|  | 5 of this software and associated documentation files (the "Software"), to deal | 
|  | 6 in the Software without restriction, including without limitation the rights | 
|  | 7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
|  | 8 copies of the Software, and to permit persons to whom the Software is | 
|  | 9 furnished to do so, subject to the following conditions: | 
|  | 10 | 
|  | 11 The above copyright notice and this permission notice shall be included in | 
|  | 12 all copies or substantial portions of the Software. | 
|  | 13 | 
|  | 14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
|  | 15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
|  | 16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
|  | 17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
|  | 18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
|  | 19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
|  | 20 THE SOFTWARE. | 
|  | 21 */ | 
|  | 22 | 
|  | 23 package fschmidt.util.java; | 
|  | 24 | 
|  | 25 import java.io.Externalizable; | 
|  | 26 import java.io.ObjectInput; | 
|  | 27 import java.io.IOException; | 
|  | 28 import java.io.ObjectOutput; | 
|  | 29 import java.io.ObjectInputStream; | 
|  | 30 import java.sql.PreparedStatement; | 
|  | 31 import java.sql.SQLException; | 
|  | 32 import java.sql.ResultSet; | 
|  | 33 import java.math.BigDecimal; | 
|  | 34 import java.util.regex.Pattern; | 
|  | 35 import java.util.regex.Matcher; | 
|  | 36 import fschmidt.db.DbStorable; | 
|  | 37 | 
|  | 38 | 
|  | 39 public final class Money implements Comparable, Externalizable, DbStorable { | 
|  | 40 	private static final int currentVersion = 0; | 
|  | 41 	private static final long serialVersionUID = 0L; | 
|  | 42 | 
|  | 43 	private /*final*/ long cents; | 
|  | 44 | 
|  | 45 	public static Money read(ObjectInput in) | 
|  | 46 		throws IOException | 
|  | 47 	{ | 
|  | 48 		if( !in.readBoolean() ) | 
|  | 49 			return null; | 
|  | 50 		return new Money(in.readLong()); | 
|  | 51 	} | 
|  | 52 | 
|  | 53 	public static void write(ObjectOutput out,Money m) | 
|  | 54 		throws IOException | 
|  | 55 	{ | 
|  | 56 		if( m==null ) { | 
|  | 57 			out.writeBoolean(false); | 
|  | 58 			return; | 
|  | 59 		} | 
|  | 60 		out.writeBoolean(true); | 
|  | 61 		out.writeLong(m.cents); | 
|  | 62 	} | 
|  | 63 | 
|  | 64 	public void writeExternal(ObjectOutput out) | 
|  | 65 		throws IOException | 
|  | 66 	{ | 
|  | 67 		out.writeInt(currentVersion); | 
|  | 68 		out.writeLong(cents); | 
|  | 69 	} | 
|  | 70 | 
|  | 71 	public void readExternal(ObjectInput in) | 
|  | 72 		throws IOException, ClassNotFoundException | 
|  | 73 	{ | 
|  | 74 		int ver = in.readInt(); | 
|  | 75 		switch(ver) { | 
|  | 76 		case 0: | 
|  | 77 			cents = in.readLong(); | 
|  | 78 			break; | 
|  | 79 		default: | 
|  | 80 			throw new RuntimeException(); | 
|  | 81 		} | 
|  | 82 	} | 
|  | 83 | 
|  | 84 	public Money() {}  // for Externalizable | 
|  | 85 | 
|  | 86 	public Money(ObjectInput in) | 
|  | 87 		throws IOException, ClassNotFoundException | 
|  | 88 	{ | 
|  | 89 		cents = in.readInt(); | 
|  | 90 	} | 
|  | 91 | 
|  | 92 	public Money(long cents) { | 
|  | 93 		this.cents = cents; | 
|  | 94 	} | 
|  | 95 | 
|  | 96 	private static final Pattern p = Pattern.compile(" *-?\\$? *[0-9,.]+ *"); | 
|  | 97 	private static final Pattern pSub = Pattern.compile("[ \\$,]"); | 
|  | 98 | 
|  | 99 	public Money(String val) | 
|  | 100 		throws NumberFormatException | 
|  | 101 	{ | 
|  | 102 		if( !p.matcher(val).matches() ) | 
|  | 103 			throw new NumberFormatException("invalid format for money: '"+val+"'"); | 
|  | 104 		Matcher mSub = pSub.matcher(val); | 
|  | 105 		val =  pSub.matcher(val).replaceAll(""); | 
|  | 106 		cents = Math.round(Float.parseFloat(val)*100); | 
|  | 107 	} | 
|  | 108 | 
|  | 109 	public Money(double d) { | 
|  | 110 		cents = (long)Math.round( (d *1000) / 10); | 
|  | 111 	} | 
|  | 112 | 
|  | 113 	public void setField(PreparedStatement stmt,int idx) | 
|  | 114 		throws SQLException | 
|  | 115 	{ | 
|  | 116 		stmt.setBigDecimal(idx,toBigDecimal()); | 
|  | 117 	} | 
|  | 118 | 
|  | 119 	public static void setMoneyNull(PreparedStatement stmt,int idx) | 
|  | 120 		throws SQLException | 
|  | 121 	{ | 
|  | 122 		stmt.setNull(idx,java.sql.Types.DECIMAL); | 
|  | 123 	} | 
|  | 124 | 
|  | 125 	public static void setMoney(PreparedStatement stmt,int idx,Money m) | 
|  | 126 		throws SQLException | 
|  | 127 	{ | 
|  | 128 		if( m==null ) { | 
|  | 129 			setMoneyNull(stmt,idx); | 
|  | 130 		} else { | 
|  | 131 			m.setField(stmt,idx); | 
|  | 132 		} | 
|  | 133 	} | 
|  | 134 | 
|  | 135 	public static Money getMoney(ResultSet rs,String columnName) | 
|  | 136 		throws SQLException | 
|  | 137 	{ | 
|  | 138 		Money m = new Money(rs,columnName); | 
|  | 139 		return rs.wasNull() ? null : m; | 
|  | 140 	} | 
|  | 141 | 
|  | 142 	private Money(ResultSet rs,String columnName) | 
|  | 143 		throws SQLException | 
|  | 144 	{ | 
|  | 145 		cents = Math.round(rs.getFloat(columnName)*100); | 
|  | 146 	} | 
|  | 147 | 
|  | 148 	public boolean equals(Object obj) { | 
|  | 149 		if( obj==null || !(obj instanceof Money) ) | 
|  | 150 			return false; | 
|  | 151 		Money m = (Money)obj; | 
|  | 152 		return cents==m.cents; | 
|  | 153 	} | 
|  | 154 | 
|  | 155 	public int compareTo(Money val) { | 
|  | 156 		return val != null ? | 
|  | 157 				(cents < val.cents ? -1 : cents==val.cents ? 0 : 1) : | 
|  | 158 				0; | 
|  | 159 	} | 
|  | 160 | 
|  | 161 	public int compareTo(Object obj) { | 
|  | 162 		return compareTo( (Money)obj ); | 
|  | 163 	} | 
|  | 164 | 
|  | 165 	public int hashCode() { | 
|  | 166 		return (int)cents; | 
|  | 167 	} | 
|  | 168 | 
|  | 169 	public String toString() { | 
|  | 170 		return toString("$"); | 
|  | 171 	} | 
|  | 172 | 
|  | 173 	public String toString(String currency) { | 
|  | 174 		StringBuilder buf = new StringBuilder(); | 
|  | 175 		buf.append( cents<0 ? -cents : cents ); | 
|  | 176 		while( buf.length() < 3 ) | 
|  | 177 			buf.insert(0,'0'); | 
|  | 178 		buf.insert(buf.length()-2,'.'); | 
|  | 179 		int start = buf.charAt(0)=='-' ? 1 : 0; | 
|  | 180 		for( int i=buf.length()-6; i>start; i-=3 ) | 
|  | 181 			buf.insert(i,','); | 
|  | 182 		buf.insert(0,currency); | 
|  | 183 		if( cents < 0 ) | 
|  | 184 			buf.insert(0,'-'); | 
|  | 185 		return buf.toString(); | 
|  | 186 	} | 
|  | 187 | 
|  | 188 	public String toSimpleString() { | 
|  | 189 		StringBuilder buf = new StringBuilder(); | 
|  | 190 		buf.append(cents); | 
|  | 191 		while( buf.length() < 3 ) | 
|  | 192 			buf.insert(0,'0'); | 
|  | 193 		buf.insert(buf.length()-2,'.'); | 
|  | 194 | 
|  | 195 		return buf.toString(); | 
|  | 196 	} | 
|  | 197 | 
|  | 198 	public boolean isZero() { | 
|  | 199 		return cents==0; | 
|  | 200 	} | 
|  | 201 | 
|  | 202 	public Money min(Money val) { | 
|  | 203 		return cents < val.cents ? this : val; | 
|  | 204 	} | 
|  | 205 | 
|  | 206 	public Money max(Money val) { | 
|  | 207 		return cents > val.cents ? this : val; | 
|  | 208 	} | 
|  | 209 | 
|  | 210 	public Money add(Money val) { | 
|  | 211 		return new Money(cents+val.cents); | 
|  | 212 	} | 
|  | 213 | 
|  | 214 	public Money subtract(Money val) { | 
|  | 215 		return new Money(cents-val.cents); | 
|  | 216 	} | 
|  | 217 | 
|  | 218 	public Money multiply(int val) { | 
|  | 219 		return new Money(cents*val); | 
|  | 220 	} | 
|  | 221 | 
|  | 222 	public Money multiply(float val) { | 
|  | 223 		return new Money(Math.round(cents*val)); | 
|  | 224 	} | 
|  | 225 | 
|  | 226 	public Money multiply(double val) { | 
|  | 227 		return new Money((long)Math.round(cents*val)); | 
|  | 228 	} | 
|  | 229 | 
|  | 230 	public double divide(Money val) { | 
|  | 231 		if ((double)val.cents == 0) { | 
|  | 232 		    return Double.NaN; | 
|  | 233 		} | 
|  | 234 		return (double)cents/(double)val.cents; | 
|  | 235 	} | 
|  | 236 | 
|  | 237 	public Money divide(int val) { | 
|  | 238 		return new Money(cents/val); | 
|  | 239 	} | 
|  | 240 | 
|  | 241 	public Money divideRoundingUp(int val) { | 
|  | 242 		return new Money((cents+val-1)/val); | 
|  | 243 	} | 
|  | 244 | 
|  | 245 	public Money divide(float val) { | 
|  | 246 		return new Money(Math.round(cents/val)); | 
|  | 247 	} | 
|  | 248 | 
|  | 249 	public Money divide(double val) { | 
|  | 250 		return new Money((long)Math.round(cents/val)); | 
|  | 251 	} | 
|  | 252 | 
|  | 253 	public Money inc() { | 
|  | 254 		return new Money(cents+1); | 
|  | 255 	} | 
|  | 256 | 
|  | 257 	public Money dec() { | 
|  | 258 		return new Money(cents-1); | 
|  | 259 	} | 
|  | 260 | 
|  | 261 	public Money avg(Money val) { | 
|  | 262 		return new Money((cents+val.cents)/2); | 
|  | 263 	} | 
|  | 264 | 
|  | 265 	public double doubleValue() { | 
|  | 266 		return cents/100.0; | 
|  | 267 	} | 
|  | 268 | 
|  | 269 	public BigDecimal toBigDecimal() { | 
|  | 270 		return BigDecimal.valueOf(cents,2); | 
|  | 271 	} | 
|  | 272 | 
|  | 273 	public long getCents() { | 
|  | 274 		return cents; | 
|  | 275 	} | 
|  | 276 | 
|  | 277 | 
|  | 278 	public static final Money ZERO = new Money(0); | 
|  | 279 	public static final Money MAX_VALUE = new Money(Integer.MAX_VALUE); | 
|  | 280 	public static final Money MIN_VALUE = new Money(Integer.MIN_VALUE); | 
|  | 281 | 
|  | 282 	private void readObject(ObjectInputStream stream) | 
|  | 283 		throws IOException, ClassNotFoundException | 
|  | 284 	{ | 
|  | 285 		stream.defaultReadObject(); | 
|  | 286 	} | 
|  | 287 | 
|  | 288 	public static void main(String[] args) { | 
|  | 289 		System.out.println(new Money("z$1,234.56 ")); | 
|  | 290 	} | 
|  | 291 | 
|  | 292 } |