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