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 }