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 }