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