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