001 package data.ooimpl; 002 003 import java.util.*; 004 005 import java.beans.PropertyChangeListener; 006 import java.beans.PropertyChangeEvent; 007 008 import java.io.*; 009 010 import data.events.*; 011 import data.*; 012 013 import util.*; 014 015 /** 016 * Pure Java implementation of the {@link Stock} interface. 017 * 018 * <p>StockImpl Stocks can only work together with {@link CatalogImpl} {@link Catalog Catalogs}, 019 * {@link StockItemImpl} {@link StockItem StockItems} and {@link DataBasketImpl} 020 * {@link DataBasket DataBaskets}.</p> 021 * 022 * @author Steffen Zschaler 023 * @version 2.0 19/08/1999 024 * @since v2.0 025 */ 026 public abstract class StockImpl extends StockItemImpl implements Stock, ListenableStock, NameContext, 027 SelfManagingDBESource, SelfManagingDBEDestination { 028 029 /** 030 * The Catalog that is associated to this Stock. 031 * 032 * @serial 033 */ 034 protected CatalogImpl m_ciCatalog; 035 036 /** 037 * The DataBasket that determines the visibility of the catalog associated with this Stock. 038 * 039 * <p>If <code>null</code>, the associated Catalog is visible with any DataBasket, otherwise it 040 * is only visible with the DataBasket specified in this field. Requests for the Catalog using 041 * a different DataBasket will the be rejected by throwing a DataBasketConflictException.</p> 042 * 043 * @serial 044 */ 045 protected DataBasket m_dbCatalogValidator; 046 047 /** 048 * The listeners listening for events from this Stock. 049 * 050 * @serial 051 */ 052 protected ListenerHelper m_lhListeners = new ListenerHelper(); 053 054 /** 055 * The map of items that are actually contained in the Stock. 056 * 057 * @serial 058 */ 059 private Map m_mpItems = new HashMap(); 060 061 /** 062 * The map of items that have been temporaryly added to the Stock. 063 * 064 * @serial 065 */ 066 private Map m_mpTemporaryAddedItems = new HashMap(); 067 068 /** 069 * The map of items that have been temporaryly removed from the Stock. 070 * 071 * @serial 072 */ 073 private Map m_mpTemporaryRemovedItems = new HashMap(); 074 075 /** 076 * The map of items that are currently being edited. 077 * 078 * @serial 079 */ 080 private Map m_mpEditingItems = new HashMap(); 081 082 /** 083 * The map of items that have been removed from the Stock to ensure referential integrity. 084 * 085 * @serial 086 */ 087 private Map m_mpRefIntegrItems = new HashMap(); 088 089 /** 090 * A map storing information about name changes in CatalogItems. 091 * 092 * <p>Key: new name.<br> 093 * Value: old name.</p> 094 * 095 * <p>This map is needed for referential integrity.</p> 096 * 097 * @serial 098 */ 099 private Map m_mpRefIntegrEdit = new HashMap(); // stores information about name changes in CatalogItems: key: new name; entry: old name 100 101 /** 102 * The monitor synchronizing access to the Stock's contents. 103 */ 104 private transient Object m_oItemsLock; 105 106 /** 107 * Get the monitor synchronizing access to the Stock's contents. 108 * 109 * @override Never 110 */ 111 protected final Object getItemsLock() { 112 if (m_oItemsLock == null) { 113 m_oItemsLock = new Object(); 114 } 115 116 return m_oItemsLock; 117 } 118 119 /** 120 * PropertyChangeListener that reacts to name changes in CatalogItems that are currently being edited. 121 * Updates {@link #getRefIntegrEditContainer}. 122 * 123 * @author Steffen Zschaler 124 * @version 2.0 19/08/1999 125 * @since v2.0 126 */ 127 class CatalogItemNameListener implements PropertyChangeListener, SerializableListener { 128 /** 129 * @override Never Instead override {@link #nameChangeOccurred}. 130 */ 131 public void propertyChange(PropertyChangeEvent e) { 132 if (e.getPropertyName().equals(NAME_PROPERTY)) { 133 synchronized (getItemsLock()) { 134 String sOld = (String)e.getOldValue(); 135 String sNew = (String)e.getNewValue(); 136 137 nameChangeOccurred(sOld, sNew); 138 } 139 } 140 } 141 142 /** 143 * Notification that a CatalogItem's name changed. 144 * 145 * <p>This will update all data structures necessary to ensure Stock and Catalog are properly linked 146 * together.</p> 147 * 148 * @override Sometimes 149 */ 150 protected void nameChangeOccurred(String sOld, String sNew) { 151 Object oData = getTemporaryAddedItemsContainer().remove(sOld); 152 if (oData != null) { 153 getTemporaryAddedItemsContainer().put(sNew, oData); 154 } 155 156 oData = getItemsContainer().remove(sOld); 157 if (oData != null) { 158 getItemsContainer().put(sNew, oData); 159 } 160 161 oData = getTemporaryRemovedItemsContainer().remove(sOld); 162 if (oData != null) { 163 getTemporaryRemovedItemsContainer().put(sNew, oData); 164 } 165 166 oData = getRefIntegrItemsContainer().remove(sOld); 167 if (oData != null) { 168 getRefIntegrItemsContainer().put(sNew, oData); 169 } 170 171 getRefIntegrEditContainer().put(sNew, getRefIntegrEditContainer().remove(sOld)); 172 } 173 } 174 175 /** 176 * The listener that listens to name changes in CatalogItems in the associated Catalog. 177 * 178 * @serial 179 */ 180 protected CatalogItemNameListener m_cinlCatalogItemNameListener = new CatalogItemNameListener(); 181 182 /** 183 * Listens for editing events from the Catalog. Installs {@link #m_cinlCatalogItemNameListener} if 184 * necessary. Also prevents editing, if there are StockItems in DataBaskets, so that we don't get any 185 * problems with name changes. Note however, that it might be possible to first edit a CatalogItem and then 186 * remove corresponding StockItems which might cause the same problem! Preventing that is not provided by 187 * the Framework. 188 * 189 * @serial 190 */ 191 protected final CatalogChangeListener m_cclEditListener = new CatalogChangeAdapter() { 192 public void canEditCatalogItem(CatalogChangeEvent e) throws VetoException { 193 synchronized (getItemsLock()) { 194 String sKey = e.getAffectedItem().getName(); 195 196 if ((getTemporaryAddedItemsContainer().containsKey(sKey)) || 197 (getTemporaryRemovedItemsContainer().containsKey(sKey))) { 198 throw new VetoException("Stock \"" + getName() + "\": Having StockItems with key \"" + 199 sKey + "\" in DataBaskets."); 200 } 201 } 202 } 203 204 public void editingCatalogItem(CatalogChangeEvent e) { 205 synchronized (getItemsLock()) { 206 String sKey = e.getAffectedItem().getName(); 207 getRefIntegrEditContainer().put(sKey, sKey); 208 209 ((CatalogItemImpl)e.getAffectedItem()).addNameListener(m_cinlCatalogItemNameListener); 210 } 211 } 212 213 public void rollbackEditCatalogItem(CatalogChangeEvent e) { 214 synchronized (getItemsLock()) { 215 String sCurKey = e.getAffectedItem().getName(); 216 String sKey = (String)getRefIntegrEditContainer().remove(sCurKey); 217 218 Object oData = getTemporaryAddedItemsContainer().remove(sCurKey); 219 if (oData != null) { 220 getTemporaryAddedItemsContainer().put(sKey, oData); 221 } 222 223 oData = getItemsContainer().remove(sCurKey); 224 if (oData != null) { 225 getItemsContainer().put(sKey, oData); 226 } 227 228 oData = getTemporaryRemovedItemsContainer().remove(sCurKey); 229 if (oData != null) { 230 getTemporaryRemovedItemsContainer().put(sKey, oData); 231 } 232 233 oData = getRefIntegrItemsContainer().remove(sCurKey); 234 if (oData != null) { 235 getRefIntegrItemsContainer().put(sKey, oData); 236 } 237 238 ((CatalogItemImpl)e.getAffectedItem()).removeNameListener(m_cinlCatalogItemNameListener); 239 } 240 } 241 242 public void commitEditCatalogItem(CatalogChangeEvent e) { 243 synchronized (getItemsLock()) { 244 // clean up 245 getRefIntegrEditContainer().remove(e.getAffectedItem().getName()); 246 247 ((CatalogItemImpl)e.getAffectedItem()).removeNameListener(m_cinlCatalogItemNameListener); 248 } 249 } 250 }; 251 252 /** 253 * The original Stock, if this is a shallow copy that was created for editing purposes. A SoftReference is 254 * used so that it can be removed when the last reference to the creator was deleted. 255 */ 256 private transient java.lang.ref.SoftReference m_srsiEditCreator = null; 257 258 /** 259 * Listens to the parent Stock of the creator, if this is a shallow copy that was created for editing 260 * purposes. It will cut the connection if the editing is either commited or rolled back. 261 * 262 * @serial 263 */ 264 private final StockChangeListener m_sclEditingListener = new StockChangeAdapter() { 265 public void commitEditStockItems(StockChangeEvent e) { 266 if (e.getAffectedKey() == getName()) { 267 for (Iterator i = e.getAffectedItems(); i.hasNext(); ) { 268 if (i.next() == StockImpl.this) { 269 ((StockImpl)e.getSource()).removeStockChangeListener(this); return; 270 } 271 } 272 } 273 } 274 275 public void rollbackEditStockItems(StockChangeEvent e) { 276 if (e.getAffectedKey() == getName()) { 277 for (Iterator i = e.getAffectedItems(); i.hasNext(); ) { 278 if (i.next() == StockImpl.this) { 279 ((StockImpl)e.getSource()).removeStockChangeListener(this); 280 281 ((StockImpl)m_srsiEditCreator.get()).removeStockChangeListener( 282 m_sclEditCreatorListener); m_srsiEditCreator = null; 283 284 return; 285 } 286 } 287 } 288 } 289 }; 290 291 /** 292 * Listens to the creator, if this is a shallow copy that was created for editing purposes. This is to 293 * follow with any events that the creator might trigger. 294 * 295 * @serial 296 */ 297 protected StockChangeListener m_sclEditCreatorListener; 298 299 /** 300 * First writes the default serializable data and then the reference to the edit creator, if any. 301 */ 302 private void writeObject(ObjectOutputStream oos) throws IOException { 303 oos.defaultWriteObject(); 304 305 if (m_srsiEditCreator != null) { 306 oos.writeObject(m_srsiEditCreator.get()); 307 } else { 308 oos.writeObject(null); 309 } 310 } 311 312 /** 313 * First reads the default serializable data and then re-establishes the reference to the edit creator, if 314 * any. 315 */ 316 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 317 ois.defaultReadObject(); 318 319 Object oEditCreator = ois.readObject(); 320 321 if (oEditCreator != null) { 322 m_srsiEditCreator = new java.lang.ref.SoftReference(oEditCreator); 323 } 324 } 325 326 /** 327 * Create a new StockImpl. 328 * 329 * @param sName the name of the new Stock. 330 * @param ciRef the Catalog that is referenced by this Stock. 331 */ 332 public StockImpl(String sName, CatalogImpl ciRef) { 333 super(sName); 334 335 if (ciRef == null) { 336 throw new NullPointerException("Catalog must not be null!"); 337 } 338 339 internalSetCatalog(ciRef); 340 } 341 342 /** 343 * Set the Catalog that this Stock refers to. This method is only used internally. 344 * 345 * <p>{@link #m_dbCatalogValidator} will be set to <code>null</code>. 346 * 347 * @param ciRef the Catalog to refer to from now on. 348 * 349 * @override Never 350 */ 351 protected void internalSetCatalog(CatalogImpl ciRef) { 352 if (m_ciCatalog != null) { 353 m_ciCatalog.removeCatalogChangeListener(m_cclEditListener); 354 } 355 356 m_ciCatalog = ciRef; 357 358 if (m_ciCatalog != null) { 359 m_ciCatalog.addCatalogChangeListener(m_cclEditListener); 360 } 361 362 m_dbCatalogValidator = null; 363 } 364 365 /** 366 * Get the map of items that are actually contained in the Stock. 367 * 368 * @override Never 369 */ 370 protected Map getItemsContainer() { 371 return m_mpItems; 372 } 373 374 /** 375 * Get the map of items that have been temporaryly added to the Stock. 376 * 377 * @override Never 378 */ 379 protected Map getTemporaryAddedItemsContainer() { 380 return m_mpTemporaryAddedItems; 381 } 382 383 /** 384 * Get the map of items that have been temporaryly removed from the Stock. 385 * 386 * @override Never 387 */ 388 protected Map getTemporaryRemovedItemsContainer() { 389 return m_mpTemporaryRemovedItems; 390 } 391 392 /** 393 * Get the map of items that are currently being edited. 394 * 395 * @override Never 396 */ 397 protected Map getEditingItemsContainer() { 398 return m_mpEditingItems; 399 } 400 401 /** 402 * Get the map of items that have been removed from the Stock to ensure referential integrity. 403 * 404 * @override Never 405 */ 406 protected Map getRefIntegrItemsContainer() { 407 return m_mpRefIntegrItems; 408 } 409 410 /** 411 * Get the map storing information about name changes in CatalogItems. 412 * 413 * @override Never 414 */ 415 protected Map getRefIntegrEditContainer() { 416 return m_mpRefIntegrEdit; 417 } 418 419 /** 420 * Set the map of items that are actually contained in the Stock. 421 * 422 * @override Never 423 */ 424 protected void setItemsContainer(Map mp) { 425 m_mpItems = mp; 426 } 427 428 /** 429 * Set the map of items that have been temporaryly added to the Stock. 430 * 431 * @override Never 432 */ 433 protected void setTemporaryAddedItemsContainer(Map mp) { 434 m_mpTemporaryAddedItems = mp; 435 } 436 437 /** 438 * Set the map of items that have been temporaryly removed from the Stock. 439 * 440 * @override Never 441 */ 442 protected void setTemporaryRemovedItemsContainer(Map mp) { 443 m_mpTemporaryRemovedItems = mp; 444 } 445 446 /** 447 * Set the map of items that are currently being edited. 448 * 449 * @override Never 450 */ 451 protected void setEditingItemsContainer(Map mp) { 452 m_mpEditingItems = mp; 453 } 454 455 /** 456 * Set the map of items that have been removed from the Stock to ensure referential integrity. 457 * 458 * @override Never 459 */ 460 protected void setRefIntegrItemsContainer(Map mp) { 461 m_mpRefIntegrItems = mp; 462 } 463 464 /** 465 * Set the map storing information about name changes in CatalogItems. 466 * 467 * @override Never 468 */ 469 protected void setRefIntegrEditContainer(Map mp) { 470 m_mpRefIntegrEdit = mp; 471 } 472 473 // Stock interface methods 474 475 /** 476 * Add the contents of a Stock to this Stock. The method calls {@link Stock#add} for each item in the source 477 * Stock so the same constraints apply and the same exceptions may be thrown. 478 * 479 * @override Never 480 * 481 * @param st the Stock whose contents is to be added to this Stock. 482 * @param db the DataBasket relative to which to perform the actions. <code>addStock</code> will add all 483 * items from the source Stock that are visible using this DataBasket. Must be either <code>null</code> or 484 * a descendant of {@link DataBasketImpl}. 485 * @param fRemove if true, the items will be removed from the source Stock prior to adding them to this 486 * Stock. Otherwise, they will be cloned prior to adding them to the Stock. 487 */ 488 public void addStock(Stock st, DataBasket db, boolean fRemove) { 489 if (st.getCatalog(db) != getCatalog(db)) { 490 throw new CatalogConflictException(); 491 } 492 493 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor())); 494 495 synchronized (oLock) { 496 synchronized (getItemsLock()) { 497 Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock)); 498 499 synchronized (oLock2) { 500 for (Iterator i = st.iterator(db, false); i.hasNext(); ) { 501 try { 502 StockItem si = (StockItem)i.next(); 503 504 if (fRemove) { 505 i.remove(); 506 } else { 507 si = (StockItem)si.clone(); 508 } 509 510 add(si, db); 511 } 512 catch (ConcurrentModificationException e) { 513 break; 514 } 515 catch (UnsupportedOperationException e) { 516 // ignore any items that could not be removed from their source 517 continue; 518 } 519 } 520 } 521 } 522 } 523 } 524 525 /** 526 * Check whether the Stock contains an item with the given key. 527 * 528 * <p>Equivalent to:<code>({@link #countItems} (sKey, db) > 0)</code>.</p> 529 * 530 * @override Never 531 * 532 * @param sKey the key for which to check containment. 533 * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of 534 * {@link DataBasketImpl}. 535 */ 536 public boolean contains(String sKey, DataBasket db) { 537 return (countItems(sKey, db) > 0); 538 } 539 540 /** 541 * Check whether the Stock contains the given StockItem. 542 * 543 * <p>Return true if the Stock contains a StockItem that is equal to the given one.</p> 544 * 545 * @param si the StockItem for which to check containment. 546 * @param db the DataBasket used to check visibility. Must be either <code>null</code> or a descendant of 547 * {@link DataBasketImpl}. 548 * 549 * @override Never 550 */ 551 public boolean contains(StockItem si, DataBasket db) { 552 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor())); 553 554 synchronized (oLock) { 555 synchronized (getItemsLock()) { 556 for (Iterator i = get(si.getName(), db, false); i.hasNext(); ) { 557 StockItem si2 = (StockItem)i.next(); 558 559 if (si.equals(si2)) { 560 return true; 561 } 562 } 563 564 return false; 565 } 566 } 567 } 568 569 /** 570 * Check whether the given Stock is completely contained in this Stock. 571 * 572 * <p>Calls {@link #contains(data.StockItem, data.DataBasket)} for each item in the given Stock.</p> 573 * 574 * @override Never 575 * 576 * @param st the Stock for which to check containment. 577 * @param db the DataBasket used to determine visibility. Must be either <code>null</code> or a descendant of 578 * {@link DataBasketImpl}. 579 */ 580 public boolean containsStock(Stock st, DataBasket db) { 581 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor())); 582 583 synchronized (oLock) { 584 synchronized (getItemsLock()) { 585 Object oLock2 = ((st instanceof StockImpl) ? (((StockImpl)st).getItemsLock()) : (oLock)); 586 587 synchronized (oLock2) { 588 for (Iterator i = st.iterator(db, false); i.hasNext(); ) { 589 if (!contains((StockItem)i.next(), db)) { 590 return false; 591 } 592 } 593 594 return true; 595 } 596 } 597 } 598 } 599 600 /** 601 * Iterate all items in the Stock. 602 * 603 * <p>This method, together with {@link Stock#get} is the only way of accessing the individual 604 * {@link StockItem StockItems} contained in a Stock. The iterator will deliver all items that are visible 605 * using the given DataBasket. Depending on the <code>fForEdit</code> parameter, the items will be retrieved 606 * in different ways. See {@link DataBasket} for an explanation of the different possibilities.</p> 607 * 608 * <p><code>canEditStockItems</code> and <code>editingStockItems</code> events will be fired if 609 * <code>fForEdit</code> == true. {@link VetoException VetoExceptions} will be converted into 610 * <code>UnSupportedOperationException</code>s.</p> 611 * 612 * @override Never 613 * 614 * @param db the DataBasket relative to which to retrieve the StockItems. Must be either <code>null</code> or a descendant of 615 * {@link DataBasketImpl}. 616 * @param fForEdit if true, the StockItems will be retrieved for editing. 617 */ 618 public Iterator iterator(final DataBasket db, final boolean fForEdit) { 619 class I implements Iterator { 620 private StockImpl m_stiOwner; 621 private Iterator m_iKeys; 622 private Iterator m_iItems; 623 624 public I(StockImpl stiOwner) { 625 super(); 626 627 m_stiOwner = stiOwner; 628 629 m_iKeys = m_stiOwner.keySet(db).iterator(); 630 } 631 632 public boolean hasNext() { 633 return findNext(); 634 } 635 636 public Object next() { 637 if (!findNext()) { 638 throw new NoSuchElementException("No more elements in Stock."); 639 } 640 641 return m_iItems.next(); 642 } 643 644 public void remove() { 645 if (m_iItems == null) { 646 throw new IllegalStateException(); 647 } 648 649 m_iItems.remove(); 650 } 651 652 private boolean findNext() { 653 if (m_iItems == null) { 654 if (m_iKeys.hasNext()) { 655 m_iItems = m_stiOwner.get((String)m_iKeys.next(), db, fForEdit); 656 } else { 657 return false; 658 } 659 } 660 661 while ((m_iItems.hasNext()) || (m_iKeys.hasNext())) { 662 if (m_iItems.hasNext()) { 663 return true; 664 } 665 666 m_iItems = m_stiOwner.get((String)m_iKeys.next(), db, fForEdit); 667 } 668 669 return false; 670 } 671 } 672 673 return new I(this); 674 } 675 676 /** 677 * Return the set of keys for which {@link StockItem StockItems} are visible using the given DataBasket. 678 * 679 * <p>The returned set is static and gives the state of the Stock at the time of the call. It will not 680 * automatically update when the contents of the Stock changes.</p> 681 * 682 * @param db the DataBasket used for determining visibility. Must be either <code>null</code> or a descendant of 683 * {@link DataBasketImpl}. 684 * 685 * @override Never 686 */ 687 public Set keySet(DataBasket db) { 688 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor())); 689 690 synchronized (oLock) { 691 synchronized (getItemsLock()) { 692 Set stKeys = new TreeSet(getItemsContainer().keySet()); 693 694 for (Iterator i = getTemporaryAddedItemsContainer().keySet().iterator(); i.hasNext(); ) { 695 String sKey = (String)i.next(); 696 697 if ((!stKeys.contains(sKey)) && (countItems(sKey, db) > 0)) { 698 stKeys.add(sKey); 699 } 700 } 701 702 return stKeys; 703 } 704 } 705 } 706 707 /** 708 * Sum up the Stock. 709 * 710 * <p>The method will determine the value of each {@link CatalogItem} in the associated Catalog and 711 * {@link Value#multiplyAccumulating(int) multiply} this by 712 * {@link Stock#countItems the number of StockItems for the respective key}. These products will be 713 * {@link Value#addAccumulating added up} and the resulting total will be returned.</p> 714 * 715 * @override Never 716 * 717 * @param db the DataBasket that is used to determine visibility. Must be either <code>null</code> or a 718 * descendant of {@link DataBasketImpl}. 719 * @param civ the CatalogItemValue used for determining the value of a CatalogItem. 720 * @param vInit the initial value. The sum of the Stock will be added to this value. 721 * 722 * @return the resulting total. Usually the returned object is the same as the one passed as 723 * <code>vInit</code>, only with a changed value. 724 */ 725 public Value sumStock(DataBasket db, CatalogItemValue civ, Value vInit) { 726 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor())); 727 728 synchronized (oLock) { 729 synchronized (getItemsLock()) { 730 Set stKeys = keySet(db); 731 732 for (Iterator i = stKeys.iterator(); i.hasNext(); ) { 733 String sKey = (String)i.next(); 734 735 try { 736 vInit.addAccumulating(civ.getValue(getCatalog(db).get(sKey, db, 737 false)).multiply(countItems(sKey, db))); 738 } 739 catch (VetoException ex) {} 740 } 741 742 return vInit; 743 } 744 } 745 } 746 747 /** 748 * Increase the {@link #sumStock Stock's value} by a given value. 749 * 750 * <p>The method will try to break the given value as exactly as possible into StockItems that will be 751 * added to the Stock. The actual algorithm used for breaking up the value will be determined by the last 752 * parameter. The return value of the method will specify the remaining value that could not be represented 753 * by StockItems by the given algorithm.</p> 754 * 755 * @param db the DataBasket relative to which to perform the operation. Must be either <code>null</code> or 756 * a descendant of {@link DataBasketImpl}. 757 * @param vTarget the value by which to increase the Stock's total value. 758 * @param sfvc the strategy used to fill the Stock. 759 * 760 * @return the value that remained and could not be represented by StockItems. 761 * 762 * @override Never 763 */ 764 public Value fillStockWithValue(DataBasket db, Value vTarget, StockFromValueCreator sfvc) { 765 Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object())); 766 767 synchronized (oLock) { 768 synchronized (getItemsLock()) { 769 return sfvc.fillStock(this, vTarget, db); 770 } 771 } 772 } 773 774 /** 775 * Get the size of this Stock. I.e. calculate the number of StockItems that can be seen when using the 776 * given DataBasket. 777 * 778 * @param db the DataBasket used to determine visibility. Must be either <code>null</code> or a descendant of 779 * {@link DataBasketImpl}. 780 * 781 * @override Never 782 */ 783 public int size(DataBasket db) { 784 Object oLock = ((db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor())); 785 786 synchronized (oLock) { 787 synchronized (getItemsLock()) { 788 Set stKeys = new HashSet(getItemsContainer().keySet()); 789 stKeys.addAll(getTemporaryAddedItemsContainer().keySet()); 790 791 int nCount = 0; 792 793 for (Iterator i = stKeys.iterator(); i.hasNext(); ) { 794 String sKey = (String)i.next(); 795 796 nCount += countItems(sKey, db); 797 } 798 799 return nCount; 800 } 801 } 802 } 803 804 /** 805 * Get the Catalog associated to this Stock. 806 * 807 * @override Never 808 */ 809 public Catalog getCatalog(DataBasket db) { 810 if ((m_dbCatalogValidator == null) || (m_dbCatalogValidator == db)) { // Only one specific DataBasket may pass 811 return m_ciCatalog; 812 } else { 813 throw new DataBasketConflictException( 814 "Cannot access catalog that is currently being edited in another transaction."); 815 } 816 } 817 818 /** 819 * Create a shallow clone of the Stock. In contrast to a {@link #clone deep clone}, the individual items in 820 * the Stock are not cloned. 821 * 822 * @override Never Instead, override {@link #createPeer} and/or {@link #fillShallowClone}. 823 */ 824 public StockItemImpl getShallowClone() { 825 StockImpl sti = createPeer(); 826 827 fillShallowClone(sti); 828 829 // Attach as listener to this Stock 830 sti.m_srsiEditCreator = new java.lang.ref.SoftReference(this); 831 addStockChangeListener(sti.m_sclEditCreatorListener); 832 if (getStock() != null) { 833 ((StockImpl)getStock()).addStockChangeListener(sti.m_sclEditingListener); 834 } 835 836 return sti; 837 } 838 839 /** 840 * Hook method called to fill the given shallow clone of this Stock. 841 * 842 * @override Sometimes Normally you do not have to override this method. 843 */ 844 protected void fillShallowClone(StockImpl stiClone) { 845 synchronized (getItemsLock()) { 846 synchronized (stiClone.getItemsLock()) { 847 stiClone.setItemsContainer(new HashMap(getItemsContainer())); 848 stiClone.setTemporaryAddedItemsContainer(new HashMap(getTemporaryAddedItemsContainer())); 849 stiClone.setTemporaryRemovedItemsContainer(new HashMap(getTemporaryRemovedItemsContainer())); 850 stiClone.setEditingItemsContainer(new HashMap(getEditingItemsContainer())); 851 stiClone.setRefIntegrItemsContainer(new HashMap(getRefIntegrItemsContainer())); 852 stiClone.setRefIntegrEditContainer(new HashMap(getRefIntegrEditContainer())); 853 } 854 } 855 } 856 857 /** 858 * Create and return a deep clone of the Stock. In contrast to a {@link #getShallowClone shallow clone}, the 859 * individual items themselves are also cloned. 860 * 861 * @override Never Instead override {@link #createPeer}. 862 */ 863 public Object clone() { 864 StockImpl sti = createPeer(); 865 866 sti.addStock(this, null, false); 867 868 return sti; 869 } 870 871 /** 872 * Create an empty Stock with the same name, associated Catalog and class. 873 * 874 * @override Always 875 */ 876 protected abstract StockImpl createPeer(); 877 878 /** 879 * Set the Stock that contains this Stock. 880 * 881 * @override Never 882 */ 883 protected void setStock(StockImpl sti) { 884 super.setStock(sti); 885 886 if (sti != null) { 887 internalSetCatalog((CatalogImpl)getAssociatedItem(sti.m_dbCatalogValidator)); //12/11/2000-sz9: Changed to use m_dbCatalogValidator 888 } 889 } 890 891 /** 892 * Compare this StockItem to the given object. 893 * 894 * @override Always The default implementation will assume <code>o</code> to be a StockItem and will 895 * compare the names. Stocks, however, will always be greater than StockItems. 896 * 897 * @exception ClassCastException if the given object cannot be converted into a {@link StockItem}. 898 */ 899 public int compareTo(Object o) { 900 if (o instanceof Stock) { 901 return super.compareTo(o); 902 } else { 903 return 1; // Stocks are always greater than StockItems 904 } 905 } 906 907 // NameContext interface methods 908 /** 909 * Check a name change of a StockItem that is contained in this Stock. 910 * 911 * <p>Stocks will not allow name changes of StockItem, as a principle.</p> 912 * 913 * @override Never 914 */ 915 public void checkNameChange(DataBasket db, String sOldName, String sNewName) throws NameContextException { 916 917 throw new NameContextException("StockItem names cannot be changed when they are part of a Stock."); 918 } 919 920 /** 921 * Stocks will not allow name changes of StockItem, as a principle. 922 * 923 * @override Never 924 */ 925 public void nameHasChanged(DataBasket db, String sOldName, String sNewName) {} 926 927 /** 928 * Stocks will not allow name changes of StockItem, as a principle. 929 * 930 * @override Never 931 */ 932 public Object getNCMonitor() { 933 return new Object(); 934 } 935 936 /** 937 * Helper method to be called in the beginning of commitAdd and rollbackRemove. Tries to maintain 938 * referential integrity by trying to make sure that a CatalogItem exists for the the StockItems that will 939 * be brought into the Stock. Must be called from within 940 * <code>synchronized ({@link #getItemsLock}) {}</code> before any other operation. 941 * 942 * @override Never 943 */ 944 protected void prepareReferentialIntegrity(DataBasket db, DataBasketEntry dbe) { 945 // try to achieve referential integrity by first trying to make sure, there'll be a CatalogItem for these 946 // StockItems in the corresponding Catalog. 947 DataBasketCondition dbc = new DataBasketConditionImpl(CatalogItemImpl.CATALOG_ITEM_MAIN_KEY, 948 dbe.getSecondaryKey(), (CatalogImpl)getCatalog(db), null, null); 949 DataBasketEntry dbeRemovedCI = db.get(dbc); 950 951 dbc = new DataBasketConditionImpl(CatalogItemImpl.CATALOG_ITEM_MAIN_KEY, dbe.getSecondaryKey(), null, 952 (CatalogImpl)getCatalog(db), null); 953 DataBasketEntry dbeAddedCI = db.get(dbc); 954 955 if (((dbeRemovedCI == null) || (dbeAddedCI == null)) && (dbeRemovedCI != dbeAddedCI)) { 956 957 if (dbeRemovedCI != null) { 958 dbeRemovedCI.rollback(); 959 } else { 960 dbeAddedCI.commit(); 961 } 962 } 963 // if both dbeRemovedCI and dbeAddedCI are not null, we cannot decide which is correct and must therefore 964 // live with that bit of inconsistency. However, as long as there's no corresponding CatalogItem, the 965 // StockItems will not be visible. 966 } 967 968 // ListenableStock interface methods 969 /** 970 * Add a listener to receive events when the Stock's contents change. 971 * 972 * @override Never 973 */ 974 public void addStockChangeListener(StockChangeListener scl) { 975 m_lhListeners.add(StockChangeListener.class, scl); 976 } 977 978 /** 979 * Remove a listener that received events when the Stock's contents changed. 980 * 981 * @override Never 982 */ 983 public void removeStockChangeListener(StockChangeListener scl) { 984 m_lhListeners.remove(StockChangeListener.class, scl); 985 } 986 987 /** 988 * Fire an event to all listeners that showed an interest in this Stock. 989 * 990 * @override Never 991 */ 992 protected void fireStockItemsAdded(StockChangeEvent e) { 993 Object[] listeners = m_lhListeners.getListenerList(); 994 for (int i = listeners.length - 2; i >= 0; i -= 2) { 995 if (listeners[i] == StockChangeListener.class) { 996 997 ((StockChangeListener)listeners[i + 1]).addedStockItems(e); 998 } 999 } 1000 } 1001 1002 /** 1003 * Fire an event to all listeners that showed an interest in this Stock. 1004 * 1005 * @override Never 1006 */ 1007 protected void fireStockItemsAddCommit(StockChangeEvent e) { 1008 Object[] listeners = m_lhListeners.getListenerList(); 1009 1010 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1011 if (listeners[i] == StockChangeListener.class) { 1012 1013 ((StockChangeListener)listeners[i + 1]).commitAddStockItems(e); 1014 } 1015 } 1016 } 1017 1018 /** 1019 * Fire an event to all listeners that showed an interest in this Stock. 1020 * 1021 * @override Never 1022 */ 1023 protected void fireStockItemsAddRollback(StockChangeEvent e) { 1024 Object[] listeners = m_lhListeners.getListenerList(); 1025 1026 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1027 if (listeners[i] == StockChangeListener.class) { 1028 1029 ((StockChangeListener)listeners[i + 1]).rollbackAddStockItems(e); 1030 } 1031 } 1032 } 1033 1034 /** 1035 * Fire an event to all listeners that showed an interest in this Stock. 1036 * 1037 * @override Never 1038 */ 1039 protected void fireStockItemsRemoved(StockChangeEvent e) { 1040 Object[] listeners = m_lhListeners.getListenerList(); 1041 1042 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1043 if (listeners[i] == StockChangeListener.class) { 1044 1045 ((StockChangeListener)listeners[i + 1]).removedStockItems(e); 1046 } 1047 } 1048 } 1049 1050 /** 1051 * Fire an event to all listeners that showed an interest in this Stock. 1052 * 1053 * @override Never 1054 */ 1055 protected void fireStockItemsRemoveCommit(StockChangeEvent e) { 1056 Object[] listeners = m_lhListeners.getListenerList(); 1057 1058 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1059 if (listeners[i] == StockChangeListener.class) { 1060 1061 ((StockChangeListener)listeners[i + 1]).commitRemoveStockItems(e); 1062 } 1063 } 1064 } 1065 1066 /** 1067 * Fire an event to all listeners that showed an interest in this Stock. 1068 * 1069 * @override Never 1070 */ 1071 protected void fireStockItemsRemoveRollback(StockChangeEvent e) { 1072 Object[] listeners = m_lhListeners.getListenerList(); 1073 1074 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1075 if (listeners[i] == StockChangeListener.class) { 1076 1077 ((StockChangeListener)listeners[i + 1]).rollbackRemoveStockItems(e); 1078 } 1079 } 1080 } 1081 1082 /** 1083 * Fire an event to all listeners that showed an interest in this Stock. 1084 * 1085 * @override Never 1086 */ 1087 protected void fireCanRemoveStockItems(StockChangeEvent e) throws VetoException { 1088 Object[] temp = m_lhListeners.getListenerList(); 1089 Object[] listeners = new Object[temp.length]; 1090 System.arraycopy(temp, 0, listeners, 0, temp.length); 1091 1092 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1093 if (listeners[i] == StockChangeListener.class) { 1094 try { 1095 ((StockChangeListener)listeners[i + 1]).canRemoveStockItems(e); 1096 } 1097 catch (VetoException ex) { 1098 for (int j = i; j < listeners.length; j += 2) { 1099 if (listeners[j] == StockChangeListener.class) { 1100 ((StockChangeListener)listeners[j + 1]).noRemoveStockItems(e); 1101 } 1102 } 1103 1104 throw ex; 1105 } 1106 } 1107 } 1108 } 1109 1110 /** 1111 * Fire an event to all listeners that showed an interest in this Stock. 1112 * 1113 * @override Never 1114 */ 1115 protected void fireCanEditStockItems(StockChangeEvent e) throws VetoException { 1116 Object[] temp = m_lhListeners.getListenerList(); 1117 Object[] listeners = new Object[temp.length]; 1118 System.arraycopy(temp, 0, listeners, 0, temp.length); 1119 1120 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1121 if (listeners[i] == StockChangeListener.class) { 1122 try { 1123 ((StockChangeListener)listeners[i + 1]).canEditStockItems(e); 1124 } 1125 catch (VetoException ex) { 1126 for (int j = i; j < listeners.length; j += 2) { 1127 if (listeners[j] == StockChangeListener.class) { 1128 ((StockChangeListener)listeners[j + 1]).noRemoveStockItems(e); 1129 } 1130 } 1131 1132 throw ex; 1133 } 1134 } 1135 } 1136 } 1137 1138 /** 1139 * Fire an event to all listeners that showed an interest in this Stock. 1140 * 1141 * @override Never 1142 */ 1143 protected void fireEditingStockItems(StockChangeEvent e) { 1144 Object[] listeners = m_lhListeners.getListenerList(); 1145 1146 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1147 if (listeners[i] == StockChangeListener.class) { 1148 1149 ((StockChangeListener)listeners[i + 1]).editingStockItems(e); 1150 } 1151 } 1152 } 1153 1154 /** 1155 * Fire an event to all listeners that showed an interest in this Stock. 1156 * 1157 * @override Never 1158 */ 1159 protected void fireStockItemsEditCommit(StockChangeEvent e) { 1160 Object[] listeners = m_lhListeners.getListenerList(); 1161 1162 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1163 if (listeners[i] == StockChangeListener.class) { 1164 1165 ((StockChangeListener)listeners[i + 1]).commitEditStockItems(e); 1166 } 1167 } 1168 } 1169 1170 /** 1171 * Fire an event to all listeners that showed an interest in this Stock. 1172 * 1173 * @override Never 1174 */ 1175 protected void fireStockItemsEditRollback(StockChangeEvent e) { 1176 Object[] listeners = m_lhListeners.getListenerList(); 1177 1178 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1179 if (listeners[i] == StockChangeListener.class) { 1180 1181 ((StockChangeListener)listeners[i + 1]).rollbackEditStockItems(e); 1182 } 1183 } 1184 } 1185 1186 /** 1187 * Helper method used to maintain StockImpl - CatalogImpl links in nested Stocks/Catalogs. For internal use only. 1188 * 1189 * @param db the DataBasket that protecting this activity. 1190 * @param nAction the action that occurred. Can be either {@link #COMMIT_ACTION}, {@link #ROLLBACK_ACTION}, 1191 * {@link #STARTEDIT_ACTION}. 1192 */ 1193 void relinkCatalog(DataBasket db, int nAction) { 1194 super.relinkCatalog(db, nAction); 1195 1196 switch (nAction) { 1197 case STARTEDIT_ACTION: 1198 1199 /** 1200 * A starting catalog editing operation requires us to set the catalog to the catalog being 1201 * edited and to remember the databasket coordinating the editing. 1202 */ 1203 internalSetCatalog((CatalogImpl)getAssociatedItem(db)); 1204 m_dbCatalogValidator = db; 1205 1206 break; 1207 case COMMIT_ACTION: 1208 1209 /* 1210 * Commiting a catalog editing action only requires to forget the DataBasket used to edit the 1211 * Catalog, as it will no longer be contained in it and all other relinks will have been 1212 * performed. 1213 */ 1214 if (db == m_dbCatalogValidator) { 1215 m_dbCatalogValidator = null; 1216 } 1217 1218 break; 1219 case ROLLBACK_ACTION: 1220 1221 /* 1222 * Rolling back a catalog editing action requires us to set the catalog based on our parent's catalog 1223 * validator, reset the catalog validator and update our children if we'returna StoringStock. The latter 1224 * will be done in StoringStockImpl.relinkCatalog. 1225 */ 1226 if (db == m_dbCatalogValidator) { 1227 DataBasket dbParent = ((StockImpl)getStock()).m_dbCatalogValidator; 1228 1229 if (dbParent != null) { 1230 // This normally shouldn't happen, but I am not sure that it really is an error situation, 1231 // so just trace it... 1232 Debug.print( 1233 "In data.ooimpl.StockImpl.relinkCatalog (db, ROLLBACK_ACTION): parent databasket is not null!", 1234 -1); 1235 } 1236 1237 internalSetCatalog((CatalogImpl)getAssociatedItem(dbParent)); 1238 1239 m_dbCatalogValidator = null; 1240 } 1241 1242 break; 1243 } 1244 } 1245 }