001 package data.ooimpl; 002 003 import data.*; 004 import data.events.*; 005 006 import java.util.*; 007 008 /** 009 * Pure Java implementation of the {@link StoringStock} interface. 010 * 011 * @author Steffen Zschaler 012 * @version 2.0 19/08/1999 013 * @since v2.0 014 */ 015 public class StoringStockImpl extends StockImpl<List<StockItemImpl>> implements StoringStock { 016 017 /** 018 * ID for serialization. 019 */ 020 private static final long serialVersionUID = 3460626053985545111L; 021 022 /** 023 * Modification counter. Increases by one with every structural modification. 024 * 025 * @serial 026 */ 027 protected int m_nModCount = Integer.MIN_VALUE; 028 029 /** 030 * Listens to the Stock's Catalog to ensure referential integrity. 031 * 032 * @serial 033 */ 034 protected CatalogChangeListener m_cclReferentialIntegrityListener; 035 036 /** 037 * true, if StockImpl's CatalogItemNameListener was already replaced by an enhanced version. 038 * Used internally only. 039 * 040 * @serial 041 */ 042 private boolean m_fChangedCatalogItemNameListener = false; 043 044 /** 045 * Enhanced CatalogItemNameListener, updating the names of the actual StockItems whenever a name change 046 * occurs. 047 * 048 * @author Steffen Zschaler 049 * @version 2.0 19/08/1999 050 * @since v2.0 051 */ 052 class SSICatalogItemNameListener extends CatalogItemNameListener { 053 054 private static final long serialVersionUID = -3046919332594065216L; 055 056 protected void nameChangeOccurred(String sOld, String sNew) { 057 super.nameChangeOccurred(sOld, sNew); 058 059 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sNew); 060 if (lAdded != null) { 061 for (Iterator<StockItemImpl> i = lAdded.iterator(); i.hasNext(); ) { 062 StockItemImpl sii = i.next(); 063 064 sii.internalSetName(sNew); 065 } 066 } 067 068 List<StockItemImpl> lItems = getItemsContainer().get(sNew); 069 if (lItems != null) { 070 for (Iterator<StockItemImpl> i = lItems.iterator(); i.hasNext(); ) { 071 StockItemImpl sii = i.next(); 072 073 sii.internalSetName(sNew); 074 } 075 } 076 077 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(sNew); 078 if (lRemoved != null) { 079 for (Iterator<StockItemImpl> i = lRemoved.iterator(); i.hasNext(); ) { 080 StockItemImpl sii = i.next(); 081 082 sii.internalSetName(sNew); 083 } 084 } 085 086 List<StockItemImpl> lRefIntegr = getRefIntegrItemsContainer().get(sNew); 087 if (lRefIntegr != null) { 088 for (Iterator<StockItemImpl> i = lRefIntegr.iterator(); i.hasNext(); ) { 089 StockItemImpl sii = i.next(); 090 091 sii.internalSetName(sNew); 092 } 093 } 094 } 095 } 096 097 /** 098 * Create a new, initially empty StoringStockImpl. 099 * 100 * @param sName the name of the new Stock. 101 * @param ciRef the Catalog that is being referenced by the Stock. 102 */ 103 public StoringStockImpl(String sName, CatalogImpl ciRef) { 104 super(sName, ciRef); 105 106 // Enhanced version. 107 m_sclEditCreatorListener = new StockChangeAdapter() { 108 109 private static final long serialVersionUID = -7948443502826415058L; 110 111 public void commitAddStockItems(StockChangeEvent e) { 112 synchronized (getItemsLock()) { 113 StockItemImpl sii = (StockItemImpl)e.getAffectedItems().next(); 114 115 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sii.getName()); 116 117 if (lAdded == null) { 118 return; 119 } 120 121 if (lAdded.remove(sii)) { 122 List<StockItemImpl> lItems = getItemsContainer().get(sii.getName()); 123 124 if (lItems == null) { 125 lItems = new LinkedList<StockItemImpl>(); 126 getItemsContainer().put(sii.getName(), lItems); 127 } 128 lItems.add(sii); 129 130 sii.setStock(StoringStockImpl.this); 131 132 fireStockItemsAddCommit(new StoringStockChangeEvent(StoringStockImpl.this, sii, 133 e.getBasket())); 134 } 135 } 136 } 137 138 public void rollbackAddStockItems(StockChangeEvent e) { 139 synchronized (getItemsLock()) { 140 StockItemImpl sii = (StockItemImpl)e.getAffectedItems().next(); 141 142 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sii.getName()); 143 144 if (lAdded == null) { 145 return; 146 } 147 148 if (lAdded.remove(sii)) { 149 if (sii.getStock().equals(StoringStockImpl.this)) { 150 sii.setStock(null); 151 } 152 153 fireStockItemsAddRollback(new StoringStockChangeEvent(StoringStockImpl.this, sii, 154 e.getBasket())); 155 } 156 } 157 } 158 159 public void canRemoveStockItems(StockChangeEvent e) throws VetoException { 160 throw new VetoException("Please use the editable version for this!"); 161 } 162 163 public void commitRemoveStockItems(StockChangeEvent e) { 164 synchronized (getItemsLock()) { 165 StockItemImpl sii = (StockItemImpl)e.getAffectedItems().next(); 166 167 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName()); 168 169 if (lRemoved == null) { 170 return; 171 } 172 173 if (lRemoved.remove(sii)) { 174 if (sii.getStock().equals(StoringStockImpl.this)) { 175 sii.setStock(null); 176 } 177 178 fireStockItemsRemoveCommit(new StoringStockChangeEvent(StoringStockImpl.this, sii, 179 e.getBasket())); 180 } 181 } 182 } 183 184 public void rollbackRemoveStockItems(StockChangeEvent e) { 185 synchronized (getItemsLock()) { 186 StockItemImpl sii = (StockItemImpl)e.getAffectedItems().next(); 187 188 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName()); 189 190 if (lRemoved == null) { 191 return; 192 } 193 194 if (lRemoved.remove(sii)) { 195 List<StockItemImpl> lItems = getItemsContainer().get(sii.getName()); 196 197 if (lItems == null) { 198 lItems = new LinkedList<StockItemImpl>(); 199 getItemsContainer().put(sii.getName(), lItems); 200 } 201 lItems.add(sii); 202 203 sii.setStock(StoringStockImpl.this); 204 205 fireStockItemsRemoveCommit(new StoringStockChangeEvent(StoringStockImpl.this, sii, 206 e.getBasket())); 207 } 208 } 209 } 210 211 public void canEditStockItems(StockChangeEvent e) throws VetoException { 212 throw new VetoException("Please use the editable version for this!"); 213 } 214 215 public void commitEditStockItems(StockChangeEvent e) { 216 synchronized (getItemsLock()) { 217 StockItemImpl sii = (StockItemImpl)e.getAffectedItems().next(); 218 219 List<StockItemImpl> lEditing = getEditingItemsContainer().get(sii.getName()); 220 221 if (lEditing == null) { 222 return; 223 } 224 225 if (lEditing.remove(sii)) { 226 fireStockItemsEditCommit(new StoringStockChangeEvent(StoringStockImpl.this, sii, 227 e.getBasket())); 228 } 229 } 230 } 231 232 public void rollbackEditStockItems(StockChangeEvent e) { 233 synchronized (getItemsLock()) { 234 StockItemImpl sii = (StockItemImpl)e.getAffectedItems().next(); 235 236 List<StockItemImpl> lEditing = getEditingItemsContainer().get(sii.getName()); 237 238 if (lEditing == null) { 239 return; 240 } 241 242 if (lEditing.remove(sii)) { 243 fireStockItemsEditRollback(new StoringStockChangeEvent(StoringStockImpl.this, sii, 244 e.getBasket())); 245 } 246 } 247 } 248 }; 249 } 250 251 /** 252 * Overridden because of referential integrity. 253 * 254 * @override Never 255 */ 256 protected void internalSetCatalog(CatalogImpl ciRef) { 257 if (m_ciCatalog != null) { 258 m_ciCatalog.removeCatalogChangeListener(m_cclReferentialIntegrityListener); 259 } 260 261 if (!m_fChangedCatalogItemNameListener) { 262 m_fChangedCatalogItemNameListener = true; 263 264 m_cinlCatalogItemNameListener = new SSICatalogItemNameListener(); 265 } 266 267 super.internalSetCatalog(ciRef); 268 269 if (m_ciCatalog != null) { 270 if (m_cclReferentialIntegrityListener == null) { 271 initReferentialIntegrityListener(); 272 } 273 274 m_ciCatalog.addCatalogChangeListener(m_cclReferentialIntegrityListener); 275 } 276 } 277 278 /** 279 * Internal helper function. 280 * 281 * @override Never 282 */ 283 private void initReferentialIntegrityListener() { 284 m_cclReferentialIntegrityListener = new CatalogChangeAdapter() { 285 286 private static final long serialVersionUID = -2030724124407592374L; 287 288 public void canRemoveCatalogItem(CatalogChangeEvent e) throws VetoException { 289 // DataBasket already locks on its monitor 290 synchronized (getItemsLock()) { 291 String sKey = e.getAffectedItem().getName(); 292 DataBasket db = e.getBasket(); 293 294 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sKey); 295 if ((lAdded != null) && (lAdded.size() > 0)) { 296 throw new VetoException("Stock \"" + getName() + 297 "\": Having temporarily added items of key \"" + sKey + "\"."); 298 } 299 300 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(sKey); 301 if (lRemoved != null) { 302 if ((db == null) && (lRemoved.size() > 0)) { 303 throw new VetoException("Stock \"" + getName() + 304 "\": Having temporarily removed items that are in a different DataBasket."); 305 } 306 307 for (Iterator<StockItemImpl> i = lRemoved.iterator(); i.hasNext(); ) { 308 StockItem si = (StockItem)i.next(); 309 310 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, 311 StoringStockImpl.this, null, si); 312 if (!db.contains(dbc)) { 313 throw new VetoException("Stock \"" + getName() + 314 "\": Having temporarily removed items that are in a different DataBasket."); 315 } 316 } 317 } 318 319 List<StockItemImpl> lItems = getItemsContainer().get(sKey); 320 if ((lItems != null) && (lItems.size() > 0)) { 321 List<StockItemImpl> lRefIntegr = new LinkedList<StockItemImpl>(lItems); 322 getRefIntegrItemsContainer().put(sKey, lRefIntegr); 323 324 for (Iterator<StockItemImpl> i = lRefIntegr.iterator(); i.hasNext(); ) { 325 remove((StockItem)i.next(), db); 326 } 327 } 328 } 329 } 330 331 public void noRemoveCatalogItem(CatalogChangeEvent e) { 332 synchronized (getItemsLock()) { 333 String sKey = e.getAffectedItem().getName(); 334 DataBasket db = e.getBasket(); 335 336 List<StockItemImpl> lRefIntegr = getRefIntegrItemsContainer().remove(sKey); 337 if (lRefIntegr != null) { 338 for (Iterator<StockItemImpl> i = lRefIntegr.iterator(); i.hasNext(); ) { 339 add((StockItem)i.next(), db); 340 } 341 } 342 } 343 } 344 345 public void removedCatalogItem(CatalogChangeEvent e) { 346 synchronized (getItemsLock()) { 347 // clean up 348 getRefIntegrItemsContainer().remove(e.getAffectedItem().getName()); 349 } 350 } 351 352 public void commitedRemoveCatalogItem(CatalogChangeEvent e) { 353 synchronized (getItemsLock()) { 354 if (!((Catalog)e.getSource()).contains(e.getAffectedItem().getName(), e.getBasket())) { 355 ciGoneForEver(e); 356 } 357 } 358 } 359 360 @SuppressWarnings("unused") 361 public void rollbackAddCatalogItem(CatalogChangeEvent e) { 362 synchronized (getItemsLock()) { 363 DataBasketCondition dbc = new DataBasketConditionImpl(CatalogItemImpl. 364 CATALOG_ITEM_MAIN_KEY, e.getAffectedItem().getName(), (CatalogImpl)e.getSource(), null, null); 365 366 if (!e.getBasket().contains(dbc)) { 367 ciGoneForEver(e); 368 } 369 } 370 } 371 372 private void ciGoneForEver(CatalogChangeEvent e) { 373 String sKey = e.getAffectedItem().getName(); 374 DataBasket db = e.getBasket(); 375 376 if (getTemporaryAddedItemsContainer().containsKey(sKey)) { 377 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, 378 StoringStockImpl.this, null); 379 380 // Rollback all items temporarily added to this Stock 381 // StoringStocks produce DataBasketEntries that may have both source and dest set, 382 // so we must rollback only the destination part. 383 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) { 384 ((StoringStockItemDBEntry)i.next()).rollbackDestination(); 385 } 386 } 387 388 getItemsContainer().remove(sKey); 389 getRefIntegrItemsContainer().remove(sKey); 390 391 if (getTemporaryRemovedItemsContainer().containsKey(sKey)) { 392 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, 393 StoringStockImpl.this, null, null); 394 395 // Commit all items temporaryly removed from this Stock 396 // StoringStocks produce DataBasketEntries that may have both source and dest set, 397 // so we must commit only the source part. 398 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) { 399 ((StoringStockItemDBEntry)i.next()).commitSource(); 400 } 401 } 402 } 403 404 // The actual instance of the associated Catalog will have changed, for children of the given key that 405 // are Stocks. 406 public void editingCatalogItem(CatalogChangeEvent e) { 407 relinkCatalog(e, STARTEDIT_ACTION); 408 } 409 410 // The actual instance of the associated Catalog will have to be changed back. 411 public void rollbackEditCatalogItem(CatalogChangeEvent e) { 412 relinkCatalog(e, ROLLBACK_ACTION); 413 } 414 415 public void commitEditCatalogItem(CatalogChangeEvent e) { 416 relinkCatalog(e, COMMIT_ACTION); 417 } 418 419 void relinkCatalog(CatalogChangeEvent e, int nAction) { 420 DataBasket db = e.getBasket(); 421 422 synchronized (getItemsLock()) { 423 List<StockItemImpl> l = getItemsContainer().get(e.getAffectedItem().getName()); 424 425 if (l != null) { 426 for (Iterator<StockItemImpl> i = l.iterator(); i.hasNext(); ) { 427 StockItemImpl sii = i.next(); 428 429 sii.relinkCatalog(db, nAction); 430 } 431 } 432 433 l = getTemporaryAddedItemsContainer().get(e.getAffectedItem().getName()); 434 435 if (l != null) { 436 for (Iterator<StockItemImpl> i = l.iterator(); i.hasNext(); ) { 437 StockItemImpl sii = i.next(); 438 439 sii.relinkCatalog(db, nAction); 440 } 441 } 442 } 443 } 444 445 @SuppressWarnings("unused") 446 public void rollbackRemoveCatalogItem(CatalogChangeEvent e) { 447 reEstablishStockCatalogLink(e.getAffectedItem().getName()); 448 } 449 }; 450 } 451 452 /** 453 * Private helper function re-establishing the Stock-Catalog connection if any items in this Stock should be 454 * Stocks themselves. 455 * 456 * @override Never 457 */ 458 private void reEstablishStockCatalogLink(String sKey) { 459 synchronized (getItemsLock()) { 460 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sKey); 461 if (lAdded != null) { 462 for (Iterator<StockItemImpl> i = lAdded.iterator(); i.hasNext(); ) { 463 // we call setStock again, which for other Stocks will adjust their Catalog link accordingly. 464 (i.next()).setStock(StoringStockImpl.this); 465 } 466 } 467 468 List<StockItemImpl> lItems = getItemsContainer().get(sKey); 469 if (lItems != null) { 470 for (Iterator<StockItemImpl> i = lItems.iterator(); i.hasNext(); ) { 471 // we call setStock again, which for other Stocks will adjust their Catalog link accordingly. 472 (i.next()).setStock(StoringStockImpl.this); 473 } 474 } 475 } 476 } 477 478 // Stock interface methods 479 480 /** 481 * Add an item to the Stock. 482 * 483 * <p>The item will only be visible to users of the same DataBasket. Only after a {@link DataBasket#commit} 484 * was performed on the DataBasket, the item will become visible to other users.</p> 485 * 486 * <p>A <code>addedStockItems</code> event will be fired.</p> 487 * 488 * @param si the item to be added. 489 * @param db the DataBasket relative to which the item will be added. Must be either <code>null</code> or a 490 * descendant of {@link DataBasketImpl}. 491 * 492 * @override Never 493 * 494 * @exception CatalogConflictException if the items key is not contained in the corresponding {@link Catalog}. 495 * @exception DataBasketConflictException if the item has already been added/removed using another DataBasket. 496 */ 497 public void add(StockItem si, DataBasket db) { 498 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object())); 499 500 synchronized (oLock) { 501 synchronized (getItemsLock()) { 502 if ((getCatalog(db) != null) && (!getCatalog(db).contains(si.getName(), db))) { 503 throw new CatalogConflictException("Couldn't find key \"" + si.getName() + 504 "\" in Catalog \"" + getCatalog(db).getName() + "\""); 505 } 506 507 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(si.getName()); 508 if ((lAdded != null) && (lAdded.contains(si))) { 509 throw new DataBasketConflictException("Cannot add item that has already been added."); 510 } 511 512 List<StockItemImpl> lItems = getItemsContainer().get(si.getName()); 513 if ((lItems != null) && (lItems.contains(si))) { 514 return; 515 } 516 517 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(si.getName()); 518 if ((lRemoved != null) && (lRemoved.contains(si))) { 519 520 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem((StockItem)lRemoved. 521 get(lRemoved.indexOf(si))); 522 523 if ((db == null) || (!db.contains(dbc))) { 524 throw new DataBasketConflictException( 525 "Cannot add item that was removed using a different DataBasket."); 526 } else { 527 DataBasketEntry dbe = db.get(dbc); 528 529 if (dbe.getDestination() == null) { 530 // just rollback the prior remove action! 531 532 db.rollback(dbc); 533 534 return; 535 } else { 536 throw new DataBasketConflictException( 537 "Cannot add item that was removed and added to another Stock!"); 538 } 539 } 540 } 541 542 // all checked, so add the stuff 543 if (db != null) { 544 if (lAdded == null) { 545 lAdded = new LinkedList<StockItemImpl>(); 546 getTemporaryAddedItemsContainer().put(si.getName(), lAdded); 547 } 548 549 lAdded.add((StockItemImpl)si); 550 551 // put information into databasket! Make sure there's only one DBE for each StockItem! 552 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(si); 553 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc); 554 555 if (sidbe != null) { 556 db.exchange(sidbe, new StoringStockItemDBEntry((StoringStockImpl)sidbe.getSource(), this, 557 (StockItemImpl)si)); 558 } else { 559 db.put(new StoringStockItemDBEntry(null, this, (StockItemImpl)si)); 560 } 561 562 } else { 563 if (lItems == null) { 564 lItems = new LinkedList<StockItemImpl>(); 565 getItemsContainer().put(si.getName(), lItems); 566 } 567 568 lItems.add((StockItemImpl)si); 569 } 570 571 m_nModCount++; 572 573 ((StockItemImpl)si).setStock(this); 574 fireStockItemsAdded(new StoringStockChangeEvent(this, (StockItemImpl)si, db)); 575 } 576 } 577 } 578 579 /** 580 * Iterate all items with a given key. 581 * 582 * <p>This method, together with {@link Stock#iterator} is the only way of accessing the individual 583 * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that have the 584 * specified key and are visible using the given DataBasket. Depending on the <code>fForEdit</code> 585 * parameter, the items will be retrieved in different ways. See {@link DataBasket} for an explanation of 586 * the different possibilities.</p> 587 * 588 * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if 589 * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into 590 * <code>UnSupportedOperationException</code>s.</p> 591 * 592 * @override Never 593 * 594 * @param sKey the key for which to retrieve the StockItems. 595 * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code> 596 * or a descendant of {@link DataBasketImpl}. 597 * @param fForEdit if true, the StockItems will be retrieved for editing. 598 */ 599 public Iterator<StockItem> get(final String sKey, final DataBasket db, final boolean fForEdit) { 600 final Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object())); 601 602 class I implements Iterator<StockItem> { 603 private Iterator<StockItemImpl> m_iItems; 604 private int m_nExpectedModCount; 605 606 private StockItemImpl m_siiCurrent; 607 608 public I() { 609 super(); 610 611 synchronized (oLock) { 612 synchronized (getItemsLock()) { 613 List<StockItemImpl> lItems = getItemsContainer().get(sKey); 614 615 if (lItems != null) { 616 lItems = new LinkedList<StockItemImpl>(lItems); 617 } else { 618 lItems = new LinkedList<StockItemImpl>(); 619 } 620 621 if (db != null) { 622 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, 623 StoringStockImpl.this, null); 624 for (Iterator<DataBasketEntry> i = db.iterator(dbc); i.hasNext(); ) { 625 DataBasketEntry dbe = i.next(); 626 627 lItems.add((StockItemImpl)dbe.getValue()); 628 } 629 } 630 631 m_iItems = lItems.iterator(); 632 m_nExpectedModCount = m_nModCount; 633 } 634 } 635 } 636 637 public boolean hasNext() { 638 return m_iItems.hasNext(); 639 } 640 641 public StockItem next() { 642 synchronized (oLock) { 643 synchronized (getItemsContainer()) { 644 if (m_nExpectedModCount != m_nModCount) { 645 throw new ConcurrentModificationException(); 646 } 647 648 m_siiCurrent = (StockItemImpl)m_iItems.next(); 649 650 if ((fForEdit) && (db != null)) { 651 //if item is temporarily added, return it 652 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sKey); 653 if ((lAdded != null) && (lAdded.contains(m_siiCurrent))) { 654 return m_siiCurrent; 655 } 656 657 try { 658 fireCanEditStockItems(new StoringStockChangeEvent(StoringStockImpl.this, 659 m_siiCurrent, db)); 660 } 661 catch (VetoException ve) { 662 return null; 663 } 664 //otherwise move item from mItems to mTemporaryRemoved 665 List<StockItemImpl> lItems = getItemsContainer().get(sKey); 666 lItems.remove(m_siiCurrent); 667 if (lItems.size() == 0) { 668 getItemsContainer().remove(sKey); 669 } 670 671 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(sKey); 672 if (lRemoved == null) { 673 lRemoved = new LinkedList<StockItemImpl>(); 674 getTemporaryRemovedItemsContainer().put(sKey, lRemoved); 675 } 676 lRemoved.add(m_siiCurrent); 677 //clone item 678 StockItemImpl siiRemoved = m_siiCurrent; 679 m_siiCurrent = siiRemoved.getShallowClone(); 680 //add clone to mTemporaryAdded and mEditingItems 681 if (lAdded == null) { 682 lAdded = new LinkedList<StockItemImpl>(); 683 getTemporaryAddedItemsContainer().put(sKey, lAdded); 684 } 685 lAdded.add(m_siiCurrent); 686 687 List<StockItemImpl> lEdit = getEditingItemsContainer().get(sKey); 688 if (lEdit == null) { 689 lEdit = new LinkedList<StockItemImpl>(); 690 getEditingItemsContainer().put(sKey, lEdit); 691 } 692 lEdit.add(m_siiCurrent); 693 694 siiRemoved.setStock(null); 695 m_siiCurrent.setStock(StoringStockImpl.this); 696 697 // put information into databasket, making sure there's only one entry per StockItem 698 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(siiRemoved); 699 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc); 700 701 if (sidbe != null) { 702 db.exchange(sidbe, new StoringStockItemDBEntry(StoringStockImpl.this, 703 (StoringStockImpl)sidbe.getDestination(), siiRemoved)); 704 } else { 705 db.put(new StoringStockItemDBEntry(StoringStockImpl.this, null, siiRemoved)); 706 } 707 708 db.put(new StoringStockItemDBEntry(null, StoringStockImpl.this, m_siiCurrent)); 709 710 fireEditingStockItems(new StoringStockChangeEvent(StoringStockImpl.this, 711 m_siiCurrent, db)); 712 fireStockItemsRemoved(new StoringStockChangeEvent(StoringStockImpl.this, 713 siiRemoved, db)); 714 fireStockItemsAdded(new StoringStockChangeEvent(StoringStockImpl.this, 715 m_siiCurrent, db)); 716 717 //Allows only ONE iterator at a time to call next() with fForEdit enabled 718 //because this method adds and removes StockItems, so other iterators have to 719 //be informed via the increased modifiaction counter 720 m_nExpectedModCount = (++m_nModCount); 721 } 722 723 return m_siiCurrent; 724 } 725 } 726 } 727 728 public void remove() { 729 synchronized (oLock) { 730 synchronized (getItemsLock()) { 731 if (m_nModCount != m_nExpectedModCount) { 732 throw new ConcurrentModificationException(); 733 } 734 735 if (m_siiCurrent == null) { 736 throw new IllegalStateException(); 737 } 738 739 try { 740 StoringStockImpl.this.remove(m_siiCurrent, db); 741 742 m_nExpectedModCount = m_nModCount; 743 744 m_siiCurrent = null; 745 } 746 catch (VetoException ve) { 747 m_siiCurrent = null; 748 749 throw new UnsupportedOperationException("VETO: " + ve); 750 } 751 } 752 } 753 } 754 } 755 756 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) { 757 return new Iterator<StockItem>() { 758 public boolean hasNext() { 759 return false; 760 } 761 762 public StockItem next() { 763 throw new NoSuchElementException(); 764 } 765 766 public void remove() {} 767 }; 768 } 769 770 return new I(); 771 } 772 773 /** 774 * Count the StockItems with a given key that are visible using a given DataBasket. 775 * 776 * @override Never 777 * 778 * @param sKey the key for which to count the StockItems. 779 * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a 780 * descendant of {@link DataBasketImpl}. 781 */ 782 public int countItems(String sKey, DataBasket db) { 783 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object())); 784 785 synchronized (oLock) { 786 synchronized (getItemsLock()) { 787 if ((getCatalog(db) != null) && (!getCatalog(db).contains(sKey, db))) { 788 return 0; 789 } 790 791 int nCount = 0; 792 793 List<StockItemImpl> lItems = getItemsContainer().get(sKey); 794 795 if (lItems != null) { 796 nCount += lItems.size(); 797 } 798 799 if ((getTemporaryAddedItemsContainer().containsKey(sKey)) && (db != null)) { 800 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null); 801 BasketEntryValue bev = BasketEntryValues.COUNT_ITEMS; 802 IntegerValue ivCount = new IntegerValue(0); 803 804 db.sumBasket(dbc, bev, ivCount); 805 806 nCount += ivCount.getValue().intValue(); 807 } 808 809 return nCount; 810 } 811 } 812 } 813 814 /** 815 * Remove one StockItem with the specified key from the Stock. 816 * 817 * <p>If there are any StockItems with the specified key, one will be removed. There is no guarantee as to 818 * which StockItem will be removed. The removed item, if any, will be returned.</p> 819 * 820 * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p> 821 * 822 * @override Never 823 * 824 * @param sKey the key for which to remove an item. 825 * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a 826 * descendant of {@link DataBasketImpl}. 827 * 828 * @return the removed item 829 * 830 * @exception VetoException if a listener vetoed the removal. 831 * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket 832 * usage. 833 */ 834 public StockItem remove(String sKey, DataBasket db) throws VetoException { 835 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object())); 836 837 synchronized (oLock) { 838 synchronized (getItemsLock()) { 839 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sKey); 840 841 if ((lAdded != null) && (db != null)) { 842 DataBasketCondition dbc = new DataBasketConditionImpl(STOCK_ITEM_MAIN_KEY, sKey, null, this, null); 843 844 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc); 845 846 if (sidbe != null) { 847 //remove and return first temporarily added item 848 return remove((StockItem)sidbe.getValue(), db); 849 } 850 } 851 852 List<StockItemImpl> lItems = getItemsContainer().get(sKey); 853 854 if (lItems != null) { 855 /* 856 * 06/27/2000-STEFFEN: Had to add checking for lItems.size here, as apparently I sometimes 857 * keep the vector even if it is empty. 858 * I don't think, it should do that, but I need to check again. 859 * Checked, apparently remove (si, db) also doesn't clean up the list. This is pretty memory 860 * ineffective, but needs some effort to fix it. For the moment, just worked around it. 861 */ 862 if (lItems.size() > 0) { 863 //remove and return last added item 864 return remove((StockItem)lItems.get(lItems.size() - 1), db); 865 } 866 } 867 } 868 } 869 870 return null; 871 } 872 873 /** 874 * Remove the given StockItem from the Stock. 875 * 876 * <p>If the given StockItem is contained in the Stock, it will be removed. The removed item, if any, will 877 * be returned.</p> 878 * 879 * <p><code>canRemoveStockItems</code> and <code>removedStockItems</code> events will be fired.</p> 880 * 881 * @override Never 882 * 883 * @param si the StockItem to be removed. 884 * @param db the DataBasket relative to which to remove the item. Must be either <code>null</code> or a 885 * descendant of {@link DataBasketImpl}. 886 * 887 * @return the removed item 888 * 889 * @exception VetoException if a listener vetoed the removal. 890 * @exception DataBasketConflictException if the item cannot be removed due to conflicts from DataBasket 891 * usage. 892 */ 893 public StockItem remove(StockItem si, DataBasket db) throws VetoException { 894 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object())); 895 896 synchronized (oLock) { 897 synchronized (getItemsLock()) { 898 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(si.getName()); 899 if ((lAdded != null) && (lAdded.contains(si))) { 900 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem((StockItem)lAdded.get( 901 lAdded.indexOf(si))); 902 903 StockItemDBEntry sidbe = null; 904 905 if (db != null) { 906 sidbe = (StockItemDBEntry)db.get(dbc); 907 } 908 909 if (sidbe == null) { 910 throw new DataBasketConflictException( 911 "Cannot remove StockItem that was added using a different DataBasket!"); 912 } else { 913 fireCanRemoveStockItems(new StoringStockChangeEvent(this, (StockItemImpl)si, db)); 914 915 if (sidbe.getSource() == null) { 916 db.rollback(dbc); 917 } else { 918 // remove only the destination part of it: 919 db.exchange(sidbe, new StoringStockItemDBEntry((StoringStockImpl)sidbe.getSource(), null, 920 (StockItemImpl)sidbe.getValue())); 921 922 si = (StockItem)lAdded.get(lAdded.indexOf(si)); 923 lAdded.remove(si); 924 925 m_nModCount++; 926 927 fireStockItemsAddRollback(new StoringStockChangeEvent(this, (StockItemImpl)si, db)); 928 } 929 930 ((StockItemImpl)si).setStock(null); 931 932 return si; 933 } 934 } 935 936 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(si.getName()); 937 if ((lRemoved != null) && (lRemoved.contains(si))) { 938 throw new DataBasketConflictException( 939 "Cannot remove an item that has already been removed!"); 940 } 941 942 List<StockItemImpl> lItems = getItemsContainer().get(si.getName()); 943 if ((lItems == null) || (!lItems.contains(si))) { 944 return null; 945 } 946 947 // remove from items container, making sure there's always only one DataBasket entry for each stockitem 948 949 fireCanRemoveStockItems(new StoringStockChangeEvent(this, 950 (StockItemImpl)lItems.get(lItems.indexOf(si)), db)); 951 952 si = (StockItem)lItems.get(lItems.indexOf(si)); 953 lItems.remove(si); 954 955 if (db != null) { 956 if (lRemoved == null) { 957 lRemoved = new LinkedList<StockItemImpl>(); 958 getTemporaryRemovedItemsContainer().put(si.getName(), lRemoved); 959 } 960 961 lRemoved.add((StockItemImpl)si); 962 963 DataBasketCondition dbc = DataBasketConditionImpl.specificStockItem(si); 964 StockItemDBEntry sidbe = (StockItemDBEntry)db.get(dbc); 965 966 if (sidbe != null) { 967 db.exchange(sidbe, new StoringStockItemDBEntry(this, 968 (StoringStockImpl)sidbe.getDestination(), (StockItemImpl)si)); 969 } else { 970 db.put(new StoringStockItemDBEntry(this, null, (StockItemImpl)si)); 971 } 972 } 973 974 m_nModCount++; 975 976 ((StockItemImpl)si).setStock(null); 977 fireStockItemsRemoved(new StoringStockChangeEvent(this, (StockItemImpl)si, db)); 978 979 return si; 980 } 981 } 982 } 983 984 // StockImpl methods 985 986 /** 987 * Overridden to accomodate for specific usage of memory. 988 * 989 * @override Never 990 */ 991 protected void fillShallowClone(StoringStockImpl stiClone) { 992 993 synchronized (getItemsLock()) { 994 synchronized (stiClone.getItemsLock()) { 995 stiClone.setItemsContainer(new HashMap<String, List<StockItemImpl>>()); 996 for (Iterator i = getItemsContainer().keySet().iterator(); i.hasNext(); ) { 997 String sKey = (String)i.next(); 998 stiClone.getItemsContainer().put(sKey, new LinkedList<StockItemImpl>(getItemsContainer().get(sKey))); 999 // shallow clone of LinkedList 1000 } 1001 stiClone.setTemporaryAddedItemsContainer(new HashMap<String, List<StockItemImpl>>()); 1002 for (Iterator i = getTemporaryAddedItemsContainer().keySet().iterator(); i.hasNext(); ) { 1003 String sKey = (String)i.next(); 1004 stiClone.getTemporaryAddedItemsContainer().put(sKey, new LinkedList<StockItemImpl>(getTemporaryAddedItemsContainer().get(sKey))); 1005 1006 } 1007 stiClone.setTemporaryRemovedItemsContainer(new HashMap<String, List<StockItemImpl>>()); 1008 for (Iterator i = getTemporaryRemovedItemsContainer().keySet().iterator(); i.hasNext(); ) { 1009 String sKey = (String)i.next(); 1010 stiClone.getTemporaryRemovedItemsContainer().put(sKey, new LinkedList<StockItemImpl>(getTemporaryRemovedItemsContainer().get(sKey))); 1011 1012 } 1013 stiClone.setEditingItemsContainer(new HashMap<String, List<StockItemImpl>>()); 1014 for (Iterator i = getEditingItemsContainer().keySet().iterator(); i.hasNext(); ) { 1015 String sKey = (String)i.next(); 1016 stiClone.getEditingItemsContainer().put(sKey, new LinkedList<StockItemImpl>(getEditingItemsContainer().get(sKey))); 1017 1018 } 1019 stiClone.setRefIntegrItemsContainer(new HashMap<String, List<StockItemImpl>>()); 1020 for (Iterator i = getRefIntegrItemsContainer().keySet().iterator(); i.hasNext(); ) { 1021 String sKey = (String)i.next(); 1022 stiClone.getRefIntegrItemsContainer().put(sKey, new LinkedList<StockItemImpl>(getRefIntegrItemsContainer().get(sKey))); 1023 1024 } 1025 stiClone.setRefIntegrEditContainer(new HashMap<String, String>()); 1026 for (Iterator<String> i = getRefIntegrEditContainer().keySet().iterator(); i.hasNext(); ) { 1027 String sKey = i.next(); 1028 stiClone.getRefIntegrEditContainer().put(sKey, getRefIntegrEditContainer().get(sKey)); 1029 } 1030 } 1031 } 1032 } 1033 1034 /** 1035 * @override Always 1036 */ 1037 protected StockImpl<List<StockItemImpl>> createPeer() { 1038 StoringStockImpl ssiPeer = new StoringStockImpl(getName(), m_ciCatalog); 1039 ssiPeer.m_dbCatalogValidator = m_dbCatalogValidator; 1040 1041 return ssiPeer; 1042 } 1043 1044 /** 1045 * Set the Stock and adjust the Catalog link for all Stocks that are contained in this Stock. 1046 * 1047 * @override Never 1048 */ 1049 protected void setStock(StockImpl sti) { 1050 super.setStock(sti); 1051 1052 if (sti != null) { 1053 synchronized (getItemsLock()) { 1054 Set<String> stKeys = getItemsContainer().keySet(); 1055 stKeys.addAll(getTemporaryAddedItemsContainer().keySet()); 1056 1057 for (Iterator<String> i = stKeys.iterator(); i.hasNext(); ) { 1058 reEstablishStockCatalogLink(i.next()); 1059 } 1060 } 1061 } 1062 } 1063 1064 /** 1065 * Helper method used to maintain StockImpl - CatalogImpl links in nested Stocks/Catalogs. For internal use only. 1066 * 1067 * @param db the DataBasket that is protecting this activity. 1068 * @param nAction the action that occurred. Can be either {@link #COMMIT_ACTION}, {@link #ROLLBACK_ACTION}, 1069 * {@link #STARTEDIT_ACTION}. 1070 */ 1071 void relinkCatalog(DataBasket db, int nAction) { 1072 super.relinkCatalog(db, nAction); 1073 1074 if (nAction == ROLLBACK_ACTION) { 1075 // Additionally refresh the links in all child stocks. 1076 synchronized (getItemsLock()) { 1077 for (Iterator<List<StockItemImpl>> i = getItemsContainer().values().iterator(); i.hasNext(); ) { 1078 List<StockItemImpl> l = i.next(); 1079 1080 for (Iterator<StockItemImpl> j = l.iterator(); j.hasNext(); ) { 1081 StockItemImpl sii = j.next(); 1082 1083 sii.relinkCatalog(db, nAction); 1084 } 1085 } 1086 1087 for (Iterator<List<StockItemImpl>> i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext(); ) { 1088 List<StockItemImpl> l = i.next(); 1089 1090 for (Iterator<StockItemImpl> j = l.iterator(); j.hasNext(); ) { 1091 StockItemImpl sii = j.next(); 1092 1093 sii.relinkCatalog(db, nAction); 1094 } 1095 } 1096 } 1097 } 1098 } 1099 1100 // SelfManagingDBESource interface methods 1101 1102 /** 1103 * Commit the removal of a StockItem. 1104 * 1105 * <p>A <code>commitRemoveStockItems</code> will be fired.</p> 1106 * 1107 * @override Never 1108 */ 1109 public void commitRemove(DataBasket db, DataBasketEntry dbe) { 1110 // DataBasket is already locking on its monitor so we just lock on ours 1111 synchronized (getItemsLock()) { 1112 StockItemImpl sii = (StockItemImpl)dbe.getValue(); 1113 1114 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName()); 1115 if (lRemoved != null) { 1116 lRemoved.remove(sii); 1117 1118 if (lRemoved.size() == 0) { 1119 getTemporaryRemovedItemsContainer().remove(sii.getName()); 1120 } 1121 1122 if (sii.getStock().equals(this)) { 1123 sii.setStock(null); 1124 } 1125 fireStockItemsRemoveCommit(new StoringStockChangeEvent(this, sii, db)); 1126 } 1127 } 1128 } 1129 1130 /** 1131 * Rollback the removal of a StockItem. 1132 * 1133 * <p>A <code>rollbackRemoveStockItems</code> will be fired. Also, the Stock will try to make sure, that 1134 * a corresponding CatalogItem exists.</p> 1135 * 1136 * @override Never 1137 */ 1138 public void rollbackRemove(DataBasket db, DataBasketEntry dbe) { 1139 synchronized (getItemsLock()) { 1140 prepareReferentialIntegrity(db, dbe); 1141 1142 StockItemImpl sii = (StockItemImpl)dbe.getValue(); 1143 1144 List<StockItemImpl> lRemoved = getTemporaryRemovedItemsContainer().get(sii.getName()); 1145 if (lRemoved != null) { 1146 lRemoved.remove(sii); 1147 1148 if (lRemoved.size() == 0) { 1149 getTemporaryRemovedItemsContainer().remove(sii.getName()); 1150 } 1151 1152 List<StockItemImpl> lItems = getItemsContainer().get(sii.getName()); 1153 if (lItems == null) { 1154 lItems = new LinkedList<StockItemImpl>(); 1155 getItemsContainer().put(sii.getName(), lItems); 1156 } 1157 1158 lItems.add(sii); 1159 1160 sii.setStock(this); 1161 1162 m_nModCount++; 1163 1164 fireStockItemsRemoveRollback(new StoringStockChangeEvent(this, sii, db)); 1165 } 1166 } 1167 } 1168 1169 // SelfManagingDBEDestination interface methods 1170 1171 /** 1172 * Commit the adding of a StockItem. 1173 * 1174 * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be 1175 * fired as a consequence of this method. Also, the Stock will try to make sure, that a corresponding 1176 * CatalogItem exists.</p> 1177 * 1178 * @override Never 1179 */ 1180 public void commitAdd(DataBasket db, DataBasketEntry dbe) { 1181 synchronized (getItemsLock()) { 1182 prepareReferentialIntegrity(db, dbe); 1183 1184 StockItemImpl sii = (StockItemImpl)dbe.getValue(); 1185 1186 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sii.getName()); 1187 if (lAdded != null) { 1188 lAdded.remove(sii); 1189 1190 if (lAdded.size() == 0) { 1191 getTemporaryAddedItemsContainer().remove(sii.getName()); 1192 } 1193 1194 List<StockItemImpl> lItems = getItemsContainer().get(sii.getName()); 1195 if (lItems == null) { 1196 lItems = new LinkedList<StockItemImpl>(); 1197 getItemsContainer().put(sii.getName(), lItems); 1198 } 1199 1200 lItems.add(sii); 1201 1202 sii.setStock(this); 1203 1204 m_nModCount++; 1205 1206 fireStockItemsAddCommit(new StoringStockChangeEvent(this, sii, db)); 1207 1208 List<StockItemImpl> lEdit = getEditingItemsContainer().get(sii.getName()); 1209 if ((lEdit != null) && (lEdit.remove(sii))) { 1210 if (lEdit.size() == 0) { 1211 getEditingItemsContainer().remove(sii.getName()); 1212 } 1213 1214 fireStockItemsEditCommit(new StoringStockChangeEvent(this, sii, db)); 1215 } 1216 } 1217 } 1218 } 1219 1220 /** 1221 * Rollback the adding of a StockItem. 1222 * 1223 * <p>A <code>commitAddStockItems</code> will be fired. A <code>commitEditStockItems</code> event may be 1224 * fired as a consequence of this method.</p> 1225 * 1226 * @override Never 1227 */ 1228 public void rollbackAdd(DataBasket db, DataBasketEntry dbe) { 1229 synchronized (getItemsLock()) { 1230 StockItemImpl sii = (StockItemImpl)dbe.getValue(); 1231 1232 List<StockItemImpl> lAdded = getTemporaryAddedItemsContainer().get(sii.getName()); 1233 if (lAdded != null) { 1234 lAdded.remove(sii); 1235 1236 if (lAdded.size() == 0) { 1237 getTemporaryAddedItemsContainer().remove(sii.getName()); 1238 } 1239 1240 if (sii.getStock().equals(this)) { 1241 sii.setStock(null); 1242 } 1243 1244 m_nModCount++; 1245 1246 fireStockItemsAddRollback(new StoringStockChangeEvent(this, sii, db)); 1247 1248 List<StockItemImpl> lEdit = getEditingItemsContainer().get(sii.getName()); 1249 if ((lEdit != null) && (lEdit.remove(sii))) { 1250 if (lEdit.size() == 0) { 1251 getEditingItemsContainer().remove(sii.getName()); 1252 } 1253 1254 fireStockItemsEditRollback(new StoringStockChangeEvent(this, sii, db)); 1255 } 1256 } 1257 } 1258 } 1259 }