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