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