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 }