001    package data.ooimpl;
002    
003    import java.beans.PropertyChangeEvent;
004    import java.beans.PropertyChangeListener;
005    import java.io.IOException;
006    import java.io.ObjectInputStream;
007    import java.io.ObjectOutputStream;
008    import java.util.ConcurrentModificationException;
009    import java.util.HashMap;
010    import java.util.HashSet;
011    import java.util.Iterator;
012    import java.util.Map;
013    import java.util.NoSuchElementException;
014    import java.util.Set;
015    import java.util.TreeSet;
016    
017    import util.Debug;
018    import util.ListenerHelper;
019    import util.SerializableListener;
020    import data.Catalog;
021    import data.CatalogConflictException;
022    import data.CatalogItem;
023    import data.CatalogItemValue;
024    import data.DataBasket;
025    import data.DataBasketCondition;
026    import data.DataBasketConditionImpl;
027    import data.DataBasketConflictException;
028    import data.DataBasketEntry;
029    import data.ListenableStock;
030    import data.NameContext;
031    import data.NameContextException;
032    import data.Stock;
033    import data.StockFromValueCreator;
034    import data.StockIdentifier;
035    import data.StockItem;
036    import data.Value;
037    import data.events.CatalogChangeAdapter;
038    import data.events.CatalogChangeEvent;
039    import data.events.CatalogChangeListener;
040    import data.events.StockChangeAdapter;
041    import data.events.StockChangeEvent;
042    import data.events.StockChangeListener;
043    import data.events.VetoException;
044    
045    /**
046     * Pure Java implementation of the {@link Stock} interface.
047     *
048     * <p>StockImpl Stocks can only work together with {@link CatalogImpl} {@link Catalog Catalogs},
049     * {@link StockItemImpl} {@link StockItem StockItems} and {@link DataBasketImpl}
050     * {@link DataBasket DataBaskets}.</p>
051     *
052     * @author Steffen Zschaler
053     * @version 2.0 19/08/1999
054     * @since v2.0
055     */
056    public abstract class StockImpl<T, ST extends StockItemImpl, CT extends CatalogItemImpl> 
057                          extends StockItemImpl 
058                          implements Stock<ST, CT>, ListenableStock<ST, CT>, NameContext,
059                                     SelfManagingDBESource<ST>, SelfManagingDBEDestination<ST> {
060    
061        /**
062         * The Catalog that is associated to this Stock.
063         *
064         * @serial
065         */
066        protected CatalogImpl<CT> m_ciCatalog;
067    
068        /**
069         * The DataBasket that determines the visibility of the catalog associated with this Stock.
070         *
071         * <p>If <code>null</code>, the associated Catalog is visible with any DataBasket, otherwise it
072         * is only visible with the DataBasket specified in this field. Requests for the Catalog using
073         * a different DataBasket will the be rejected by throwing a DataBasketConflictException.</p>
074         *
075         * @serial
076         */
077        protected DataBasket m_dbCatalogValidator;
078    
079        /**
080         * The listeners listening for events from this Stock.
081         *
082         * @serial
083         */
084        protected ListenerHelper m_lhListeners = new ListenerHelper();
085    
086        /**
087         * The map of items that are actually contained in the Stock.
088         *
089         * @serial
090         */
091        private Map<String, T> m_mpItems = new HashMap<String, T>();
092    
093        /**
094         * The map of items that have been temporaryly added to the Stock.
095         *
096         * @serial
097         */
098        private Map<String, T> m_mpTemporaryAddedItems = new HashMap<String, T>();
099    
100        /**
101         * The map of items that have been temporaryly removed from the Stock.
102         *
103         * @serial
104         */
105        private Map<String, T> m_mpTemporaryRemovedItems = new HashMap<String, T>();
106    
107        /**
108         * The map of items that are currently being edited.
109         *
110         * @serial
111         */
112        private Map<String, T> m_mpEditingItems = new HashMap<String, T>();
113    
114        /**
115         * The map of items that have been removed from the Stock to ensure referential integrity.
116         *
117         * @serial
118         */
119        private Map<String, T> m_mpRefIntegrItems = new HashMap<String, T>();
120    
121        /**
122         * A map storing information about name changes in CatalogItems.
123         *
124         * <p>Key: new name.<br>
125         * Value: old name.</p>
126         *
127         * <p>This map is needed for referential integrity.</p>
128         *
129         * @serial
130         */
131        private Map<String, String> m_mpRefIntegrEdit = new HashMap<String, String>(); // stores information about name changes in CatalogItems: key: new name; entry: old name
132    
133        /**
134         * The monitor synchronizing access to the Stock's contents.
135         */
136        private transient Object m_oItemsLock;
137    
138        /**
139         * Get the monitor synchronizing access to the Stock's contents.
140         *
141         * @override Never
142         */
143        protected final Object getItemsLock() {
144            if (m_oItemsLock == null) {
145                m_oItemsLock = new Object();
146            }
147    
148            return m_oItemsLock;
149        }
150    
151        /**
152         * PropertyChangeListener that reacts to name changes in CatalogItems that are currently being edited.
153         * Updates {@link #getRefIntegrEditContainer}.
154         *
155         * @author Steffen Zschaler
156         * @version 2.0 19/08/1999
157         * @since v2.0
158         */
159        class CatalogItemNameListener implements PropertyChangeListener, SerializableListener {
160            /**
161                     * ID for serialization.
162                     */
163                    private static final long serialVersionUID = 5399016682356439452L;
164    
165                    /**
166             * @override Never Instead override {@link #nameChangeOccurred}.
167             */
168            public void propertyChange(PropertyChangeEvent e) {
169                if (e.getPropertyName().equals(NAME_PROPERTY)) {
170                    synchronized (getItemsLock()) {
171                        String sOld = (String)e.getOldValue();
172                        String sNew = (String)e.getNewValue();
173    
174                        nameChangeOccurred(sOld, sNew);
175                    }
176                }
177            }
178    
179            /**
180             * Notification that a CatalogItem's name changed.
181             *
182             * <p>This will update all data structures necessary to ensure Stock and Catalog are properly linked
183             * together.</p>
184             *
185             * @override Sometimes
186             */
187                    protected void nameChangeOccurred(String sOld, String sNew) {
188                T data = getTemporaryAddedItemsContainer().remove(sOld);
189                if (data != null) {
190                    getTemporaryAddedItemsContainer().put(sNew, data);
191                }
192    
193                data = getItemsContainer().remove(sOld);
194                if (data != null) {
195                    getItemsContainer().put(sNew, data);
196                }
197    
198                data = getTemporaryRemovedItemsContainer().remove(sOld);
199                if (data != null) {
200                    getTemporaryRemovedItemsContainer().put(sNew, data);
201                }
202    
203                data = getRefIntegrItemsContainer().remove(sOld);
204                if (data != null) {
205                    getRefIntegrItemsContainer().put(sNew, data);
206                }
207    
208                getRefIntegrEditContainer().put(sNew, getRefIntegrEditContainer().remove(sOld));
209            }
210        }
211    
212        /**
213         * The listener that listens to name changes in CatalogItems in the associated Catalog.
214         *
215         * @serial
216         */
217        protected CatalogItemNameListener m_cinlCatalogItemNameListener = new CatalogItemNameListener();
218    
219        /**
220         * Listens for editing events from the Catalog. Installs {@link #m_cinlCatalogItemNameListener} if
221         * necessary. Also prevents editing, if there are StockItems in DataBaskets, so that we don't get any
222         * problems with name changes. Note however, that it might be possible to first edit a CatalogItem and then
223         * remove corresponding StockItems which might cause the same problem! Preventing that is not provided by
224         * the Framework.
225         *
226         * @serial
227         */
228        protected final CatalogChangeListener m_cclEditListener = new CatalogChangeAdapter() {
229    
230                    private static final long serialVersionUID = 9082408669231392420L;
231    
232                    public void canEditCatalogItem(CatalogChangeEvent e) throws VetoException {
233                synchronized (getItemsLock()) {
234                    String sKey = e.getAffectedItem().getName();
235    
236                    if ((getTemporaryAddedItemsContainer().containsKey(sKey)) ||
237                            (getTemporaryRemovedItemsContainer().containsKey(sKey))) {
238                        throw new VetoException("Stock \"" + getName() + "\": Having StockItems with key \"" +
239                                sKey + "\" in DataBaskets.");
240                    }
241                }
242            }
243    
244            public void editingCatalogItem(CatalogChangeEvent e) {
245                synchronized (getItemsLock()) {
246                    String sKey = e.getAffectedItem().getName();
247                    getRefIntegrEditContainer().put(sKey, sKey);
248    
249                    ((CatalogItemImpl)e.getAffectedItem()).addNameListener(m_cinlCatalogItemNameListener);
250                }
251            }
252    
253                    public void rollbackEditCatalogItem(CatalogChangeEvent e) {
254                synchronized (getItemsLock()) {
255                    String sCurKey = e.getAffectedItem().getName();
256                    String sKey = (String)getRefIntegrEditContainer().remove(sCurKey);
257    
258                    T data = getTemporaryAddedItemsContainer().remove(sCurKey);
259                    if (data != null) {
260                        getTemporaryAddedItemsContainer().put(sKey, data);
261                    }
262    
263                    data = getItemsContainer().remove(sCurKey);
264                    if (data != null) {
265                        getItemsContainer().put(sKey, data);
266                    }
267    
268                    data = getTemporaryRemovedItemsContainer().remove(sCurKey);
269                    if (data != null) {
270                        getTemporaryRemovedItemsContainer().put(sKey, data);
271                    }
272    
273                    data = getRefIntegrItemsContainer().remove(sCurKey);
274                    if (data != null) {
275                        getRefIntegrItemsContainer().put(sKey, data);
276                    }
277    
278                    ((CatalogItemImpl)e.getAffectedItem()).removeNameListener(m_cinlCatalogItemNameListener);
279                }
280            }
281    
282            public void commitEditCatalogItem(CatalogChangeEvent e) {
283                synchronized (getItemsLock()) {
284                    // clean up
285                    getRefIntegrEditContainer().remove(e.getAffectedItem().getName());
286    
287                    ((CatalogItemImpl)e.getAffectedItem()).removeNameListener(m_cinlCatalogItemNameListener);
288                }
289            }
290        };
291    
292        /**
293         * The original Stock, if this is a shallow copy that was created for editing purposes. A SoftReference is
294         * used so that it can be removed when the last reference to the creator was deleted.
295         */
296        private transient java.lang.ref.SoftReference<StockImpl> m_srsiEditCreator = null;
297    
298        /**
299         * Listens to the parent Stock of the creator, if this is a shallow copy that was created for editing
300         * purposes. It will cut the connection if the editing is either commited or rolled back.
301         *
302         * @serial
303         */
304        @SuppressWarnings("unused")
305            private final StockChangeListener m_sclEditingListener = new StockChangeAdapter<ST, CT>() {
306    
307            private static final long serialVersionUID = 9100060380733555123L;
308    
309                    public void commitEditStockItems(StockChangeEvent<ST, CT> e) {
310                if (e.getAffectedKey() == getName()) {
311                    for (Iterator<ST> i = e.getAffectedItems(); i.hasNext(); ) {
312                        if (i.next() == StockImpl.this) {
313                            ((StockImpl)e.getSource()).removeStockChangeListener(this); return;
314                        }
315                    }
316                }
317            }
318    
319            public void rollbackEditStockItems(StockChangeEvent<ST, CT> e) {
320                if (e.getAffectedKey() == getName()) {
321                    for (Iterator<ST> i = e.getAffectedItems(); i.hasNext(); ) {
322                        if (i.next() == StockImpl.this) {
323                            ((StockImpl)e.getSource()).removeStockChangeListener(this);
324    
325                            ((StockImpl)m_srsiEditCreator.get()).removeStockChangeListener(
326                                    m_sclEditCreatorListener); m_srsiEditCreator = null;
327    
328                            return;
329                        }
330                    }
331                }
332            }
333        };
334    
335        /**
336         * Listens to the creator, if this is a shallow copy that was created for editing purposes. This is to
337         * follow with any events that the creator might trigger.
338         *
339         * @serial
340         */
341        protected StockChangeListener m_sclEditCreatorListener;
342    
343        /**
344         * First writes the default serializable data and then the reference to the edit creator, if any.
345         */
346        private void writeObject(ObjectOutputStream oos) throws IOException {
347            oos.defaultWriteObject();
348    
349            if (m_srsiEditCreator != null) {
350                oos.writeObject(m_srsiEditCreator.get());
351            } else {
352                oos.writeObject(null);
353            }
354        }
355    
356        /**
357         * First reads the default serializable data and then re-establishes the reference to the edit creator, if
358         * any.
359         */
360        private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
361            ois.defaultReadObject();
362    
363            Object oEditCreator = ois.readObject();
364    
365            if (oEditCreator != null) {
366                m_srsiEditCreator = new java.lang.ref.SoftReference<StockImpl>((StockImpl)oEditCreator);
367            }
368        }
369    
370        /**
371         * Create a new StockImpl.
372         *
373         * @param sName the name of the new Stock.
374         * @param ciRef the Catalog that is referenced by this Stock.
375         */
376        public StockImpl(String sName, CatalogImpl<CT> ciRef) {
377            super(sName);
378    
379            if (ciRef == null) {
380                throw new NullPointerException("Catalog must not be null!");
381            }
382    
383            internalSetCatalog(ciRef);
384        }
385        
386        /**
387         * Create a new StockImpl.
388         *
389         * @param siId  the id of the new Stock.
390         * @param ciRef the Catalog that is referenced by this Stock.
391         */
392        public StockImpl(StockIdentifier<ST, CT> siId, CatalogImpl<CT> ciRef) {
393            this(siId.getName(), ciRef);
394        }
395    
396        /**
397         * Set the Catalog that this Stock refers to. This method is only used internally.
398         *
399         * <p>{@link #m_dbCatalogValidator} will be set to <code>null</code>.
400         *
401         * @param ciRef the Catalog to refer to from now on.
402         *
403         * @override Never
404         */
405        protected void internalSetCatalog(CatalogImpl<CT> ciRef) {
406            if (m_ciCatalog != null) {
407                m_ciCatalog.removeCatalogChangeListener(m_cclEditListener);
408            }
409    
410            m_ciCatalog = ciRef;
411    
412            if (m_ciCatalog != null) {
413                m_ciCatalog.addCatalogChangeListener(m_cclEditListener);
414            }
415    
416            m_dbCatalogValidator = null;
417        }
418    
419        /**
420         * Get the map of items that are actually contained in the Stock.
421         *
422         * @override Never
423         */
424        protected Map<String, T> getItemsContainer() {
425            return m_mpItems;
426        }
427    
428        /**
429         * Get the map of items that have been temporaryly added to the Stock.
430         *
431         * @override Never
432         */
433        protected Map<String, T> getTemporaryAddedItemsContainer() {
434            return m_mpTemporaryAddedItems;
435        }
436    
437        /**
438         * Get the map of items that have been temporaryly removed from the Stock.
439         *
440         * @override Never
441         */
442        protected Map<String, T> getTemporaryRemovedItemsContainer() {
443            return m_mpTemporaryRemovedItems;
444        }
445    
446        /**
447         * Get the map of items that are currently being edited.
448         *
449         * @override Never
450         */
451        protected Map<String, T> getEditingItemsContainer() {
452            return m_mpEditingItems;
453        }
454    
455        /**
456         * Get the map of items that have been removed from the Stock to ensure referential integrity.
457         *
458         * @override Never
459         */
460        protected Map<String, T> getRefIntegrItemsContainer() {
461            return m_mpRefIntegrItems;
462        }
463    
464        /**
465         * Get the map storing information about name changes in CatalogItems.
466         *
467         * @override Never
468         */
469        protected Map<String, String> getRefIntegrEditContainer() {
470            return m_mpRefIntegrEdit;
471        }
472    
473        /**
474         * Set the map of items that are actually contained in the Stock.
475         *
476         * @override Never
477         */
478        protected void setItemsContainer(Map<String, T> mp) {
479            m_mpItems = mp;
480        }
481    
482        /**
483         * Set the map of items that have been temporaryly added to the Stock.
484         *
485         * @override Never
486         */
487        protected void setTemporaryAddedItemsContainer(Map<String, T> mp) {
488            m_mpTemporaryAddedItems = mp;
489        }
490    
491        /**
492         * Set the map of items that have been temporaryly removed from the Stock.
493         *
494         * @override Never
495         */
496        protected void setTemporaryRemovedItemsContainer(Map<String, T> mp) {
497            m_mpTemporaryRemovedItems = mp;
498        }
499    
500        /**
501         * Set the map of items that are currently being edited.
502         *
503         * @override Never
504         */
505        protected void setEditingItemsContainer(Map<String, T> mp) {
506            m_mpEditingItems = mp;
507        }
508    
509        /**
510         * Set the map of items that have been removed from the Stock to ensure referential integrity.
511         *
512         * @override Never
513         */
514        protected void setRefIntegrItemsContainer(Map<String, T> mp) {
515            m_mpRefIntegrItems = mp;
516        }
517    
518        /**
519         * Set the map storing information about name changes in CatalogItems.
520         *
521         * @override Never
522         */
523        protected void setRefIntegrEditContainer(Map<String, String> mp) {
524            m_mpRefIntegrEdit = mp;
525        }
526    
527        // Stock interface methods
528    
529        /**
530         * Add the contents of a Stock to this Stock. The method calls {@link Stock#add} for each item in the source
531         * Stock so the same constraints apply and the same exceptions may be thrown.
532         *
533         * @override Never
534         *
535         * @param st the Stock whose contents is to be added to this Stock.
536         * @param db the DataBasket relative to which to perform the actions. <code>addStock</code> will add all
537         * items from the source Stock that are visible using this DataBasket. Must be either <code>null</code> or
538         * a descendant of {@link DataBasketImpl}.
539         * @param fRemove if true, the items will be removed from the source Stock prior to adding them to this
540         * Stock. Otherwise, they will be cloned prior to adding them to the Stock.
541         */
542        @SuppressWarnings("unchecked")
543            public void addStock(Stock<ST, CT> st, DataBasket db, boolean fRemove) {
544            if (st.getCatalog(db) != getCatalog(db)) {
545                throw new CatalogConflictException();
546            }
547    
548            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
549    
550            synchronized (oLock) {
551                synchronized (getItemsLock()) {
552                    Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock));
553    
554                    synchronized (oLock2) {
555                        for (Iterator<ST> i = st.iterator(db, false); i.hasNext(); ) {
556                            try {
557                                ST si = i.next();
558    
559                                if (fRemove) {
560                                    i.remove();
561                                } else {
562                                    si = (ST)si.clone();
563                                }
564    
565                                add(si, db);
566                            }
567                            catch (ConcurrentModificationException e) {
568                                break;
569                            }
570                            catch (UnsupportedOperationException e) {
571                                // ignore any items that could not be removed from their source
572                                continue;
573                            }
574                        }
575                    }
576                }
577            }
578        }
579    
580        /**
581         * Check whether the Stock contains an item with the given key.
582         *
583         * <p>Equivalent to:<code>({@link #countItems} (sKey, db) > 0)</code>.</p>
584         *
585         * @override Never
586         *
587         * @param sKey the key for which to check containment.
588         * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of
589         * {@link DataBasketImpl}.
590         */
591        public boolean contains(String sKey, DataBasket db) {
592            return (countItems(sKey, db) > 0);
593        }
594    
595        /**
596         * Check whether the Stock contains the given StockItem.
597         *
598         * <p>Return true if the Stock contains a StockItem that is equal to the given one.</p>
599         *
600         * @param si the StockItem for which to check containment.
601         * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of
602         * {@link DataBasketImpl}.
603         *
604         * @override Never
605         */
606        public boolean contains(ST si, DataBasket db) {
607            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
608    
609            synchronized (oLock) {
610                synchronized (getItemsLock()) {
611                    for (Iterator<ST> i = get(si.getName(), db, false); i.hasNext(); ) {
612                        StockItem si2 = i.next();
613    
614                        if (si.equals(si2)) {
615                            return true;
616                        }
617                    }
618    
619                    return false;
620                }
621            }
622        }
623    
624        /**
625         * Check whether the given Stock is completely contained in this Stock.
626         *
627         * <p>Calls {@link #contains(data.StockItem, data.DataBasket)} for each item in the given Stock.</p>
628         *
629         * @override Never
630         *
631         * @param st the Stock for which to check containment.
632         * @param db the DataBasket used to determine visibility. Must be either <code>null</code> or a descendant of
633         * {@link DataBasketImpl}.
634         */
635        public boolean containsStock(Stock<ST, CT> st, DataBasket db) {
636            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
637    
638            synchronized (oLock) {
639                synchronized (getItemsLock()) {
640                    Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock));
641    
642                    synchronized (oLock2) {
643                        for (Iterator<ST> i = st.iterator(db, false); i.hasNext(); ) {
644                            if (!contains(i.next(), db)) {
645                                return false;
646                            }
647                        }
648    
649                        return true;
650                    }
651                }
652            }
653        }
654    
655        /**
656         * Iterate all items in the Stock.
657         *
658         * <p>This method, together with {@link Stock#get} is the only way of accessing the individual
659         * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that are visible
660         * using the given DataBasket. Depending on the <code>fForEdit</code> parameter, the items will be retrieved
661         * in different ways. See {@link DataBasket} for an explanation of the different possibilities.</p>
662         *
663         * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if
664         * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into
665         * <code>UnSupportedOperationException</code>s.</p>
666         *
667         * @override Never
668         *
669         * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code> or a descendant of
670         * {@link DataBasketImpl}.
671         * @param fForEdit if true, the StockItems will be retrieved for editing.
672         */
673        public Iterator<ST> iterator(final DataBasket db, final boolean fForEdit) {
674            class I<IST extends StockItemImpl> implements Iterator<IST> {
675                private StockImpl<T, IST, CT> m_stiOwner;
676                private Iterator<String> m_iKeys;
677                private Iterator<IST> m_iItems;
678    
679                public I(StockImpl<T, IST, CT> stiOwner) {
680                    super();
681    
682                    m_stiOwner = stiOwner;
683    
684                    m_iKeys = m_stiOwner.keySet(db).iterator();
685                }
686    
687                public boolean hasNext() {
688                    return findNext();
689                }
690    
691                public IST next() {
692                    if (!findNext()) {
693                        throw new NoSuchElementException("No more elements in Stock.");
694                    }
695    
696                    return m_iItems.next();
697                }
698    
699                public void remove() {
700                    if (m_iItems == null) {
701                        throw new IllegalStateException();
702                    }
703    
704                    m_iItems.remove();
705                }
706    
707                private boolean findNext() {
708                    if (m_iItems == null) {
709                        if (m_iKeys.hasNext()) {
710                            m_iItems = m_stiOwner.get((String)m_iKeys.next(), db, fForEdit);
711                        } else {
712                            return false;
713                        }
714                    }
715    
716                    while ((m_iItems.hasNext()) || (m_iKeys.hasNext())) {
717                        if (m_iItems.hasNext()) {
718                            return true;
719                        }
720    
721                        m_iItems = m_stiOwner.get((String)m_iKeys.next(), db, fForEdit);
722                    }
723    
724                    return false;
725                }
726            }
727    
728            return new I<ST>(this);
729        }
730    
731        /**
732         * Return the set of keys for which {@link StockItem StockItems} are visible using the given DataBasket.
733         *
734         * <p>The returned set is static and gives the state of the Stock at the time of the call. It will not
735         * automatically update when the contents of the Stock changes.</p>
736         *
737         * @param db the DataBasket used for determining visibility. Must be either <code>null</code> or a descendant of
738         * {@link DataBasketImpl}.
739         *
740         * @override Never
741         */
742            public Set<String> keySet(DataBasket db) {
743            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
744    
745            synchronized (oLock) {
746                synchronized (getItemsLock()) {
747                    Set<String> stKeys = new TreeSet<String>(getItemsContainer().keySet());
748    
749                    for (Iterator<String> i = getTemporaryAddedItemsContainer().keySet().iterator(); i.hasNext(); ) {
750                        String sKey = i.next();
751    
752                        if ((!stKeys.contains(sKey)) && (countItems(sKey, db) > 0)) {
753                            stKeys.add(sKey);
754                        }
755                    }
756    
757                    return stKeys;
758                }
759            }
760        }
761    
762        /**
763         * Sum up the Stock.
764         *
765         * <p>The method will determine the value of each {@link CatalogItem} in the associated Catalog and
766         * {@link Value#multiplyAccumulating(int) multiply} this by
767         * {@link Stock#countItems the number of StockItems for the respective key}. These products will be
768         * {@link Value#addAccumulating added up} and the resulting total will be returned.</p>
769         *
770         * @override Never
771         *
772         * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a
773         * descendant of {@link DataBasketImpl}.
774         * @param civ the CatalogItemValue used for determining the value of a CatalogItem.
775         * @param vInit the initial value. The sum of the Stock will be added to this value.
776         *
777         * @return the resulting total. Usually the returned object is the same as the one passed as
778         * <code>vInit</code>, only with a changed value.
779         */
780        public Value sumStock(DataBasket db, CatalogItemValue civ, Value vInit) {
781            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
782    
783            synchronized (oLock) {
784                synchronized (getItemsLock()) {
785                    Set<String> stKeys = keySet(db);
786    
787                    for (Iterator<String> i = stKeys.iterator(); i.hasNext(); ) {
788                        String sKey = i.next();
789    
790                        try {
791                            vInit.addAccumulating(civ.getValue(getCatalog(db).get(sKey, db,
792                                    false)).multiply(countItems(sKey, db)));
793                        }
794                        catch (VetoException ex) {}
795                    }
796    
797                    return vInit;
798                }
799            }
800        }
801    
802        /**
803         * Increase the {@link #sumStock Stock's value} by a given value.
804         *
805         * <p>The method will try to break the given value as exactly as possible into StockItems that will be
806         * added to the Stock. The actual algorithm used for breaking up the value will be determined by the last
807         * parameter. The return value of the method will specify the remaining value that could not be represented
808         * by StockItems by the given algorithm.</p>
809         *
810         * @param db the DataBasket relative to which to perform the operation. Must be either <code>null</code> or
811         * a descendant of {@link DataBasketImpl}.
812         * @param vTarget the value by which to increase the Stock's total value.
813         * @param sfvc the strategy used to fill the Stock.
814         *
815         * @return the value that remained and could not be represented by StockItems.
816         *
817         * @override Never
818         */
819        public Value fillStockWithValue(DataBasket db, Value vTarget, StockFromValueCreator sfvc) {
820            Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
821    
822            synchronized (oLock) {
823                synchronized (getItemsLock()) {
824                    return sfvc.fillStock(this, vTarget, db);
825                }
826            }
827        }
828    
829        /**
830         * Get the size of this Stock. I.e. calculate the number of StockItems that can be seen when using the
831         * given DataBasket.
832         *
833         * @param db the DataBasket used to determine visibility. Must be either <code>null</code> or a descendant of
834         * {@link DataBasketImpl}.
835         *
836         * @override Never
837         */
838            public int size(DataBasket db) {
839            Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
840    
841            synchronized (oLock) {
842                synchronized (getItemsLock()) {
843                    Set<String> stKeys = new HashSet<String>(getItemsContainer().keySet());
844                    stKeys.addAll(getTemporaryAddedItemsContainer().keySet());
845    
846                    int nCount = 0;
847    
848                    for (Iterator<String> i = stKeys.iterator(); i.hasNext(); ) {
849                        String sKey = i.next();
850    
851                        nCount += countItems(sKey, db);
852                    }
853    
854                    return nCount;
855                }
856            }
857        }
858    
859        /**
860         * Get the Catalog associated to this Stock.
861         *
862         * @override Never
863         */
864        public Catalog<CT> getCatalog(DataBasket db) {
865            if ((m_dbCatalogValidator == null) || (m_dbCatalogValidator == db)) { // Only one specific DataBasket may pass
866                return m_ciCatalog;
867            } else {
868                throw new DataBasketConflictException(
869                        "Cannot access catalog that is currently being edited in another transaction.");
870            }
871        }
872    
873        /**
874         * Create a shallow clone of the Stock. In contrast to a {@link #clone deep clone}, the individual items in
875         * the Stock are not cloned.
876         *
877         * @override Never Instead, override {@link #createPeer} and/or {@link #fillShallowClone}.
878         */
879        public StockItemImpl getShallowClone() {
880            StockImpl<T, ST, CT> sti = createPeer();
881    
882            fillShallowClone(sti);
883    
884            // Attach as listener to this Stock
885            sti.m_srsiEditCreator = new java.lang.ref.SoftReference<StockImpl>(this);
886            addStockChangeListener(sti.m_sclEditCreatorListener);
887            if (getStock() != null) {
888                ((StockImpl)getStock()).addStockChangeListener(sti.m_sclEditingListener);
889            }
890    
891            return sti;
892        }
893    
894        /**
895         * Hook method called to fill the given shallow clone of this Stock.
896         *
897         * @override Sometimes Normally you do not have to override this method.
898         */
899            protected void fillShallowClone(StockImpl<T, ST, CT> stiClone) {
900            synchronized (getItemsLock()) {
901                synchronized (stiClone.getItemsLock()) {
902                    stiClone.setItemsContainer(new HashMap<String, T>(getItemsContainer()));
903                    stiClone.setTemporaryAddedItemsContainer(new HashMap<String, T>(getTemporaryAddedItemsContainer()));
904                    stiClone.setTemporaryRemovedItemsContainer(new HashMap<String, T>(getTemporaryRemovedItemsContainer()));
905                    stiClone.setEditingItemsContainer(new HashMap<String, T>(getEditingItemsContainer()));
906                    stiClone.setRefIntegrItemsContainer(new HashMap<String, T>(getRefIntegrItemsContainer()));
907                    stiClone.setRefIntegrEditContainer(new HashMap<String, String>(getRefIntegrEditContainer()));
908                }
909            }
910        }
911    
912        /**
913         * Create and return a deep clone of the Stock. In contrast to a {@link #getShallowClone shallow clone}, the
914         * individual items themselves are also cloned.
915         *
916         * @override Never Instead override {@link #createPeer}.
917         */
918        public Object clone() {
919            StockImpl<T, ST, CT> sti = createPeer();
920    
921            sti.addStock(this, null, false);
922    
923            return sti;
924        }
925    
926        /**
927         * Create an empty Stock with the same name, associated Catalog and class.
928         *
929         * @override Always
930         */
931        protected abstract StockImpl<T, ST, CT> createPeer();
932    
933        /**
934         * Set the Stock that contains this Stock.
935         *
936         * @override Never
937         */
938        @SuppressWarnings("unchecked")
939            protected void setStock(StockImpl<?, ?, ?> sti) {
940            super.setStock(sti);
941    
942            if (sti != null) {
943                internalSetCatalog((CatalogImpl)getAssociatedItem(sti.m_dbCatalogValidator)); //12/11/2000-sz9: Changed to use m_dbCatalogValidator
944            }
945        }
946    
947        /**
948         * Compare this StockItem to the given object.
949         *
950         * @override Always The default implementation will assume <code>o</code> to be a StockItem and will
951         * compare the names. Stocks, however, will always be greater than StockItems.
952         *
953         * @exception ClassCastException if the given object cannot be converted into a {@link StockItem}.
954         */
955        public int compareTo(Object o) {
956            if (o instanceof Stock) {
957                return super.compareTo(o);
958            } else {
959                return 1; // Stocks are always greater than StockItems
960            }
961        }
962    
963        // NameContext interface methods
964        /**
965         * Check a name change of a StockItem that is contained in this Stock.
966         *
967         * <p>Stocks will not allow name changes of StockItem, as a principle.</p>
968         *
969         * @override Never
970         */
971        public void checkNameChange(DataBasket db, String sOldName, String sNewName) throws NameContextException {
972    
973            throw new NameContextException("StockItem names cannot be changed when they are part of a Stock.");
974        }
975    
976        /**
977         * Stocks will not allow name changes of StockItem, as a principle.
978         *
979         * @override Never
980         */
981        public void nameHasChanged(DataBasket db, String sOldName, String sNewName) {}
982    
983        /**
984         * Stocks will not allow name changes of StockItem, as a principle.
985         *
986         * @override Never
987         */
988        public Object getNCMonitor() {
989            return new Object();
990        }
991    
992        /**
993         * Helper method to be called in the beginning of commitAdd and rollbackRemove. Tries to maintain
994         * referential integrity by trying to make sure that a CatalogItem exists for the the StockItems that will
995         * be brought into the Stock. Must be called from within
996         * <code>synchronized ({@link #getItemsLock}) {}</code> before any other operation.
997         *
998         * @override Never
999         */
1000        protected void prepareReferentialIntegrity(DataBasket db, DataBasketEntry dbe) {
1001            // try to achieve referential integrity by first trying to make sure, there'll be a CatalogItem for these
1002            // StockItems in the corresponding Catalog.
1003            DataBasketCondition dbc = new DataBasketConditionImpl(CatalogItemImpl.CATALOG_ITEM_MAIN_KEY,
1004                    dbe.getSecondaryKey(), (CatalogImpl)getCatalog(db), null, null);
1005            DataBasketEntry dbeRemovedCI = db.get(dbc);
1006    
1007            dbc = new DataBasketConditionImpl(CatalogItemImpl.CATALOG_ITEM_MAIN_KEY, dbe.getSecondaryKey(), null,
1008                    (CatalogImpl)getCatalog(db), null);
1009            DataBasketEntry dbeAddedCI = db.get(dbc);
1010    
1011            if (((dbeRemovedCI == null) || (dbeAddedCI == null)) && (dbeRemovedCI != dbeAddedCI)) {
1012    
1013                if (dbeRemovedCI != null) {
1014                    dbeRemovedCI.rollback();
1015                } else {
1016                    dbeAddedCI.commit();
1017                }
1018            }
1019            // if both dbeRemovedCI and dbeAddedCI are not null, we cannot decide which is correct and must therefore
1020            // live with that bit of inconsistency. However, as long as there's no corresponding CatalogItem, the
1021            // StockItems will not be visible.
1022        }
1023    
1024        // ListenableStock interface methods
1025        /**
1026         * Add a listener to receive events when the Stock's contents change.
1027         *
1028         * @override Never
1029         */
1030        public void addStockChangeListener(StockChangeListener scl) {
1031            m_lhListeners.add(StockChangeListener.class, scl);
1032        }
1033    
1034        /**
1035         * Remove a listener that received events when the Stock's contents changed.
1036         *
1037         * @override Never
1038         */
1039        public void removeStockChangeListener(StockChangeListener scl) {
1040            m_lhListeners.remove(StockChangeListener.class, scl);
1041        }
1042    
1043        /**
1044         * Fire an event to all listeners that showed an interest in this Stock.
1045         *
1046         * @override Never
1047         */
1048        @SuppressWarnings("unchecked")
1049            protected void fireStockItemsAdded(StockChangeEvent e) {
1050            Object[] listeners = m_lhListeners.getListenerList();
1051            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1052                if (listeners[i] == StockChangeListener.class) {
1053    
1054                    ((StockChangeListener)listeners[i + 1]).addedStockItems(e);
1055                }
1056            }
1057        }
1058    
1059        /**
1060         * Fire an event to all listeners that showed an interest in this Stock.
1061         *
1062         * @override Never
1063         */
1064        @SuppressWarnings("unchecked")
1065        protected void fireStockItemsAddCommit(StockChangeEvent e) {
1066            Object[] listeners = m_lhListeners.getListenerList();
1067    
1068            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1069                if (listeners[i] == StockChangeListener.class) {
1070    
1071                    ((StockChangeListener)listeners[i + 1]).commitAddStockItems(e);
1072                }
1073            }
1074        }
1075    
1076        /**
1077         * Fire an event to all listeners that showed an interest in this Stock.
1078         *
1079         * @override Never
1080         */
1081        @SuppressWarnings("unchecked")
1082        protected void fireStockItemsAddRollback(StockChangeEvent e) {
1083            Object[] listeners = m_lhListeners.getListenerList();
1084    
1085            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1086                if (listeners[i] == StockChangeListener.class) {
1087    
1088                    ((StockChangeListener)listeners[i + 1]).rollbackAddStockItems(e);
1089                }
1090            }
1091        }
1092    
1093        /**
1094         * Fire an event to all listeners that showed an interest in this Stock.
1095         *
1096         * @override Never
1097         */
1098        @SuppressWarnings("unchecked")
1099        protected void fireStockItemsRemoved(StockChangeEvent e) {
1100            Object[] listeners = m_lhListeners.getListenerList();
1101    
1102            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1103                if (listeners[i] == StockChangeListener.class) {
1104    
1105                    ((StockChangeListener)listeners[i + 1]).removedStockItems(e);
1106                }
1107            }
1108        }
1109    
1110        /**
1111         * Fire an event to all listeners that showed an interest in this Stock.
1112         *
1113         * @override Never
1114         */
1115        @SuppressWarnings("unchecked")
1116        protected void fireStockItemsRemoveCommit(StockChangeEvent e) {
1117            Object[] listeners = m_lhListeners.getListenerList();
1118    
1119            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1120                if (listeners[i] == StockChangeListener.class) {
1121    
1122                    ((StockChangeListener)listeners[i + 1]).commitRemoveStockItems(e);
1123                }
1124            }
1125        }
1126    
1127        /**
1128         * Fire an event to all listeners that showed an interest in this Stock.
1129         *
1130         * @override Never
1131         */
1132        @SuppressWarnings("unchecked")
1133        protected void fireStockItemsRemoveRollback(StockChangeEvent e) {
1134            Object[] listeners = m_lhListeners.getListenerList();
1135    
1136            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1137                if (listeners[i] == StockChangeListener.class) {
1138    
1139                    ((StockChangeListener)listeners[i + 1]).rollbackRemoveStockItems(e);
1140                }
1141            }
1142        }
1143    
1144        /**
1145         * Fire an event to all listeners that showed an interest in this Stock.
1146         *
1147         * @override Never
1148         */
1149        @SuppressWarnings("unchecked")
1150        protected void fireCanRemoveStockItems(StockChangeEvent e) throws VetoException {
1151            Object[] temp = m_lhListeners.getListenerList();
1152            Object[] listeners = new Object[temp.length];
1153            System.arraycopy(temp, 0, listeners, 0, temp.length);
1154    
1155            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1156                if (listeners[i] == StockChangeListener.class) {
1157                    try {
1158                        ((StockChangeListener)listeners[i + 1]).canRemoveStockItems(e);
1159                    }
1160                    catch (VetoException ex) {
1161                        for (int j = i; j < listeners.length; j += 2) {
1162                            if (listeners[j] == StockChangeListener.class) {
1163                                ((StockChangeListener)listeners[j + 1]).noRemoveStockItems(e);
1164                            }
1165                        }
1166    
1167                        throw ex;
1168                    }
1169                }
1170            }
1171        }
1172    
1173        /**
1174         * Fire an event to all listeners that showed an interest in this Stock.
1175         *
1176         * @override Never
1177         */
1178        @SuppressWarnings("unchecked")
1179        protected void fireCanEditStockItems(StockChangeEvent e) throws VetoException {
1180            Object[] temp = m_lhListeners.getListenerList();
1181            Object[] listeners = new Object[temp.length];
1182            System.arraycopy(temp, 0, listeners, 0, temp.length);
1183    
1184            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1185                if (listeners[i] == StockChangeListener.class) {
1186                    try {
1187                        ((StockChangeListener)listeners[i + 1]).canEditStockItems(e);
1188                    }
1189                    catch (VetoException ex) {
1190                        for (int j = i; j < listeners.length; j += 2) {
1191                            if (listeners[j] == StockChangeListener.class) {
1192                                ((StockChangeListener)listeners[j + 1]).noRemoveStockItems(e);
1193                            }
1194                        }
1195    
1196                        throw ex;
1197                    }
1198                }
1199            }
1200        }
1201    
1202        /**
1203         * Fire an event to all listeners that showed an interest in this Stock.
1204         *
1205         * @override Never
1206         */
1207        @SuppressWarnings("unchecked")
1208        protected void fireEditingStockItems(StockChangeEvent e) {
1209            Object[] listeners = m_lhListeners.getListenerList();
1210    
1211            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1212                if (listeners[i] == StockChangeListener.class) {
1213    
1214                    ((StockChangeListener)listeners[i + 1]).editingStockItems(e);
1215                }
1216            }
1217        }
1218    
1219        /**
1220         * Fire an event to all listeners that showed an interest in this Stock.
1221         *
1222         * @override Never
1223         */
1224        @SuppressWarnings("unchecked")
1225        protected void fireStockItemsEditCommit(StockChangeEvent e) {
1226            Object[] listeners = m_lhListeners.getListenerList();
1227    
1228            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1229                if (listeners[i] == StockChangeListener.class) {
1230    
1231                    ((StockChangeListener)listeners[i + 1]).commitEditStockItems(e);
1232                }
1233            }
1234        }
1235    
1236        /**
1237         * Fire an event to all listeners that showed an interest in this Stock.
1238         *
1239         * @override Never
1240         */
1241        @SuppressWarnings("unchecked")
1242        protected void fireStockItemsEditRollback(StockChangeEvent e) {
1243            Object[] listeners = m_lhListeners.getListenerList();
1244    
1245            for (int i = listeners.length - 2; i >= 0; i -= 2) {
1246                if (listeners[i] == StockChangeListener.class) {
1247    
1248                    ((StockChangeListener)listeners[i + 1]).rollbackEditStockItems(e);
1249                }
1250            }
1251        }
1252    
1253        /**
1254         * Helper method used to maintain StockImpl - CatalogImpl links in nested Stocks/Catalogs. For internal use only.
1255         *
1256         * @param db the DataBasket that protecting this activity.
1257         * @param nAction the action that occurred. Can be either {@link #COMMIT_ACTION}, {@link #ROLLBACK_ACTION},
1258         * {@link #STARTEDIT_ACTION}.
1259         */
1260        @SuppressWarnings("unchecked")
1261        void relinkCatalog(DataBasket db, int nAction) {
1262            super.relinkCatalog(db, nAction);
1263    
1264            switch (nAction) {
1265                case STARTEDIT_ACTION:
1266    
1267                    /**
1268                     * A starting catalog editing operation requires us to set the catalog to the catalog being
1269                     * edited and to remember the databasket coordinating the editing.
1270                     */
1271                    internalSetCatalog((CatalogImpl)getAssociatedItem(db));
1272                    m_dbCatalogValidator = db;
1273    
1274                    break;
1275                case COMMIT_ACTION:
1276    
1277                    /*
1278                     * Commiting a catalog editing action only requires to forget the DataBasket used to edit the
1279                     * Catalog, as it will no longer be contained in it and all other relinks will have been
1280                     * performed.
1281                     */
1282                    if (db == m_dbCatalogValidator) {
1283                        m_dbCatalogValidator = null;
1284                    }
1285    
1286                    break;
1287                case ROLLBACK_ACTION:
1288    
1289                    /*
1290                     * Rolling back a catalog editing action requires us to set the catalog based on our parent's catalog
1291                     * validator, reset the catalog validator and update our children if we'returna StoringStock. The latter
1292                     * will be done in StoringStockImpl.relinkCatalog.
1293                     */
1294                    if (db == m_dbCatalogValidator) {
1295                        DataBasket dbParent = ((StockImpl)getStock()).m_dbCatalogValidator;
1296    
1297                        if (dbParent != null) {
1298                            // This normally shouldn't happen, but I am not sure that it really is an error situation,
1299                            // so just trace it...
1300                            Debug.print(
1301                                    "In data.ooimpl.StockImpl.relinkCatalog (db, ROLLBACK_ACTION): parent databasket is not null!",
1302                                    -1);
1303                        }
1304    
1305                        internalSetCatalog((CatalogImpl)getAssociatedItem(dbParent));
1306    
1307                        m_dbCatalogValidator = null;
1308                    }
1309    
1310                    break;
1311            }
1312        }
1313    }