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