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