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 }