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    }