001 package data.ooimpl; 002 003 import java.util.Iterator; 004 005 import data.AbstractCurrency; 006 import data.Catalog; 007 import data.CatalogItem; 008 import data.CatalogItemValue; 009 import data.Currency; 010 import data.DataBasket; 011 import data.DataBasketConflictException; 012 import data.IntegerValue; 013 import data.MoneyBag; 014 import data.NotEnoughMoneyException; 015 import data.NumberValue; 016 import data.Value; 017 import data.events.VetoException; 018 019 /** 020 * Pure Java implementation of the {@link MoneyBag} interface. 021 * 022 * @author Steffen Zschaler 023 * @version 2.0 19/08/1999 024 * @since v2.0 025 */ 026 public class MoneyBagImpl extends CountingStockImpl<StockItemImpl, CurrencyItemImpl> implements MoneyBag { 027 028 /** 029 * ID for serialization. 030 */ 031 private static final long serialVersionUID = 8541409273318233452L; 032 033 /** 034 * Create a new MoneyBagImpl. 035 * 036 * @param sName the name of the MoneyBag. 037 * @param ci the Currency associated to the MoneyBag. 038 * @deprecated As of version 3.0, replaced by MoneyBagImpl (String sName, AbstractCurrency ac). 039 */ 040 /* public MoneyBagImpl(String sName, CurrencyImpl ci) { 041 super(sName, ci); 042 }*/ 043 044 /** 045 * Create a new MoneyBagImpl. 046 * 047 * @param sName the name of the MoneyBag. 048 * @param ac the Currency associated to the MoneyBag. 049 */ 050 public MoneyBagImpl(String sName, AbstractCurrency ac) { 051 super(sName, ac); 052 } 053 054 public MoneyBagImpl(String sName, Catalog c) { 055 super(sName, (AbstractCurrency) c); 056 } 057 058 /** 059 * Tries to transfer money from this DataBasket into another one. 060 * @param mbDest the MoneyBag to transfer the money to 061 * @param db a transaction DataBasket 062 * @param nvAmount the amount of money to transfer 063 */ 064 public void transferMoney(MoneyBag mbDest, DataBasket db, NumberValue nvAmount) { 065 synchronized (getItemsLock()) { 066 067 if (nvAmount.isAddZero()) { 068 return; 069 } 070 071 NumberValue nvNull = (NumberValue)nvAmount.subtract(nvAmount); 072 NumberValue currentValue = (NumberValue)sumStock(null, CatalogItemValue.EVALUATE_OFFER, 073 (NumberValue)nvNull.clone()); 074 //not enough change, throw an exception 075 if (currentValue.compareTo(nvAmount) < 0) { 076 throw new NotEnoughMoneyException("Not enough money", 077 NotEnoughMoneyException.NOT_ENOUGH_MONEY); 078 } 079 080 CatalogItem ciGreatest = nextLowerUnit(null); 081 NumberValue vGreatest = null; 082 083 int iResult = nextUnit(this, mbDest, db, nvAmount, vGreatest); 084 085 Value vNotUsable = (NumberValue)nvNull.clone(); 086 //if result < 0 we cannot use the highest available currency item, we have to try the 087 //next smaller one instead, if next smaller currency item is also not possible, try even 088 //next smaller one. Repeat until solution is found or it is sure, that no solution exists 089 while (iResult < 0 && ciGreatest != null) { 090 //vNotUsable is the amount of money that cannot be taken into account, because the 091 //belonging currency items cannot be exchanged (they are too big) 092 vNotUsable = vNotUsable.add(ciGreatest.getValue(). 093 multiply((countItems(ciGreatest.getName(), null)))); 094 //vStillUsable is the money in the moneybag which must be taken into consideration for 095 //computing the change. 096 //vStillUsable = currentValue (=the whole moneybag's value) - vNotUsable 097 Value vStillUsable = currentValue.subtract(vNotUsable); 098 //v = vStillUsable - nvAmount (the money to be exchanged) 099 Value v = vStillUsable.subtract(nvAmount); 100 //if v < 0 we can stop here, because there is not enough money left that could be used for exchange 101 if (v.compareTo((Value)nvNull.clone()) < 0) { 102 throw new NotEnoughMoneyException("No fitting units", 103 NotEnoughMoneyException.NO_FITTING_UNITS); 104 } 105 //otherwise try to compute a solution 106 iResult = nextUnit(this, mbDest, db, nvAmount, ciGreatest.getValue()); 107 ciGreatest = nextLowerUnit(ciGreatest.getValue()); 108 } 109 110 if (iResult < 0 && ciGreatest == null) { 111 throw new NotEnoughMoneyException("No fitting units", 112 NotEnoughMoneyException.NO_FITTING_UNITS); 113 } 114 } 115 } 116 117 /** 118 * Searches the optimal way to return the change. 119 * 120 * @param mbSrc the source MoneyBag from which money is removed 121 * @param mbDest the destination MoneyBag to which money is added 122 * @param vToChange the amount of money to change 123 * @param vLimit the maximum value of currency units to be considered 124 * 125 * @return an NumberValue indicating success (0) or failure (-1). 126 */ 127 private int nextUnit(MoneyBag mbSrc, MoneyBag mbDest, DataBasket db, Value vToChange, Value vLimit) { 128 //determine value of greatest possible currency unit 129 CatalogItem ciGreatest = nextLowerUnit(vLimit); 130 if (ciGreatest == null) { 131 return -1; 132 } 133 Value vGreatest = ciGreatest.getValue(); 134 //subtract from the money to change 135 NumberValue vDiff = (NumberValue)vToChange.subtract(vGreatest); 136 137 //we subtracted too much, go back one step and indicate an error 138 if (vDiff.isLessZero()) { 139 return -1; 140 } 141 //we found a solution, update MoneyBags and indicate success (return "0") to calling level 142 if (vDiff.isAddZero()) { 143 try { 144 mbSrc.remove(ciGreatest.getName(), 1, db); 145 mbDest.add(ciGreatest.getName(), 1, db); 146 } 147 catch (VetoException ex) { 148 ex.printStackTrace(); 149 } 150 return 0; 151 } 152 //there is money left to change, this requires further examination 153 if (vDiff.isGreaterZero()) { 154 int iResult = 1; 155 //create a temporary DataBasket if none exists, otherwise use the existing db 156 DataBasket dbTemp = (db == null) ? new DataBasketImpl() : db; 157 //update MoneyBags 158 try { 159 mbDest.add(ciGreatest.getName(), 1, dbTemp); 160 mbSrc.remove(ciGreatest.getName(), 1, dbTemp); 161 } 162 catch (VetoException ex) { 163 ex.printStackTrace(); 164 } 165 166 //initialize values for further recursion 167 CatalogItem ciMax = nextLowerUnit(null); 168 Value vMax = null; 169 //call nextUnit and check if we can still subtract the maximum currency unit 170 //(as we already know a maximum currency unit, initializing vMax (see line above) with 171 //vGreatest would seem better, but method nextUnit() starts with calling nextLowerUnit 172 //which would decrease vMax (and vGreatest), even if it was possible use vGreatest once again. 173 iResult = nextUnit(mbSrc, mbDest, db, vDiff, vMax); 174 //subtracted too much during recursion? 175 while (iResult < 0 && ciMax != null) { 176 //try again with next currency unit 177 iResult = nextUnit(mbSrc, mbDest, db, vDiff, ciMax.getValue()); 178 ciMax = nextLowerUnit(ciMax.getValue()); 179 } 180 //if all available currency units have been checked without success, we have to 181 //step back one level indicating a failure 182 if (iResult < 0 && ciMax == null) { 183 dbTemp.rollback(); 184 return -1; 185 } 186 187 //subtraction lead to 0 (all change could be returned)? Then we are done. Return 188 //success to caller. 189 if (iResult == 0) { 190 //if there was no DataBasket passed, we have to commit 191 //if a DataBasket has been passed, we cannot commit here (otherwise one could not 192 //rollback it later) 193 if (db == null) { 194 dbTemp.commit(); 195 } 196 return 0; 197 } 198 199 } 200 //never executed 201 return 0; 202 } 203 204 /** 205 * Helper method for {@link getChange}. Searches and returns the biggest currency unit in this 206 * MoneyBag which is worth less than <code>vLimit</code>. If there is no matching currency unit, 207 * <code>null</code> is returnded. 208 * 209 * @param vLimit the limit which the returned currency unit must not exceed. 210 * @return a CatalogItem that represents the greatest available currency unit which does not exceed 211 * <code>vLimit</code> 212 */ 213 private CatalogItem nextLowerUnit(Value vLimit) { 214 CatalogItem cGreatest = null; 215 //iterate over source catalog 216 for (Iterator<CurrencyItemImpl> it = getCatalog(null).iterator(null, false); it.hasNext();) { 217 CatalogItem ci = it.next(); 218 Value vCurrency = ci.getValue(); 219 //if a catalog value greater than the current one (but less than the limit) is found... 220 if ((cGreatest == null || vCurrency.compareTo(cGreatest.getValue()) > 0 ) && 221 (vLimit == null || vCurrency.compareTo(vLimit) < 0)) { 222 //... and if the moneybag really contains that bank note/coin... 223 if (get(ci.getName(), null, false).hasNext()) { 224 //...make it the new greatest one 225 cGreatest = ci; 226 } 227 } 228 } 229 return cGreatest; 230 } 231 232 /** 233 * Return a String representation of the MoneyBag. 234 * 235 * <p>In addition to the representation created by the super class this will calculate the total amount of 236 * this MoneyBag and append this in formatted form to the end of the representation.</p> 237 * 238 * @override Sometimes 239 */ 240 @SuppressWarnings("unchecked") 241 public String toString() { 242 synchronized (getItemsLock()) { 243 String sReturn = super.toString(); 244 245 try { 246 sReturn += ((getCatalog(null) != null) ? 247 (" Total: " + ((Currency)getCatalog(null)).toString((NumberValue)sumStock(null, 248 new CatalogItemValue(), new IntegerValue(0)))) : ("")); 249 } 250 catch (DataBasketConflictException dbce) { 251 // Catalog not accessible, do not compute total... 252 } 253 254 return sReturn; 255 } 256 } 257 258 }