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