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    }