001    /*
002     $Id: Sequence.java,v 1.4 2005/04/12 16:38:42 jstrachan Exp $
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    
055    /**
056     * Represents a sequence of objects which represents zero or many instances of
057     * of objects of a given type. The type can be ommitted in which case any type of
058     * object can be added.
059     *
060     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
061     * @version $Revision: 1.4 $
062     */
063    public class Sequence extends ArrayList implements GroovyObject {
064    
065        private MetaClass metaClass = InvokerHelper.getMetaClass(this);
066        private Class type;
067        private int hashCode;
068    
069        public Sequence() {
070            this(null);
071        }
072    
073        public Sequence(Class type) {
074            this.type = type;
075        }
076    
077        public Sequence(Class type, List content) {
078            super(content.size());
079            this.type = type;
080            addAll(content);
081        }
082    
083        /**
084         * Sets the contents of this sequence to that
085         * of the given collection.
086         */
087        public void set(Collection collection) {
088            checkCollectionType(collection);
089            clear();
090            addAll(collection);
091        }
092        
093        public boolean equals(Object that) {
094            if (that instanceof Sequence) {
095                return equals((Sequence) that);
096            }
097            return false;
098        }
099    
100        public boolean equals(Sequence that) {
101            if (size() == that.size()) {
102                for (int i = 0; i < size(); i++) {
103                    if (!InvokerHelper.compareEqual(this.get(i), that.get(i))) {
104                        return false;
105                    }
106                }
107                return true;
108            }
109            return false;
110        }
111    
112        public int hashCode() {
113            if (hashCode == 0) {
114                for (int i = 0; i < size(); i++) {
115                    Object value = get(i);
116                    int hash = (value != null) ? value.hashCode() : 0xbabe;
117                    hashCode ^= hash;
118                }
119                if (hashCode == 0) {
120                    hashCode = 0xbabe;
121                }
122            }
123            return hashCode;
124        }
125    
126        public int minimumSize() {
127            return 0;
128        }
129    
130        /**
131         * @return the type of the elements in the sequence or null if there is no
132         * type constraint on this sequence
133         */
134        public Class type() {
135            return type;
136        }
137        
138        public void add(int index, Object element) {
139            checkType(element);
140            hashCode = 0;
141            super.add(index, element);
142        }
143    
144        public boolean add(Object element) {
145            checkType(element);
146            hashCode = 0;
147            return super.add(element);
148        }
149    
150        public boolean addAll(Collection c) {
151            checkCollectionType(c);
152            hashCode = 0;
153            return super.addAll(c);
154        }
155    
156        public boolean addAll(int index, Collection c) {
157            checkCollectionType(c);
158            hashCode = 0;
159            return super.addAll(index, c);
160        }
161    
162        public void clear() {
163            hashCode = 0;
164            super.clear();
165        }
166    
167        public Object remove(int index) {
168            hashCode = 0;
169            return super.remove(index);
170        }
171    
172        protected void removeRange(int fromIndex, int toIndex) {
173            hashCode = 0;
174            super.removeRange(fromIndex, toIndex);
175        }
176    
177        public Object set(int index, Object element) {
178            hashCode = 0;
179            return super.set(index, element);
180        }
181    
182        // GroovyObject interface
183        //-------------------------------------------------------------------------
184        public Object invokeMethod(String name, Object args) {
185            try {
186            return getMetaClass().invokeMethod(this, name, args);
187            }
188            catch (MissingMethodException e) {
189                // lets apply the method to each item in the collection
190                List answer = new ArrayList(size());
191                for (Iterator iter = iterator(); iter.hasNext(); ) {
192                    Object element = iter.next();
193                    Object value = InvokerHelper.invokeMethod(element, name, args);
194                    answer.add(value);
195                }
196                return answer;
197            }
198        }
199    
200        public Object getProperty(String property) {
201            return getMetaClass().getProperty(this, property);
202        }
203    
204        public void setProperty(String property, Object newValue) {
205            getMetaClass().setProperty(this, property, newValue);
206        }
207    
208        public MetaClass getMetaClass() {
209            return metaClass;
210        }
211    
212        public void setMetaClass(MetaClass metaClass) {
213            this.metaClass = metaClass;
214        }
215    
216        // Implementation methods
217        //-------------------------------------------------------------------------
218        
219        /**
220         * Checks that each member of the given collection are of the correct
221         * type
222         */
223        protected void checkCollectionType(Collection c) {
224            if (type != null) {
225                for (Iterator iter = c.iterator(); iter.hasNext(); ) {
226                    Object element = iter.next();
227                    checkType(element);
228                }
229            }
230        }
231    
232    
233        /** 
234         * Checks that the given object instance is of the correct type
235         * otherwise a runtime exception is thrown
236         */
237        protected void checkType(Object object) {
238            if (object == null) {
239                throw new NullPointerException("Sequences cannot contain null, use a List instead");
240            }
241            if (type != null) {
242                if (!type.isInstance(object)) {
243                    throw new IllegalArgumentException(
244                        "Invalid type of argument for sequence of type: "
245                            + type.getName()
246                            + " cannot add object: "
247                            + object);
248                }
249            }
250        }
251    }