001 package data.ooimpl; 002 003 import java.util.*; 004 import java.io.*; 005 006 import data.*; 007 import data.events.*; 008 009 import util.*; 010 011 /** 012 * Pure Java implementation of the {@link Catalog} interface. 013 * 014 * <p>CatalogImpl can only work together with DataBaskets that are descendants of {@link DataBasketImpl}.</p> 015 * 016 * @author Steffen Zschaler 017 * @version 2.0 19/08/1999 018 * @since v2.0 019 */ 020 public class CatalogImpl<T extends CatalogItemImpl> extends CatalogItemImpl implements 021 ListenableCatalog<T>, NameContext, SelfManagingDBESource<T>, SelfManagingDBEDestination<T> { 022 023 /** 024 * ID for serialization. 025 */ 026 private static final long serialVersionUID = 1257361351441562071L; 027 028 /** 029 * The listeners that registered to be informed of changes in this Catalog's contents. 030 * 031 * @serial 032 */ 033 protected ListenerHelper m_lhListeners = new ListenerHelper(); 034 035 /** 036 * Modification counter. Will be increased by one for each structural modification. 037 * 038 * @serial 039 */ 040 protected int m_nModCount = Integer.MIN_VALUE; 041 042 /** 043 * The items in this catalog. 044 * 045 * @serial 046 */ 047 private Map<String, T> m_mpciItems = new HashMap<String, T>(); 048 049 /** 050 * The items that have been temporaryly removed from this Catalog. 051 * 052 * @serial 053 */ 054 private Map<String, T> m_mpciTemporaryRemovedItems = new HashMap<String, T>(); 055 056 /** 057 * The items that have been temporaryly added to this Catalog. 058 * 059 * @serial 060 */ 061 private Map<String, T> m_mpciTemporaryAddedItems = new HashMap<String, T>(); 062 063 /** 064 * The items that are currently being edited. 065 * 066 * @serial 067 */ 068 private Map<String, T> m_mpciEditingItems = new HashMap<String, T>(); 069 070 /** 071 * The original Catalog, if this is a clone created for editing. 072 * 073 * <p>SoftReference, so that garbage collector will take care of it if the EditCreator is no longer used AND 074 * is no longer referenced by any DataBasket or anything else.</p> 075 */ 076 private transient java.lang.ref.SoftReference<Catalog> m_srciEditCreator = null; 077 078 /** 079 * Listener listening to the creator's parent Catalog to know when editing is finished, if this is a 080 * shallow clone created for editing. 081 * 082 * @serial 083 */ 084 private final CatalogChangeListener<T> m_cclEditingListener = new CatalogChangeAdapter<T>() { 085 086 private static final long serialVersionUID = -1565986662026481883L; 087 088 public void commitEditCatalogItem(CatalogChangeEvent<T> e) { 089 if (e.getAffectedItem() == CatalogImpl.this) { 090 ((CatalogImpl)e.getSource()).removeCatalogChangeListener(this); 091 } 092 } 093 094 public void rollbackEditCatalogItem(CatalogChangeEvent<T> e) { 095 if (e.getAffectedItem() == CatalogImpl.this) { 096 ((CatalogImpl)e.getSource()).removeCatalogChangeListener(this); 097 098 ((CatalogImpl)m_srciEditCreator.get()).removeCatalogChangeListener(m_cclEditCreatorListener); 099 m_srciEditCreator = null; 100 } 101 } 102 }; 103 104 /** 105 * Listener listening to the creator to follow with any commits or rollbacks, if this is a shallow clone 106 * created for editing. 107 * 108 * @serial 109 */ 110 private final CatalogChangeListener<T> m_cclEditCreatorListener = new CatalogChangeAdapter<T>() { 111 112 private static final long serialVersionUID = -2401376477528659735L; 113 114 115 public void commitedAddCatalogItem(CatalogChangeEvent<T> e) { 116 synchronized (getItemsLock()) { 117 if (getTemporaryAddedItemsContainer().containsKey(e.getAffectedItem().getName())) { 118 getTemporaryAddedItemsContainer().remove(e.getAffectedItem().getName()); 119 getItemsContainer().put(e.getAffectedItem().getName(), e.getAffectedItem()); 120 121 m_nModCount++; 122 123 ((CatalogItemImpl)e.getAffectedItem()).setCatalog(CatalogImpl.this); 124 fireCatalogItemAddCommit(e.getAffectedItem(), e.getBasket()); 125 } 126 } 127 } 128 129 public void rolledbackAddCatalogItem(CatalogChangeEvent<T> e) { 130 synchronized (getItemsLock()) { 131 if (getTemporaryAddedItemsContainer().containsKey(e.getAffectedItem().getName())) { 132 getTemporaryAddedItemsContainer().remove(e.getAffectedItem().getName()); 133 134 m_nModCount++; 135 136 ((CatalogItemImpl)e.getAffectedItem()).setCatalog(null); fireCatalogItemAddRollback(e. 137 getAffectedItem(), e.getBasket()); 138 } 139 } 140 } 141 142 public void canRemoveCatalogItem(CatalogChangeEvent<T> e) throws VetoException { 143 throw new VetoException("Please use the editable version of the Catalog for that."); 144 } 145 146 public void commitedRemoveCatalogItem(CatalogChangeEvent<T> e) { 147 synchronized (getItemsLock()) { 148 if (getTemporaryRemovedItemsContainer().containsKey(e.getAffectedItem().getName())) { 149 getTemporaryRemovedItemsContainer().remove(e.getAffectedItem().getName()); 150 151 m_nModCount++; 152 153 ((CatalogItemImpl)e.getAffectedItem()).setCatalog(null); fireCatalogItemRemoveCommit(e. 154 getAffectedItem(), e.getBasket()); 155 } 156 } 157 } 158 159 public void rolledbackRemoveCatalogItem(CatalogChangeEvent<T> e) { 160 synchronized (getItemsLock()) { 161 if (getTemporaryRemovedItemsContainer().containsKey(e.getAffectedItem().getName())) { 162 getTemporaryRemovedItemsContainer().remove(e.getAffectedItem().getName()); 163 getItemsContainer().put(e.getAffectedItem().getName(), e.getAffectedItem()); 164 165 m_nModCount++; 166 167 ((CatalogItemImpl)e.getAffectedItem()).setCatalog(CatalogImpl.this); 168 fireCatalogItemRemoveRollback(e.getAffectedItem(), e.getBasket()); 169 } 170 } 171 } 172 173 public void canEditCatalogItem(CatalogChangeEvent<T> e) throws VetoException { 174 throw new VetoException("Please use the editable version of the Catalog for that."); 175 } 176 177 public void commitEditCatalogItem(CatalogChangeEvent<T> e) { 178 synchronized (getItemsLock()) { 179 if (getEditingItemsContainer().get(e.getAffectedItem().getName()) == e.getAffectedItem()) { 180 getEditingItemsContainer().remove(e.getAffectedItem().getName()); 181 182 fireCommitEditCatalogItem(e.getAffectedItem(), e.getBasket()); 183 } 184 } 185 } 186 187 public void rollbackEditCatalogItem(CatalogChangeEvent<T> e) { 188 synchronized (getItemsLock()) { 189 if (getEditingItemsContainer().get(e.getAffectedItem().getName()) == e.getAffectedItem()) { 190 getEditingItemsContainer().remove(e.getAffectedItem().getName()); 191 192 fireRollbackEditCatalogItem(e.getAffectedItem(), e.getBasket()); 193 } 194 } 195 } 196 }; 197 198 /** 199 * Monitor synchronizing access to the several items maps. 200 */ 201 private transient Object m_oItemsLock; 202 /** 203 * Get the monitor synchronizing access to the several items maps. 204 * 205 * @override Never 206 */ 207 protected final Object getItemsLock() { 208 if (m_oItemsLock == null) { 209 m_oItemsLock = new Object(); 210 } 211 212 return m_oItemsLock; 213 } 214 215 /** 216 * First writes the default serializable data and then the reference to the edit creator, if any. 217 */ 218 private void writeObject(ObjectOutputStream oos) throws IOException { 219 oos.defaultWriteObject(); 220 221 if (m_srciEditCreator != null) { 222 oos.writeObject(m_srciEditCreator.get()); 223 } else { 224 oos.writeObject(null); 225 } 226 } 227 228 /** 229 * First reads the default serializable data and then re-establishes the reference to the edit creator, if 230 * any. 231 */ 232 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 233 ois.defaultReadObject(); 234 235 Object oEditCreator = ois.readObject(); 236 237 if (oEditCreator != null) { 238 m_srciEditCreator = new java.lang.ref.SoftReference<Catalog>((Catalog)oEditCreator); 239 } 240 } 241 242 /** 243 * Create a new, initially empty CatalogImpl. 244 * 245 * @param sName the name of the Catalog. 246 */ 247 public CatalogImpl(String sName) { 248 super(sName); 249 } 250 251 /** 252 * Create a new, initially empty CatalogImpl. 253 * 254 * @param id The identifier of the Catalog. 255 */ 256 public CatalogImpl(CatalogIdentifier<T> id) { 257 super(id.getName()); 258 } 259 260 /** 261 * Get the map of items that are completely contained in this Catalog. 262 * 263 * @override Never 264 */ 265 protected Map<String, T> getItemsContainer() { 266 return m_mpciItems; 267 } 268 269 /** 270 * Get the map of items that have been temporaryly removed from this Catalog. 271 * 272 * @override Never 273 */ 274 protected Map<String, T> getTemporaryRemovedItemsContainer() { 275 return m_mpciTemporaryRemovedItems; 276 } 277 278 /** 279 * Get the map of items that have been temporaryly added to this Catalog. 280 * 281 * @override Never 282 */ 283 protected Map<String, T> getTemporaryAddedItemsContainer() { 284 return m_mpciTemporaryAddedItems; 285 } 286 287 /** 288 * Get the map of items that are currently being edited. 289 * 290 * @override Never 291 */ 292 protected Map<String, T> getEditingItemsContainer() { 293 return m_mpciEditingItems; 294 } 295 296 /** 297 * Set the map of items that are completely contained in this Catalog. 298 * 299 * <p>Must be called from within lock on {@link #getItemsLock}.</p> 300 * 301 * @override Never 302 */ 303 private void setItemsContainer(Map<String, T> mpNew) { 304 m_mpciItems = mpNew; 305 } 306 307 /** 308 * Set the map of items that have been temporaryly removed from this Catalog. 309 * 310 * <p>Must be called from within lock on {@link #getItemsLock}.</p> 311 * 312 * @override Never 313 */ 314 private void setTemporaryRemovedItemsContainer(Map<String, T> mpNew) { 315 m_mpciTemporaryRemovedItems = mpNew; 316 } 317 318 /** 319 * Set the map of items that have been temporaryly added to this Catalog. 320 * 321 * <p>Must be called from within lock on {@link #getItemsLock}.</p> 322 * 323 * @override Never 324 */ 325 private void setTemporaryAddedItemsContainer(Map<String, T> mpNew) { 326 m_mpciTemporaryAddedItems = mpNew; 327 } 328 329 /** 330 * Set the map of items that are currently being edited. 331 * 332 * <p>Must be called from within lock on {@link #getItemsLock}.</p> 333 * 334 * @override Never 335 */ 336 private void setEditingItemsContainer(Map<String, T> mpNew) { 337 m_mpciEditingItems = mpNew; 338 } 339 340 // Catalog interface methods 341 342 /** 343 * Add the given item to the Catalog. 344 * 345 * @override Never 346 * 347 * @param ci the CatalogItem to be added. 348 * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a 349 * descendant of {@link DataBasketImpl}. 350 * 351 * @exception NotEditableException if the Catalog is currently not editable. 352 * @exception DuplicateKeyException if a CatalogItem of the same name does already exist in the Catalog. 353 * @exception DataBasketConflictException if the CatalogItem cannot be added because an item of the same 354 * name has already been added/removed using another DataBasket. 355 */ 356 public void add(final T ci, DataBasket db) { 357 if (!isEditable()) { 358 throw new NotEditableException(); 359 } 360 361 //T cii = (CatalogItemImpl)ci; 362 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 363 364 synchronized (oLock) { 365 synchronized (getItemsLock()) { 366 //check if ci is part of catalog 367 if (getItemsContainer().containsKey(ci.getName())) { 368 throw new DuplicateKeyException("Key " + ci.getName() + " already existent in Catalog " + 369 getName() + "."); 370 } 371 //check if ci has already been temporarily added 372 if (getTemporaryAddedItemsContainer().containsKey(ci.getName())) { 373 if ((db != null) && (db.contains(new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, 374 ci.getName(), null, this, ci)))) { 375 throw new DuplicateKeyException("Key " + ci.getName() + 376 " already existent in Catalog " + getName() + "."); 377 } else { 378 throw new DataBasketConflictException("CatalogItem with key " + ci.getName() + 379 " already added to Catalog " + getName() + " using a different DataBasket."); 380 } 381 } 382 //check if ci has been temporarily removed 383 if (getTemporaryRemovedItemsContainer().containsKey(ci.getName())) { 384 //dbc describing entry for ci with this catalog as source 385 DataBasketCondition dbc = new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, ci.getName(), 386 this, null, ci); 387 388 if ((db != null) && (db.contains(dbc))) { 389 CatalogItemDataBasketEntry cidbe = (CatalogItemDataBasketEntry)db.get(dbc); 390 //if db says, ci has not been added to another catalog, do a rollback 391 if (cidbe.getDestination() == null) { 392 db.rollback(dbc); 393 m_nModCount++; 394 } else { 395 //Cannot rollback a removed item that was added to another Catalog. 396 //We might end up having one CatalogItem in several Catalogs... 397 throw new DataBasketConflictException("CatalogItem with key " + ci.getName() + 398 " has already been temporarily removed from Catalog " + getName() + 399 " and added to another Catalog."); 400 } 401 return; 402 } else { 403 //ci has been removed using a different DataBasket 404 throw new DataBasketConflictException("CatalogItem with key " + ci.getName() + 405 " already removed from Catalog " + getName() + 406 " using a different DataBasket."); 407 } 408 } 409 410 // everything properly checked -> add the stuff 411 if (db != null) { 412 getTemporaryAddedItemsContainer().put(ci.getName(), ci); 413 //dbc that matches ci if it has a source, i.e. has been removed temporarily from another 414 //catalog (move operation, i.e. via TTFS) 415 DataBasketCondition<T> dbc = new DataBasketConditionImpl<T>(CATALOG_ITEM_MAIN_KEY, ci.getName(), null, null, null) { 416 private static final long serialVersionUID = -7894810147593310162L; 417 public boolean match(DataBasketEntry<T> dbe) { 418 return ((dbe.getDestination() == null) && (dbe.getSource() != null) && 419 (dbe.getValue() == ci)); 420 } 421 }; 422 423 if (db.contains(dbc)) { 424 CatalogItemDataBasketEntry cidbe = (CatalogItemDataBasketEntry)db.get(dbc); 425 //update the already existing DataBasketEntry 426 cidbe.setDestination(this); 427 if (db instanceof ListenableDataBasket) { 428 ((ListenableDataBasket)db).fireDataBasketChanged(); 429 } 430 } else { 431 //create new DataBasketEntry and save it 432 // db.put(new CatalogItemDataBasketEntry(null, this, cii)); 433 db.put(new CatalogItemDataBasketEntry(null, this, ci)); 434 } 435 } else { 436 // null DataBasket, so add directly 437 getItemsContainer().put(ci.getName(), ci); 438 } 439 m_nModCount++; 440 // cii.setCatalog(this); 441 // fireCatalogItemAdded(cii, db); 442 ci.setCatalog(this); 443 fireCatalogItemAdded(ci, db); 444 } 445 } 446 } 447 448 /** 449 * Remove the given item from the Catalog. 450 * 451 * @override Never 452 * 453 * @param ci the CatalogItem to be removed. 454 * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a 455 * descendant of {@link DataBasketImpl}. 456 * 457 * @exception NotEditableException if the Catalog is currently not editable. 458 * @exception VetoException if one of the listeners vetos the removal. 459 * @exception DataBasketConflictException if the CatalogItem cannot be removed because an item of the same 460 * name has already been added using another DataBasket. 461 */ 462 // MAY HAVE TO CHECK THAT ci IS REALLY CONTAINED IN THE CATALOG (AND NOT JUST ITS KEY) 463 public T remove(final T ci, DataBasket db) throws VetoException { 464 if (!isEditable()) { 465 throw new NotEditableException(); 466 } 467 468 CatalogItemImpl cii = (CatalogItemImpl)ci; 469 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 470 471 synchronized (oLock) { 472 synchronized (getItemsLock()) { 473 if (getTemporaryAddedItemsContainer().containsKey(ci.getName())) { 474 // the CatalogItem has been added to this Catalog, but only temporarily up to now 475 // Check whether the adding was really about the same item (not just the same key) 476 DataBasketCondition dbc = new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, ci.getName(), 477 null, this, ci); 478 479 if ((db != null) && (db.contains(dbc))) { 480 481 CatalogItemDataBasketEntry cidbe = (CatalogItemDataBasketEntry)db.get(dbc); 482 fireCanRemoveCatalogItem(ci, db); 483 if (cidbe.getSource() == null) { //ci has no other Catalog from which it has been removed temporarily 484 db.rollback(dbc); 485 } else {//ci has no other Catalog from which it has been removed temporarily 486 // only rollback the destination part of the the databasketentry 487 cidbe.setDestination(null); 488 489 getTemporaryAddedItemsContainer().remove(ci.getName()); 490 491 m_nModCount++; 492 493 fireCatalogItemAddRollback(ci, db); 494 495 if (db instanceof ListenableDataBasket) { 496 ((ListenableDataBasket)db).fireDataBasketChanged(); 497 } 498 } 499 500 cii.setCatalog(null); 501 502 return ci; 503 } else { 504 // someone was faster than you... 505 throw new DataBasketConflictException("CatalogItem " + ci.getName() + 506 " has only been temporaryly added to Catalog " + getName() + 507 " using a different DataBasket, and can therefore not be removed."); 508 } 509 } 510 511 if (getTemporaryRemovedItemsContainer().containsKey(ci.getName())) { 512 throw new DataBasketConflictException("CatalogItem " + ci.getName() + 513 " already removed from Catalog " + getName() + "."); 514 } 515 516 if (!getItemsContainer().containsKey(ci.getName())) { 517 // the CatalogItem is not in the Catalog and never was, too. 518 return null; 519 } 520 521 // item is in items container and must be transferred into temporarily removed items 522 523 fireCanRemoveCatalogItem(ci, db); 524 525 if (db != null) { 526 getItemsContainer().remove(ci.getName()); 527 getTemporaryRemovedItemsContainer().put(ci.getName(), ci); 528 529 DataBasketCondition<T> dbc = new DataBasketConditionImpl<T>(CATALOG_ITEM_MAIN_KEY, ci.getName(), 530 null, null, null) { 531 private static final long serialVersionUID = -3618752855294544806L; 532 533 public boolean match(DataBasketEntry<T> dbe) { 534 return ((dbe.getSource() == null) && (dbe.getValue() == ci)); 535 } 536 }; 537 538 if (db.contains(dbc)) { 539 ((CatalogItemDataBasketEntry)db.get(dbc)).setSource(this); 540 541 if (db instanceof ListenableDataBasket) { 542 ((ListenableDataBasket)db).fireDataBasketChanged(); 543 } 544 } else { 545 db.put(new CatalogItemDataBasketEntry(this, null, (CatalogItemImpl)ci)); 546 } 547 } else { 548 getItemsContainer().remove(ci.getName()); 549 } 550 551 m_nModCount++; 552 cii.setCatalog(null); 553 fireCatalogItemRemoved(ci, db); 554 return ci; 555 } 556 } 557 } 558 559 /** 560 * Remove the indicated item from the Catalog. 561 * 562 * @override Never 563 * 564 * @param sKey the key of the CatalogItem to be removed. 565 * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a 566 * descendant of {@link DataBasketImpl}. 567 * 568 * @exception NotEditableException if the Catalog is currently not editable. 569 * @exception VetoException if one of the listeners vetos the removal. 570 * @exception DataBasketConflictException if the CatalogItem cannot be removed because an item of the same 571 * name has already been added using another DataBasket. 572 */ 573 public T remove(String sKey, DataBasket db) throws VetoException { 574 if (!isEditable()) { 575 throw new NotEditableException(); 576 } 577 578 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 579 580 synchronized (oLock) { 581 synchronized (getItemsLock()) { 582 T ci = null; 583 584 if (getItemsContainer().containsKey(sKey)) { 585 ci = (T)getItemsContainer().get(sKey); 586 } else { 587 if (getTemporaryAddedItemsContainer().containsKey(sKey)) { 588 ci = (T)getTemporaryAddedItemsContainer().get(sKey); 589 } else { 590 if (getTemporaryRemovedItemsContainer().containsKey(sKey)) { 591 throw new DataBasketConflictException("CatalogItem " + sKey + 592 " already removed from Catalog " + getName() + "."); 593 } else { 594 return null; 595 } 596 } 597 } 598 599 return remove(ci, db); 600 } 601 } 602 } 603 604 /** 605 * Get the indicated item from the Catalog. 606 * 607 * @override Never 608 * 609 * @param sKey the key for which to retrieve the item. 610 * @param db the DataBasket relative to which to perform the operation. Must be <code>null</code> or a 611 * descendant of {@link DataBasketImpl}. 612 * @param fForEdit if true, the item will be retrieved for editing. Only in this case changes made to the 613 * CatalogItem can be undone via a rollback of the DataBasket. Therefore the DataBasket must not be null 614 * if a CatalogItem is desired to be editable! 615 * A number of events is fired if fForEdit is true: 616 * <ol> 617 * <li><code>canEditCatalogItem</code> with the original item.</li> 618 * <li>a {@link CatalogItemImpl#getShallowClone shallow clone} of the item is created.</li> 619 * <li><code>editingCatalogItem</code> with the newly created clone.</li> 620 * <li><code>addCatalogItem</code> with the newly created clone and <code>removeCatalogItem</code> with 621 * the original item.</li> 622 * <li>The newly created clone is returned for editing.</li> 623 * </ol> 624 * 625 * @exception NotEditableException if the Catalog is not currently editable, but an attempt is made to edit 626 * one of its items. 627 * @exception VetoException if one of the listeners vetos the editing. 628 * @exception DataBasketConflictException if the CatalogItem cannot be retrieved because it is not visible 629 * to users of the given DataBasket. 630 */ 631 public T get(String sKey, DataBasket db, boolean fForEdit) throws VetoException { 632 633 // If we aren't editable ourselves, but try to edit an item: TOO BAD! 634 if ((!isEditable()) && (fForEdit)) { 635 throw new NotEditableException(); 636 } 637 638 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 639 synchronized (oLock) { 640 synchronized (getItemsLock()) { 641 // First check, whether item in question was just added by some transaction 642 if (getTemporaryAddedItemsContainer().containsKey(sKey)) { 643 // It was, so it should only be visible to that transaction 644 T ci = getTemporaryAddedItemsContainer().get(sKey); 645 646 if (db != null) { 647 // Search in db for ci. 648 DataBasketCondition dbc = new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, sKey, 649 null, this, ci); 650 651 DataBasketEntry<?> dbe = db.get(dbc); 652 653 if (dbe != null) { 654 655 if (fForEdit) { 656 if (dbe.getSource() != null) { 657 // This is a little too complex for us... 658 throw new DataBasketConflictException( 659 "Cannot edit a CatalogItem that was removed temporarily from its Catalog."); 660 } else { 661 if (!getEditingItemsContainer().containsKey(sKey)) { 662 fireCanEditCatalogItem(ci, db); 663 664 // No need for cloning, was added temporarily only anyway... 665 getEditingItemsContainer().put(sKey, ci); 666 667 fireEditingCatalogItem(ci, db); 668 } 669 670 return ci; 671 } 672 } else { 673 return ci; 674 } 675 } else { 676 return null; 677 } 678 } else { 679 return null; 680 } 681 } 682 683 // Item is not currently under control of any transaction. Does it exist at all? 684 if (getItemsContainer().containsKey(sKey)) { 685 // Yup! Prepare for retrieval 686 T ci = getItemsContainer().get(sKey); 687 688 if ((db != null) && (fForEdit)) { 689 // Prepare for editing 690 fireCanEditCatalogItem(ci, db); 691 692 // Create shallow clone, which will be editable. 693 T cci = ((CatalogItemImpl)ci).<T>getInternalShallowClone(); 694 695 // Reorganize containers 696 getItemsContainer().remove(sKey); 697 getTemporaryRemovedItemsContainer().put(sKey, ci); 698 getTemporaryAddedItemsContainer().put(sKey, (T)cci); 699 700 // Mark as editable 701 getEditingItemsContainer().put(sKey, (T)cci); 702 703 // Store undo/commit data in db 704 db.put(new CatalogItemDataBasketEntry(this, null, (CatalogItemImpl)ci)); 705 db.put(new CatalogItemDataBasketEntry(null, this, cci)); 706 707 // Relink catalog structure 708 ((CatalogItemImpl)ci).setCatalog(null); 709 ((CatalogItemImpl)cci).setCatalog(this); 710 711 // Fire all events 712 fireEditingCatalogItem(cci, db); 713 fireCatalogItemRemoved(ci, db); 714 fireCatalogItemAdded(cci, db); 715 716 // Notify iterators of change 717 m_nModCount++; 718 719 return (T)cci; 720 } else { 721 return ci; 722 } 723 } 724 } 725 } 726 727 return null; 728 } 729 730 /** 731 * Convenience method, which gets an editable ShallowClone of this Catalog from its parent Catalog and 732 * returns it. If the Catalog is already editable or there is no parent Catalog, no copy will be created 733 * and the original Catalog is returned.<br> 734 * @param db the DataBasket which is passed to get method. Must not be null! 735 * @return this Catalog, if editable, otherwise a clone of this Catalog. 736 * @throws VetoException 737 */ 738 public CatalogImpl getEditableCopy(DataBasket db) throws VetoException { 739 if (isEditable()) { 740 return this; 741 } else { 742 CatalogImpl parent = (CatalogImpl)getCatalog(); 743 parent = parent.getEditableCopy(db); 744 return (CatalogImpl)parent.get(getName(), db, true); 745 } 746 } 747 748 /** 749 * Check whether the Catalog contains a certain CatalogItem. 750 * 751 * <p>Will return true only if an item of the given key is contained in the Catalog and if that item is 752 * visible to users of the given DataBasket.</p> 753 * 754 * @override Never 755 * 756 * @param sKey the key for which to check containment. 757 * @param db the DataBasket that defines visibility of items. Must be <code>null</code> or a 758 * descendant of {@link DataBasketImpl}. 759 */ 760 public boolean contains(String sKey, DataBasket db) { 761 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 762 763 synchronized (oLock) { 764 synchronized (getItemsLock()) { 765 if (getItemsContainer().containsKey(sKey)) { 766 return true; 767 } 768 769 if ((db != null) && (getTemporaryAddedItemsContainer().containsKey(sKey))) { 770 CatalogItem ci = (CatalogItem)getTemporaryAddedItemsContainer().get(sKey); 771 772 DataBasketCondition dbc = new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, sKey, null, 773 this, ci); 774 775 return (db.contains(dbc)); 776 } 777 778 return false; 779 } 780 } 781 } 782 783 /** 784 * Return an iterator of all items in the Catalog. 785 * 786 * <p>The iterator will conceptually call {@link #get} for each CatalogItem, using the given parameters.</p> 787 * 788 * @override Never 789 * 790 * @param db the DataBasket that defines visibility. 791 * @param fForEdit if true, the items are retrieved for editing. VetoException will be converted into 792 * <code>UnsupportedOperationException</code>s. 793 */ 794 public Iterator<T> iterator(final DataBasket db, final boolean fForEdit) { 795 final Object oLock = ((db != null) ? (((DataBasketImpl)db).getDBIMonitor()) : (new Object())); 796 797 return new Iterator<T>() { 798 private Iterator<String> m_iItems; 799 private int m_nExpectedModCount; 800 private T m_ciCurrent; 801 802 { 803 synchronized (oLock) { 804 synchronized (getItemsLock()) { 805 m_iItems = keySet(db).iterator(); 806 807 m_nExpectedModCount = m_nModCount; 808 } 809 } 810 } 811 812 public boolean hasNext() { 813 return m_iItems.hasNext(); 814 } 815 816 public T next() { 817 synchronized (oLock) { 818 synchronized (getItemsLock()) { 819 if (m_nModCount != m_nExpectedModCount) { 820 throw new ConcurrentModificationException(); 821 } 822 823 String sKey = m_iItems.next(); 824 825 try { 826 m_ciCurrent = CatalogImpl.this.get(sKey, db, fForEdit); 827 828 if (fForEdit) { 829 m_nExpectedModCount = m_nModCount; 830 } 831 } 832 catch (VetoException ve) { 833 throw new UnsupportedOperationException("VETO: " + ve); 834 } 835 836 return m_ciCurrent; 837 } 838 } 839 } 840 841 public void remove() { 842 synchronized (oLock) { 843 synchronized (getItemsLock()) { 844 if (m_nModCount != m_nExpectedModCount) { 845 throw new ConcurrentModificationException(); 846 } 847 848 if (m_ciCurrent == null) { 849 throw new IllegalStateException(); 850 } 851 852 try { 853 CatalogImpl.this.remove(m_ciCurrent, db); 854 855 m_nExpectedModCount = m_nModCount; 856 857 m_ciCurrent = null; 858 } 859 catch (VetoException ve) { 860 m_ciCurrent = null; 861 862 throw new UnsupportedOperationException(); 863 } 864 } 865 } 866 } 867 }; 868 } 869 870 /** 871 * Get a set of all keys currently in the Catalog. 872 * 873 * <p>This will retrieve a static set that gives the state of the Catalog at the time of the call.</p> 874 * 875 * @param db the DataBasket used to determine visibility of elements. Must be <code>null</code> or a 876 * descendant of {@link DataBasketImpl}. 877 * 878 * @override Never 879 */ 880 public Set<String> keySet(DataBasket db) { 881 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 882 synchronized (oLock) { 883 synchronized (getItemsLock()) { 884 Set<String> stReturn = new TreeSet<String>(getItemsContainer().keySet()); 885 886 if (db != null) { 887 for (Iterator<T> i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext(); ) { 888 final T ci = i.next(); 889 890 if (!stReturn.contains(ci.getName())) { 891 DataBasketCondition dbc = new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, 892 ci.getName(), null, this, ci); 893 if (db.contains(dbc)) { 894 stReturn.add(ci.getName()); 895 } 896 } 897 } 898 } 899 900 return stReturn; 901 } 902 } 903 } 904 905 /** 906 * Calculate the size of the Catalog. I.e. count the CatalogItems that are visible to users of the given 907 * DataBasket. 908 * 909 * @override Never 910 * 911 * @param db the DataBasket used to determine visibility. Must be <code>null</code> or a 912 * descendant of {@link DataBasketImpl}. 913 */ 914 public int size(DataBasket db) { 915 return keySet(db).size(); 916 } 917 918 /** 919 * Set the Catalog that contains this Catalog. 920 * 921 * @override Never 922 */ 923 void setCatalog(CatalogImpl ci) { 924 super.setCatalog(ci); 925 926 if (ci != null) { 927 // necessary, so that children of clones prepared for editing will always know who their 928 // correct parents are. 929 for (Iterator<T> i = getItemsContainer().values().iterator(); i.hasNext(); ) { 930 ((CatalogItemImpl)i.next()).setCatalog(this); 931 } 932 933 for (Iterator<T> i = getTemporaryAddedItemsContainer().values().iterator(); i.hasNext(); ) { 934 ((CatalogItemImpl)i.next()).setCatalog(this); 935 } 936 } 937 } 938 939 /** 940 * Create a shallow clone of this Catalog. A shallow clone means that the individual items themselves will 941 * not be cloned. 942 * 943 * @override Never Instead override {@link #createPeer}. 944 */ 945 protected CatalogItemImpl getShallowClone() { 946 CatalogImpl<T> ci = createPeer(); 947 synchronized (getItemsLock()) { 948 synchronized (ci.getItemsLock()) { 949 ci.setItemsContainer(new HashMap<String, T>(getItemsContainer())); 950 ci.setEditingItemsContainer(new HashMap<String, T>(getEditingItemsContainer())); 951 ci.setTemporaryAddedItemsContainer(new HashMap<String, T>(getTemporaryAddedItemsContainer())); 952 ci.setTemporaryRemovedItemsContainer(new HashMap<String, T>(getTemporaryRemovedItemsContainer())); 953 } 954 } 955 // attach as listener to this CatalogImpl 956 ci.m_srciEditCreator = new java.lang.ref.SoftReference<Catalog>(this); 957 addCatalogChangeListener(ci.m_cclEditCreatorListener); 958 if (getCatalog() != null) { 959 ((CatalogImpl)getCatalog()).addCatalogChangeListener(ci.m_cclEditingListener); 960 } 961 return ci; 962 } 963 964 /** 965 * Create and return an empty CatalogImpl of the same name and class. 966 * 967 * @override Always 968 */ 969 public CatalogImpl<T> createPeer() { 970 return new CatalogImpl<T>(getName()); 971 } 972 973 /** 974 * Return a {@link String} representation of this Catalog. 975 * 976 * @override Sometimes 977 */ 978 public String toString() { 979 synchronized (getItemsLock()) { 980 String sReturn = "Catalog \"" + getName() + "\" ["; 981 982 boolean fFirst = true; 983 for (Iterator<T> i = iterator (null, false); i.hasNext();) { 984 sReturn += ((fFirst)?(""):(", ")) + i.next(); 985 fFirst = false; 986 } 987 return sReturn + "]"; 988 } 989 } 990 991 992 // SelfManagingDBESource interface methods 993 994 /** 995 * Commit the removal of a CatalogItem. 996 */ 997 public void commitRemove(DataBasket db, DataBasketEntry<T> dbe) { 998 // The databasket already locks on its own monitor, so we just lock on ours. 999 synchronized (getItemsLock()) { 1000 if (dbe.getSource() == this) {//check necessary if method called directly and not via db.commit() 1001 T cii = dbe.getValue(); 1002 1003 getTemporaryRemovedItemsContainer().remove(cii.getName()); 1004 1005 if (cii.getCatalog() == this) { 1006 cii.setCatalog(null); 1007 } 1008 1009 ((CatalogItemDataBasketEntry)dbe).setSource(null); 1010 1011 fireCatalogItemRemoveCommit(cii, db); 1012 } 1013 } 1014 } 1015 1016 /** 1017 * Roll back the removal of a CatalogItem. 1018 */ 1019 public void rollbackRemove(DataBasket db, DataBasketEntry<T> dbe) { 1020 // the databasket already locks on its own monitor, so we just lock on ours. 1021 synchronized (getItemsLock()) { 1022 if (dbe.getSource() == this) {//check necessary if method called directly and not via db.rollback() 1023 T cii = dbe.getValue(); 1024 1025 if (getTemporaryRemovedItemsContainer().get(cii.getName()) == cii) { 1026 getTemporaryRemovedItemsContainer().remove(cii.getName()); 1027 getItemsContainer().put(cii.getName(), cii); 1028 cii.setCatalog(this); 1029 1030 m_nModCount++; 1031 1032 fireCatalogItemRemoveRollback(cii, db); 1033 } 1034 1035 ((CatalogItemDataBasketEntry)dbe).setSource(null); 1036 } 1037 } 1038 } 1039 1040 // SelfManagingDBEDestination interface methods 1041 /** 1042 * Commit the adding of a CatalogItem. In addition to the <code>addedCatalogItemCommit</code> event this 1043 * may trigger an <code>editingCatalogItemCommit</code> event with the CatalogItem that has been edited. 1044 */ 1045 public void commitAdd(DataBasket db, DataBasketEntry<T> dbe) { 1046 // the databasket already locks on its own monitor, so we just lock on ours. 1047 synchronized (getItemsLock()) { 1048 if (dbe.getDestination() == this) { //check necessary if method called directly and not via db.commit() 1049 T cii = (T)dbe.getValue(); 1050 1051 if (getTemporaryAddedItemsContainer().get(cii.getName()) == cii) { 1052 getTemporaryAddedItemsContainer().remove(cii.getName()); 1053 1054 getItemsContainer().put(cii.getName(), (T) cii); 1055 cii.setCatalog(this); 1056 1057 m_nModCount++; 1058 1059 fireCatalogItemAddCommit(cii, db); 1060 1061 if (getEditingItemsContainer().remove(cii.getName()) != null) { 1062 fireCommitEditCatalogItem(cii, db); 1063 } 1064 } 1065 1066 ((CatalogItemDataBasketEntry)dbe).setDestination(null); 1067 } 1068 } 1069 } 1070 1071 /** 1072 * Roll back the adding of a CatalogItem. In addition to the <code>addedCatalogItemRollback</code> event 1073 * this may trigger an <code>editingCatalogItemRollback</code> event with the CatalogItem that has been 1074 * edited. 1075 */ 1076 public void rollbackAdd(DataBasket db, DataBasketEntry<T> dbe) { 1077 // the databasket already locks on its own monitor, so we just lock on ours. 1078 synchronized (getItemsLock()) { 1079 if (dbe.getDestination() == this) {//check necessary if method called directly and not via db.rollback() 1080 T cii = dbe.getValue(); 1081 1082 getTemporaryAddedItemsContainer().remove(cii.getName()); 1083 1084 if (cii.getCatalog() == this) { 1085 cii.setCatalog(null); 1086 } 1087 1088 ((CatalogItemDataBasketEntry)dbe).setDestination(null); 1089 1090 m_nModCount++; 1091 1092 fireCatalogItemAddRollback(cii, db); 1093 1094 if (getEditingItemsContainer().remove(cii.getName()) != null) { 1095 fireRollbackEditCatalogItem(cii, db); 1096 } 1097 } 1098 } 1099 } 1100 1101 // NameContext interface methods 1102 /** 1103 * Check a name change of a CatalogItem in this Catalog. 1104 * 1105 * <p>The name change will be allowed if the item is editable and the new name can be guaranteed to be 1106 * unique.</p> 1107 * 1108 * @override Sometimes Override to enforce stricter naming conventions. 1109 */ 1110 public void checkNameChange(DataBasket db, String sOldName, String sNewName) throws NameContextException { 1111 1112 if (sOldName == sNewName) { 1113 return; 1114 } 1115 1116 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 1117 1118 synchronized (oLock) { 1119 synchronized (getItemsLock()) { 1120 if (!getEditingItemsContainer().containsKey(sOldName)) { 1121 throw new NameContextException( 1122 "Item must be made editable before you can change its name!"); 1123 } 1124 1125 if ((getTemporaryRemovedItemsContainer().containsKey(sNewName)) || 1126 (getItemsContainer().containsKey(sNewName)) || 1127 (getTemporaryAddedItemsContainer().containsKey(sNewName))) { 1128 throw new NameContextException("Name conflict: name \"" + sNewName + 1129 "\" already existent (though maybe temporarily removed!)"); 1130 } 1131 1132 DataBasketCondition dbc = new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, sOldName, null, 1133 this, null); 1134 1135 if ((db == null) || (!db.contains(dbc))) { 1136 throw new NameContextException("DataBasket conflict: No corresponding item with name \"" + 1137 sOldName + "\" in the given DataBasket."); 1138 } 1139 } 1140 } 1141 } 1142 1143 /** 1144 * Synchronize the Catalog's internal data with the name change. 1145 * 1146 * @override Never 1147 */ 1148 public void nameHasChanged(DataBasket db, String sOldName, String sNewName) { 1149 if (sOldName == sNewName) { 1150 return; 1151 } 1152 1153 Object oLock = (db == null) ? (new Object()) : (((DataBasketImpl)db).getDBIMonitor()); 1154 1155 synchronized (oLock) { 1156 synchronized (getItemsLock()) { 1157 getEditingItemsContainer().put(sNewName, getEditingItemsContainer().remove(sOldName)); 1158 getTemporaryAddedItemsContainer().put(sNewName, 1159 getTemporaryAddedItemsContainer().remove(sOldName)); 1160 1161 DataBasketCondition dbc = new DataBasketConditionImpl(CATALOG_ITEM_MAIN_KEY, sOldName, null, this, null); 1162 DataBasketEntry<?> dbe = db.get(dbc); 1163 db.exchange(dbe, new CatalogItemDataBasketEntry(null, this, (CatalogItemImpl)dbe.getValue())); 1164 1165 m_nModCount++; 1166 } 1167 } 1168 } 1169 1170 /** 1171 * Return the monitor used to synchronize access to the Catalog's internal data. 1172 * 1173 * @override Never 1174 */ 1175 public final Object getNCMonitor() { 1176 return getItemsLock(); 1177 } 1178 1179 // ListenableCatalog interface methods 1180 1181 /** 1182 * Add a listener that listens for changes in this Catalog's contents. 1183 * 1184 * @override Never 1185 * 1186 * @param ccl the listener 1187 */ 1188 public void addCatalogChangeListener(CatalogChangeListener ccl) { 1189 m_lhListeners.add(CatalogChangeListener.class, ccl); 1190 } 1191 1192 /** 1193 * Remove a listener that listened for changes in this Catalog's contents. 1194 * 1195 * @override Never 1196 * 1197 * @param ccl the listener 1198 */ 1199 public void removeCatalogChangeListener(CatalogChangeListener ccl) { 1200 m_lhListeners.remove(CatalogChangeListener.class, ccl); 1201 } 1202 1203 /** 1204 * Fire an event to all listeners listening to this Catalog. 1205 * 1206 * @override Never 1207 */ 1208 @SuppressWarnings("unchecked") 1209 protected void fireCatalogItemAdded(T ci, DataBasket db) { 1210 Object[] listeners = m_lhListeners.getListenerList(); 1211 CatalogChangeEvent<T> cce = null; 1212 1213 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1214 if (listeners[i] == CatalogChangeListener.class) { 1215 if (cce == null) { 1216 cce = new CatalogChangeEvent<T>(this, ci, db); 1217 } 1218 1219 ((CatalogChangeListener<T>)listeners[i + 1]).addedCatalogItem(cce); 1220 } 1221 } 1222 } 1223 1224 /** 1225 * Fire an event to all listeners listening to this Catalog. 1226 * 1227 * @override Never 1228 */ 1229 @SuppressWarnings("unchecked") 1230 protected void fireCatalogItemAddCommit(T ci, DataBasket db) { 1231 Object[] listeners = m_lhListeners.getListenerList(); 1232 CatalogChangeEvent cce = null; 1233 1234 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1235 if (listeners[i] == CatalogChangeListener.class) { 1236 if (cce == null) { 1237 cce = new CatalogChangeEvent<T>(this, ci, db); 1238 } 1239 1240 ((CatalogChangeListener<T>)listeners[i + 1]).commitedAddCatalogItem(cce); 1241 } 1242 } 1243 } 1244 1245 /** 1246 * Fire an event to all listeners listening to this Catalog. 1247 * 1248 * @override Never 1249 */ 1250 @SuppressWarnings("unchecked") 1251 protected void fireCatalogItemAddRollback(T ci, DataBasket db) { 1252 Object[] listeners = m_lhListeners.getListenerList(); 1253 CatalogChangeEvent cce = null; 1254 1255 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1256 if (listeners[i] == CatalogChangeListener.class) { 1257 if (cce == null) { 1258 cce = new CatalogChangeEvent<T>(this, ci, db); 1259 } 1260 1261 ((CatalogChangeListener<T>)listeners[i + 1]).rolledbackAddCatalogItem(cce); 1262 } 1263 } 1264 } 1265 1266 /** 1267 * Fire an event to all listeners listening to this Catalog. 1268 * 1269 * @override Never 1270 */ 1271 @SuppressWarnings("unchecked") 1272 protected void fireCatalogItemRemoved(T ci, DataBasket db) { 1273 Object[] listeners = m_lhListeners.getListenerList(); 1274 CatalogChangeEvent cce = null; 1275 1276 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1277 if (listeners[i] == CatalogChangeListener.class) { 1278 if (cce == null) { 1279 cce = new CatalogChangeEvent<T>(this, ci, db); 1280 } 1281 1282 ((CatalogChangeListener<T>)listeners[i + 1]).removedCatalogItem(cce); 1283 } 1284 } 1285 } 1286 1287 /** 1288 * Fire an event to all listeners listening to this Catalog. 1289 * 1290 * @override Never 1291 */ 1292 @SuppressWarnings("unchecked") 1293 protected void fireCatalogItemRemoveCommit(T ci, DataBasket db) { 1294 Object[] listeners = m_lhListeners.getListenerList(); 1295 CatalogChangeEvent cce = null; 1296 1297 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1298 if (listeners[i] == CatalogChangeListener.class) { 1299 if (cce == null) { 1300 cce = new CatalogChangeEvent<T>(this, ci, db); 1301 } 1302 1303 ((CatalogChangeListener<T>)listeners[i + 1]).commitedRemoveCatalogItem(cce); 1304 } 1305 } 1306 } 1307 1308 /** 1309 * Fire an event to all listeners listening to this Catalog. 1310 * 1311 * @override Never 1312 */ 1313 @SuppressWarnings("unchecked") 1314 protected void fireCatalogItemRemoveRollback(T ci, DataBasket db) { 1315 Object[] listeners = m_lhListeners.getListenerList(); 1316 CatalogChangeEvent cce = null; 1317 1318 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1319 if (listeners[i] == CatalogChangeListener.class) { 1320 if (cce == null) { 1321 cce = new CatalogChangeEvent<T>(this, ci, db); 1322 } 1323 1324 ((CatalogChangeListener<T>)listeners[i + 1]).rolledbackRemoveCatalogItem(cce); 1325 } 1326 } 1327 } 1328 1329 /** 1330 * Fire an event to all listeners listening to this Catalog. 1331 * 1332 * @override Never 1333 */ 1334 @SuppressWarnings("unchecked") 1335 protected void fireCanRemoveCatalogItem(T ci, DataBasket db) throws VetoException { 1336 Object[] temp = m_lhListeners.getListenerList(); 1337 Object[] listeners = new Object[temp.length]; 1338 System.arraycopy(temp, 0, listeners, 0, temp.length); 1339 1340 CatalogChangeEvent cce = null; 1341 1342 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1343 if (listeners[i] == CatalogChangeListener.class) { 1344 if (cce == null) { 1345 cce = new CatalogChangeEvent<T>(this, ci, db); 1346 } 1347 1348 try { 1349 ((CatalogChangeListener<T>)listeners[i + 1]).canRemoveCatalogItem(cce); 1350 } 1351 catch (VetoException e) { 1352 for (int j = i; j < listeners.length; j += 2) { 1353 if (listeners[j] == CatalogChangeListener.class) { 1354 ((CatalogChangeListener<T>)listeners[j + 1]).noRemoveCatalogItem(cce); 1355 } 1356 } 1357 1358 throw e; 1359 } 1360 } 1361 } 1362 } 1363 1364 /** 1365 * Fire an event to all listeners listening to this Catalog. 1366 * 1367 * @override Never 1368 */ 1369 @SuppressWarnings("unchecked") 1370 protected void fireCanEditCatalogItem(T ci, DataBasket db) throws VetoException { 1371 Object[] temp = m_lhListeners.getListenerList(); 1372 Object[] listeners = new Object[temp.length]; 1373 System.arraycopy(temp, 0, listeners, 0, temp.length); 1374 1375 CatalogChangeEvent cce = null; 1376 1377 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1378 if (listeners[i] == CatalogChangeListener.class) { 1379 if (cce == null) { 1380 cce = new CatalogChangeEvent<T>(this, ci, db); 1381 } 1382 1383 try { 1384 ((CatalogChangeListener<T>)listeners[i + 1]).canEditCatalogItem(cce); 1385 } 1386 catch (VetoException e) { 1387 for (int j = i; j < listeners.length; j += 2) { 1388 if (listeners[j] == CatalogChangeListener.class) { 1389 ((CatalogChangeListener<T>)listeners[j + 1]).noEditCatalogItem(cce); 1390 } 1391 } 1392 1393 throw e; 1394 } 1395 } 1396 } 1397 } 1398 1399 /** 1400 * Fire an event to all listeners listening to this Catalog. 1401 * 1402 * @override Never 1403 */ 1404 @SuppressWarnings("unchecked") 1405 protected void fireEditingCatalogItem(T ci, DataBasket db) { 1406 Object[] listeners = m_lhListeners.getListenerList(); 1407 1408 CatalogChangeEvent cce = null; 1409 1410 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1411 if (listeners[i] == CatalogChangeListener.class) { 1412 if (cce == null) { 1413 cce = new CatalogChangeEvent<T>(this, ci, db); 1414 } 1415 1416 ((CatalogChangeListener<T>)listeners[i + 1]).editingCatalogItem(cce); 1417 } 1418 } 1419 } 1420 1421 /** 1422 * Fire an event to all listeners listening to this Catalog. 1423 * 1424 * @override Never 1425 */ 1426 @SuppressWarnings("unchecked") 1427 protected void fireCommitEditCatalogItem(T ci, DataBasket db) { 1428 Object[] listeners = m_lhListeners.getListenerList(); 1429 1430 CatalogChangeEvent cce = null; 1431 1432 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1433 if (listeners[i] == CatalogChangeListener.class) { 1434 if (cce == null) { 1435 cce = new CatalogChangeEvent<T>(this, ci, db); 1436 } 1437 1438 ((CatalogChangeListener<T>)listeners[i + 1]).commitEditCatalogItem(cce); 1439 } 1440 } 1441 } 1442 1443 /** 1444 * Fire an event to all listeners listening to this Catalog. 1445 * 1446 * @override Never 1447 */ 1448 @SuppressWarnings("unchecked") 1449 protected void fireRollbackEditCatalogItem(T ci, DataBasket db) { 1450 Object[] listeners = m_lhListeners.getListenerList(); 1451 1452 CatalogChangeEvent cce = null; 1453 1454 for (int i = listeners.length - 2; i >= 0; i -= 2) { 1455 if (listeners[i] == CatalogChangeListener.class) { 1456 if (cce == null) { 1457 cce = new CatalogChangeEvent<T>(this, ci, db); 1458 } 1459 1460 ((CatalogChangeListener<T>)listeners[i + 1]).rollbackEditCatalogItem(cce); 1461 } 1462 } 1463 } 1464 }