001    /*
002     $Id: Sequence.java 4098 2006-10-10 16:09:48Z blackdrag $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package groovy.lang;
047    
048    import java.util.ArrayList;
049    import java.util.Collection;
050    import java.util.Iterator;
051    import java.util.List;
052    
053    import org.codehaus.groovy.runtime.InvokerHelper;
054    import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
055    
056    /**
057     * Represents a sequence of objects which represents zero or many instances of
058     * of objects of a given type. The type can be ommitted in which case any type of
059     * object can be added.
060     *
061     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
062     * @version $Revision: 4098 $
063     */
064    public class Sequence extends ArrayList implements GroovyObject {
065    
066        private MetaClass metaClass = InvokerHelper.getMetaClass(this);
067        private Class type;
068        private int hashCode;
069    
070        public Sequence() {
071            this(null);
072        }
073    
074        public Sequence(Class type) {
075            this.type = type;
076        }
077    
078        public Sequence(Class type, List content) {
079            super(content.size());
080            this.type = type;
081            addAll(content);
082        }
083    
084        /**
085         * Sets the contents of this sequence to that
086         * of the given collection.
087         */
088        public void set(Collection collection) {
089            checkCollectionType(collection);
090            clear();
091            addAll(collection);
092        }
093        
094        public boolean equals(Object that) {
095            if (that instanceof Sequence) {
096                return equals((Sequence) that);
097            }
098            return false;
099        }
100    
101        public boolean equals(Sequence that) {
102            if (size() == that.size()) {
103                for (int i = 0; i < size(); i++) {
104                    if (!DefaultTypeTransformation.compareEqual(this.get(i), that.get(i))) {
105                        return false;
106                    }
107                }
108                return true;
109            }
110            return false;
111        }
112    
113        public int hashCode() {
114            if (hashCode == 0) {
115                for (int i = 0; i < size(); i++) {
116                    Object value = get(i);
117                    int hash = (value != null) ? value.hashCode() : 0xbabe;
118                    hashCode ^= hash;
119                }
120                if (hashCode == 0) {
121                    hashCode = 0xbabe;
122                }
123            }
124            return hashCode;
125        }
126    
127        public int minimumSize() {
128            return 0;
129        }
130    
131        /**
132         * @return the type of the elements in the sequence or null if there is no
133         * type constraint on this sequence
134         */
135        public Class type() {
136            return type;
137        }
138        
139        public void add(int index, Object element) {
140            checkType(element);
141            hashCode = 0;
142            super.add(index, element);
143        }
144    
145        public boolean add(Object element) {
146            checkType(element);
147            hashCode = 0;
148            return super.add(element);
149        }
150    
151        public boolean addAll(Collection c) {
152            checkCollectionType(c);
153            hashCode = 0;
154            return super.addAll(c);
155        }
156    
157        public boolean addAll(int index, Collection c) {
158            checkCollectionType(c);
159            hashCode = 0;
160            return super.addAll(index, c);
161        }
162    
163        public void clear() {
164            hashCode = 0;
165            super.clear();
166        }
167    
168        public Object remove(int index) {
169            hashCode = 0;
170            return super.remove(index);
171        }
172    
173        protected void removeRange(int fromIndex, int toIndex) {
174            hashCode = 0;
175            super.removeRange(fromIndex, toIndex);
176        }
177    
178        public Object set(int index, Object element) {
179            hashCode = 0;
180            return super.set(index, element);
181        }
182    
183        // GroovyObject interface
184        //-------------------------------------------------------------------------
185        public Object invokeMethod(String name, Object args) {
186            try {
187            return getMetaClass().invokeMethod(this, name, args);
188            }
189            catch (MissingMethodException e) {
190                // lets apply the method to each item in the collection
191                List answer = new ArrayList(size());
192                for (Iterator iter = iterator(); iter.hasNext(); ) {
193                    Object element = iter.next();
194                    Object value = InvokerHelper.invokeMethod(element, name, args);
195                    answer.add(value);
196                }
197                return answer;
198            }
199        }
200    
201        public Object getProperty(String property) {
202            return getMetaClass().getProperty(this, property);
203        }
204    
205        public void setProperty(String property, Object newValue) {
206            getMetaClass().setProperty(this, property, newValue);
207        }
208    
209        public MetaClass getMetaClass() {
210            return metaClass;
211        }
212    
213        public void setMetaClass(MetaClass metaClass) {
214            this.metaClass = metaClass;
215        }
216    
217        // Implementation methods
218        //-------------------------------------------------------------------------
219        
220        /**
221         * Checks that each member of the given collection are of the correct
222         * type
223         */
224        protected void checkCollectionType(Collection c) {
225            if (type != null) {
226                for (Iterator iter = c.iterator(); iter.hasNext(); ) {
227                    Object element = iter.next();
228                    checkType(element);
229                }
230            }
231        }
232    
233    
234        /** 
235         * Checks that the given object instance is of the correct type
236         * otherwise a runtime exception is thrown
237         */
238        protected void checkType(Object object) {
239            if (object == null) {
240                throw new NullPointerException("Sequences cannot contain null, use a List instead");
241            }
242            if (type != null) {
243                if (!type.isInstance(object)) {
244                    throw new IllegalArgumentException(
245                        "Invalid type of argument for sequence of type: "
246                            + type.getName()
247                            + " cannot add object: "
248                            + object);
249                }
250            }
251        }
252    }