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