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