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