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