001 package data.ooimpl;
002
003 import data.*;
004 import data.events.*;
005
006 import java.util.*;
007
008 /**
009 * Pure Java implementation of the {@link StoringStock} interface.
010 *
011 * @author Steffen Zschaler
012 * @version 2.0 19/08/1999
013 * @since v2.0
014 */
015 public class StoringStockImpl<T extends StockItemImpl, CT extends CatalogItemImpl>
016 extends StockImpl<List<T>, T, CT> implements StoringStock<T, CT> {
017
018 /**
019 * ID for serialization.
020 */
021 private static final long serialVersionUID = 3460626053985545111L;
022
023 /**
024 * Modification counter. Increases by one with every structural modification.
025 *
026 * @serial
027 */
028 protected int m_nModCount = Integer.MIN_VALUE;
029
030 /**
031 * Listens to the Stock's Catalog to ensure referential integrity.
032 *
033 * @serial
034 */
035 protected CatalogChangeListener m_cclReferentialIntegrityListener;
036
037 /**
038 * true, if StockImpl's CatalogItemNameListener was already replaced by an enhanced version.
039 * Used internally only.
040 *
041 * @serial
042 */
043 private boolean m_fChangedCatalogItemNameListener = false;
044
045 /**
046 * Enhanced CatalogItemNameListener, updating the names of the actual StockItems whenever a name change
047 * occurs.
048 *
049 * @author Steffen Zschaler
050 * @version 2.0 19/08/1999
051 * @since v2.0
052 */
053 class SSICatalogItemNameListener extends CatalogItemNameListener {
054
055 private static final long serialVersionUID = -3046919332594065216L;
056
057 protected void nameChangeOccurred(String sOld, String sNew) {
058 super.nameChangeOccurred(sOld, sNew);
059
060 List<T> lAdded = getTemporaryAddedItemsContainer().get(sNew);
061 if (lAdded != null) {
062 for (Iterator<T> i = lAdded.iterator(); i.hasNext(); ) {
063 StockItemImpl sii = i.next();
064
065 sii.internalSetName(sNew);
066 }
067 }
068
069 List<T> lItems = getItemsContainer().get(sNew);
070 if (lItems != null) {
071 for (Iterator<T> i = lItems.iterator(); i.hasNext(); ) {
072 StockItemImpl sii = i.next();
073
074 sii.internalSetName(sNew);
075 }
076 }
077
078 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sNew);
079 if (lRemoved != null) {
080 for (Iterator<T> i = lRemoved.iterator(); i.hasNext(); ) {
081 StockItemImpl sii = i.next();
082
083 sii.internalSetName(sNew);
084 }
085 }
086
087 List<T> lRefIntegr = getRefIntegrItemsContainer().get(sNew);
088 if (lRefIntegr != null) {
089 for (Iterator<T> i = lRefIntegr.iterator(); i.hasNext(); ) {
090 StockItemImpl sii = i.next();
091
092 sii.internalSetName(sNew);
093 }
094 }
095 }
096 }
097
098 /**
099 * Create a new, initially empty StoringStockImpl.
100 *
101 * @param id the id of the new Stock.
102 * @param ciRef the Catalog that is being referenced by the Stock.
103 */
104 public StoringStockImpl(StockIdentifier<T, CT> id, Catalog<CT> ciRef) {
105 super(id.getName(), (CatalogImpl<CT>)ciRef);
106 }
107
108 /**
109 * Create a new, initially empty StoringStockImpl.
110 *
111 * @param sName the name of the new Stock.
112 * @param ciRef the Catalog that is being referenced by the Stock.
113 */
114 public StoringStockImpl(String sName, Catalog<CT> ciRef) {
115 super(sName, (CatalogImpl<CT>)ciRef);
116
117 // Enhanced version.
118 m_sclEditCreatorListener = new StockChangeAdapter<T, CT>() {
119
120 private static final long serialVersionUID = -7948443502826415058L;
121
122 public void commitAddStockItems(StockChangeEvent<T, CT> e) {
123 synchronized (getItemsLock()) {
124 T sii = e.getAffectedItems().next();
125
126 List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
127
128 if (lAdded == null) {
129 return;
130 }
131
132 if (lAdded.remove(sii)) {
133 List<T> lItems = getItemsContainer().get(sii.getName());
134
135 if (lItems == null) {
136 lItems = new LinkedList<T>();
137 getItemsContainer().put(sii.getName(), lItems);
138 }
139 lItems.add(sii);
140
141 sii.setStock(StoringStockImpl.this);
142
143 fireStockItemsAddCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
144 e.getBasket()));
145 }
146 }
147 }
148
149 public void rollbackAddStockItems(StockChangeEvent<T, CT> e) {
150 synchronized (getItemsLock()) {
151 T sii = e.getAffectedItems().next();
152
153 List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
154
155 if (lAdded == null) {
156 return;
157 }
158
159 if (lAdded.remove(sii)) {
160 if (sii.getStock().equals(StoringStockImpl.this)) {
161 sii.setStock(null);
162 }
163
164 fireStockItemsAddRollback(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
165 e.getBasket()));
166 }
167 }
168 }
169
170 public void canRemoveStockItems(StockChangeEvent<T, CT> e) throws VetoException {
171 throw new VetoException("Please use the editable version for this!");
172 }
173
174 public void commitRemoveStockItems(StockChangeEvent<T, CT> e) {
175 synchronized (getItemsLock()) {
176 T sii = e.getAffectedItems().next();
177
178 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
179
180 if (lRemoved == null) {
181 return;
182 }
183
184 if (lRemoved.remove(sii)) {
185 if (sii.getStock().equals(StoringStockImpl.this)) {
186 sii.setStock(null);
187 }
188
189 fireStockItemsRemoveCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
190 e.getBasket()));
191 }
192 }
193 }
194
195 public void rollbackRemoveStockItems(StockChangeEvent<T, CT> e) {
196 synchronized (getItemsLock()) {
197 T sii = e.getAffectedItems().next();
198
199 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
200
201 if (lRemoved == null) {
202 return;
203 }
204
205 if (lRemoved.remove(sii)) {
206 List<T> lItems = getItemsContainer().get(sii.getName());
207
208 if (lItems == null) {
209 lItems = new LinkedList<T>();
210 getItemsContainer().put(sii.getName(), lItems);
211 }
212 lItems.add(sii);
213
214 sii.setStock(StoringStockImpl.this);
215
216 fireStockItemsRemoveCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
217 e.getBasket()));
218 }
219 }
220 }
221
222 public void canEditStockItems(StockChangeEvent<T, CT> e) throws VetoException {
223 throw new VetoException("Please use the editable version for this!");
224 }
225
226 public void commitEditStockItems(StockChangeEvent<T, CT> e) {
227 synchronized (getItemsLock()) {
228 T sii = e.getAffectedItems().next();
229
230 List<T> lEditing = getEditingItemsContainer().get(sii.getName());
231
232 if (lEditing == null) {
233 return;
234 }
235
236 if (lEditing.remove(sii)) {
237 fireStockItemsEditCommit(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
238 e.getBasket()));
239 }
240 }
241 }
242
243 public void rollbackEditStockItems(StockChangeEvent<T, CT> e) {
244 synchronized (getItemsLock()) {
245 T sii = e.getAffectedItems().next();
246
247 List<T> lEditing = getEditingItemsContainer().get(sii.getName());
248
249 if (lEditing == null) {
250 return;
251 }
252
253 if (lEditing.remove(sii)) {
254 fireStockItemsEditRollback(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this, sii,
255 e.getBasket()));
256 }
257 }
258 }
259 };
260 }
261
262 /**
263 * Overridden because of referential integrity.
264 *
265 * @override Never
266 */
267 protected void internalSetCatalog(CatalogImpl<CT> ciRef) {
268 if (m_ciCatalog != null) {
269 m_ciCatalog.removeCatalogChangeListener(m_cclReferentialIntegrityListener);
270 }
271
272 if (!m_fChangedCatalogItemNameListener) {
273 m_fChangedCatalogItemNameListener = true;
274
275 m_cinlCatalogItemNameListener = new SSICatalogItemNameListener();
276 }
277
278 super.internalSetCatalog(ciRef);
279
280 if (m_ciCatalog != null) {
281 if (m_cclReferentialIntegrityListener == null) {
282 initReferentialIntegrityListener();
283 }
284
285 m_ciCatalog.addCatalogChangeListener(m_cclReferentialIntegrityListener);
286 }
287 }
288
289 /**
290 * Internal helper function.
291 *
292 * @override Never
293 */
294 private void initReferentialIntegrityListener() {
295 m_cclReferentialIntegrityListener = new CatalogChangeAdapter<CT>() {
296
297 private static final long serialVersionUID = -2030724124407592374L;
298
299 public void canRemoveCatalogItem(CatalogChangeEvent<CT> e) throws VetoException {
300 // DataBasket already locks on its monitor
301 synchronized (getItemsLock()) {
302 String sKey = e.getAffectedItem().getName();
303 DataBasket db = e.getBasket();
304
305 List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
306 if ((lAdded != null) && (lAdded.size() > 0)) {
307 throw new VetoException("Stock \"" + getName() +
308 "\": Having temporarily added items of key \"" + sKey + "\".");
309 }
310
311 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sKey);
312 if (lRemoved != null) {
313 if ((db == null) && (lRemoved.size() > 0)) {
314 throw new VetoException("Stock \"" + getName() +
315 "\": Having temporarily removed items that are in a different DataBasket.");
316 }
317
318 for (Iterator<T> i = lRemoved.iterator(); i.hasNext(); ) {
319 StockItem si = (StockItem)i.next();
320
321 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
322 StoringStockImpl.this, null, si);
323 if (!db.contains(dbc)) {
324 throw new VetoException("Stock \"" + getName() +
325 "\": Having temporarily removed items that are in a different DataBasket.");
326 }
327 }
328 }
329
330 List<T> lItems = getItemsContainer().get(sKey);
331 if ((lItems != null) && (lItems.size() > 0)) {
332 List<T> lRefIntegr = new LinkedList<T>(lItems);
333 getRefIntegrItemsContainer().put(sKey, lRefIntegr);
334
335 for (Iterator<T> i = lRefIntegr.iterator(); i.hasNext(); ) {
336 remove(i.next(), db);
337 }
338 }
339 }
340 }
341
342 public void noRemoveCatalogItem(CatalogChangeEvent<CT> e) {
343 synchronized (getItemsLock()) {
344 String sKey = e.getAffectedItem().getName();
345 DataBasket db = e.getBasket();
346
347 List<T> lRefIntegr = getRefIntegrItemsContainer().remove(sKey);
348 if (lRefIntegr != null) {
349 for (Iterator<T> i = lRefIntegr.iterator(); i.hasNext(); ) {
350 add(i.next(), db);
351 }
352 }
353 }
354 }
355
356 public void removedCatalogItem(CatalogChangeEvent<CT> e) {
357 synchronized (getItemsLock()) {
358 // clean up
359 getRefIntegrItemsContainer().remove(e.getAffectedItem().getName());
360 }
361 }
362
363 public void commitedRemoveCatalogItem(CatalogChangeEvent<CT> e) {
364 synchronized (getItemsLock()) {
365 if (!((Catalog)e.getSource()).contains(e.getAffectedItem().getName(), e.getBasket())) {
366 ciGoneForEver(e);
367 }
368 }
369 }
370
371 @SuppressWarnings("unused")
372 public void rollbackAddCatalogItem(CatalogChangeEvent<CT> e) {
373 synchronized (getItemsLock()) {
374 DataBasketCondition dbc = new DataBasketConditionImpl(CatalogItemImpl.
375 CATALOG_ITEM_MAIN_KEY, e.getAffectedItem().getName(), (CatalogImpl)e.getSource(), null, null);
376
377 if (!e.getBasket().contains(dbc)) {
378 ciGoneForEver(e);
379 }
380 }
381 }
382
383 private void ciGoneForEver(CatalogChangeEvent<CT> e) {
384 String sKey = e.getAffectedItem().getName();
385 DataBasket db = e.getBasket();
386
387 if (getTemporaryAddedItemsContainer().containsKey(sKey)) {
388 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null,
389 StoringStockImpl.this, null);
390
391 // Rollback all items temporarily added to this Stock
392 // StoringStocks produce DataBasketEntries that may have both source and dest set,
393 // so we must rollback only the destination part.
394 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
395 ((StoringStockItemDBEntry)i.next()).rollbackDestination();
396 }
397 }
398
399 getItemsContainer().remove(sKey);
400 getRefIntegrItemsContainer().remove(sKey);
401
402 if (getTemporaryRemovedItemsContainer().containsKey(sKey)) {
403 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
404 StoringStockImpl.this, null, null);
405
406 // Commit all items temporaryly removed from this Stock
407 // StoringStocks produce DataBasketEntries that may have both source and dest set,
408 // so we must commit only the source part.
409 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
410 ((StoringStockItemDBEntry)i.next()).commitSource();
411 }
412 }
413 }
414
415 // The actual instance of the associated Catalog will have changed, for children of the given key that
416 // are Stocks.
417 public void editingCatalogItem(CatalogChangeEvent<CT> e) {
418 relinkCatalog(e, STARTEDIT_ACTION);
419 }
420
421 // The actual instance of the associated Catalog will have to be changed back.
422 public void rollbackEditCatalogItem(CatalogChangeEvent<CT> e) {
423 relinkCatalog(e, ROLLBACK_ACTION);
424 }
425
426 public void commitEditCatalogItem(CatalogChangeEvent<CT> e) {
427 relinkCatalog(e, COMMIT_ACTION);
428 }
429
430 void relinkCatalog(CatalogChangeEvent<CT> e, int nAction) {
431 DataBasket db = e.getBasket();
432
433 synchronized (getItemsLock()) {
434 List<T> l = getItemsContainer().get(e.getAffectedItem().getName());
435
436 if (l != null) {
437 for (Iterator<T> i = l.iterator(); i.hasNext(); ) {
438 StockItemImpl sii = i.next();
439
440 sii.relinkCatalog(db, nAction);
441 }
442 }
443
444 l = getTemporaryAddedItemsContainer().get(e.getAffectedItem().getName());
445
446 if (l != null) {
447 for (Iterator<T> i = l.iterator(); i.hasNext(); ) {
448 StockItemImpl sii = i.next();
449
450 sii.relinkCatalog(db, nAction);
451 }
452 }
453 }
454 }
455
456 @SuppressWarnings("unused")
457 public void rollbackRemoveCatalogItem(CatalogChangeEvent<CT> e) {
458 reEstablishStockCatalogLink(e.getAffectedItem().getName());
459 }
460 };
461 }
462
463 /**
464 * Private helper function re-establishing the Stock-Catalog connection if any items in this Stock should be
465 * Stocks themselves.
466 *
467 * @override Never
468 */
469 private void reEstablishStockCatalogLink(String sKey) {
470 synchronized (getItemsLock()) {
471 List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
472 if (lAdded != null) {
473 for (Iterator<T> i = lAdded.iterator(); i.hasNext(); ) {
474 // we call setStock again, which for other Stocks will adjust their Catalog link accordingly.
475 (i.next()).setStock(StoringStockImpl.this);
476 }
477 }
478
479 List<T> lItems = getItemsContainer().get(sKey);
480 if (lItems != null) {
481 for (Iterator<T> i = lItems.iterator(); i.hasNext(); ) {
482 // we call setStock again, which for other Stocks will adjust their Catalog link accordingly.
483 (i.next()).setStock(StoringStockImpl.this);
484 }
485 }
486 }
487 }
488
489 // Stock interface methods
490
491 /**
492 * Add an item to the Stock.
493 *
494 * <p>The item will only be visible to users of the same DataBasket. Only after a {@link DataBasket#commit}
495 * was performed on the DataBasket, the item will become visible to other users.</p>
496 *
497 * <p>A <code>addedStockItems</code> event will be fired.</p>
498 *
499 * @param si the item to be added.
500 * @param db the DataBasket relative to which the item will be added. Must be either <code>null</code> or a
501 * descendant of {@link DataBasketImpl}.
502 *
503 * @override Never
504 *
505 * @exception CatalogConflictException if the items key is not contained in the corresponding {@link Catalog}.
506 * @exception DataBasketConflictException if the item has already been added/removed using another DataBasket.
507 */
508 public void add(T si, DataBasket db) {
509 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
510
511 synchronized (oLock) {
512 synchronized (getItemsLock()) {
513 if ((getCatalog(db) != null) && (!getCatalog(db).contains(si.getName(), db))) {
514 throw new CatalogConflictException("Couldn't find key \"" + si.getName() +
515 "\" in Catalog \"" + getCatalog(db).getName() + "\"");
516 }
517
518 List<T> lAdded = getTemporaryAddedItemsContainer().get(si.getName());
519 if ((lAdded != null) && (lAdded.contains(si))) {
520 throw new DataBasketConflictException("Cannot add item that has already been added.");
521 }
522
523 List<T> lItems = getItemsContainer().get(si.getName());
524 if ((lItems != null) && (lItems.contains(si))) {
525 return;
526 }
527
528 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(si.getName());
529 if ((lRemoved != null) && (lRemoved.contains(si))) {
530
531 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem((StockItem)lRemoved.
532 get(lRemoved.indexOf(si)));
533
534 if ((db == null) || (!db.contains(dbc))) {
535 throw new DataBasketConflictException(
536 "Cannot add item that was removed using a different DataBasket.");
537 } else {
538 DataBasketEntry<?> dbe = db.get(dbc);
539
540 if (dbe.getDestination() == null) {
541 // just rollback the prior remove action!
542
543 db.rollback(dbc);
544
545 return;
546 } else {
547 throw new DataBasketConflictException(
548 "Cannot add item that was removed and added to another Stock!");
549 }
550 }
551 }
552
553 // all checked, so add the stuff
554 if (db != null) {
555 if (lAdded == null) {
556 lAdded = new LinkedList<T>();
557 getTemporaryAddedItemsContainer().put(si.getName(), lAdded);
558 }
559
560 lAdded.add(si);
561
562 // put information into databasket! Make sure there's only one DBE for each StockItem!
563 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(si);
564 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
565
566 if (sidbe != null) {
567 db.exchange(sidbe, new StoringStockItemDBEntry((StoringStockImpl)sidbe.getSource(), this,
568 (StockItemImpl)si));
569 } else {
570 db.put(new StoringStockItemDBEntry(null, this, (StockItemImpl)si));
571 }
572
573 } else {
574 if (lItems == null) {
575 lItems = new LinkedList<T>();
576 getItemsContainer().put(si.getName(), lItems);
577 }
578
579 lItems.add(si);
580 }
581
582 m_nModCount++;
583
584 ((StockItemImpl)si).setStock(this);
585 fireStockItemsAdded(new StoringStockChangeEvent<T, CT>(this, si, db));
586 }
587 }
588 }
589
590 /**
591 * Iterate all items with a given key.
592 *
593 * <p>This method, together with {@link Stock#iterator} is the only way of accessing the individual
594 * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that have the
595 * specified key and are visible using the given DataBasket. Depending on the <code>fForEdit</code>
596 * parameter, the items will be retrieved in different ways. See {@link DataBasket} for an explanation of
597 * the different possibilities.</p>
598 *
599 * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if
600 * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into
601 * <code>UnSupportedOperationException</code>s.</p>
602 *
603 * @override Never
604 *
605 * @param sKey the key for which to retrieve the StockItems.
606 * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code>
607 * or a descendant of {@link DataBasketImpl}.
608 * @param fForEdit if true, the StockItems will be retrieved for editing.
609 */
610 public Iterator<T> get(final String sKey, final DataBasket db, final boolean fForEdit) {
611 final Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
612
613 class I implements Iterator<T> {
614 private Iterator<T> m_iItems;
615 private int m_nExpectedModCount;
616
617 private T m_siiCurrent;
618
619 @SuppressWarnings("unchecked")
620 public I() {
621 super();
622
623 synchronized (oLock) {
624 synchronized (getItemsLock()) {
625 List<T> lItems = getItemsContainer().get(sKey);
626
627 if (lItems != null) {
628 lItems = new LinkedList<T>(lItems);
629 } else {
630 lItems = new LinkedList<T>();
631 }
632
633 if (db != null) {
634 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null,
635 StoringStockImpl.this, null);
636 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
637 DataBasketEntry dbe = i.next();
638
639 lItems.add((T)dbe.getValue());
640 }
641 }
642
643 m_iItems = lItems.iterator();
644 m_nExpectedModCount = m_nModCount;
645 }
646 }
647 }
648
649 public boolean hasNext() {
650 return m_iItems.hasNext();
651 }
652
653 @SuppressWarnings("unchecked")
654 public T next() {
655 synchronized (oLock) {
656 synchronized (getItemsContainer()) {
657 if (m_nExpectedModCount != m_nModCount) {
658 throw new ConcurrentModificationException();
659 }
660
661 m_siiCurrent = (T)m_iItems.next();
662
663 if ((fForEdit) && (db != null)) {
664 //if item is temporarily added, return it
665 List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
666 if ((lAdded != null) && (lAdded.contains(m_siiCurrent))) {
667 return m_siiCurrent;
668 }
669
670 try {
671 fireCanEditStockItems(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
672 (T)m_siiCurrent, db));
673 }
674 catch (VetoException ve) {
675 return null;
676 }
677 //otherwise move item from mItems to mTemporaryRemoved
678 List<T> lItems = getItemsContainer().get(sKey);
679 lItems.remove(m_siiCurrent);
680 if (lItems.size() == 0) {
681 getItemsContainer().remove(sKey);
682 }
683
684 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sKey);
685 if (lRemoved == null) {
686 lRemoved = new LinkedList<T>();
687 getTemporaryRemovedItemsContainer().put(sKey, lRemoved);
688 }
689 lRemoved.add(m_siiCurrent);
690 //clone item
691 T siiRemoved = m_siiCurrent;
692 m_siiCurrent = (T) siiRemoved.getShallowClone();
693 //add clone to mTemporaryAdded and mEditingItems
694 if (lAdded == null) {
695 lAdded = new LinkedList<T>();
696 getTemporaryAddedItemsContainer().put(sKey, lAdded);
697 }
698 lAdded.add(m_siiCurrent);
699
700 List<T> lEdit = getEditingItemsContainer().get(sKey);
701 if (lEdit == null) {
702 lEdit = new LinkedList<T>();
703 getEditingItemsContainer().put(sKey, lEdit);
704 }
705 lEdit.add(m_siiCurrent);
706
707 siiRemoved.setStock(null);
708 m_siiCurrent.setStock(StoringStockImpl.this);
709
710 // put information into databasket, making sure there's only one entry per StockItem
711 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(siiRemoved);
712 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
713
714 if (sidbe != null) {
715 db.exchange(sidbe, new StoringStockItemDBEntry(StoringStockImpl.this,
716 (StoringStockImpl)sidbe.getDestination(), siiRemoved));
717 } else {
718 db.put(new StoringStockItemDBEntry(StoringStockImpl.this, null, siiRemoved));
719 }
720
721 db.put(new StoringStockItemDBEntry(null, StoringStockImpl.this, m_siiCurrent));
722
723 fireEditingStockItems(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
724 m_siiCurrent, db));
725 fireStockItemsRemoved(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
726 siiRemoved, db));
727 fireStockItemsAdded(new StoringStockChangeEvent<T, CT>(StoringStockImpl.this,
728 m_siiCurrent, db));
729
730 //Allows only ONE iterator at a time to call next() with fForEdit enabled
731 //because this method adds and removes StockItems, so other iterators have to
732 //be informed via the increased modifiaction counter
733 m_nExpectedModCount = (++m_nModCount);
734 }
735
736 return m_siiCurrent;
737 }
738 }
739 }
740
741 public void remove() {
742 synchronized (oLock) {
743 synchronized (getItemsLock()) {
744 if (m_nModCount != m_nExpectedModCount) {
745 throw new ConcurrentModificationException();
746 }
747
748 if (m_siiCurrent == null) {
749 throw new IllegalStateException();
750 }
751
752 try {
753 StoringStockImpl.this.remove(m_siiCurrent, db);
754
755 m_nExpectedModCount = m_nModCount;
756
757 m_siiCurrent = null;
758 }
759 catch (VetoException ve) {
760 m_siiCurrent = null;
761
762 throw new UnsupportedOperationException("VETO: " + ve);
763 }
764 }
765 }
766 }
767 }
768
769 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
770 return new Iterator<T>() {
771 public boolean hasNext() {
772 return false;
773 }
774
775 public T next() {
776 throw new NoSuchElementException();
777 }
778
779 public void remove() {}
780 };
781 }
782
783 return new I();
784 }
785
786 /**
787 * Count the StockItems with a given key that are visible using a given DataBasket.
788 *
789 * @override Never
790 *
791 * @param sKey the key for which to count the StockItems.
792 * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a
793 * descendant of {@link DataBasketImpl}.
794 */
795 public int countItems(String sKey, DataBasket db) {
796 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
797
798 synchronized (oLock) {
799 synchronized (getItemsLock()) {
800 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
801 return 0;
802 }
803
804 int nCount = 0;
805
806 List<T> lItems = getItemsContainer().get(sKey);
807
808 if (lItems != null) {
809 nCount += lItems.size();
810 }
811
812 if ((getTemporaryAddedItemsContainer().containsKey(sKey)) && (db != null)) {
813 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null);
814 BasketEntryValue bev = BasketEntryValues.COUNT_ITEMS;
815 IntegerValue ivCount = new IntegerValue(0);
816
817 db.sumBasket(dbc, bev, ivCount);
818
819 nCount += ivCount.getValue().intValue();
820 }
821
822 return nCount;
823 }
824 }
825 }
826
827 /**
828 * Remove one StockItem with the specified key from the Stock.
829 *
830 * <p>If there are any StockItems with the specified key, one will be removed. There is no guarantee as to
831 * which StockItem will be removed. The removed item, if any, will be returned.</p>
832 *
833 * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
834 *
835 * @override Never
836 *
837 * @param sKey the key for which to remove an item.
838 * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
839 * descendant of {@link DataBasketImpl}.
840 *
841 * @return the removed item
842 *
843 * @exception VetoException if a listener vetoed the removal.
844 * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
845 * usage.
846 */
847 @SuppressWarnings("unchecked")
848 public StockItem remove(String sKey, DataBasket db) throws VetoException {
849 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
850
851 synchronized (oLock) {
852 synchronized (getItemsLock()) {
853 List<T> lAdded = getTemporaryAddedItemsContainer().get(sKey);
854
855 if ((lAdded != null) && (db != null)) {
856 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null);
857
858 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
859
860 if (sidbe != null) {
861 //remove and return first temporarily added item
862 return remove((T)sidbe.getValue(), db);
863 }
864 }
865
866 List<T> lItems = getItemsContainer().get(sKey);
867
868 if (lItems != null) {
869 /*
870 * 06/27/2000-STEFFEN: Had to add checking for lItems.size here, as apparently I sometimes
871 * keep the vector even if it is empty.
872 * I don't think, it should do that, but I need to check again.
873 * Checked, apparently remove (si, db) also doesn't clean up the list. This is pretty memory
874 * ineffective, but needs some effort to fix it. For the moment, just worked around it.
875 */
876 if (lItems.size() > 0) {
877 //remove and return last added item
878 return remove(lItems.get(lItems.size() - 1), db);
879 }
880 }
881 }
882 }
883
884 return null;
885 }
886
887 /**
888 * Remove the given StockItem from the Stock.
889 *
890 * <p>If the given StockItem is contained in the Stock, it will be removed. The removed item, if any, will
891 * be returned.</p>
892 *
893 * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
894 *
895 * @override Never
896 *
897 * @param si the StockItem to be removed.
898 * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
899 * descendant of {@link DataBasketImpl}.
900 *
901 * @return the removed item
902 *
903 * @exception VetoException if a listener vetoed the removal.
904 * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
905 * usage.
906 */
907 public T remove(T si, DataBasket db) throws VetoException {
908 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object()));
909
910 synchronized (oLock) {
911 synchronized (getItemsLock()) {
912 List<T> lAdded = getTemporaryAddedItemsContainer().get(si.getName());
913 if ((lAdded != null) && (lAdded.contains(si))) {
914 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem((StockItem)lAdded.get(
915 lAdded.indexOf(si)));
916
917 StockItemDBEntry sidbe = null;
918
919 if (db != null) {
920 sidbe = (StockItemDBEntry)db.get(dbc);
921 }
922
923 if (sidbe == null) {
924 throw new DataBasketConflictException(
925 "Cannot remove StockItem that was added using a different DataBasket!");
926 } else {
927 fireCanRemoveStockItems(new StoringStockChangeEvent<T, CT>(this, si, db));
928
929 if (sidbe.getSource() == null) {
930 db.rollback(dbc);
931 } else {
932 // remove only the destination part of it:
933 db.exchange(sidbe, new StoringStockItemDBEntry((StoringStockImpl)sidbe.getSource(), null,
934 (StockItemImpl)sidbe.getValue()));
935
936 si = lAdded.get(lAdded.indexOf(si));
937 lAdded.remove(si);
938
939 m_nModCount++;
940
941 fireStockItemsAddRollback(new StoringStockChangeEvent<T, CT>(this, si, db));
942 }
943
944 ((StockItemImpl)si).setStock(null);
945
946 return si;
947 }
948 }
949
950 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(si.getName());
951 if ((lRemoved != null) && (lRemoved.contains(si))) {
952 throw new DataBasketConflictException(
953 "Cannot remove an item that has already been removed!");
954 }
955
956 List<T> lItems = getItemsContainer().get(si.getName());
957 if ((lItems == null) || (!lItems.contains(si))) {
958 return null;
959 }
960
961 // remove from items container, making sure there's always only one DataBasket entry for each stockitem
962
963 fireCanRemoveStockItems(new StoringStockChangeEvent<T, CT>(this,
964 lItems.get(lItems.indexOf(si)), db));
965
966 si = lItems.get(lItems.indexOf(si));
967 lItems.remove(si);
968
969 if (db != null) {
970 if (lRemoved == null) {
971 lRemoved = new LinkedList<T>();
972 getTemporaryRemovedItemsContainer().put(si.getName(), lRemoved);
973 }
974
975 lRemoved.add(si);
976
977 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(si);
978 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc);
979
980 if (sidbe != null) {
981 db.exchange(sidbe, new StoringStockItemDBEntry(this,
982 (StoringStockImpl)sidbe.getDestination(), (StockItemImpl)si));
983 } else {
984 db.put(new StoringStockItemDBEntry(this, null, (StockItemImpl)si));
985 }
986 }
987
988 m_nModCount++;
989
990 ((StockItemImpl)si).setStock(null);
991 fireStockItemsRemoved(new StoringStockChangeEvent<T, CT>(this, si, db));
992
993 return si;
994 }
995 }
996 }
997
998 // StockImpl methods
999
1000 /**
1001 * Overridden to accomodate for specific usage of memory.
1002 *
1003 * @override Never
1004 */
1005 protected void fillShallowClone(StoringStockImpl<T, CT> stiClone) {
1006
1007 synchronized (getItemsLock()) {
1008 synchronized (stiClone.getItemsLock()) {
1009 stiClone.setItemsContainer(new HashMap<String, List<T>>());
1010 for (Iterator i = getItemsContainer().keySet().iterator(); i.hasNext(); ) {
1011 String sKey = (String)i.next();
1012 stiClone.getItemsContainer().put(sKey, new LinkedList<T>(getItemsContainer().get(sKey)));
1013 // shallow clone of LinkedList
1014 }
1015 stiClone.setTemporaryAddedItemsContainer(new HashMap<String, List<T>>());
1016 for (Iterator i = getTemporaryAddedItemsContainer().keySet().iterator(); i.hasNext(); ) {
1017 String sKey = (String)i.next();
1018 stiClone.getTemporaryAddedItemsContainer().put(sKey, new LinkedList<T>(getTemporaryAddedItemsContainer().get(sKey)));
1019
1020 }
1021 stiClone.setTemporaryRemovedItemsContainer(new HashMap<String, List<T>>());
1022 for (Iterator i = getTemporaryRemovedItemsContainer().keySet().iterator(); i.hasNext(); ) {
1023 String sKey = (String)i.next();
1024 stiClone.getTemporaryRemovedItemsContainer().put(sKey, new LinkedList<T>(getTemporaryRemovedItemsContainer().get(sKey)));
1025
1026 }
1027 stiClone.setEditingItemsContainer(new HashMap<String, List<T>>());
1028 for (Iterator i = getEditingItemsContainer().keySet().iterator(); i.hasNext(); ) {
1029 String sKey = (String)i.next();
1030 stiClone.getEditingItemsContainer().put(sKey, new LinkedList<T>(getEditingItemsContainer().get(sKey)));
1031
1032 }
1033 stiClone.setRefIntegrItemsContainer(new HashMap<String, List<T>>());
1034 for (Iterator i = getRefIntegrItemsContainer().keySet().iterator(); i.hasNext(); ) {
1035 String sKey = (String)i.next();
1036 stiClone.getRefIntegrItemsContainer().put(sKey, new LinkedList<T>(getRefIntegrItemsContainer().get(sKey)));
1037
1038 }
1039 stiClone.setRefIntegrEditContainer(new HashMap<String, String>());
1040 for (Iterator<String> i = getRefIntegrEditContainer().keySet().iterator(); i.hasNext(); ) {
1041 String sKey = i.next();
1042 stiClone.getRefIntegrEditContainer().put(sKey, getRefIntegrEditContainer().get(sKey));
1043 }
1044 }
1045 }
1046 }
1047
1048 /**
1049 * @override Always
1050 */
1051 protected StockImpl<List<T>, T, CT> createPeer() {
1052 StoringStockImpl<T, CT> ssiPeer = new StoringStockImpl<T, CT>(getName(), m_ciCatalog);
1053 ssiPeer.m_dbCatalogValidator = m_dbCatalogValidator;
1054
1055 return ssiPeer;
1056 }
1057
1058 /**
1059 * Set the Stock and adjust the Catalog link for all Stocks that are contained in this Stock.
1060 *
1061 * @override Never
1062 */
1063 protected void setStock(StockImpl sti) {
1064 super.setStock(sti);
1065
1066 if (sti != null) {
1067 synchronized (getItemsLock()) {
1068 Set<String> stKeys = getItemsContainer().keySet();
1069 stKeys.addAll(getTemporaryAddedItemsContainer().keySet());
1070
1071 for (Iterator<String> i = stKeys.iterator(); i.hasNext(); ) {
1072 reEstablishStockCatalogLink(i.next());
1073 }
1074 }
1075 }
1076 }
1077
1078 /**
1079 * Helper method used to maintain StockImpl - CatalogImpl links in nested Stocks/Catalogs. For internal use only.
1080 *
1081 * @param db the DataBasket that is protecting this activity.
1082 * @param nAction the action that occurred. Can be either {@link #COMMIT_ACTION}, {@link #ROLLBACK_ACTION},
1083 * {@link #STARTEDIT_ACTION}.
1084 */
1085 void relinkCatalog(DataBasket db, int nAction) {
1086 super.relinkCatalog(db, nAction);
1087
1088 if (nAction == ROLLBACK_ACTION) {
1089 // Additionally refresh the links in all child stocks.
1090 synchronized (getItemsLock()) {
1091 for (Iterator<List<T>> i = getItemsContainer().values().iterator(); i.hasNext(); ) {
1092 List<T> l = i.next();
1093
1094 for (Iterator<T> j = l.iterator(); j.hasNext(); ) {
1095 StockItemImpl sii = j.next();
1096
1097 sii.relinkCatalog(db, nAction);
1098 }
1099 }
1100
1101 for (Iterator<List<T>> i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext(); ) {
1102 List<T> l = i.next();
1103
1104 for (Iterator<T> j = l.iterator(); j.hasNext(); ) {
1105 StockItemImpl sii = j.next();
1106
1107 sii.relinkCatalog(db, nAction);
1108 }
1109 }
1110 }
1111 }
1112 }
1113
1114 // SelfManagingDBESource interface methods
1115
1116 /**
1117 * Commit the removal of a StockItem.
1118 *
1119 * <p>A <code>commitRemoveStockItems</code> will be fired.</p>
1120 *
1121 * @override Never
1122 */
1123 public void commitRemove(DataBasket db, DataBasketEntry<T> dbe) {
1124 // DataBasket is already locking on its monitor so we just lock on ours
1125 synchronized (getItemsLock()) {
1126 T sii = dbe.getValue();
1127
1128 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
1129 if (lRemoved != null) {
1130 lRemoved.remove(sii);
1131
1132 if (lRemoved.size() == 0) {
1133 getTemporaryRemovedItemsContainer().remove(sii.getName());
1134 }
1135
1136 if (sii.getStock().equals(this)) {
1137 sii.setStock(null);
1138 }
1139 fireStockItemsRemoveCommit(new StoringStockChangeEvent<T, CT>(this, sii, db));
1140 }
1141 }
1142 }
1143
1144 /**
1145 * Rollback the removal of a StockItem.
1146 *
1147 * <p>A <code>rollbackRemoveStockItems</code> will be fired. Also, the Stock will try to make sure, that
1148 * a corresponding CatalogItem exists.</p>
1149 *
1150 * @override Never
1151 */
1152 public void rollbackRemove(DataBasket db, DataBasketEntry<T> dbe) {
1153 synchronized (getItemsLock()) {
1154 prepareReferentialIntegrity(db, dbe);
1155
1156 T sii = dbe.getValue();
1157
1158 List<T> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName());
1159 if (lRemoved != null) {
1160 lRemoved.remove(sii);
1161
1162 if (lRemoved.size() == 0) {
1163 getTemporaryRemovedItemsContainer().remove(sii.getName());
1164 }
1165
1166 List<T> lItems = getItemsContainer().get(sii.getName());
1167 if (lItems == null) {
1168 lItems = new LinkedList<T>();
1169 getItemsContainer().put(sii.getName(), lItems);
1170 }
1171
1172 lItems.add(sii);
1173
1174 sii.setStock(this);
1175
1176 m_nModCount++;
1177
1178 fireStockItemsRemoveRollback(new StoringStockChangeEvent<T, CT>(this, sii, db));
1179 }
1180 }
1181 }
1182
1183 // SelfManagingDBEDestination interface methods
1184
1185 /**
1186 * Commit the adding of a StockItem.
1187 *
1188 * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
1189 * fired as a consequence of this method. Also, the Stock will try to make sure, that a corresponding
1190 * CatalogItem exists.</p>
1191 *
1192 * @override Never
1193 */
1194 public void commitAdd(DataBasket db, DataBasketEntry<T> dbe) {
1195 synchronized (getItemsLock()) {
1196 prepareReferentialIntegrity(db, dbe);
1197
1198 T sii = dbe.getValue();
1199
1200 List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
1201 if (lAdded != null) {
1202 lAdded.remove(sii);
1203
1204 if (lAdded.size() == 0) {
1205 getTemporaryAddedItemsContainer().remove(sii.getName());
1206 }
1207
1208 List<T> lItems = getItemsContainer().get(sii.getName());
1209 if (lItems == null) {
1210 lItems = new LinkedList<T>();
1211 getItemsContainer().put(sii.getName(), lItems);
1212 }
1213
1214 lItems.add(sii);
1215
1216 sii.setStock(this);
1217
1218 m_nModCount++;
1219
1220 fireStockItemsAddCommit(new StoringStockChangeEvent<T, CT>(this, sii, db));
1221
1222 List<T> lEdit = getEditingItemsContainer().get(sii.getName());
1223 if ((lEdit != null) && (lEdit.remove(sii))) {
1224 if (lEdit.size() == 0) {
1225 getEditingItemsContainer().remove(sii.getName());
1226 }
1227
1228 fireStockItemsEditCommit(new StoringStockChangeEvent<T, CT>(this, sii, db));
1229 }
1230 }
1231 }
1232 }
1233
1234 /**
1235 * Rollback the adding of a StockItem.
1236 *
1237 * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
1238 * fired as a consequence of this method.</p>
1239 *
1240 * @override Never
1241 */
1242 public void rollbackAdd(DataBasket db, DataBasketEntry<T> dbe) {
1243 synchronized (getItemsLock()) {
1244 T sii = dbe.getValue();
1245
1246 List<T> lAdded = getTemporaryAddedItemsContainer().get(sii.getName());
1247 if (lAdded != null) {
1248 lAdded.remove(sii);
1249
1250 if (lAdded.size() == 0) {
1251 getTemporaryAddedItemsContainer().remove(sii.getName());
1252 }
1253
1254 if (sii.getStock().equals(this)) {
1255 sii.setStock(null);
1256 }
1257
1258 m_nModCount++;
1259
1260 fireStockItemsAddRollback(new StoringStockChangeEvent<T, CT>(this, sii, db));
1261
1262 List<T> lEdit = getEditingItemsContainer().get(sii.getName());
1263 if ((lEdit != null) && (lEdit.remove(sii))) {
1264 if (lEdit.size() == 0) {
1265 getEditingItemsContainer().remove(sii.getName());
1266 }
1267
1268 fireStockItemsEditRollback(new StoringStockChangeEvent<T, CT>(this, sii, db));
1269 }
1270 }
1271 }
1272 }
1273 }