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 }