001    /*
002      Copyright (C) 2003 Renaud Pawlak <renaud@aopsys.com>, 
003                         Laurent Martelli <laurent@aopsys.com>
004      
005      This program is free software; you can redistribute it and/or modify
006      it under the terms of the GNU Lesser General Public License as
007      published by the Free Software Foundation; either version 2 of the
008      License, or (at your option) any later version.
009    
010      This program is distributed in the hope that it will be useful,
011      but WITHOUT ANY WARRANTY; without even the implied warranty of
012      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013      GNU Lesser General Public License for more details.
014    
015      You should have received a copy of the GNU Lesser General Public License
016      along with this program; if not, write to the Free Software
017      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
018    
019    package org.objectweb.jac.core.rtti;
020    
021    import java.lang.reflect.Array;
022    import java.lang.reflect.Field;
023    import java.lang.reflect.InvocationTargetException;
024    import java.util.Arrays;
025    import java.util.Collection;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Vector;
029    import org.apache.log4j.Logger;
030    import org.objectweb.jac.util.WrappedThrowableException;
031    import java.util.ArrayList;
032    
033    /**
034     * This class defines a meta item that corresponds to a
035     * <code>java.lang.reflect.Field</code> meta element that is of an
036     * array, a collection, or a map type.<p>
037     *
038     * @see java.util.Collection
039     * @see java.util.Map
040     * @see java.util.Arrays
041     * @see java.lang.reflect.Field
042     *
043     * @author Renaud Pawlak
044     * @author Laurent Martelli
045     */
046    
047    public class CollectionItem extends FieldItem {
048    
049        static Logger logger = Logger.getLogger("rtti.collection");
050     
051        protected ClassItem componentType;
052    
053        /**
054         * Default contructor to create a new collection item object.<p>
055         *
056         * @param delegate the <code>java.lang.reflect.Field</code> actual
057         * meta item */
058    
059        /**
060         * Creates a CollectionItem for a real field
061         * @param delegate the field the collection corresponds to
062         * @param parent the class the collection belongs to
063         */
064        public CollectionItem(Field delegate, ClassItem parent) 
065            throws InvalidDelegateException 
066        {
067            super(delegate,parent);
068            type = delegate.getType();
069            if (!RttiAC.isCollectionType(type)) {
070                throw new InvalidDelegateException(delegate,"not a collection type");
071            }  
072        }
073    
074        /**
075         * Creates a CollectionItem for an expression field.
076         * @param name the expression
077         * @param path the list of FieldItem the expression is made of
078         * @param parent the class the collection belongs to
079         */
080        public CollectionItem(String name, List path, ClassItem parent) 
081            throws InvalidDelegateException 
082        {
083            super(name,path,parent);
084            if (!RttiAC.isCollectionType(type)) {
085                throw new InvalidDelegateException(parent.getName()+"."+name,"Not a collection type");
086            }  
087        }
088    
089        /**
090         * Creates a CollectionItem for a calculated field.
091         * @param name the name of the collection
092         * @param getter the getter of the calculated collection
093         * @param parent the class the collection belongs to
094         */
095        public CollectionItem(String name, MethodItem getter, ClassItem parent) {
096            super(name,getter,parent);
097        }
098    
099        MethodItem[] addingMethods = MethodItem.emptyArray;
100       
101        /**
102         * Gets the method items that access this collection for adding.<p>
103         *
104         * @return value of addingMethods.  
105         * @see #hasAdder()
106         */
107    
108        public MethodItem[] getAddingMethods() {
109            ((ClassItem)parent).buildFieldInfo();
110            return addingMethods;
111        }
112    
113        /**
114         * Returns true if the collection has at least one adding method.
115         * @see #getAddingMethods()
116         */
117        public boolean hasAdder() {
118            ((ClassItem)parent).buildFieldInfo();
119            return addingMethods.length>0;
120        }
121       
122        MethodItem adder;
123        /**
124         * Returns <em>the</em> adder of the collection, or null if it has
125         * no adder.  
126         */
127        public MethodItem getAdder() {
128            ((ClassItem)parent).buildFieldInfo();
129            if (adder!=null) 
130                return adder;
131            else if (addingMethods.length>0)
132                return addingMethods[0];
133            else if (isExpression)
134                return ((CollectionItem)getPathTop()).getAdder();
135            else
136                return null;
137        }
138        /**
139         * Sets <em>the</em> adder of the collection.  
140         * @param adder the adder
141         */
142        public void setAdder(MethodItem adder) {
143            this.adder = adder;
144        }
145    
146        /**
147         * Sets the methods that access this collection for adding.<p>
148         *
149         * @param addingMethods value to assign to addingMethods.
150         */
151    
152        public void setAddingMethods( MethodItem[] addingMethods ) {
153            this.addingMethods = addingMethods;
154        }
155    
156        /**
157         * Adds a new adding method for this field.<p>
158         *
159         * @param addingMethod the method to add
160         */
161    
162        public void addAddingMethod(MethodItem addingMethod) {
163            if (addingMethods.length == 0) {
164                addingMethods = new MethodItem[] { addingMethod };
165            } else {
166                MethodItem[] tmp = new MethodItem[addingMethods.length + 1];
167                System.arraycopy(addingMethods, 0, tmp, 0, addingMethods.length);
168                tmp[addingMethods.length] = addingMethod;
169                addingMethods = tmp;
170                if (getLongName().startsWith("org.objectweb.jac."))
171                    logger.debug("Adders for "+getLongName()+": "+Arrays.asList(tmp));
172                else
173                    logger.warn("Adders for "+getLongName()+": "+Arrays.asList(tmp));
174            }
175        }
176    
177        /**
178         * Returns the actual collection item. In the case of an expression
179         * field, this is the last element of the path, otherwise it is the
180         * field itself.  
181         */
182        public CollectionItem getCollection() {
183            if (isExpression) {
184                return (CollectionItem)getPathTop();
185            } else {
186                return this;
187            }
188        }
189    
190        /**
191         * Returns the type of the objects contained in this collection.
192         * @return the type of objects in this collection or null if it is
193         * undefined.
194         */
195        public ClassItem getComponentType() {
196            ((ClassItem)parent).buildFieldInfo();
197            if (componentType!=null)
198                return componentType;
199            if (isArray()) {
200                componentType = ClassRepository.get().getClass(getType().getComponentType());
201            } else {
202    
203                if (isExpression) {
204                    componentType = ((CollectionItem)getPathTop()).getComponentType();
205                } else {
206                    MethodItem adder = getAdder();
207                    if (adder!=null && adder.getParameterCount()>0) {
208                        if (isMap()) {
209                            if (isIndex())
210                                componentType = adder.getParameterTypeItem(
211                                    adder.getParameterCount()-1);
212                            else {
213                                componentType = 
214                                    ClassRepository.get().getClass(
215                                        "java.util.Map$Entry");
216                            }
217                        } else {
218                            if (adder.getParameterCount()==1)
219                                componentType = adder.getParameterTypeItem(0);
220                            else if (adder.getParameterCount()==2) {
221                                int itemArg = adder.getCollectionItemArgument();
222                                if (itemArg!=-1)
223                                    componentType = adder.getParameterTypeItem(itemArg);
224                            } 
225                            if (componentType==null)
226                                logger.warn(
227                                    "Cannot determine component type of "+getName()+
228                                    " from adder "+adder.getFullName());
229                        }
230                    }
231                }
232            }
233    
234            if (componentType==null) {
235                if (!isMap() || isIndex())
236                    logger.warn("Component type of "+this+" is null");
237                if (adder==null && !isCalculated())
238                    logger.warn("no adder fo "+this+" "+getParent());
239            }
240            return componentType;
241        }
242    
243        /**
244         * Sets the component type of the collection.
245         * @param componentType the component type
246         */
247        public void setComponentType(ClassItem componentType) {
248            this.componentType = componentType;
249        }
250    
251        public boolean isAddingMethod(MethodItem method) {
252            ((ClassItem)parent).buildFieldInfo();
253            return Arrays.asList(addingMethods).contains(method);
254        }
255    
256        /**
257         * Adds an item to the collection, using the adder method if it has one.
258         * @param substance object on which to add
259         * @param value object to add to the collection
260         */
261        public final void addThroughAdder(Object substance, Object value) {
262            logger.debug("addThroughAdder "+substance+"."+getName()+","+value);
263            MethodItem adder = getAdder();
264            if (adder!=null) {
265                try {
266                    //Log.trace("rtti.field",this+": invoking "+adders[i]);
267                    adder.invoke(substance,new Object[] { value });
268                    return;
269                } catch (WrappedThrowableException e) {
270                    Throwable target = e.getWrappedThrowable();
271                    if (target instanceof InvocationTargetException)
272                        throw e;
273                    else
274                        logger.error("addThroughAdder "+substance+"."+getName()+","+value,e);
275                } 
276            }
277    
278            logger.warn("No adder for collection " + this);
279            add(substance,value,null);
280        }
281    
282    
283        public final void putThroughAdder(Object substance, Object value, Object key) {
284            logger.debug("putThroughAdder "+substance+"."+getName()+","+key+"->"+value);
285            MethodItem adder = getAdder();
286            if (adder!=null) {
287                try {
288                    //Log.trace("rtti.field",this+": invoking "+adders[i]);
289                    if (adder.getParameterCount()==2)
290                        adder.invoke(substance,new Object[] { key, value });
291                    else if (adder.getParameterCount()==1)
292                        adder.invoke(substance,new Object[] { value });               
293                    else
294                        throw new RuntimeException("putThroughAdder("+substance+","+value+","+key+
295                                                   ") :Wrong number off parameters for adder "+adder);
296                    return;
297                } catch (WrappedThrowableException e) {
298                    Throwable target = e.getWrappedThrowable();
299                    if (target instanceof InvocationTargetException)
300                        throw e;
301                    else
302                        logger.error("putThroughAdder "+substance+"."+getName()+","+value,e);
303                } 
304            }
305    
306            logger.warn("No adder for collection " + this);
307            add(substance,value,key);
308        }
309    
310        MethodItem[] removingMethods = MethodItem.emptyArray;
311       
312        /**
313         * Gets the methods that access this collection for removing.<p>
314         *
315         * @return value of removingMethods.
316         */
317    
318        public MethodItem[] getRemovingMethods() {
319            ((ClassItem)parent).buildFieldInfo();
320            return removingMethods;
321        }
322       
323        /**
324         * Returns true if the collection has at least one removing method.
325         * @see #getRemovingMethods()
326         */
327        public boolean hasRemover() {
328            ((ClassItem)parent).buildFieldInfo();
329            return removingMethods.length>0;
330        }
331    
332        MethodItem remover;
333        /**
334         * Returns <em>the</em> remover of the collection, or null if it has
335         * no remover.  
336         */
337        public MethodItem getRemover() {
338            ((ClassItem)parent).buildFieldInfo();
339            if (remover!=null) 
340                return remover;
341            else if (removingMethods.length>0)
342                return removingMethods[0];
343            else if (isExpression)
344                return ((CollectionItem)getPathTop()).getRemover();
345            else
346                return null;
347        }
348    
349        /**
350         * Sets <em>the</em> remover of the collection.  
351         * @param remover the remover
352         */
353        public void setRemover(MethodItem remover) {
354            this.remover = remover;
355        }
356    
357        /**
358         * Sets the methods that access this collection for removing.<p>
359         *
360         * @param removingMethods value to assign to removingMethods.
361         */
362    
363        public void setRemovingMethods( MethodItem[] removingMethods ) {
364            this.removingMethods = removingMethods;
365        }
366    
367        /**
368         * Adds a new removing method for this field.<p>
369         *
370         * @param removingMethod the method to add
371         */
372    
373        public void addRemovingMethod( MethodItem removingMethod ) {
374            if (removingMethods == null) {
375                removingMethods = new MethodItem[] { removingMethod };
376            } else {
377                MethodItem[] tmp = new MethodItem[removingMethods.length + 1];
378                System.arraycopy(removingMethods, 0, tmp, 0, removingMethods.length);
379                tmp[removingMethods.length] = removingMethod;
380                removingMethods = tmp;
381            }
382        }
383    
384        public boolean isRemovingMethod(MethodItem method) {
385            ((ClassItem)parent).buildFieldInfo();
386            return Arrays.asList(removingMethods).contains(method);
387        }
388    
389        /**
390         * Clears all the methods that has been set to be removers or
391         * adders for this collection item.
392         *
393         * @see #addAddingMethod(MethodItem)
394         * @see #setAddingMethods(MethodItem[])
395         * @see MethodItem#removeAddedCollection(CollectionItem)
396         * @see #addRemovingMethod(MethodItem)
397         * @see #setRemovingMethods(MethodItem[])
398         * @see MethodItem#removeRemovedCollection(CollectionItem) */
399    
400        public void clearMethods() {
401            super.clearMethods();
402            if (removingMethods != null) {
403                for (int i=0; i<removingMethods.length; i++) {
404                    removingMethods[i].removeRemovedCollection(this);
405                }
406                removingMethods = null;
407            }
408            if (addingMethods != null) {
409                for (int i=0; i<addingMethods.length; i++) {
410                    addingMethods[i].removeAddedCollection(this);
411                }
412                addingMethods = null;
413            }
414        }
415    
416        /**
417         * Clears the collection on the given object.<p>
418         *
419         * For maps and collections, it delegates to the <code>clear</code>
420         * method. For array, it resets the adding index so that added
421         * elements will crush existing ones.<p>
422         *
423         * @param substance the object where to clean the collection
424         */
425    
426        public void clear(Object substance) {
427            logger.debug(this+".clear("+substance+")");
428            Field f = (Field) delegate;
429    
430            try {
431                Object collection = f.get(substance);
432                logger.debug("collection="+System.identityHashCode(collection));
433                if (collection!=null) {
434                    logger.debug("type="+collection.getClass().getName());
435                    if (collection instanceof Collection) { 
436                        ((Collection)collection).clear();
437                    } else if (collection instanceof Map) { 
438                        ((Map)collection).clear();
439                    } else if (getType().isArray()) {
440                        f.set(substance, Array.newInstance(getType().getComponentType(),0));
441                    }
442                }
443            } catch (Exception e) { 
444                logger.error("clear failed for "+this+" on "+substance,e);
445            }
446        }
447    
448        /**
449         * Tells if this collection is actually an array.<p>
450         *
451         * @return true if an array
452         */
453    
454        public boolean isArray() {
455            return getType()!=null && getType().isArray();
456        }
457    
458        /**
459         * Gets the collection object represented by this collection
460         * item.
461         *
462         * <p>It returns a <code>java.util.Collection</code> representation of
463         * the actual collection (i.e.  either a <code>Collection</code>, a
464         * <code>Map</code>, or an array).
465         *
466         * <p>The programmer should rather use the methods that gets the
467         * actual collection by using the accessor for this collection if
468         * any since it allows possible aspects applications if needed.
469         *
470         * @return the actual object collection representation
471         * @see #getActualCollectionThroughAccessor(Object)
472         * @see #toCollection(Object) */
473    
474        public Collection getActualCollection(Object substance) {
475            Object value = null;
476    
477            try {
478                value = ((Field)delegate).get(substance);
479            } catch (Exception e) {
480                logger.error("getActualCollection("+getName()+") failed",e);
481                return null;
482            }
483            return toCollection(value);
484        }
485    
486        /**
487         * Gets the collection object represented by this collection item
488         * by using the accessor defined in the class containing this
489         * collection if any.
490         *
491         * <p>Use this method to be sure that all the aspects will be
492         * applied to the substance when the collection is retrieved.
493         *
494         * <p>It returns a <code>java.util.Collection</code> representation of
495         * the actual collection (i.e.  either a <code>Collection</code>, a
496         * <code>Map</code>, or an array).
497         *
498         * @return the actual object collection representation
499         * @see #getActualCollection(Object)
500         * @see #toCollection(Object) 
501         */
502        public Collection getActualCollectionThroughAccessor(Object substance) {
503            Object value = getThroughAccessor(substance);
504            return value!=null ? toCollection(value) : new ArrayList();
505        }
506    
507        /**
508         * A useful method to convert any kind of collection to a
509         * <code>java.util.Collection</code> compatible instance.
510         *
511         * <p>Supported converted value types are
512         * <code>java.util.Collection</code>, <code>java.util.Map</code>,
513         * and Java arrays.
514         *
515         * @param value an object of one of the supported types
516         * @return the collection compliant converted value */
517    
518        public Collection toCollection(Object value) {
519            if (value == null)  return null;
520    
521            if (value instanceof Collection) { 
522                return (Collection)value;
523            } else if (value instanceof Map) { 
524                if (isIndex()) 
525                    return ((Map)value).values();
526                else
527                    return ((Map)value).entrySet();
528            } else if (value.getClass().isArray()) {
529                if (value.getClass().getComponentType()==Object.class) {
530                    return Arrays.asList( (Object[]) value );
531                } else {
532                    int length = Array.getLength(value);
533                    Vector result = new Vector(length);
534                    for (int i=0; i<length; i++) {
535                        result.add(Array.get(value,i));
536                    }
537                    return result;
538                }
539            }
540            return null;
541        }
542    
543        /**
544         * Adds an item to the collection.<p>
545         *
546         * The <code>extraInfos</code> param must represent a key if the
547         * collection is an hastable. If the collection is a
548         * <code>java.util</code> collection or if it is an array, the
549         * extra informations can be null (in this case, the new item is
550         * added at the end of the collection), and they can be not
551         * null. In this case, it must be an <code>Integer</code> instance
552         * that represents the index of the new item within the
553         * collection (must be a list in this case).<p>
554         *
555         * @param substance the collection where to add the new item
556         * @param newItem the item to add
557         * @param extraInfos this optional parameter must be used when the
558         * collection need some other information when adding a item to it
559         * as, for instance the key for a map or the index for a list 
560         */
561        public void add(Object substance, Object newItem, Object extraInfos) {
562    
563            Field f = (Field) delegate;
564    
565            try {
566                if (isSet() || isList()) { 
567                    // Set and List
568                    if ( extraInfos == null ) {
569                        ((Collection)f.get(substance)).add(newItem);
570                    } else {
571                        ((List)f.get(substance)).add( 
572                            ((Integer)extraInfos).intValue(), newItem);
573                    }
574                } else if (isMap()) { 
575                    // Map
576                    ((Map)f.get(substance)).put(extraInfos, newItem);
577                } else if (isArray()) {
578                    // Arrays
579                    // THIS IS BUGGED !!!
580                    if (extraInfos == null) {
581                        Object oldArray = f.get(substance);
582                        int index = 0;
583                        if (oldArray != null) {
584                            index = Array.getLength(oldArray);
585                        }
586                        Object newArray;
587                        f.set( substance, 
588                               newArray = Array.newInstance( 
589                                   getType().getComponentType(), index+1 ) );
590    
591                        if (oldArray != null) {
592                            for(int i=0; i<index; i++) {
593                                Array.set(newArray, i, Array.get(oldArray, i));
594                            }
595                        }
596                   
597                        Array.set(newArray, index, newItem);
598    
599                    } else {
600                        ((Object[])f.get(substance))[((Integer)extraInfos).intValue()] = newItem;
601                        //arrayAddingIndex = ((Integer)extraInfos).intValue() + 1;
602                    }
603                }
604            } catch ( Exception e ) { 
605                logger.error("add "+substance+"."+getName()+" "+newItem+","+extraInfos,e);
606            }
607        }
608    
609        /**
610         * Removes an item from the collection, using the remover method if it has one.
611         * @param substance object on which to remove
612         * @param item object to remove from the collection
613         */
614        public void removeThroughRemover(Object substance, Object item) {
615            ((ClassItem)parent).buildFieldInfo();
616            MethodItem remover = getRemover();
617            if (remover!=null) {
618                remover.invoke(substance,new Object[] {item});
619            } else {
620                logger.warn("No remover for collection "+this);
621                remove(substance,item,null);
622            }
623        }
624    
625        /**
626         * Removes an item from the collection.<p>
627         *
628         * The <code>extraInfos</code> param must represent a key if the
629         * collection is an hastable. If the collection is a
630         * <code>java.util</code> collection or if it is an array, the
631         * extra informations can be null (in this case, the new item is
632         * added at the end of the collection), and they can be not
633         * null. In this case, it must be an <code>Integer</code> instance
634         * that represents the index of the new item within the
635         * collection (must be a list in this case).<p>
636         *
637         * @param substance the collection where to remove the new item
638         * @param item the item to remove (can be null if extra infos are
639         * not null)
640         * @param extraInfos this optional parameter must be used when the
641         * collection need some other information when adding a item to it
642         * as, for instance the key for a map, or the index in a list */
643    
644        public void remove(Object substance, Object item, Object extraInfos) {
645            Field f = (Field)delegate;
646    
647            try {      
648          
649                if (Collection.class.isAssignableFrom(getType())) { 
650                    // Set and List
651                    if (extraInfos == null) {
652                        ((Collection)f.get(substance)).remove(item);
653                    } else {
654                        int index = ((Integer)extraInfos).intValue();
655                        ((List)f.get(substance)).remove(index);
656                    }
657                } else if (Map.class.isAssignableFrom(getType())) {
658                    // Map
659                    ((Map)f.get(substance)).remove(extraInfos);
660                } else if (getType().isArray()) {
661                    // Arrays
662                    // THIS IS BUGGED !!!
663                    if (extraInfos == null) {
664                        Object oldArray = f.get(substance);
665                        int index = 0;
666                        if (oldArray != null) {
667                            index = Array.getLength(oldArray);
668                        }
669                        Object newArray;
670                        f.set( substance, 
671                               newArray = Array.newInstance( 
672                                   getType().getComponentType(), index-1 ) );
673    
674                        if (oldArray != null) {
675                            for( int i=0; i<index; i++) {
676                                Array.set(newArray, i, Array.get(oldArray,i));
677                            }
678                        }
679                   
680                        //Array.set( newArray, index, newItem );
681    
682                    } else {
683                        //((Object[])f.get( substance ))[((Integer)extraInfos).intValue()] = item;
684                        //arrayAddingIndex = ((Integer)extraInfos).intValue() + 1;
685                    }
686                }  
687            } catch (Exception e) { 
688                logger.error("remove "+substance+"."+getName()+" "+item+","+extraInfos,e);
689            }
690          
691        }
692    
693        /**
694         * Tells wether an object's collection contains an given object
695         * @param substance
696         * @param object the object to search
697         * @return true if substance.collection.contains(object)
698         */
699        public boolean contains(Object substance, Object object) {
700            if (!isArray()) {
701                Collection collection = (Collection)getThroughAccessor(substance);
702                return collection.contains(object);
703            } else {
704                throw new RuntimeException(
705                    "CollectionItem.contains(substance,object) is not implemented for arrays");
706            }
707        }
708    
709        public Object getMap(Object substance, Object key) {
710            if (!isMap()) {
711                throw new RuntimeException("Cannot call getMap() on "+this);
712            }
713            try {
714                return getMap(substance).get(key);
715            } catch(IllegalAccessException e) {
716                logger.error("getMap "+substance+"."+getName()+" "+key,e); 
717            }
718            return null;
719        }
720    
721        protected Map getMap(Object substance) throws IllegalAccessException {
722            return (Map)((Field)delegate).get(substance);
723        }
724    
725        /**
726         * Tells if this collection item is compliant with a
727         * <code>java.util.List</code> interface.
728         *
729         * @return true if compliant */
730    
731        public boolean isList() {
732            return List.class.isAssignableFrom(getType());
733        }
734       
735        /**
736         * Tells if this collection item is compliant with a
737         * <code>java.util.Map</code> interface.
738         *
739         * @return true if compliant */
740    
741        public boolean isMap() {
742            return Map.class.isAssignableFrom(getType());
743        }
744       
745        public boolean isIndex() {
746            return RttiAC.isIndex(this);
747        }
748    
749        /**
750         * Tells if this collection item is compliant with a
751         * <code>java.util.Set</code> interface.
752         *
753         * @return true if compliant */
754    
755        public boolean isSet() {
756            return java.util.Set.class.isAssignableFrom(getType());
757        }
758       
759        /**
760         * Always returns false for collections (maybe not very
761         * semantically clear).
762         *
763         * @return false */
764    
765        public boolean isPrimitive() {
766            return false;
767        }
768    
769        /**
770         * Always returns false for collections (maybe not very
771         * semantically clear).
772         *
773         * @return false */
774    
775        public boolean isReference() {
776            return false;
777        }
778    
779        public FieldItem clone(ClassItem parent) {
780            CollectionItem clone = null;
781            try {
782                if (isCalculated)
783                    clone = new CollectionItem(name,getter,parent);
784                else 
785                    clone = new CollectionItem((Field)delegate,parent);
786                clone.setAdder(adder);
787                clone.setRemover(remover);
788            } catch(Exception e) {
789                logger.error("Failed to clone collection "+this);
790            }
791            return clone;      
792        }
793    
794        public static final CollectionItem[] emptyArray = new CollectionItem[0];
795    }