001    package data.ooimpl;
002    
003    import data.*;
004    import data.events.*;
005    
006    import java.util.*;
007    
008    /**
009     * Pure Java implementation of the {@link StoringStock} interface.
010     *
011     * @author Steffen Zschaler
012     * @version 2.0 19/08/1999
013     * @since v2.0
014     */
015    public class StoringStockImpl<T extends StockItemImpl, CT extends CatalogItemImpl> 
016                                 extends StockImpl<List<T>, T, CT> implements StoringStock<T, CT> {
017    
018        /**
019             * ID for serialization.
020             */
021            private static final long serialVersionUID = 3460626053985545111L;
022    
023            /**
024         * Modification counter. Increases by one with every structural modification.
025         *
026         * @serial
027         */
028        protected int m_nModCount = Integer.MIN_VALUE;
029    
030        /**
031         * Listens to the Stock's Catalog to ensure referential integrity.
032         *
033         * @serial
034         */
035        protected CatalogChangeListener m_cclReferentialIntegrityListener;
036    
037        /**
038         * true, if StockImpl's CatalogItemNameListener was already replaced by an enhanced version.
039         * Used internally only.
040         *
041         * @serial
042         */
043        private boolean m_fChangedCatalogItemNameListener = false;
044    
045        /**
046         * Enhanced CatalogItemNameListener, updating the names of the actual StockItems whenever a name change
047         * occurs.
048         *
049         * @author Steffen Zschaler
050         * @version 2.0 19/08/1999
051         * @since v2.0
052         */
053        class SSICatalogItemNameListener extends CatalogItemNameListener {
054                    
055            private static final long serialVersionUID = -3046919332594065216L;
056    
057                    protected void nameChangeOccurred(String sOld, String sNew) {
058                super.nameChangeOccurred(sOld, sNew);
059    
060                List<T> lAdded = getTemporaryAddedItemsContainer().get(sNew);
061                if (lAdded != null) {
062                    for (Iterator<T> i = lAdded.iterator(); i.hasNext(); ) {
063                        StockItemImpl sii = i.next();
064    
065                        sii.internalSetName(sNew);
066                    }
067                }
068    
069                List<T> lItems = getItemsContainer().get(sNew);
070                if (lItems != null) {
071                    for (Iterator<T> i = lItems.iterator(); i.hasNext(); ) {
072                        StockItemImpl sii = i.next();
073    
074                        sii.internalSetName(sNew);
075                    }
076                }
077    
078                List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sNew);
079                if (lRemoved != null) {
080                    for (Iterator<T> i = lRemoved.iterator(); i.hasNext(); ) {
081                        StockItemImpl sii = i.next();
082    
083                        sii.internalSetName(sNew);
084                    }
085                }
086    
087                List<T> lRefIntegr = getRefIntegrItemsContainer().get(sNew);
088                if (lRefIntegr != null) {
089                    for (Iterator<T> i = lRefIntegr.iterator(); i.hasNext(); ) {
090                        StockItemImpl sii = i.next();
091    
092                        sii.internalSetName(sNew);
093                    }
094                }
095            }
096        }
097        
098        /**
099         * Create a new, initially empty StoringStockImpl.
100         *
101         * @param id the id of the new Stock.
102         * @param ciRef the Catalog that is being referenced by the Stock.
103         */
104        public StoringStockImpl(StockIdentifier<T, CT> id, Catalog<CT> ciRef) {
105            super(id.getName(), (CatalogImpl<CT>)ciRef);
106        }
107    
108        /**
109         * Create a new, initially empty StoringStockImpl.
110         *
111         * @param sName the name of the new Stock.
112         * @param ciRef the Catalog that is being referenced by the Stock.
113         */
114        public StoringStockImpl(String sName, Catalog<CT> ciRef) {
115            super(sName, (CatalogImpl<CT>)ciRef);
116    
117            // Enhanced version.
118            m_sclEditCreatorListener = new StockChangeAdapter<T, CT>() {
119    
120                            private static final long serialVersionUID = -7948443502826415058L;
121    
122                            public void commitAddStockItems(StockChangeEvent<T, CT> e) {
123                    synchronized (getItemsLock()) {
124                        T sii = e.getAffectedItems().next();
125    
126                        List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
127    
128                        if (lAdded == null) {
129                            return;
130                        }
131    
132                        if (lAdded.remove(sii)) {
133                            List<T> lItems = getItemsContainer().get(sii.getName());
134    
135                            if (lItems == null) {
136                                lItems = new LinkedList<T>();
137                                getItemsContainer().put(sii.getName(), lItems);
138                            }
139                            lItems.add(sii);
140    
141                            sii.setStock(StoringStockImpl.this);
142    
143                            fireStockItemsAddCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
144                                    e.getBasket()));
145                        }
146                    }
147                }
148    
149                            public void rollbackAddStockItems(StockChangeEvent<T, CT> e) {
150                    synchronized (getItemsLock()) {
151                        T sii = e.getAffectedItems().next();
152    
153                        List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
154    
155                        if (lAdded == null) {
156                            return;
157                        }
158    
159                        if (lAdded.remove(sii)) {
160                            if (sii.getStock().equals(StoringStockImpl.this)) {
161                                sii.setStock(null);
162                            }
163    
164                            fireStockItemsAddRollback(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
165                                    e.getBasket()));
166                        }
167                    }
168                }
169    
170                public void canRemoveStockItems(StockChangeEvent<T, CT> e) throws VetoException {
171                    throw new VetoException("Please use the editable version for this!");
172                }
173    
174                            public void commitRemoveStockItems(StockChangeEvent<T, CT> e) {
175                    synchronized (getItemsLock()) {
176                        T sii = e.getAffectedItems().next();
177    
178                        List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
179    
180                        if (lRemoved == null) {
181                            return;
182                        }
183    
184                        if (lRemoved.remove(sii)) {
185                            if (sii.getStock().equals(StoringStockImpl.this)) {
186                                sii.setStock(null);
187                            }
188    
189                            fireStockItemsRemoveCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
190                                    e.getBasket()));
191                        }
192                    }
193                }
194    
195                            public void rollbackRemoveStockItems(StockChangeEvent<T, CT> e) {
196                    synchronized (getItemsLock()) {
197                        T sii = e.getAffectedItems().next();
198    
199                        List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
200    
201                        if (lRemoved == null) {
202                            return;
203                        }
204    
205                        if (lRemoved.remove(sii)) {
206                            List<T> lItems = getItemsContainer().get(sii.getName());
207    
208                            if (lItems == null) {
209                                lItems = new LinkedList<T>();
210                                getItemsContainer().put(sii.getName(), lItems);
211                            }
212                            lItems.add(sii);
213    
214                            sii.setStock(StoringStockImpl.this);
215    
216                            fireStockItemsRemoveCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
217                                    e.getBasket()));
218                        }
219                    }
220                }
221    
222                public void canEditStockItems(StockChangeEvent<T, CT> e) throws VetoException {
223                    throw new VetoException("Please use the editable version for this!");
224                }
225    
226                            public void commitEditStockItems(StockChangeEvent<T, CT> e) {
227                    synchronized (getItemsLock()) {
228                        T sii = e.getAffectedItems().next();
229    
230                        List<T> lEditing = getEditingItemsContainer().get(sii.getName());
231    
232                        if (lEditing == null) {
233                            return;
234                        }
235    
236                        if (lEditing.remove(sii)) {
237                            fireStockItemsEditCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
238                                    e.getBasket()));
239                        }
240                    }
241                }
242    
243                            public void rollbackEditStockItems(StockChangeEvent<T, CT> e) {
244                    synchronized (getItemsLock()) {
245                        T sii = e.getAffectedItems().next();
246    
247                        List<T> lEditing = getEditingItemsContainer().get(sii.getName());
248    
249                        if (lEditing == null) {
250                            return;
251                        }
252    
253                        if (lEditing.remove(sii)) {
254                            fireStockItemsEditRollback(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
255                                    e.getBasket()));
256                        }
257                    }
258                }
259            };
260        }
261    
262        /**
263         * Overridden because of referential integrity.
264         *
265         * @override Never
266         */
267        protected void internalSetCatalog(CatalogImpl<CT> ciRef) {
268            if (m_ciCatalog != null) {
269                m_ciCatalog.removeCatalogChangeListener(m_cclReferentialIntegrityListener);
270            }
271    
272            if (!m_fChangedCatalogItemNameListener) {
273                m_fChangedCatalogItemNameListener = true;
274    
275                m_cinlCatalogItemNameListener = new SSICatalogItemNameListener();
276            }
277    
278            super.internalSetCatalog(ciRef);
279    
280            if (m_ciCatalog != null) {
281                if (m_cclReferentialIntegrityListener == null) {
282                    initReferentialIntegrityListener();
283                }
284    
285                m_ciCatalog.addCatalogChangeListener(m_cclReferentialIntegrityListener);
286            }
287        }
288    
289        /**
290         * Internal helper function.
291         *
292         * @override Never
293         */
294        private void initReferentialIntegrityListener() {
295            m_cclReferentialIntegrityListener = new CatalogChangeAdapter<CT>() {
296                
297                            private static final long serialVersionUID = -2030724124407592374L;
298    
299                            public void canRemoveCatalogItem(CatalogChangeEvent<CT> e) throws VetoException {
300                    // DataBasket already locks on its monitor
301                    synchronized (getItemsLock()) {
302                        String sKey = e.getAffectedItem().getName();
303                        DataBasket db = e.getBasket();
304    
305                        List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
306                        if ((lAdded != null) && (lAdded.size() > 0)) {
307                            throw new VetoException("Stock \"" + getName() +
308                                    "\": Having temporarily added items of key \"" + sKey + "\".");
309                        }
310    
311                        List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sKey);
312                        if (lRemoved != null) {
313                            if ((db == null) && (lRemoved.size() > 0)) {
314                                throw new VetoException("Stock \"" + getName() +
315                                        "\": Having temporarily removed items that are in a different DataBasket.");
316                            }
317    
318                            for (Iterator<T> i = lRemoved.iterator(); i.hasNext(); ) {
319                                StockItem si = (StockItem)i.next();
320    
321                                DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
322                                        StoringStockImpl.this, null, si);
323                                if (!db.contains(dbc)) {
324                                    throw new VetoException("Stock \"" + getName() +
325                                            "\": Having temporarily removed items that are in a different DataBasket.");
326                                }
327                            }
328                        }
329    
330                        List<T> lItems = getItemsContainer().get(sKey);
331                        if ((lItems != null) && (lItems.size() > 0)) {
332                            List<T> lRefIntegr = new LinkedList<T>(lItems);
333                            getRefIntegrItemsContainer().put(sKey, lRefIntegr);
334    
335                            for (Iterator<T> i = lRefIntegr.iterator(); i.hasNext(); ) {
336                                remove(i.next(), db);
337                            }
338                        }
339                    }
340                }
341    
342                public void noRemoveCatalogItem(CatalogChangeEvent<CT> e) {
343                    synchronized (getItemsLock()) {
344                        String sKey = e.getAffectedItem().getName();
345                        DataBasket db = e.getBasket();
346    
347                        List<T> lRefIntegr = getRefIntegrItemsContainer().remove(sKey);
348                        if (lRefIntegr != null) {
349                            for (Iterator<T> i = lRefIntegr.iterator(); i.hasNext(); ) {
350                                add(i.next(), db);
351                            }
352                        }
353                    }
354                }
355    
356                public void removedCatalogItem(CatalogChangeEvent<CT> e) {
357                    synchronized (getItemsLock()) {
358                        // clean up
359                        getRefIntegrItemsContainer().remove(e.getAffectedItem().getName());
360                    }
361                }
362    
363                public void commitedRemoveCatalogItem(CatalogChangeEvent<CT> e) {
364                    synchronized (getItemsLock()) {
365                        if (!((Catalog)e.getSource()).contains(e.getAffectedItem().getName(), e.getBasket())) {
366                            ciGoneForEver(e);
367                        }
368                    }
369                }
370    
371                @SuppressWarnings("unused")
372                            public void rollbackAddCatalogItem(CatalogChangeEvent<CT> e) {
373                    synchronized (getItemsLock()) {
374                        DataBasketCondition dbc = new DataBasketConditionImpl(CatalogItemImpl.
375                                CATALOG_ITEM_MAIN_KEY, e.getAffectedItem().getName(), (CatalogImpl)e.getSource(), null, null);
376    
377                        if (!e.getBasket().contains(dbc)) {
378                            ciGoneForEver(e);
379                        }
380                    }
381                }
382    
383                private void ciGoneForEver(CatalogChangeEvent<CT> e) {
384                    String sKey = e.getAffectedItem().getName();
385                    DataBasket db = e.getBasket();
386    
387                    if (getTemporaryAddedItemsContainer().containsKey(sKey)) {
388                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null,
389                                StoringStockImpl.this, null);
390    
391                        // Rollback all items temporarily added to this Stock
392                        // StoringStocks produce DataBasketEntries that may have both source and dest set,
393                        // so we must rollback only the destination part.
394                        for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
395                            ((StoringStockItemDBEntry)i.next()).rollbackDestination();
396                        }
397                    }
398    
399                    getItemsContainer().remove(sKey);
400                    getRefIntegrItemsContainer().remove(sKey);
401    
402                    if (getTemporaryRemovedItemsContainer().containsKey(sKey)) {
403                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
404                                StoringStockImpl.this, null, null);
405    
406                        // Commit all items temporaryly removed from this Stock
407                        // StoringStocks produce DataBasketEntries that may have both source and dest set,
408                        // so we must commit only the source part.
409                        for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
410                            ((StoringStockItemDBEntry)i.next()).commitSource();
411                        }
412                    }
413                }
414    
415                // The actual instance of the associated Catalog will have changed, for children of the given key that
416                // are Stocks.
417                public void editingCatalogItem(CatalogChangeEvent<CT> e) {
418                    relinkCatalog(e, STARTEDIT_ACTION);
419                }
420    
421                // The actual instance of the associated Catalog will have to be changed back.
422                public void rollbackEditCatalogItem(CatalogChangeEvent<CT> e) {
423                    relinkCatalog(e, ROLLBACK_ACTION);
424                }
425    
426                public void commitEditCatalogItem(CatalogChangeEvent<CT> e) {
427                    relinkCatalog(e, COMMIT_ACTION);
428                }
429    
430                void relinkCatalog(CatalogChangeEvent<CT> e, int nAction) {
431                    DataBasket db = e.getBasket();
432    
433                    synchronized (getItemsLock()) {
434                        List<T> l = getItemsContainer().get(e.getAffectedItem().getName());
435    
436                        if (l != null) {
437                            for (Iterator<T> i = l.iterator(); i.hasNext(); ) {
438                                StockItemImpl sii = i.next();
439    
440                                sii.relinkCatalog(db, nAction);
441                            }
442                        }
443    
444                        l = getTemporaryAddedItemsContainer().get(e.getAffectedItem().getName());
445    
446                        if (l != null) {
447                            for (Iterator<T> i = l.iterator(); i.hasNext(); ) {
448                                StockItemImpl sii = i.next();
449    
450                                sii.relinkCatalog(db, nAction);
451                            }
452                        }
453                    }
454                }
455    
456                @SuppressWarnings("unused")
457                            public void rollbackRemoveCatalogItem(CatalogChangeEvent<CT> e) {
458                    reEstablishStockCatalogLink(e.getAffectedItem().getName());
459                }
460            };
461        }
462    
463        /**
464         * Private helper function re-establishing the Stock-Catalog connection if any items in this Stock should be
465         * Stocks themselves.
466         *
467         * @override Never
468         */
469        private void reEstablishStockCatalogLink(String sKey) {
470            synchronized (getItemsLock()) {
471                List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
472                if (lAdded != null) {
473                    for (Iterator<T> i = lAdded.iterator(); i.hasNext(); ) {
474                        // we call setStock again, which for other Stocks will adjust their Catalog link accordingly.
475                        (i.next()).setStock(StoringStockImpl.this);
476                    }
477                }
478    
479                List<T> lItems = getItemsContainer().get(sKey);
480                if (lItems != null) {
481                    for (Iterator<T> i = lItems.iterator(); i.hasNext(); ) {
482                        // we call setStock again, which for other Stocks will adjust their Catalog link accordingly.
483                        (i.next()).setStock(StoringStockImpl.this);
484                    }
485                }
486            }
487        }
488    
489        // Stock interface methods
490    
491        /**
492         * Add an item to the Stock.
493         *
494         * <p>The item will only be visible to users of the same DataBasket. Only after a {@link DataBasket#commit}
495         * was performed on the DataBasket, the item will become visible to other users.</p>
496         *
497         * <p>A <code>addedStockItems</code> event will be fired.</p>
498         *
499         * @param si the item to be added.
500         * @param db the DataBasket relative to which the item will be added. Must be either <code>null</code> or a
501         * descendant of {@link DataBasketImpl}.
502         *
503         * @override Never
504         *
505         * @exception CatalogConflictException if the items key is not contained in the corresponding {@link Catalog}.
506         * @exception DataBasketConflictException if the item has already been added/removed using another DataBasket.
507         */
508        public void add(T si, DataBasket db) {
509            Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
510    
511            synchronized (oLock) {
512                synchronized (getItemsLock()) {
513                    if ((getCatalog(db) != null) && (!getCatalog(db).contains(si.getName(), db))) {
514                        throw new CatalogConflictException("Couldn't find key \"" + si.getName() +
515                                "\" in Catalog \"" + getCatalog(db).getName() + "\"");
516                    }
517    
518                    List<T> lAdded = getTemporaryAddedItemsContainer().get(si.getName());
519                    if ((lAdded != null) && (lAdded.contains(si))) {
520                        throw new DataBasketConflictException("Cannot add item that has already been added.");
521                    }
522    
523                    List<T> lItems = getItemsContainer().get(si.getName());
524                    if ((lItems != null) && (lItems.contains(si))) {
525                        return;
526                    }
527    
528                    List<T> lRemoved = getTemporaryRemovedItemsContainer().get(si.getName());
529                    if ((lRemoved != null) && (lRemoved.contains(si))) {
530    
531                        DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem((StockItem)lRemoved.
532                                get(lRemoved.indexOf(si)));
533    
534                        if ((db == null) || (!db.contains(dbc))) {
535                            throw new DataBasketConflictException(
536                                    "Cannot add item that was removed using a different DataBasket.");
537                        } else {
538                            DataBasketEntry<?> dbe = db.get(dbc);
539    
540                            if (dbe.getDestination() == null) {
541                                // just rollback the prior remove action!
542    
543                                db.rollback(dbc);
544    
545                                return;
546                            } else {
547                                throw new DataBasketConflictException(
548                                        "Cannot add item that was removed and added to another Stock!");
549                            }
550                        }
551                    }
552    
553                    // all checked, so add the stuff
554                    if (db != null) {
555                        if (lAdded == null) {
556                            lAdded = new LinkedList<T>();
557                            getTemporaryAddedItemsContainer().put(si.getName(), lAdded);
558                        }
559    
560                        lAdded.add(si);
561    
562                        // put information into databasket! Make sure there's only one DBE for each StockItem!
563                        DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(si);
564                        StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
565    
566                        if (sidbe != null) {
567                            db.exchange(sidbe, new StoringStockItemDBEntry((StoringStockImpl)sidbe.getSource(), this,
568                                    (StockItemImpl)si));
569                        } else {
570                            db.put(new StoringStockItemDBEntry(null, this, (StockItemImpl)si));
571                        }
572    
573                    } else {
574                        if (lItems == null) {
575                            lItems = new LinkedList<T>();
576                            getItemsContainer().put(si.getName(), lItems);
577                        }
578    
579                        lItems.add(si);
580                    }
581    
582                    m_nModCount++;
583    
584                    ((StockItemImpl)si).setStock(this);
585                    fireStockItemsAdded(new StoringStockChangeEvent<T, CT>(this, si, db));
586                }
587            }
588        }
589    
590        /**
591         * Iterate all items with a given key.
592         *
593         * <p>This method, together with {@link Stock#iterator} is the only way of accessing the individual
594         * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that have the
595         * specified key and are visible using the given DataBasket. Depending on the <code>fForEdit</code>
596         * parameter, the items will be retrieved in different ways. See {@link DataBasket} for an explanation of
597         * the different possibilities.</p>
598         *
599         * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if
600         * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into
601         * <code>UnSupportedOperationException</code>s.</p>
602         *
603         * @override Never
604         *
605         * @param sKey the key for which to retrieve the StockItems.
606         * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code>
607         * or a descendant of {@link DataBasketImpl}.
608         * @param fForEdit if true, the StockItems will be retrieved for editing.
609         */
610        public Iterator<T> get(final String sKey, final DataBasket db, final boolean fForEdit) {
611            final Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
612    
613            class I implements Iterator<T> {
614                private Iterator<T> m_iItems;
615                private int m_nExpectedModCount;
616    
617                private T m_siiCurrent;
618    
619                @SuppressWarnings("unchecked")
620                            public I() {
621                    super();
622    
623                    synchronized (oLock) {
624                        synchronized (getItemsLock()) {
625                            List<T> lItems = getItemsContainer().get(sKey);
626    
627                            if (lItems != null) {
628                                lItems = new LinkedList<T>(lItems);
629                            } else {
630                                lItems = new LinkedList<T>();
631                            }
632    
633                            if (db != null) {
634                                DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null,
635                                        StoringStockImpl.this, null);
636                                for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
637                                    DataBasketEntry dbe = i.next();
638    
639                                    lItems.add((T)dbe.getValue());
640                                }
641                            }
642    
643                            m_iItems = lItems.iterator();
644                            m_nExpectedModCount = m_nModCount;
645                        }
646                    }
647                }
648    
649                public boolean hasNext() {
650                    return m_iItems.hasNext();
651                }
652    
653                @SuppressWarnings("unchecked")
654                            public T next() {
655                    synchronized (oLock) {
656                        synchronized (getItemsContainer()) {
657                            if (m_nExpectedModCount != m_nModCount) {
658                                throw new ConcurrentModificationException();
659                            }
660    
661                            m_siiCurrent = (T)m_iItems.next();
662    
663                            if ((fForEdit) && (db != null)) {
664                                //if item is temporarily added, return it
665                                List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
666                                if ((lAdded != null) && (lAdded.contains(m_siiCurrent))) {
667                                    return m_siiCurrent;
668                                }
669    
670                                try {
671                                    fireCanEditStockItems(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
672                                            (T)m_siiCurrent, db));
673                                }
674                                catch (VetoException ve) {
675                                    return null;
676                                }
677                                //otherwise move item from mItems to mTemporaryRemoved
678                                List<T> lItems = getItemsContainer().get(sKey);
679                                lItems.remove(m_siiCurrent);
680                                if (lItems.size() == 0) {
681                                    getItemsContainer().remove(sKey);
682                                }
683    
684                                List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sKey);
685                                if (lRemoved == null) {
686                                    lRemoved = new LinkedList<T>();
687                                    getTemporaryRemovedItemsContainer().put(sKey, lRemoved);
688                                }
689                                lRemoved.add(m_siiCurrent);
690                                //clone item
691                                T siiRemoved = m_siiCurrent;
692                                m_siiCurrent = (T) siiRemoved.getShallowClone();
693                                //add clone to mTemporaryAdded and mEditingItems
694                                if (lAdded == null) {
695                                    lAdded = new LinkedList<T>();
696                                    getTemporaryAddedItemsContainer().put(sKey, lAdded);
697                                }
698                                lAdded.add(m_siiCurrent);
699    
700                                List<T> lEdit = getEditingItemsContainer().get(sKey);
701                                if (lEdit == null) {
702                                    lEdit = new LinkedList<T>();
703                                    getEditingItemsContainer().put(sKey, lEdit);
704                                }
705                                lEdit.add(m_siiCurrent);
706    
707                                siiRemoved.setStock(null);
708                                m_siiCurrent.setStock(StoringStockImpl.this);
709    
710                                // put information into databasket, making sure there's only one entry per StockItem
711                                DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(siiRemoved);
712                                StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
713    
714                                if (sidbe != null) {
715                                    db.exchange(sidbe, new StoringStockItemDBEntry(StoringStockImpl.this,
716                                            (StoringStockImpl)sidbe.getDestination(), siiRemoved));
717                                } else {
718                                    db.put(new StoringStockItemDBEntry(StoringStockImpl.this, null, siiRemoved));
719                                }
720    
721                                db.put(new StoringStockItemDBEntry(null, StoringStockImpl.this, m_siiCurrent));
722    
723                                fireEditingStockItems(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
724                                        m_siiCurrent, db));
725                                fireStockItemsRemoved(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
726                                        siiRemoved, db));
727                                fireStockItemsAdded(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
728                                        m_siiCurrent, db));
729    
730                                //Allows only ONE iterator at a time to call next() with fForEdit enabled
731                                //because this method adds and removes StockItems, so other iterators have to
732                                //be informed via the increased modifiaction counter
733                                m_nExpectedModCount = (++m_nModCount);
734                            }
735    
736                            return m_siiCurrent;
737                        }
738                    }
739                }
740    
741                public void remove() {
742                    synchronized (oLock) {
743                        synchronized (getItemsLock()) {
744                            if (m_nModCount != m_nExpectedModCount) {
745                                throw new ConcurrentModificationException();
746                            }
747    
748                            if (m_siiCurrent == null) {
749                                throw new IllegalStateException();
750                            }
751    
752                            try {
753                                StoringStockImpl.this.remove(m_siiCurrent, db);
754    
755                                m_nExpectedModCount = m_nModCount;
756    
757                                m_siiCurrent = null;
758                            }
759                            catch (VetoException ve) {
760                                m_siiCurrent = null;
761    
762                                throw new UnsupportedOperationException("VETO: " + ve);
763                            }
764                        }
765                    }
766                }
767            }
768    
769            if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
770                return new Iterator<T>() {
771                    public boolean hasNext() {
772                        return false;
773                    }
774    
775                    public T next() {
776                        throw new NoSuchElementException();
777                    }
778    
779                    public void remove() {}
780                };
781            }
782    
783            return new I();
784        }
785    
786        /**
787         * Count the StockItems with a given key that are visible using a given DataBasket.
788         *
789         * @override Never
790         *
791         * @param sKey the key for which to count the StockItems.
792         * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a
793         * descendant of {@link DataBasketImpl}.
794         */
795        public int countItems(String sKey, DataBasket db) {
796            Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
797    
798            synchronized (oLock) {
799                synchronized (getItemsLock()) {
800                    if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
801                        return 0;
802                    }
803    
804                    int nCount = 0;
805    
806                    List<T> lItems = getItemsContainer().get(sKey);
807    
808                    if (lItems != null) {
809                        nCount += lItems.size();
810                    }
811    
812                    if ((getTemporaryAddedItemsContainer().containsKey(sKey)) && (db != null)) {
813                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null);
814                        BasketEntryValue bev = BasketEntryValues.COUNT_ITEMS;
815                        IntegerValue ivCount = new IntegerValue(0);
816    
817                        db.sumBasket(dbc, bev, ivCount);
818    
819                        nCount += ivCount.getValue().intValue();
820                    }
821    
822                    return nCount;
823                }
824            }
825        }
826    
827        /**
828         * Remove one StockItem with the specified key from the Stock.
829         *
830         * <p>If there are any StockItems with the specified key, one will be removed. There is no guarantee as to
831         * which StockItem will be removed. The removed item, if any, will be returned.</p>
832         *
833         * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
834         *
835         * @override Never
836         *
837         * @param sKey the key for which to remove an item.
838         * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
839         * descendant of {@link DataBasketImpl}.
840         *
841         * @return the removed item
842         *
843         * @exception VetoException if a listener vetoed the removal.
844         * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
845         * usage.
846         */
847        @SuppressWarnings("unchecked")
848            public StockItem remove(String sKey, DataBasket db) throws VetoException {
849            Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
850    
851            synchronized (oLock) {
852                synchronized (getItemsLock()) {
853                    List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
854    
855                    if ((lAdded != null) && (db != null)) {
856                        DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null);
857    
858                        StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
859    
860                        if (sidbe != null) {
861                            //remove and return first temporarily added item
862                            return remove((T)sidbe.getValue(), db);
863                        }
864                    }
865    
866                    List<T> lItems = getItemsContainer().get(sKey);
867    
868                    if (lItems != null) {
869                        /*
870                         * 06/27/2000-STEFFEN: Had to add checking for lItems.size here, as apparently I sometimes
871                         * keep the vector even if it is empty.
872                         * I don't think, it should do that, but I need to check again.
873                         * Checked, apparently remove (si, db) also doesn't clean up the list. This is pretty memory
874                         * ineffective, but needs some effort to fix it. For the moment, just worked around it.
875                         */
876                        if (lItems.size() > 0) {
877                            //remove and return last added item
878                            return remove(lItems.get(lItems.size() - 1), db);
879                        }
880                    }
881                }
882            }
883    
884            return null;
885        }
886    
887        /**
888         * Remove the given StockItem from the Stock.
889         *
890         * <p>If the given StockItem is contained in the Stock, it will be removed. The removed item, if any, will
891         * be returned.</p>
892         *
893         * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
894         *
895         * @override Never
896         *
897         * @param si the StockItem to be removed.
898         * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
899         * descendant of {@link DataBasketImpl}.
900         *
901         * @return the removed item
902         *
903         * @exception VetoException if a listener vetoed the removal.
904         * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
905         * usage.
906         */
907        public T remove(T si, DataBasket db) throws VetoException {
908            Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
909    
910            synchronized (oLock) {
911                synchronized (getItemsLock()) {
912                    List<T> lAdded = getTemporaryAddedItemsContainer().get(si.getName());
913                    if ((lAdded != null) && (lAdded.contains(si))) {
914                        DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem((StockItem)lAdded.get(
915                                lAdded.indexOf(si)));
916    
917                        StockItemDBEntry sidbe = null;
918    
919                        if (db != null) {
920                            sidbe = (StockItemDBEntry)db.get(dbc);
921                        }
922    
923                        if (sidbe == null) {
924                            throw new DataBasketConflictException(
925                                    "Cannot remove StockItem that was added using a different DataBasket!");
926                        } else {
927                            fireCanRemoveStockItems(new StoringStockChangeEvent<T, CT>(this, si, db));
928    
929                            if (sidbe.getSource() == null) {
930                                db.rollback(dbc);
931                            } else {
932                                // remove only the destination part of it:
933                                db.exchange(sidbe, new StoringStockItemDBEntry((StoringStockImpl)sidbe.getSource(), null,
934                                        (StockItemImpl)sidbe.getValue()));
935    
936                                si = lAdded.get(lAdded.indexOf(si));
937                                lAdded.remove(si);
938    
939                                m_nModCount++;
940    
941                                fireStockItemsAddRollback(new StoringStockChangeEvent<T, CT>(this, si, db));
942                            }
943    
944                            ((StockItemImpl)si).setStock(null);
945    
946                            return si;
947                        }
948                    }
949    
950                    List<T> lRemoved = getTemporaryRemovedItemsContainer().get(si.getName());
951                    if ((lRemoved != null) && (lRemoved.contains(si))) {
952                        throw new DataBasketConflictException(
953                                "Cannot remove an item that has already been removed!");
954                    }
955    
956                    List<T> lItems = getItemsContainer().get(si.getName());
957                    if ((lItems == null) || (!lItems.contains(si))) {
958                        return null;
959                    }
960    
961                    // remove from items container, making sure there's always only one DataBasket entry for each stockitem
962    
963                    fireCanRemoveStockItems(new StoringStockChangeEvent<T, CT>(this,
964                            lItems.get(lItems.indexOf(si)), db));
965    
966                    si = lItems.get(lItems.indexOf(si));
967                    lItems.remove(si);
968    
969                    if (db != null) {
970                        if (lRemoved == null) {
971                            lRemoved = new LinkedList<T>();
972                            getTemporaryRemovedItemsContainer().put(si.getName(), lRemoved);
973                        }
974    
975                        lRemoved.add(si);
976    
977                        DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(si);
978                        StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
979    
980                        if (sidbe != null) {
981                            db.exchange(sidbe, new StoringStockItemDBEntry(this,
982                                    (StoringStockImpl)sidbe.getDestination(), (StockItemImpl)si));
983                        } else {
984                            db.put(new StoringStockItemDBEntry(this, null, (StockItemImpl)si));
985                        }
986                    }
987    
988                    m_nModCount++;
989    
990                    ((StockItemImpl)si).setStock(null);
991                    fireStockItemsRemoved(new StoringStockChangeEvent<T, CT>(this, si, db));
992    
993                    return si;
994                }
995            }
996        }
997    
998        // StockImpl methods
999    
1000        /**
1001         * Overridden to accomodate for specific usage of memory.
1002         *
1003         * @override Never
1004         */
1005        protected void fillShallowClone(StoringStockImpl<T, CT> stiClone) {
1006    
1007            synchronized (getItemsLock()) {
1008                synchronized (stiClone.getItemsLock()) {
1009                    stiClone.setItemsContainer(new HashMap<String, List<T>>());
1010                    for (Iterator i = getItemsContainer().keySet().iterator(); i.hasNext(); ) {
1011                        String sKey = (String)i.next();
1012                        stiClone.getItemsContainer().put(sKey, new LinkedList<T>(getItemsContainer().get(sKey)));
1013                        // shallow clone of LinkedList
1014                    }
1015                    stiClone.setTemporaryAddedItemsContainer(new HashMap<String, List<T>>());
1016                    for (Iterator i = getTemporaryAddedItemsContainer().keySet().iterator(); i.hasNext(); ) {
1017                        String sKey = (String)i.next();
1018                        stiClone.getTemporaryAddedItemsContainer().put(sKey, new LinkedList<T>(getTemporaryAddedItemsContainer().get(sKey)));
1019    
1020                    }
1021                    stiClone.setTemporaryRemovedItemsContainer(new HashMap<String, List<T>>());
1022                    for (Iterator i = getTemporaryRemovedItemsContainer().keySet().iterator(); i.hasNext(); ) {
1023                        String sKey = (String)i.next();
1024                        stiClone.getTemporaryRemovedItemsContainer().put(sKey, new LinkedList<T>(getTemporaryRemovedItemsContainer().get(sKey)));
1025    
1026                    }
1027                    stiClone.setEditingItemsContainer(new HashMap<String, List<T>>());
1028                    for (Iterator i = getEditingItemsContainer().keySet().iterator(); i.hasNext(); ) {
1029                        String sKey = (String)i.next();
1030                        stiClone.getEditingItemsContainer().put(sKey, new LinkedList<T>(getEditingItemsContainer().get(sKey)));
1031    
1032                    }
1033                    stiClone.setRefIntegrItemsContainer(new HashMap<String, List<T>>());
1034                    for (Iterator i = getRefIntegrItemsContainer().keySet().iterator(); i.hasNext(); ) {
1035                        String sKey = (String)i.next();
1036                        stiClone.getRefIntegrItemsContainer().put(sKey, new LinkedList<T>(getRefIntegrItemsContainer().get(sKey)));
1037    
1038                    }
1039                    stiClone.setRefIntegrEditContainer(new HashMap<String, String>());
1040                    for (Iterator<String> i = getRefIntegrEditContainer().keySet().iterator(); i.hasNext(); ) {
1041                        String sKey = i.next();
1042                        stiClone.getRefIntegrEditContainer().put(sKey, getRefIntegrEditContainer().get(sKey));
1043                    }
1044                }
1045            }
1046        }
1047    
1048        /**
1049         * @override Always
1050         */
1051        protected StockImpl<List<T>, T, CT> createPeer() {
1052            StoringStockImpl<T, CT> ssiPeer = new StoringStockImpl<T, CT>(getName(), m_ciCatalog);
1053            ssiPeer.m_dbCatalogValidator = m_dbCatalogValidator;
1054    
1055            return ssiPeer;
1056        }
1057    
1058        /**
1059         * Set the Stock and adjust the Catalog link for all Stocks that are contained in this Stock.
1060         *
1061         * @override Never
1062         */
1063        protected void setStock(StockImpl sti) {
1064            super.setStock(sti);
1065    
1066            if (sti != null) {
1067                synchronized (getItemsLock()) {
1068                    Set<String> stKeys = getItemsContainer().keySet();
1069                    stKeys.addAll(getTemporaryAddedItemsContainer().keySet());
1070    
1071                    for (Iterator<String> i = stKeys.iterator(); i.hasNext(); ) {
1072                        reEstablishStockCatalogLink(i.next());
1073                    }
1074                }
1075            }
1076        }
1077    
1078        /**
1079         * Helper method used to maintain StockImpl - CatalogImpl links in nested Stocks/Catalogs. For internal use only.
1080         *
1081         * @param db the DataBasket that is protecting this activity.
1082         * @param nAction the action that occurred. Can be either {@link #COMMIT_ACTION}, {@link #ROLLBACK_ACTION},
1083         * {@link #STARTEDIT_ACTION}.
1084         */
1085        void relinkCatalog(DataBasket db, int nAction) {
1086            super.relinkCatalog(db, nAction);
1087    
1088            if (nAction == ROLLBACK_ACTION) {
1089                // Additionally refresh the links in all child stocks.
1090                synchronized (getItemsLock()) {
1091                    for (Iterator<List<T>> i = getItemsContainer().values().iterator(); i.hasNext(); ) {
1092                        List<T> l = i.next();
1093    
1094                        for (Iterator<T> j = l.iterator(); j.hasNext(); ) {
1095                            StockItemImpl sii = j.next();
1096    
1097                            sii.relinkCatalog(db, nAction);
1098                        }
1099                    }
1100    
1101                    for (Iterator<List<T>> i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext(); ) {
1102                        List<T> l = i.next();
1103    
1104                        for (Iterator<T> j = l.iterator(); j.hasNext(); ) {
1105                            StockItemImpl sii = j.next();
1106    
1107                            sii.relinkCatalog(db, nAction);
1108                        }
1109                    }
1110                }
1111            }
1112        }
1113    
1114        // SelfManagingDBESource interface methods
1115    
1116        /**
1117         * Commit the removal of a StockItem.
1118         *
1119         * <p>A <code>commitRemoveStockItems</code> will be fired.</p>
1120         *
1121         * @override Never
1122         */
1123        public void commitRemove(DataBasket db, DataBasketEntry<T> dbe) {
1124            // DataBasket is already locking on its monitor so we just lock on ours
1125            synchronized (getItemsLock()) {
1126                T sii = dbe.getValue();
1127    
1128                List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
1129                if (lRemoved != null) {
1130                    lRemoved.remove(sii);
1131    
1132                    if (lRemoved.size() == 0) {
1133                        getTemporaryRemovedItemsContainer().remove(sii.getName());
1134                    }
1135    
1136                    if (sii.getStock().equals(this)) {
1137                        sii.setStock(null);
1138                    }
1139                    fireStockItemsRemoveCommit(new StoringStockChangeEvent<T, CT>(this, sii, db));
1140                }
1141            }
1142        }
1143    
1144        /**
1145         * Rollback the removal of a StockItem.
1146         *
1147         * <p>A <code>rollbackRemoveStockItems</code> will be fired. Also, the Stock will try to make sure, that
1148         * a corresponding CatalogItem exists.</p>
1149         *
1150         * @override Never
1151         */
1152        public void rollbackRemove(DataBasket db, DataBasketEntry<T> dbe) {
1153            synchronized (getItemsLock()) {
1154                prepareReferentialIntegrity(db, dbe);
1155    
1156                T sii = dbe.getValue();
1157    
1158                List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
1159                if (lRemoved != null) {
1160                    lRemoved.remove(sii);
1161    
1162                    if (lRemoved.size() == 0) {
1163                        getTemporaryRemovedItemsContainer().remove(sii.getName());
1164                    }
1165    
1166                    List<T> lItems = getItemsContainer().get(sii.getName());
1167                    if (lItems == null) {
1168                        lItems = new LinkedList<T>();
1169                        getItemsContainer().put(sii.getName(), lItems);
1170                    }
1171    
1172                    lItems.add(sii);
1173    
1174                    sii.setStock(this);
1175    
1176                    m_nModCount++;
1177    
1178                    fireStockItemsRemoveRollback(new StoringStockChangeEvent<T, CT>(this, sii, db));
1179                }
1180            }
1181        }
1182    
1183        // SelfManagingDBEDestination interface methods
1184    
1185        /**
1186         * Commit the adding of a StockItem.
1187         *
1188         * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
1189         * fired as a consequence of this method. Also, the Stock will try to make sure, that a corresponding
1190         * CatalogItem exists.</p>
1191         *
1192         * @override Never
1193         */
1194        public void commitAdd(DataBasket db, DataBasketEntry<T> dbe) {
1195            synchronized (getItemsLock()) {
1196                prepareReferentialIntegrity(db, dbe);
1197    
1198                T sii = dbe.getValue();
1199    
1200                List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
1201                if (lAdded != null) {
1202                    lAdded.remove(sii);
1203    
1204                    if (lAdded.size() == 0) {
1205                        getTemporaryAddedItemsContainer().remove(sii.getName());
1206                    }
1207    
1208                    List<T> lItems = getItemsContainer().get(sii.getName());
1209                    if (lItems == null) {
1210                        lItems = new LinkedList<T>();
1211                        getItemsContainer().put(sii.getName(), lItems);
1212                    }
1213    
1214                    lItems.add(sii);
1215    
1216                    sii.setStock(this);
1217    
1218                    m_nModCount++;
1219    
1220                    fireStockItemsAddCommit(new StoringStockChangeEvent<T, CT>(this, sii, db));
1221    
1222                    List<T> lEdit = getEditingItemsContainer().get(sii.getName());
1223                    if ((lEdit != null) && (lEdit.remove(sii))) {
1224                        if (lEdit.size() == 0) {
1225                            getEditingItemsContainer().remove(sii.getName());
1226                        }
1227    
1228                        fireStockItemsEditCommit(new StoringStockChangeEvent<T, CT>(this, sii, db));
1229                    }
1230                }
1231            }
1232        }
1233    
1234        /**
1235         * Rollback the adding of a StockItem.
1236         *
1237         * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
1238         * fired as a consequence of this method.</p>
1239         *
1240         * @override Never
1241         */
1242            public void rollbackAdd(DataBasket db, DataBasketEntry<T> dbe) {
1243            synchronized (getItemsLock()) {
1244                T sii = dbe.getValue();
1245    
1246                List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
1247                if (lAdded != null) {
1248                    lAdded.remove(sii);
1249    
1250                    if (lAdded.size() == 0) {
1251                        getTemporaryAddedItemsContainer().remove(sii.getName());
1252                    }
1253    
1254                    if (sii.getStock().equals(this)) {
1255                        sii.setStock(null);
1256                    }
1257    
1258                    m_nModCount++;
1259    
1260                    fireStockItemsAddRollback(new StoringStockChangeEvent<T, CT>(this, sii, db));
1261    
1262                    List<T> lEdit = getEditingItemsContainer().get(sii.getName());
1263                    if ((lEdit != null) && (lEdit.remove(sii))) {
1264                        if (lEdit.size() == 0) {
1265                            getEditingItemsContainer().remove(sii.getName());
1266                        }
1267    
1268                        fireStockItemsEditRollback(new StoringStockChangeEvent<T, CT>(this, sii, db));
1269                    }
1270                }
1271            }
1272        }
1273    }