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    }