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    }