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