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