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