001 package data.ooimpl;
002
003 import java.util.*;
004
005 import data.events.*;
006 import data.*;
007
008 /**
009 * Pure Java implementation of the {@link CountingStock} interface.
010 *
011 * @author Steffen Zschaler
012 * @version 2.0 19/08/1999
013 * @since v2.0
014 */
015 public class CountingStockImpl extends StockImpl<Integer> implements CountingStock {
016
017 /**
018 * ID for serialization.
019 */
020 private static final long serialVersionUID = -2142141301277486912L;
021
022 /**
023 * Listens for the Catalog to ensure referential integrity.
024 *
025 * @serial
026 */
027 protected CatalogChangeListener m_cclReferentialIntegrityListener;
028
029 /**
030 * Create a new, initially empty CountingStockImpl.
031 *
032 * @param sName the name of the Stock.
033 * @param ciRef the Catalog referenced by the Stock.
034 */
035 public CountingStockImpl(String sName, CatalogImpl ciRef) {
036 super(sName, ciRef);
037
038 // enhanced version.
039 m_sclEditCreatorListener = new StockChangeAdapter() {
040
041 private static final long serialVersionUID = -688456510743605439L;
042
043 public void commitAddStockItems(StockChangeEvent e) {
044 synchronized (getItemsLock()) {
045 String sKey = e.getAffectedKey();
046
047 Integer iAdded = getTemporaryAddedItemsContainer().remove(sKey);
048
049 if (iAdded == null) {
050 return;
051 }
052
053 iAdded = new Integer(iAdded.intValue() - e.countAffectedItems());
054 if (iAdded.intValue() > 0) {
055 getTemporaryAddedItemsContainer().put(sKey, iAdded);
056 }
057
058 Integer iItems = getItemsContainer().get(sKey);
059
060 if (iItems == null) {
061 iItems = new Integer(e.countAffectedItems());
062 } else {
063 iItems = new Integer(iItems.intValue() + e.countAffectedItems());
064 }
065
066 if (iAdded.intValue() < 0) {
067 iItems = new Integer(iItems.intValue() + iAdded.intValue());
068 }
069
070 if (iItems.intValue() > 0) {
071 getItemsContainer().put(sKey, iItems);
072 }
073
074 fireStockItemsAddCommit(new CountingStockChangeEvent(CountingStockImpl.this, e.getBasket(),
075 sKey,
076 ((iAdded.intValue() < 0) ? (e.countAffectedItems() + iAdded.intValue()) :
077 (e.countAffectedItems()))));
078 }
079 }
080
081 public void rollbackAddStockItems(StockChangeEvent e) {
082 synchronized (getItemsLock()) {
083 String sKey = e.getAffectedKey();
084
085 Integer iAdded = getTemporaryAddedItemsContainer().remove(sKey);
086
087 if (iAdded == null) {
088 return;
089 }
090
091 iAdded = new Integer(iAdded.intValue() - e.countAffectedItems());
092 if (iAdded.intValue() > 0) {
093 getTemporaryAddedItemsContainer().put(sKey, iAdded);
094 }
095
096 fireStockItemsAddRollback(new CountingStockChangeEvent(CountingStockImpl.this,
097 e.getBasket(), sKey,
098 ((iAdded.intValue() < 0) ? (e.countAffectedItems() + iAdded.intValue()) :
099 (e.countAffectedItems()))));
100 }
101 }
102
103 public void canRemoveStockItems(StockChangeEvent e) throws VetoException {
104 throw new VetoException("Please use the editable version for this!");
105 }
106
107 public void commitRemoveStockItems(StockChangeEvent e) {
108 synchronized (getItemsLock()) {
109 String sKey = e.getAffectedKey();
110
111 Integer iRemoved = getTemporaryRemovedItemsContainer().remove(sKey);
112
113 if (iRemoved == null) {
114 return;
115 }
116
117 iRemoved = new Integer(iRemoved.intValue() - e.countAffectedItems());
118 if (iRemoved.intValue() > 0) {
119 getTemporaryRemovedItemsContainer().put(sKey, iRemoved);
120 }
121
122 fireStockItemsRemoveCommit(new CountingStockChangeEvent(CountingStockImpl.this,
123 e.getBasket(), sKey,
124 ((iRemoved.intValue() < 0) ? (e.countAffectedItems() + iRemoved.intValue()) :
125 (e.countAffectedItems()))));
126 }
127 }
128
129 public void rollbackRemoveStockItems(StockChangeEvent e) {
130 synchronized (getItemsLock()) {
131 String sKey = e.getAffectedKey();
132
133 Integer iRemoved = getTemporaryRemovedItemsContainer().remove(sKey);
134
135 if (iRemoved == null) {
136 return;
137 }
138
139 iRemoved = new Integer(iRemoved.intValue() - e.countAffectedItems());
140 if (iRemoved.intValue() > 0) {
141 getTemporaryRemovedItemsContainer().put(sKey, iRemoved);
142 }
143
144 Integer iItems = getItemsContainer().get(sKey);
145
146 if (iItems == null) {
147 iItems = new Integer(e.countAffectedItems());
148 } else {
149 iItems = new Integer(iItems.intValue() + e.countAffectedItems());
150 }
151
152 if (iRemoved.intValue() < 0) {
153 iItems = new Integer(iItems.intValue() + iRemoved.intValue());
154 }
155
156 if (iItems.intValue() > 0) {
157 getItemsContainer().put(sKey, iItems);
158 }
159
160 fireStockItemsRemoveRollback(new CountingStockChangeEvent(CountingStockImpl.this,
161 e.getBasket(), sKey,
162 ((iRemoved.intValue() < 0) ? (e.countAffectedItems() + iRemoved.intValue()) :
163 (e.countAffectedItems()))));
164 }
165 }
166
167 public void canEditStockItems(StockChangeEvent e) throws VetoException {
168 throw new VetoException("Please use the editable version for this!");
169 }
170
171 public void commitEditStockItems(StockChangeEvent e) {
172 synchronized (getItemsLock()) {
173 String sKey = e.getAffectedKey();
174
175 Integer iEditing = getEditingItemsContainer().remove(sKey);
176
177 if (iEditing == null) {
178 return;
179 }
180
181 iEditing = new Integer(iEditing.intValue() - e.countAffectedItems());
182 if (iEditing.intValue() > 0) {
183 getEditingItemsContainer().put(sKey, iEditing);
184 }
185
186 fireStockItemsEditCommit(new CountingStockChangeEvent(CountingStockImpl.this, e.getBasket(),
187 sKey,
188 ((iEditing.intValue() < 0) ? (e.countAffectedItems() + iEditing.intValue()) :
189 (e.countAffectedItems()))));
190 }
191 }
192
193 public void rollbackEditStockItems(StockChangeEvent e) {
194 synchronized (getItemsLock()) {
195 String sKey = e.getAffectedKey();
196
197 Integer iEditing = getEditingItemsContainer().remove(sKey);
198
199 if (iEditing == null) {
200 return;
201 }
202
203 iEditing = new Integer(iEditing.intValue() - e.countAffectedItems());
204 if (iEditing.intValue() > 0) {
205 getEditingItemsContainer().put(sKey, iEditing);
206 }
207
208 fireStockItemsEditRollback(new CountingStockChangeEvent(CountingStockImpl.this,
209 e.getBasket(), sKey,
210 ((iEditing.intValue() < 0) ? (e.countAffectedItems() + iEditing.intValue()) :
211 (e.countAffectedItems()))));
212 }
213 }
214 };
215 }
216
217 /**
218 * Overridden to ensure referential integrity.
219 *
220 * @override Never
221 */
222 protected void internalSetCatalog(CatalogImpl ciRef) {
223 if (m_ciCatalog != null) {
224 m_ciCatalog.removeCatalogChangeListener(m_cclReferentialIntegrityListener);
225 }
226
227 super.internalSetCatalog(ciRef);
228
229 if (m_ciCatalog != null) {
230 if (m_cclReferentialIntegrityListener == null) {
231 initReferentialIntegrityListener();
232 }
233
234 m_ciCatalog.addCatalogChangeListener(m_cclReferentialIntegrityListener);
235 }
236 }
237
238 /**
239 * Private helper function creating the listener that ensures referential integrity.
240 *
241 * @override Never
242 */
243 private void initReferentialIntegrityListener() {
244 m_cclReferentialIntegrityListener = new CatalogChangeAdapter() {
245
246 private static final long serialVersionUID = 8586930963211243988L;
247
248 public void canRemoveCatalogItem(CatalogChangeEvent e) throws VetoException {
249 // DataBasket already locks on its monitor!
250 synchronized (getItemsLock()) {
251 String sKey = e.getAffectedItem().getName();
252
253 if (getTemporaryAddedItemsContainer().containsKey(sKey)) {
254 throw new VetoException("Stock " + getName() +
255 ": Having temporarily added items for key \"" + sKey + "\"");
256 }
257
258 if (getTemporaryRemovedItemsContainer().containsKey(sKey)) {
259 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
260 CountingStockImpl.this, null, null);
261 BasketEntryValue bev = new BasketEntryValue() {
262 public Value getEntryValue(DataBasketEntry dbe) {
263 return new IntegerValue((Integer)dbe.getValue());
264 }
265 };
266
267 Integer iCount = getTemporaryRemovedItemsContainer().get(sKey);
268
269 IntegerValue ivCount = new IntegerValue(new Integer(0));
270 int nCount = ((IntegerValue)e.getBasket().sumBasket(dbc, bev,
271 ivCount)).getValue().intValue();
272
273 if (iCount.intValue() > nCount) {
274 throw new VetoException("Stock " + getName() +
275 ": Having temporaryly removed items that are in another DataBasket. (Key: \"" +
276 sKey + "\")");
277 }
278 }
279
280 if (getItemsContainer().containsKey(sKey)) {
281 int nCount = (getItemsContainer().get(sKey)).intValue();
282
283 remove(sKey, nCount, e.getBasket());
284
285 getRefIntegrItemsContainer().put(sKey, new Integer(nCount));
286 }
287 }
288 }
289
290 public void noRemoveCatalogItem(CatalogChangeEvent e) {
291 synchronized (getItemsLock()) {
292 String sKey = e.getAffectedItem().getName();
293
294 if (getRefIntegrItemsContainer().containsKey(sKey)) {
295 int nCount = (getRefIntegrItemsContainer().remove(sKey)).intValue();
296
297 add(sKey, nCount, e.getBasket());
298 }
299 }
300 }
301
302 public void removedCatalogItem(CatalogChangeEvent e) {
303 synchronized (getItemsLock()) {
304 // clean up
305 getRefIntegrItemsContainer().remove(e.getAffectedItem().getName());
306 }
307 }
308
309 @SuppressWarnings("unused")
310 public void commitRemoveCatalogItem(CatalogChangeEvent e) {
311 synchronized (getItemsLock()) {
312 if (!((Catalog)e.getSource()).contains(e.getAffectedItem().getName(), e.getBasket())) {
313 ciGoneForEver(e);
314 }
315 }
316 }
317
318 @SuppressWarnings("unused")
319 public void rollbackAddCatalogItem(CatalogChangeEvent e) {
320 synchronized (getItemsLock()) {
321 DataBasketCondition dbc = new DataBasketConditionImpl(CatalogItemImpl.
322 CATALOG_ITEM_MAIN_KEY, e.getAffectedItem().getName(), (CatalogImpl)e.getSource(), null, null);
323
324 if (!e.getBasket().contains(dbc)) {
325 ciGoneForEver(e);
326 }
327 }
328 }
329
330 private void ciGoneForEver(CatalogChangeEvent e) {
331
332 String sKey = e.getAffectedItem().getName();
333 DataBasket db = e.getBasket();
334
335 if (getTemporaryAddedItemsContainer().containsKey(sKey)) {
336 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null,
337 CountingStockImpl.this, null);
338
339 // Rollback all items temporaryly added to this Stock
340 // CountingStocks produce only DataBasketEntries that have either source or dest set,
341 // so a complete rollback will be OK.
342 // However, we cannot simply write db.rollback (dbc), as this would remove the handled
343 // entries immediately, thus invalidating the iterator that was used to perform the
344 // commit or rollback that lead to this method being called.
345 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
346 (i.next()).rollback();
347 }
348 }
349
350 getItemsContainer().remove(sKey);
351 getRefIntegrItemsContainer().remove(sKey);
352
353 if (getTemporaryRemovedItemsContainer().containsKey(sKey)) {
354 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey,
355 CountingStockImpl.this, null, null);
356
357 // Commit all items temporaryly removed from this Stock
358 // CountingStocks produce only DataBasketEntries that have either source or dest set,
359 // so a complete commit will be OK.
360 // However, we cannot simply write db.commit (dbc), as this would remove the handled
361 // entries immediately, thus invalidating the iterator that was used to perform the
362 // commit or rollback that lead to this method being called.
363 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
364 (i.next()).commit();
365 }
366 }
367 }
368 };
369 }
370
371 // Stock interface methods
372
373 /**
374 * Add an item to the Stock.
375 *
376 * <p>The item will only be visible to users of the same DataBasket. Only after a {@link DataBasket#commit}
377 * was performed on the DataBasket, the item will become visible to other users.</p>
378 *
379 * <p>A <code>addedStockItems</code> event will be fired.</p>
380 *
381 * @param si the item to be added.
382 * @param db the DataBasket relative to which the item will be added. Must be either <code>null</code> or
383 * a descendant of {@link DataBasketImpl}.
384 *
385 * @override Never
386 *
387 * @exception CatalogConflictException if the items key is not contained in the corresponding {@link Catalog}.
388 * @exception DataBasketConflictException if the item has already been added/removed using another DataBasket.
389 */
390 public void add(StockItem si, DataBasket db) {
391 add(si.getName(), 1, db);
392 }
393
394 /**
395 * Overridden for efficiency reasons.
396 *
397 * @override Never
398 */
399 public void addStock(Stock st, DataBasket db, boolean fRemove) {
400 if (st.getCatalog(db) != getCatalog(db)) {
401 throw new CatalogConflictException();
402 }
403
404 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
405
406 synchronized (oLock) {
407 synchronized (getItemsLock()) {
408 Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock));
409
410 synchronized (oLock2) {
411 for (Iterator<String> i = st.keySet(db).iterator(); i.hasNext(); ) {
412 String sKey = i.next();
413
414 add(sKey, st.countItems(sKey, db), db);
415
416 if (fRemove) {
417 for (Iterator<StockItem> ii = st.get(sKey, db, false); ii.hasNext(); ) {
418 try {
419 ii.next();
420 ii.remove();
421 }
422 catch (ConcurrentModificationException e) {
423 break;
424 }
425 catch (Exception e) {
426 // ignore any items that could not be removed from their source
427 continue;
428 }
429 }
430 }
431 }
432 }
433 }
434 }
435 }
436
437 /**
438 * Iterate all items with a given key.
439 *
440 * <p>This method, together with {@link #iterator} is the only way of accessing the individual
441 * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that have the
442 * specified key and are visible using the given DataBasket. Depending on the <code>fForEdit</code>
443 * parameter, the items will be retrieved in different ways. See {@link DataBasket} for an explanation of
444 * the different possibilities.</p>
445 *
446 * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if
447 * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into
448 * <code>UnSupportedOperationException</code>s.</p>
449 *
450 * @override Never
451 *
452 * @param sKey the key for which to retrieve the StockItems.
453 * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code>
454 * or a descendant of {@link DataBasketImpl}.
455 * @param fForEdit if true, the StockItems will be retrieved for editing.
456 */
457 public Iterator<StockItem> get(final String sKey, final DataBasket db, boolean fForEdit) {
458 class I implements Iterator<StockItem> {
459 private boolean m_fNextCalled = false;
460 private int m_nCount;
461 private CountingStockImpl m_cstiOwner;
462 private StockItemImpl m_siiLast;
463
464 public I(CountingStockImpl cstiOwner, int nCount) {
465 super();
466
467 m_cstiOwner = cstiOwner;
468 m_nCount = nCount;
469 }
470
471 public boolean hasNext() {
472 return (m_nCount > 0);
473 }
474
475 public StockItem next() {
476 if ((m_nCount--) <= 0) { //first checks m_nCount, then decreases
477 m_fNextCalled = false;
478 throw new NoSuchElementException();
479 }
480
481 m_fNextCalled = true;
482 m_siiLast = new StockItemImpl(sKey);
483 m_siiLast.setStock(m_cstiOwner);
484
485 return m_siiLast;
486 }
487
488 public void remove() {
489 if (m_fNextCalled) {
490 m_fNextCalled = false;
491
492 try {
493 m_cstiOwner.remove(sKey, 1, db);
494 }
495 catch (VetoException ex) {
496 throw new UnsupportedOperationException("VetoException: " + ex.getMessage());
497 }
498
499 m_siiLast.setStock(null);
500 } else {
501 throw new IllegalStateException();
502 }
503 }
504 }
505
506 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
507 return new Iterator<StockItem>() {
508 public boolean hasNext() {
509 return false;
510 }
511
512 public StockItem next() {
513 throw new NoSuchElementException();
514 }
515
516 public void remove() {}
517 };
518 }
519
520 return new I(this, countItems(sKey, db));
521 }
522
523 /**
524 * Count the StockItems with a given key that are visible using a given DataBasket.
525 *
526 * @override Never
527 *
528 * @param sKey the key for which to count the StockItems.
529 * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a
530 * descendant of {@link DataBasketImpl}.
531 */
532 public int countItems(String sKey, DataBasket db) {
533 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
534
535 int nCount = 0;
536
537 synchronized (oLock) {
538 synchronized (getItemsLock()) {
539 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
540 return 0;
541 }
542
543 Integer iCount = getItemsContainer().get(sKey);
544
545 if (iCount != null) {
546 nCount = iCount.intValue();
547 }
548 //cannot use the value of mTemporaryAdded to get the temporary added items,
549 //because different DataBaskets might have added items to it, and we only want
550 //the items added with THIS databasket
551 if (db != null) {
552 DataBasketCondition dbc = new DataBasketConditionImpl(
553 STOCK_ITEM_MAIN_KEY, sKey, null, this, null);
554
555 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) {
556 StockItemDBEntry sidbe = (StockItemDBEntry)i.next();
557
558 nCount += sidbe.count();
559 }
560 }
561 }
562 }
563
564 return nCount;
565 }
566
567 /**
568 * Check whether the Stock contains the given StockItem.
569 *
570 * <p>Return true if the Stock contains a StockItem that is equal to the given one.</p>
571 *
572 * @param si the StockItem for which to check containment.
573 * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of
574 * {@link DataBasketImpl}.
575 *
576 * @override Never
577 */
578 public boolean contains(StockItem si, DataBasket db) {
579 return contains(si.getName(), db);
580 }
581
582 /**
583 * Reimplemented for efficiency reasons.
584 *
585 * @override Never
586 */
587 public boolean containsStock(Stock st, DataBasket db) {
588 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
589
590 synchronized (oLock) {
591 synchronized (getItemsLock()) {
592 Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock));
593
594 synchronized (oLock2) {
595 for (Iterator<String> i = st.keySet(db).iterator(); i.hasNext(); ) {
596 String sKey = i.next();
597
598 if (countItems(sKey, db) < st.countItems(sKey, db)) {
599 return false;
600 }
601 }
602
603 return true;
604 }
605 }
606 }
607 }
608
609 /**
610 * Remove one StockItem with the specified key from the Stock.
611 *
612 * <p>If there are any StockItems with the specified key, one will be removed. There is no guarantee as to
613 * which StockItem will be removed. The removed item, if any, will be returned.</p>
614 *
615 * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
616 *
617 * @override Never
618 *
619 * @param sKey the key for which to remove an item.
620 * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
621 * descendant of {@link DataBasketImpl}.
622 *
623 * @return the removed item
624 *
625 * @exception VetoException if a listener vetoed the removal.
626 * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
627 * usage.
628 */
629 public StockItem remove(String sKey, DataBasket db) throws VetoException {
630 remove(sKey, 1, db);
631
632 StockItemImpl sii = new StockItemImpl(sKey);
633 sii.setStock(null);
634
635 return sii;
636 }
637
638 /**
639 * Remove the given StockItem from the Stock.
640 *
641 * <p>If the given StockItem is contained in the Stock, it will be removed. The removed item, if any, will
642 * be returned.</p>
643 *
644 * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p>
645 *
646 * @override Never
647 *
648 * @param si the StockItem to be removed.
649 * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a
650 * descendant of {@link DataBasketImpl}.
651 *
652 * @return the removed item
653 *
654 * @exception VetoException if a listener vetoed the removal.
655 * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket
656 * usage.
657 */
658 public StockItem remove(StockItem si, DataBasket db) throws VetoException {
659 return remove(si.getName(), db);
660 }
661
662 /**
663 * @override Always
664 */
665 protected StockImpl<Integer> createPeer() {
666 CountingStockImpl csiPeer = new CountingStockImpl(getName(), m_ciCatalog);
667 csiPeer.m_dbCatalogValidator = m_dbCatalogValidator;
668
669 return csiPeer;
670 }
671
672 // CountingStock interface methods
673 /**
674 * Add a number of items of a given key to the Stock.
675 *
676 * <p>As with any Stock the added items will not at once be visible to users of other DataBaskets.</p>
677 *
678 * <p>In general the method behaves as though it would call {@link Stock#add} <code>nCount</code> times.
679 * Especially, the same exceptions might occur and the same constraints hold.</p>
680 *
681 * @override Never
682 *
683 * @param sKey the key for which to add a number of items.
684 * @param nCount how many items are to be added?
685 * @param db the DataBasket relative to which the adding is performed. Must be either <code>null</code> or a
686 * descendant of {@link DataBasketImpl}.
687 *
688 * @exception IllegalArgumentException if <code>nCount <= 0</code>.
689 * @exception CatalogConflictException if the key cannot be found in the Catalog.
690 */
691 public void add(String sKey, int nCount, DataBasket db) {
692 if (nCount <= 0) {
693 throw new IllegalArgumentException("nCount must be greater than 0.");
694 }
695
696 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
697
698 synchronized (oLock) {
699 synchronized (getItemsLock()) {
700 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
701 throw new CatalogConflictException("Couldn't find key \"" + sKey + "\" in Catalog \"" +
702 getCatalog(db).getName() + "\"");
703 }
704 if (db != null) {
705 //use an array for anTempCount to both make it final and be able to change its value
706 final int[] anTempCount = {
707 nCount};
708 //if there are already temporary removed StockItems, rollback the right amount of them
709 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, this,
710 null, null) {
711
712 private static final long serialVersionUID = -1737586945326470002L;
713
714 public boolean match(DataBasketEntry dbe) {
715 //this test seems redundant as we have already tested if ncount <= 0
716 //however, if two or more DataBasketEntries for this StockItem exist, it
717 //is possible that ncount StockItems have already been rolled back, so anTempCount[0]
718 //has been set to 0 (see * some lines below) but yet another DataBasketEntry is
719 //examined. Here we have to stop.
720 if (anTempCount[0] == 0) {
721 return false;
722 }
723
724 StockItemDBEntry sidbe = (StockItemDBEntry)dbe;
725
726 if (anTempCount[0] >= sidbe.count()) {
727 anTempCount[0] -= sidbe.count(); // *
728 return true;
729 } else {
730 CountingStockItemDBEntry csidbe = (CountingStockItemDBEntry)sidbe;
731 csidbe.partialRollback(anTempCount[0]);
732 anTempCount[0] = 0;
733
734 return false;
735 }
736 }
737 };
738
739 db.rollback(dbc);
740
741 nCount = anTempCount[0];
742
743 if (nCount > 0) {
744 Integer iCount = getTemporaryAddedItemsContainer().remove(sKey);
745
746 if (iCount == null) {
747 iCount = new Integer(nCount);
748 } else {
749 iCount = new Integer(iCount.intValue() + nCount);
750 }
751
752 getTemporaryAddedItemsContainer().put(sKey, iCount);
753
754 db.put(new CountingStockItemDBEntry(sKey, null, this, nCount));
755 fireStockItemsAdded(new CountingStockChangeEvent(this, db, sKey, nCount));
756 } else {
757 if (db instanceof ListenableDataBasket) {
758 ((ListenableDataBasket)db).fireDataBasketChanged();
759 }
760 }
761 } else {
762 Integer iCount = getItemsContainer().get(sKey);
763 if (iCount == null) {
764 iCount = new Integer(nCount);
765 } else {
766 iCount = new Integer(iCount.intValue() + nCount);
767 }
768
769 getItemsContainer().put(sKey, iCount);
770 fireStockItemsAdded(new CountingStockChangeEvent(this, null, sKey, nCount));
771 }
772 }
773 }
774 }
775
776 /**
777 * Remove a number of items of a given key from the Stock.
778 *
779 * <p>In general the method behaves as though it would call
780 * {@link Stock#remove(java.lang.String, data.DataBasket)} <code>nCount</code> times. Especially, the same
781 * exceptions might occur and the same constraints hold.</p>
782 *
783 * @override Never
784 *
785 * @param sKey the key for which to remove a number of items.
786 * @param nCount how many items are to be removed?
787 * @param db the DataBasket relative to which the removal is performed. Must be either <code>null</code> or
788 * a descendant of {@link DataBasketImpl}.
789 *
790 * @exception VetoException if a listener vetos the removal.
791 * @exception NotEnoughElementsException if there are not enough elements to fulfill the request. If this
792 * exception is thrown no items will have been removed.
793 * @exception IllegalArgumentException if <code>nCount <= 0</code>
794 */
795 public void remove(String sKey, int nCount, DataBasket db) throws VetoException {
796 if (nCount <= 0) {
797 throw new IllegalArgumentException("nCount must be greater than 0.");
798 }
799
800 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()));
801
802 synchronized (oLock) {
803 synchronized (getItemsLock()) {
804 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) {
805 throw new CatalogConflictException("Couldn't find key \"" + sKey + "\" in Catalog \"" +
806 getCatalog(db).getName() + "\"");
807 }
808
809 if (countItems(sKey, db) < nCount) {
810 throw new NotEnoughElementsException();
811 }
812
813 fireCanRemoveStockItems(new CountingStockChangeEvent(this, db, sKey, nCount));
814
815 if (db != null) {
816 final int[] anTempCount = {
817 nCount};
818 //if there are temporary added StockItems, rollback the right amount of them
819 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null) {
820
821 private static final long serialVersionUID = -8776152524534177819L;
822
823 //if there are already temporary removed StockItems, rollback the right amount of them
824 public boolean match(DataBasketEntry dbe) {
825 //this test seems redundant as we have already tested if ncount <= 0
826 //however, if two or more DataBasketEntries for this StockItem exist, it
827 //is possible that ncount StockItems have already been rolled back, so anTempCount[0]
828 //has been set to 0 (see * some lines below) but yet another DataBasketEntry is
829 //examined. Here we have to stop.
830 if (anTempCount[0] == 0) {
831 return false;
832 }
833
834 StockItemDBEntry sidbe = (StockItemDBEntry)dbe;
835
836 if (anTempCount[0] >= sidbe.count()) {
837 anTempCount[0] -= sidbe.count(); // *
838 return true;
839 } else {
840 CountingStockItemDBEntry csidbe = (CountingStockItemDBEntry)sidbe;
841 csidbe.partialRollback(anTempCount[0]);
842 anTempCount[0] = 0;
843 return false;
844 }
845 }
846 };
847
848 db.rollback(dbc);
849
850 nCount = anTempCount[0];
851
852 if (nCount > 0) {
853 Integer iCount = getItemsContainer().remove(sKey);
854
855 if (iCount.intValue() > nCount) {
856 getItemsContainer().put(sKey, new Integer(iCount.intValue() - nCount));
857 }
858
859 iCount = getTemporaryRemovedItemsContainer().remove(sKey);
860
861 if (iCount == null) {
862 iCount = new Integer(nCount);
863 } else {
864 iCount = new Integer(iCount.intValue() + nCount);
865 }
866
867 getTemporaryRemovedItemsContainer().put(sKey, iCount);
868
869 db.put(new CountingStockItemDBEntry(sKey, this, null, nCount));
870 } else {
871 if (db instanceof ListenableDataBasket) {
872 ((ListenableDataBasket)db).fireDataBasketChanged();
873 }
874 }
875 } else {
876 Integer iCount = getItemsContainer().get(sKey);
877
878 if (iCount.intValue() > nCount) {
879 iCount = new Integer(iCount.intValue() - nCount);
880 getItemsContainer().put(sKey, iCount);
881 } else {
882 getItemsContainer().remove(sKey);
883 }
884 }
885
886 fireStockItemsRemoved(new CountingStockChangeEvent(this, db, sKey, nCount));
887 }
888 }
889 }
890
891 // Object standard methods
892 /**
893 * Get a String representation of the Stock.
894 *
895 * @override Sometimes
896 */
897 public String toString() {
898 synchronized (getItemsLock()) {
899 String sReturn = "Stock \"" + getName() + "\" [";
900
901 boolean fFirst = true;
902 for (Iterator<String> i = keySet(null).iterator(); i.hasNext(); ) {
903 String sKey = i.next();
904
905 sReturn += ((fFirst) ? ("") : (", ")) + sKey + ": " + countItems(sKey, null);
906 fFirst = false;
907 }
908
909 return sReturn + "]";
910 }
911 }
912
913
914 // SelfManagingDBESource interface methods
915 /**
916 * Commit the removal of StockItems.
917 *
918 * <p>A <code>commitRemoveStockItems</code> will be fired.</p>
919 *
920 * @override Never
921 */
922 public void commitRemove(DataBasket db, DataBasketEntry dbe) {
923 // DataBasket already locks on its monitor, so we just lock on ours.
924 synchronized (getItemsLock()) {
925 Integer iCount = getTemporaryRemovedItemsContainer().remove(dbe.getSecondaryKey());
926
927 int nRemains = iCount.intValue() - ((StockItemDBEntry)dbe).count();
928
929 if (nRemains > 0) {
930 getTemporaryRemovedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
931 }
932
933 fireStockItemsRemoveCommit(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(),
934 ((StockItemDBEntry)dbe).count()));
935 }
936 }
937
938 /**
939 * Rollback the removal of StockItems.
940 *
941 * <p>A <code>rollbackRemoveStockItems</code> will be fired. Also, the Stock will try to make sure, that
942 * a corresponding CatalogItem exists.</p>
943 *
944 * @override Never
945 */
946 public void rollbackRemove(DataBasket db, DataBasketEntry dbe) {
947 synchronized (getItemsLock()) {
948 prepareReferentialIntegrity(db, dbe);
949
950 Integer iCount = getTemporaryRemovedItemsContainer().remove(dbe.getSecondaryKey());
951
952 int nCount = ((StockItemDBEntry)dbe).count();
953 int nRemains = iCount.intValue() - nCount;
954
955 if (nRemains > 0) {
956 getTemporaryRemovedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
957 }
958
959 iCount = (Integer)getItemsContainer().remove(dbe.getSecondaryKey());
960
961 if (iCount == null) {
962 iCount = new Integer(nCount);
963 } else {
964 iCount = new Integer(iCount.intValue() + nCount);
965 }
966
967 getItemsContainer().put(dbe.getSecondaryKey(), iCount);
968
969 fireStockItemsRemoveRollback(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(), nCount));
970 }
971 }
972
973 // SelfManagingDBEDestination interface methods
974 /**
975 * Commit the adding of StockItems.
976 *
977 * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
978 * fired as a consequence of this method. Also, the Stock will try to make sure, that a corresponding
979 * CatalogItem exists.</p>
980 *
981 * @override Never
982 */
983 public void commitAdd(DataBasket db, DataBasketEntry dbe) {
984 synchronized (getItemsLock()) {
985 prepareReferentialIntegrity(db, dbe);
986
987 Integer iCount = getTemporaryAddedItemsContainer().remove(dbe.getSecondaryKey());
988
989 int nCount = ((StockItemDBEntry)dbe).count();
990 int nRemains = iCount.intValue() - nCount;
991
992 if (nRemains > 0) {
993 getTemporaryAddedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
994 }
995
996 iCount = getItemsContainer().remove(dbe.getSecondaryKey());
997
998 if (iCount == null) {
999 iCount = new Integer(nCount);
1000 } else {
1001 iCount = new Integer(iCount.intValue() + nCount);
1002 }
1003
1004 getItemsContainer().put(dbe.getSecondaryKey(), iCount);
1005
1006 fireStockItemsAddCommit(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(), nCount));
1007 }
1008 }
1009
1010 /**
1011 * Rollback the adding of StockItems.
1012 *
1013 * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be
1014 * fired as a consequence of this method.</p>
1015 *
1016 * @override Never
1017 */
1018 public void rollbackAdd(DataBasket db, DataBasketEntry dbe) {
1019 synchronized (getItemsLock()) {
1020 Integer iCount = getTemporaryAddedItemsContainer().remove(dbe.getSecondaryKey());
1021
1022 int nRemains = iCount.intValue() - ((StockItemDBEntry)dbe).count();
1023
1024 if (nRemains > 0) {
1025 getTemporaryAddedItemsContainer().put(dbe.getSecondaryKey(), new Integer(nRemains));
1026 }
1027
1028 fireStockItemsAddRollback(new CountingStockChangeEvent(this, db, dbe.getSecondaryKey(),
1029 ((StockItemDBEntry)dbe).count()));
1030 }
1031 }
1032
1033 }