001    package data.ooimpl;
002    
003    import java.util.*;
004    
005    import data.events.*;
006    import data.*;
007    
008    /**
009     * Pure Java implementation of the {@link CountingStock} interface.
010     *
011     * @author Steffen Zschaler
012     * @version 2.0 19/08/1999
013     * @since v2.0
014     */
015    public class CountingStockImpl extends StockImpl implements CountingStock {
016    
017        /**
018         * Listens for the Catalog to ensure referential integrity.
019         *
020         * @serial
021         */
022        protected CatalogChangeListener m_cclReferentialIntegrityListener;
023    
024        /**
025         * Create a new, initially empty CountingStockImpl.
026         *
027         * @param sName the name of the Stock.
028         * @param ciRef the Catalog referenced by the Stock.
029         */
030        public CountingStockImpl(String sName, CatalogImpl ciRef) {
031            super(sName, ciRef);
032    
033            // enhanced version.
034            m_sclEditCreatorListener = new StockChangeAdapter() {
035                public void commitAddStockItems(StockChangeEvent e) {
036                    synchronized (getItemsLock()) {
037                        String sKey = e.getAffectedKey();
038    
039                        Integer iAdded = (Integer)getTemporaryAddedItemsContainer().remove(sKey);
040    
041                        if (iAdded == null) {
042                            return;
043                        }
044    
045                        iAdded = new Integer(iAdded.intValue() - e.countAffectedItems());
046                        if (iAdded.intValue() > 0) {
047                            getTemporaryAddedItemsContainer().put(sKey, iAdded);
048                        }
049    
050                        Integer iItems = (Integer)getItemsContainer().get(sKey);
051    
052                        if (iItems == null) {
053                            iItems = new Integer(e.countAffectedItems());
054                        } else {
055                            iItems = new Integer(iItems.intValue() + e.countAffectedItems());
056                        }
057    
058                        if (iAdded.intValue() < 0) {
059                            iItems = new Integer(iItems.intValue() + iAdded.intValue());
060                        }
061    
062                        if (iItems.intValue() > 0) {
063                            getItemsContainer().put(sKey, iItems);
064                        }
065    
066                        fireStockItemsAddCommit(new CountingStockChangeEvent(CountingStockImpl.this, e.getBasket(),
067                                sKey,
068                                ((iAdded.intValue() < 0) ? (e.countAffectedItems() + iAdded.intValue()) :
069                                (e.countAffectedItems()))));
070                    }
071                }
072    
073                public void rollbackAddStockItems(StockChangeEvent e) {
074                    synchronized (getItemsLock()) {
075                        String sKey = e.getAffectedKey();
076    
077                        Integer iAdded = (Integer)getTemporaryAddedItemsContainer().remove(sKey);
078    
079                        if (iAdded == null) {
080                            return;
081                        }
082    
083                        iAdded = new Integer(iAdded.intValue() - e.countAffectedItems());
084                        if (iAdded.intValue() > 0) {
085                            getTemporaryAddedItemsContainer().put(sKey, iAdded);
086                        }
087    
088                        fireStockItemsAddRollback(new CountingStockChangeEvent(CountingStockImpl.this,
089                                e.getBasket(), sKey,
090                                ((iAdded.intValue() < 0) ? (e.countAffectedItems() + iAdded.intValue()) :
091                                (e.countAffectedItems()))));
092                    }
093                }
094    
095                public void canRemoveStockItems(StockChangeEvent e) throws VetoException {
096                    throw new VetoException("Please use the editable version for this!");
097                }
098    
099                public void commitRemoveStockItems(StockChangeEvent e) {
100                    synchronized (getItemsLock()) {
101                        String sKey = e.getAffectedKey();
102    
103                        Integer iRemoved = (Integer)getTemporaryRemovedItemsContainer().remove(sKey);
104    
105                        if (iRemoved == null) {
106                            return;
107                        }
108    
109                        iRemoved = new Integer(iRemoved.intValue() - e.countAffectedItems());
110                        if (iRemoved.intValue() > 0) {
111                            getTemporaryRemovedItemsContainer().put(sKey, iRemoved);
112                        }
113    
114                        fireStockItemsRemoveCommit(new CountingStockChangeEvent(CountingStockImpl.this,
115                                e.getBasket(), sKey,
116                                ((iRemoved.intValue() < 0) ? (e.countAffectedItems() + iRemoved.intValue()) :
117                                (e.countAffectedItems()))));
118                    }
119                }
120    
121                public void rollbackRemoveStockItems(StockChangeEvent e) {
122                    synchronized (getItemsLock()) {
123                        String sKey = e.getAffectedKey();
124    
125                        Integer iRemoved = (Integer)getTemporaryRemovedItemsContainer().remove(sKey);
126    
127                        if (iRemoved == null) {
128                            return;
129                        }
130    
131                        iRemoved = new Integer(iRemoved.intValue() - e.countAffectedItems());
132                        if (iRemoved.intValue() > 0) {
133                            getTemporaryRemovedItemsContainer().put(sKey, iRemoved);
134                        }
135    
136                        Integer iItems = (Integer)getItemsContainer().get(sKey);
137    
138                        if (iItems == null) {
139                            iItems = new Integer(e.countAffectedItems());
140                        } else {
141                            iItems = new Integer(iItems.intValue() + e.countAffectedItems());
142                        }
143    
144                        if (iRemoved.intValue() < 0) {
145                            iItems = new Integer(iItems.intValue() + iRemoved.intValue());
146                        }
147    
148                        if (iItems.intValue() > 0) {
149                            getItemsContainer().put(sKey, iItems);
150                        }
151    
152                        fireStockItemsRemoveRollback(new CountingStockChangeEvent(CountingStockImpl.this,
153                                e.getBasket(), sKey,
154                                ((iRemoved.intValue() < 0) ? (e.countAffectedItems() + iRemoved.intValue()) :
155                                (e.countAffectedItems()))));
156                    }
157                }
158    
159                public void canEditStockItems(StockChangeEvent e) throws VetoException {
160                    throw new VetoException("Please use the editable version for this!");
161                }
162    
163                public void commitEditStockItems(StockChangeEvent e) {
164                    synchronized (getItemsLock()) {
165                        String sKey = e.getAffectedKey();
166    
167                        Integer iEditing = (Integer)getEditingItemsContainer().remove(sKey);
168    
169                        if (iEditing == null) {
170                            return;
171                        }
172    
173                        iEditing = new Integer(iEditing.intValue() - e.countAffectedItems());
174                        if (iEditing.intValue() > 0) {
175                            getEditingItemsContainer().put(sKey, iEditing);
176                        }
177    
178                        fireStockItemsEditCommit(new CountingStockChangeEvent(CountingStockImpl.this, e.getBasket(),
179                                sKey,
180                                ((iEditing.intValue() < 0) ? (e.countAffectedItems() + iEditing.intValue()) :
181                                (e.countAffectedItems()))));
182                    }
183                }
184    
185                public void rollbackEditStockItems(StockChangeEvent e) {
186                    synchronized (getItemsLock()) {
187                        String sKey = e.getAffectedKey();
188    
189                        Integer iEditing = (Integer)getEditingItemsContainer().remove(sKey);
190    
191                        if (iEditing == null) {
192                            return;
193                        }
194    
195                        iEditing = new Integer(iEditing.intValue() - e.countAffectedItems());
196                        if (iEditing.intValue() > 0) {
197                            getEditingItemsContainer().put(sKey, iEditing);
198                        }
199    
200                        fireStockItemsEditRollback(new CountingStockChangeEvent(CountingStockImpl.this,
201                                e.getBasket(), sKey,
202                                ((iEditing.intValue() < 0) ? (e.countAffectedItems() + iEditing.intValue()) :
203                                (e.countAffectedItems()))));
204                    }
205                }
206            };
207        }
208    
209        /**
210         * Overridden to ensure referential integrity.
211         *
212         * @override Never
213         */
214        protected void internalSetCatalog(CatalogImpl ciRef) {
215            if (m_ciCatalog != null) {
216                m_ciCatalog.removeCatalogChangeListener(m_cclReferentialIntegrityListener);
217            }
218    
219            super.internalSetCatalog(ciRef);
220    
221            if (m_ciCatalog != null) {
222                if (m_cclReferentialIntegrityListener == null) {
223                    initReferentialIntegrityListener();
224                }
225    
226                m_ciCatalog.addCatalogChangeListener(m_cclReferentialIntegrityListener);
227            }
228        }
229    
230        /**
231         * Private helper function creating the listener that ensures referential integrity.
232         *
233         * @override Never
234         */
235        private void initReferentialIntegrityListener() {
236            m_cclReferentialIntegrityListener = new CatalogChangeAdapter() {
237                public void canRemoveCatalogItem(CatalogChangeEvent e) throws VetoException {
238                    // DataBasket already locks on its monitor!
239                    synchronized (getItemsLock()) {
240                        String sKey = e.getAffectedItem().getName();
241    
242                        if (getTemporaryAddedItemsContainer().containsKey(sKey)) {
243                            throw new VetoException("Stock " + getName() +
244                                    ": Having temporarily added items for key \"" + sKey + "\"");
245                        }
246    
247                        if (getTemporaryRemovedItemsContainer().containsKey(sKey)) {
248                            DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
249                                    CountingStockImpl.this, null, null);
250                            BasketEntryValue bev = new BasketEntryValue() {
251                                public Value getEntryValue(DataBasketEntry dbe) {
252                                    return new IntegerValue((Integer)dbe.getValue());
253                                }
254                            };
255    
256                            Integer iCount = (Integer)getTemporaryRemovedItemsContainer().get(sKey);
257    
258                            IntegerValue ivCount = new IntegerValue(new Integer(0));
259                            int nCount = ((IntegerValue)e.getBasket().sumBasket(dbc, bev,
260                                    ivCount)).getValue().intValue();
261    
262                            if (iCount.intValue() > nCount) {
263                                throw new VetoException("Stock " + getName() +
264                                        ": Having temporaryly removed items that are in another DataBasket. (Key: \"" +
265                                        sKey + "\")");
266                            }
267                        }
268    
269                        if (getItemsContainer().containsKey(sKey)) {
270                            int nCount = ((Integer)getItemsContainer().get(sKey)).intValue();
271    
272                            remove(sKey, nCount, e.getBasket());
273    
274                            getRefIntegrItemsContainer().put(sKey, new Integer(nCount));
275                        }
276                    }
277                }
278    
279                public void noRemoveCatalogItem(CatalogChangeEvent e) {
280                    synchronized (getItemsLock()) {
281                        String sKey = e.getAffectedItem().getName();
282    
283                        if (getRefIntegrItemsContainer().containsKey(sKey)) {
284                            int nCount = ((Integer)getRefIntegrItemsContainer().remove(sKey)).intValue();
285    
286                            add(sKey, nCount, e.getBasket());
287                        }
288                    }
289                }
290    
291                public void removedCatalogItem(CatalogChangeEvent e) {
292                    synchronized (getItemsLock()) {
293                        // clean up
294                        getRefIntegrItemsContainer().remove(e.getAffectedItem().getName());
295                    }
296                }
297    
298                public void commitRemoveCatalogItem(CatalogChangeEvent e) {
299                    synchronized (getItemsLock()) {
300                        if (!((Catalog)e.getSource()).contains(e.getAffectedItem().getName(), e.getBasket())) {
301                            ciGoneForEver(e);
302                        }
303                    }
304                }
305    
306                public void rollbackAddCatalogItem(CatalogChangeEvent e) {
307                    synchronized (getItemsLock()) {
308                        DataBasketCondition dbc = new DataBasketConditionImpl(CatalogItemImpl.
309                                CATALOG_ITEM_MAIN_KEY, e.getAffectedItem().getName(), (CatalogImpl)e.getSource(), null, null);
310    
311                        if (!e.getBasket().contains(dbc)) {
312                            ciGoneForEver(e);
313                        }
314                    }
315                }
316    
317                private void ciGoneForEver(CatalogChangeEvent e) {
318    
319                    String sKey = e.getAffectedItem().getName();
320                    DataBasket db = e.getBasket();
321    
322                    if (getTemporaryAddedItemsContainer().containsKey(sKey)) {
323                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null,
324                                CountingStockImpl.this, null);
325    
326                        // Rollback all items temporaryly added to this Stock
327                        // CountingStocks produce only DataBasketEntries that have either source or dest set,
328                        // so a complete rollback will be OK.
329                        // However, we cannot simply write db.rollback (dbc), as this would remove the handled
330                        // entries immediately, thus invalidating the iterator that was used to perform the
331                        // commit or rollback that lead to this method being called.
332                        for (Iterator i = db.iterator(dbc); i.hasNext(); ) {
333                            ((DataBasketEntry)i.next()).rollback();
334                        }
335                    }
336    
337                    getItemsContainer().remove(sKey);
338                    getRefIntegrItemsContainer().remove(sKey);
339    
340                    if (getTemporaryRemovedItemsContainer().containsKey(sKey)) {
341                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
342                                CountingStockImpl.this, null, null);
343    
344                        // Commit all items temporaryly removed from this Stock
345                        // CountingStocks produce only DataBasketEntries that have either source or dest set,
346                        // so a complete commit will be OK.
347                        // However, we cannot simply write db.commit (dbc), as this would remove the handled
348                        // entries immediately, thus invalidating the iterator that was used to perform the
349                        // commit or rollback that lead to this method being called.
350                        for (Iterator i = db.iterator(dbc); i.hasNext(); ) {
351                            ((DataBasketEntry)i.next()).commit();
352                        }
353                    }
354                }
355            };
356        }
357    
358        // Stock interface methods
359    
360        /**
361         * Add an item to the Stock.
362         *
363         * <p>The item will only be visible to users of the same DataBasket. Only after a {@link DataBasket#commit}
364         * was performed on the DataBasket, the item will become visible to other users.</p>
365         *
366         * <p>A <code>addedStockItems</code> event will be fired.</p>
367         *
368         * @param si the item to be added.
369         * @param db the DataBasket relative to which the item will be added. Must be either <code>null</code> or
370         * a descendant of {@link DataBasketImpl}.
371         *
372         * @override Never
373         *
374         * @exception CatalogConflictException if the items key is not contained in the corresponding {@link Catalog}.
375         * @exception DataBasketConflictException if the item has already been added/removed using another DataBasket.
376         */
377        public void add(StockItem si, DataBasket db) {
378            add(si.getName(), 1, db);
379        }
380    
381        /**
382         * Overridden for efficiency reasons.
383         *
384         * @override Never
385         */
386        public void addStock(Stock st, DataBasket db, boolean fRemove) {
387            if (st.getCatalog(db) != getCatalog(db)) {
388                throw new CatalogConflictException();
389            }
390    
391            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
392    
393            synchronized (oLock) {
394                synchronized (getItemsLock()) {
395                    Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock));
396    
397                    synchronized (oLock2) {
398                        for (Iterator i = st.keySet(db).iterator(); i.hasNext(); ) {
399                            String sKey = (String)i.next();
400    
401                            add(sKey, st.countItems(sKey, db), db);
402    
403                            if (fRemove) {
404                                for (Iterator ii = st.get(sKey, db, false); ii.hasNext(); ) {
405                                    try {
406                                        ii.next();
407                                        ii.remove();
408                                    }
409                                    catch (ConcurrentModificationException e) {
410                                        break;
411                                    }
412                                    catch (Exception e) {
413                                        // ignore any items that could not be removed from their source
414                                        continue;
415                                    }
416                                }
417                            }
418                        }
419                    }
420                }
421            }
422        }
423    
424        /**
425         * Iterate all items with a given key.
426         *
427         * <p>This method, together with {@link #iterator} is the only way of accessing the individual
428         * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that have the
429         * specified key and are visible using the given DataBasket. Depending on the <code>fForEdit</code>
430         * parameter, the items will be retrieved in different ways. See {@link DataBasket} for an explanation of
431         * the different possibilities.</p>
432         *
433         * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if
434         * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into
435         * <code>UnSupportedOperationException</code>s.</p>
436         *
437         * @override Never
438         *
439         * @param sKey the key for which to retrieve the StockItems.
440         * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code>
441         * or a descendant of {@link DataBasketImpl}.
442         * @param fForEdit if true, the StockItems will be retrieved for editing.
443         */
444        public Iterator get(final String sKey, final DataBasket db, boolean fForEdit) {
445            class I implements Iterator {
446                private boolean m_fNextCalled = false;
447                private int m_nCount;
448                private CountingStockImpl m_cstiOwner;
449                private StockItemImpl m_siiLast;
450    
451                public I(CountingStockImpl cstiOwner, int nCount) {
452                    super();
453    
454                    m_cstiOwner = cstiOwner;
455                    m_nCount = nCount;
456                }
457    
458                public boolean hasNext() {
459                    return (m_nCount > 0);
460                }
461    
462                public Object next() {
463                    if ((m_nCount--) <= 0) {    //first checks m_nCount, then decreases
464                        m_fNextCalled = false;
465                        throw new NoSuchElementException();
466                    }
467    
468                    m_fNextCalled = true;
469                    m_siiLast = new StockItemImpl(sKey);
470                    m_siiLast.setStock(m_cstiOwner);
471    
472                    return m_siiLast;
473                }
474    
475                public void remove() {
476                    if (m_fNextCalled) {
477                        m_fNextCalled = false;
478    
479                        try {
480                            m_cstiOwner.remove(sKey, 1, db);
481                        }
482                        catch (VetoException ex) {
483                            throw new UnsupportedOperationException("VetoException: " + ex.getMessage());
484                        }
485    
486                        m_siiLast.setStock(null);
487                    } else {
488                        throw new IllegalStateException();
489                    }
490                }
491            }
492    
493            if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
494                return new Iterator() {
495                    public boolean hasNext() {
496                        return false;
497                    }
498    
499                    public Object next() {
500                        throw new NoSuchElementException();
501                    }
502    
503                    public void remove() {}
504                };
505            }
506    
507            return new I(this, countItems(sKey, db));
508        }
509    
510        /**
511         * Count the StockItems with a given key that are visible using a given DataBasket.
512         *
513         * @override Never
514         *
515         * @param sKey the key for which to count the StockItems.
516         * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a
517         * descendant of {@link DataBasketImpl}.
518         */
519        public int countItems(String sKey, DataBasket db) {
520            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
521    
522            int nCount = 0;
523    
524            synchronized (oLock) {
525                synchronized (getItemsLock()) {
526                    if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
527                        return 0;
528                    }
529    
530                    Integer iCount = (Integer)getItemsContainer().get(sKey);
531    
532                    if (iCount != null) {
533                        nCount = iCount.intValue();
534                    }
535                    //cannot use the value of mTemporaryAdded to get the temporary added items,
536                    //because different DataBaskets might have added items to it, and we only want
537                    //the items added with THIS databasket
538                    if (db != null) {
539                        DataBasketCondition dbc = new DataBasketConditionImpl(
540                                STOCK_ITEM_MAIN_KEY, sKey, null, this, null);
541    
542                        for (Iterator i = db.iterator(dbc); i.hasNext(); ) {
543                            StockItemDBEntry sidbe = (StockItemDBEntry)i.next();
544    
545                            nCount += sidbe.count();
546                        }
547                    }
548                }
549            }
550    
551            return nCount;
552        }
553    
554        /**
555         * Check whether the Stock contains the given StockItem.
556         *
557         * <p>Return true if the Stock contains a StockItem that is equal to the given one.</p>
558         *
559         * @param si the StockItem for which to check containment.
560         * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of
561         * {@link DataBasketImpl}.
562         *
563         * @override Never
564         */
565        public boolean contains(StockItem si, DataBasket db) {
566            return contains(si.getName(), db);
567        }
568    
569        /**
570         * Reimplemented for efficiency reasons.
571         *
572         * @override Never
573         */
574        public boolean containsStock(Stock st, DataBasket db) {
575            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
576    
577            synchronized (oLock) {
578                synchronized (getItemsLock()) {
579                    Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock));
580    
581                    synchronized (oLock2) {
582                        for (Iterator i = st.keySet(db).iterator(); i.hasNext(); ) {
583                            String sKey = (String)i.next();
584    
585                            if (countItems(sKey, db) < st.countItems(sKey, db)) {
586                                return false;
587                            }
588                        }
589    
590                        return true;
591                    }
592                }
593            }
594        }
595    
596        /**
597         * Remove one StockItem with the specified key from the Stock.
598         *
599         * <p>If there are any StockItems with the specified key, one will be removed. There is no guarantee as to
600         * which StockItem will be removed. The removed item, if any, will be returned.</p>
601         *
602         * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
603         *
604         * @override Never
605         *
606         * @param sKey the key for which to remove an item.
607         * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
608         * descendant of {@link DataBasketImpl}.
609         *
610         * @return the removed item
611         *
612         * @exception VetoException if a listener vetoed the removal.
613         * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
614         * usage.
615         */
616        public StockItem remove(String sKey, DataBasket db) throws VetoException {
617            remove(sKey, 1, db);
618    
619            StockItemImpl sii = new StockItemImpl(sKey);
620            sii.setStock(null);
621    
622            return sii;
623        }
624    
625        /**
626         * Remove the given StockItem from the Stock.
627         *
628         * <p>If the given StockItem is contained in the Stock, it will be removed. The removed item, if any, will
629         * be returned.</p>
630         *
631         * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
632         *
633         * @override Never
634         *
635         * @param si the StockItem to be removed.
636         * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
637         * descendant of {@link DataBasketImpl}.
638         *
639         * @return the removed item
640         *
641         * @exception VetoException if a listener vetoed the removal.
642         * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
643         * usage.
644         */
645        public StockItem remove(StockItem si, DataBasket db) throws VetoException {
646            return remove(si.getName(), db);
647        }
648    
649        /**
650         * @override Always
651         */
652        protected StockImpl createPeer() {
653            CountingStockImpl csiPeer = new CountingStockImpl(getName(), m_ciCatalog);
654            csiPeer.m_dbCatalogValidator = m_dbCatalogValidator;
655    
656            return csiPeer;
657        }
658    
659        // CountingStock interface methods
660        /**
661         * Add a number of items of a given key to the Stock.
662         *
663         * <p>As with any Stock the added items will not at once be visible to users of other DataBaskets.</p>
664         *
665         * <p>In general the method behaves as though it would call {@link Stock#add} <code>nCount</code> times.
666         * Especially, the same exceptions might occur and the same constraints hold.</p>
667         *
668         * @override Never
669         *
670         * @param sKey the key for which to add a number of items.
671         * @param nCount how many items are to be added?
672         * @param db the DataBasket relative to which the adding is performed. Must be either <code>null</code> or a
673         * descendant of {@link DataBasketImpl}.
674         *
675         * @exception IllegalArgumentException if <code>nCount <= 0</code>.
676         * @exception CatalogConflictException if the key cannot be found in the Catalog.
677         */
678        public void add(String sKey, int nCount, DataBasket db) {
679            if (nCount <= 0) {
680                throw new IllegalArgumentException("nCount must be greater than 0.");
681            }
682    
683            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
684    
685            synchronized (oLock) {
686                synchronized (getItemsLock()) {
687                    if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
688                        throw new CatalogConflictException("Couldn't find key \"" + sKey + "\" in Catalog \"" +
689                                getCatalog(db).getName() + "\"");
690                    }
691                    if (db != null) {
692                        //use an array for anTempCount to both make it final and be able to change its value
693                        final int[] anTempCount = {
694                                nCount};
695                        //if there are already temporary removed StockItems, rollback the right amount of them
696                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, this,
697                                null, null) {
698                            public boolean match(DataBasketEntry dbe) {
699                                //this test seems redundant as we have already tested if ncount <= 0
700                                //however, if two or more DataBasketEntries for this StockItem exist, it
701                                //is possible that ncount StockItems have already been rolled back, so anTempCount[0]
702                                //has been set to 0 (see * some lines below) but yet another DataBasketEntry is
703                                //examined. Here we have to stop.
704                                if (anTempCount[0] == 0) {
705                                    return false;
706                                }
707    
708                                StockItemDBEntry sidbe = (StockItemDBEntry)dbe;
709    
710                                if (anTempCount[0] >= sidbe.count()) {
711                                    anTempCount[0] -= sidbe.count(); // *
712                                    return true;
713                                } else {
714                                    CountingStockItemDBEntry csidbe = (CountingStockItemDBEntry)sidbe;
715                                    csidbe.partialRollback(anTempCount[0]);
716                                    anTempCount[0] = 0;
717    
718                                    return false;
719                                }
720                            }
721                        };
722    
723                        db.rollback(dbc);
724    
725                        nCount = anTempCount[0];
726    
727                        if (nCount > 0) {
728                            Integer iCount = (Integer)getTemporaryAddedItemsContainer().remove(sKey);
729    
730                            if (iCount == null) {
731                                iCount = new Integer(nCount);
732                            } else {
733                                iCount = new Integer(iCount.intValue() + nCount);
734                            }
735    
736                            getTemporaryAddedItemsContainer().put(sKey, iCount);
737    
738                            db.put(new CountingStockItemDBEntry(sKey, null, this, nCount));
739                            fireStockItemsAdded(new CountingStockChangeEvent(this, db, sKey, nCount));
740                        } else {
741                            if (db instanceof ListenableDataBasket) {
742                                ((ListenableDataBasket)db).fireDataBasketChanged();
743                            }
744                        }
745                    } else {
746                        Integer iCount = (Integer)getItemsContainer().get(sKey);
747                        if (iCount == null) {
748                            iCount = new Integer(nCount);
749                        } else {
750                            iCount = new Integer(iCount.intValue() + nCount);
751                        }
752    
753                        getItemsContainer().put(sKey, iCount);
754                        fireStockItemsAdded(new CountingStockChangeEvent(this, null, sKey, nCount));
755                    }
756                }
757            }
758        }
759    
760        /**
761         * Remove a number of items of a given key from the Stock.
762         *
763         * <p>In general the method behaves as though it would call
764         * {@link Stock#remove(java.lang.String, data.DataBasket)} <code>nCount</code> times. Especially, the same
765         * exceptions might occur and the same constraints hold.</p>
766         *
767         * @override Never
768         *
769         * @param sKey the key for which to remove a number of items.
770         * @param nCount how many items are to be removed?
771         * @param db the DataBasket relative to which the removal is performed. Must be either <code>null</code> or
772         * a descendant of {@link DataBasketImpl}.
773         *
774         * @exception VetoException if a listener vetos the removal.
775         * @exception NotEnoughElementsException if there are not enough elements to fulfill the request. If this
776         * exception is thrown no items will have been removed.
777         * @exception IllegalArgumentException if <code>nCount <= 0</code>
778         */
779        public void remove(String sKey, int nCount, DataBasket db) throws VetoException {
780            if (nCount <= 0) {
781                throw new IllegalArgumentException("nCount must be greater than 0.");
782            }
783    
784            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
785    
786            synchronized (oLock) {
787                synchronized (getItemsLock()) {
788                    if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
789                        throw new CatalogConflictException("Couldn't find key \"" + sKey + "\" in Catalog \"" +
790                                getCatalog(db).getName() + "\"");
791                    }
792    
793                    if (countItems(sKey, db) < nCount) {
794                        throw new NotEnoughElementsException();
795                    }
796    
797                    fireCanRemoveStockItems(new CountingStockChangeEvent(this, db, sKey, nCount));
798    
799                    if (db != null) {
800                        final int[] anTempCount = {
801                                nCount};
802                        //if there are temporary added StockItems, rollback the right amount of them
803                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null) {
804                            //if there are already temporary removed StockItems, rollback the right amount of them
805                            public boolean match(DataBasketEntry dbe) {
806                                //this test seems redundant as we have already tested if ncount <= 0
807                                //however, if two or more DataBasketEntries for this StockItem exist, it
808                                //is possible that ncount StockItems have already been rolled back, so anTempCount[0]
809                                //has been set to 0 (see * some lines below) but yet another DataBasketEntry is
810                                //examined. Here we have to stop.
811                                if (anTempCount[0] == 0) {
812                                    return false;
813                                }
814    
815                                StockItemDBEntry sidbe = (StockItemDBEntry)dbe;
816    
817                                if (anTempCount[0] >= sidbe.count()) {
818                                    anTempCount[0] -= sidbe.count(); // *
819                                    return true;
820                                } else {
821                                    CountingStockItemDBEntry csidbe = (CountingStockItemDBEntry)sidbe;
822                                    csidbe.partialRollback(anTempCount[0]);
823                                    anTempCount[0] = 0;
824                                    return false;
825                                }
826                            }
827                        };
828    
829                        db.rollback(dbc);
830    
831                        nCount = anTempCount[0];
832    
833                        if (nCount > 0) {
834                            Integer iCount = (Integer)getItemsContainer().remove(sKey);
835    
836                            if (iCount.intValue() > nCount) {
837                                getItemsContainer().put(sKey, new Integer(iCount.intValue() - nCount));
838                            }
839    
840                            iCount = (Integer)getTemporaryRemovedItemsContainer().remove(sKey);
841    
842                            if (iCount == null) {
843                                iCount = new Integer(nCount);
844                            } else {
845                                iCount = new Integer(iCount.intValue() + nCount);
846                            }
847    
848                            getTemporaryRemovedItemsContainer().put(sKey, iCount);
849    
850                            db.put(new CountingStockItemDBEntry(sKey, this, null, nCount));
851                        } else {
852                            if (db instanceof ListenableDataBasket) {
853                                ((ListenableDataBasket)db).fireDataBasketChanged();
854                            }
855                        }
856                    } else {
857                        Integer iCount = (Integer)getItemsContainer().get(sKey);
858    
859                        if (iCount.intValue() > nCount) {
860                            iCount = new Integer(iCount.intValue() - nCount);
861                            getItemsContainer().put(sKey, iCount);
862                        } else {
863                            getItemsContainer().remove(sKey);
864                        }
865                    }
866    
867                    fireStockItemsRemoved(new CountingStockChangeEvent(this, db, sKey, nCount));
868                }
869            }
870        }
871    
872        // Object standard methods
873        /**
874         * Get a String representation of the Stock.
875         *
876         * @override Sometimes
877         */
878        public String toString() {
879            synchronized (getItemsLock()) {
880                String sReturn = "Stock \"" + getName() + "\" [";
881    
882                boolean fFirst = true;
883                for (Iterator i = keySet(null).iterator(); i.hasNext(); ) {
884                    String sKey = (String)i.next();
885    
886                    sReturn += ((fFirst) ? ("") : (", ")) + sKey + ": " + countItems(sKey, null);
887                    fFirst = false;
888                }
889    
890                return sReturn + "]";
891            }
892        }
893    
894    
895        // SelfManagingDBESource interface methods
896        /**
897         * Commit the removal of StockItems.
898         *
899         * <p>A <code>commitRemoveStockItems</code> will be fired.</p>
900         *
901         * @override Never
902         */
903        public void commitRemove(DataBasket db, DataBasketEntry dbe) {
904            // DataBasket already locks on its monitor, so we just lock on ours.
905            synchronized (getItemsLock()) {
906                Integer iCount = (Integer)getTemporaryRemovedItemsContainer().remove(dbe.getSecondaryKey());
907    
908                int nRemains = iCount.intValue() - ((StockItemDBEntry)dbe).count();
909    
910                if (nRemains > 0) {
911                    getTemporaryRemovedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
912                }
913    
914                fireStockItemsRemoveCommit(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(),
915                        ((StockItemDBEntry)dbe).count()));
916            }
917        }
918    
919        /**
920         * Rollback the removal of StockItems.
921         *
922         * <p>A <code>rollbackRemoveStockItems</code> will be fired. Also, the Stock will try to make sure, that
923         * a corresponding CatalogItem exists.</p>
924         *
925         * @override Never
926         */
927        public void rollbackRemove(DataBasket db, DataBasketEntry dbe) {
928            synchronized (getItemsLock()) {
929                prepareReferentialIntegrity(db, dbe);
930    
931                Integer iCount = (Integer)getTemporaryRemovedItemsContainer().remove(dbe.getSecondaryKey());
932    
933                int nCount = ((StockItemDBEntry)dbe).count();
934                int nRemains = iCount.intValue() - nCount;
935    
936                if (nRemains > 0) {
937                    getTemporaryRemovedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
938                }
939    
940                iCount = (Integer)getItemsContainer().remove(dbe.getSecondaryKey());
941    
942                if (iCount == null) {
943                    iCount = new Integer(nCount);
944                } else {
945                    iCount = new Integer(iCount.intValue() + nCount);
946                }
947    
948                getItemsContainer().put(dbe.getSecondaryKey(), iCount);
949    
950                fireStockItemsRemoveRollback(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(), nCount));
951            }
952        }
953    
954        // SelfManagingDBEDestination interface methods
955        /**
956         * Commit the adding of StockItems.
957         *
958         * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
959         * fired as a consequence of this method. Also, the Stock will try to make sure, that a corresponding
960         * CatalogItem exists.</p>
961         *
962         * @override Never
963         */
964        public void commitAdd(DataBasket db, DataBasketEntry dbe) {
965            synchronized (getItemsLock()) {
966                prepareReferentialIntegrity(db, dbe);
967    
968                Integer iCount = (Integer)getTemporaryAddedItemsContainer().remove(dbe.getSecondaryKey());
969    
970                int nCount = ((StockItemDBEntry)dbe).count();
971                int nRemains = iCount.intValue() - nCount;
972    
973                if (nRemains > 0) {
974                    getTemporaryAddedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
975                }
976    
977                iCount = (Integer)getItemsContainer().remove(dbe.getSecondaryKey());
978    
979                if (iCount == null) {
980                    iCount = new Integer(nCount);
981                } else {
982                    iCount = new Integer(iCount.intValue() + nCount);
983                }
984    
985                getItemsContainer().put(dbe.getSecondaryKey(), iCount);
986    
987                fireStockItemsAddCommit(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(), nCount));
988            }
989        }
990    
991        /**
992         * Rollback the adding of StockItems.
993         *
994         * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
995         * fired as a consequence of this method.</p>
996         *
997         * @override Never
998         */
999        public void rollbackAdd(DataBasket db, DataBasketEntry dbe) {
1000            synchronized (getItemsLock()) {
1001                Integer iCount = (Integer)getTemporaryAddedItemsContainer().remove(dbe.getSecondaryKey());
1002    
1003                int nRemains = iCount.intValue() - ((StockItemDBEntry)dbe).count();
1004    
1005                if (nRemains > 0) {
1006                    getTemporaryAddedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
1007                }
1008    
1009                fireStockItemsAddRollback(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(),
1010                        ((StockItemDBEntry)dbe).count()));
1011            }
1012        }
1013    }