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