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