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 }