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