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