001    /*
002     $Id: ObjectRange.java,v 1.14 2005/01/13 13:09:17 blackdrag 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 org.codehaus.groovy.runtime.InvokerHelper;
049    import org.codehaus.groovy.runtime.IteratorClosureAdapter;
050    
051    import java.util.AbstractList;
052    import java.util.Iterator;
053    import java.util.List;
054    
055    /**
056     * Represents an inclusive list of objects from a value to a value using
057     * comparators
058     *
059     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
060     * @version $Revision: 1.14 $
061     */
062    public class ObjectRange extends AbstractList implements Range {
063    
064        private Comparable from;
065        private Comparable to;
066        private int size = -1;
067        private final boolean reverse;
068    
069        public ObjectRange(Comparable from, Comparable to) {
070            this.reverse = InvokerHelper.compareGreaterThan(from, to);
071            if (this.reverse) {
072                constructorHelper(to, from);
073            } else {
074                constructorHelper(from, to);
075            }
076        }
077    
078        public ObjectRange(Comparable from, Comparable to, boolean reverse) {
079            constructorHelper(from, to);
080    
081            this.reverse = reverse;
082        }
083    
084        private void constructorHelper(Comparable from, Comparable to) {
085            if (from == null) {
086                throw new IllegalArgumentException("Must specify a non-null value for the 'from' index in a Range");
087            }
088            if (to == null) {
089                throw new IllegalArgumentException("Must specify a non-null value for the 'to' index in a Range");
090            }
091            if (from.getClass() == to.getClass()) {
092                this.from = from;
093                this.to = to;
094            } else {
095                this.from = normaliseType(from);
096                this.to = normaliseType(to);
097            }
098        }
099    
100        public int hashCode() {
101            /** @todo should code this the Josh Bloch way */
102            return from.hashCode() ^ to.hashCode() + (reverse ? 1 : 0);
103        }
104    
105        public boolean equals(Object that) {
106            if (that instanceof ObjectRange) {
107                return equals((ObjectRange) that);
108            } else if (that instanceof List) {
109                return equals((List) that);
110            }
111            return false;
112        }
113    
114        public boolean equals(ObjectRange that) {
115            return this.reverse == that.reverse
116                    && InvokerHelper.compareEqual(this.from, that.from)
117                    && InvokerHelper.compareEqual(this.to, that.to);
118        }
119    
120        public boolean equals(List that) {
121            int size = size();
122            if (that.size() == size) {
123                for (int i = 0; i < size; i++) {
124                    if (!InvokerHelper.compareEqual(get(i), that.get(i))) {
125                        return false;
126                    }
127                }
128                return true;
129            }
130            return false;
131        }
132    
133        public Comparable getFrom() {
134            return from;
135        }
136    
137        public Comparable getTo() {
138            return to;
139        }
140    
141        public boolean isReverse() {
142            return reverse;
143        }
144    
145        public Object get(int index) {
146            if (index < 0) {
147                throw new IndexOutOfBoundsException("Index: " + index + " should not be negative");
148            }
149            if (index >= size()) {
150                throw new IndexOutOfBoundsException("Index: " + index + " is too big for range: " + this);
151            }
152            Object value = null;
153            if (reverse) {
154                value = to;
155    
156                for (int i = 0; i < index; i++) {
157                    value = decrement(value);
158                }
159            } else {
160                value = from;
161                for (int i = 0; i < index; i++) {
162                    value = increment(value);
163                }
164            }
165            return value;
166        }
167    
168        public Iterator iterator() {
169            return new Iterator() {
170                int index = 0;
171                Object value = (reverse) ? to : from;
172    
173                public boolean hasNext() {
174                    return index < size();
175                }
176    
177                public Object next() {
178                    if (index++ > 0) {
179                        if (index > size()) {
180                            value = null;
181                        } else {
182                            if (reverse) {
183                                value = decrement(value);
184                            } else {
185                                value = increment(value);
186                            }
187                        }
188                    }
189                    return value;
190                }
191    
192                public void remove() {
193                    ObjectRange.this.remove(index);
194                }
195            };
196        }
197    
198        public int size() {
199            if (size == -1) {
200                // lets lazily calculate the size
201                size = 0;
202                Object value = from;
203                while (to.compareTo(value) >= 0) {
204                    value = increment(value);
205                    size++;
206                }
207            }
208            return size;
209        }
210    
211        public List subList(int fromIndex, int toIndex) {
212            if (fromIndex < 0) {
213                throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
214            }
215            int size = size();
216            if (toIndex > size) {
217                throw new IndexOutOfBoundsException("toIndex = " + toIndex);
218            }
219            if (fromIndex > toIndex) {
220                throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
221            }
222            if (--toIndex >= size) {
223                return new ObjectRange((Comparable) get(fromIndex), getTo(), reverse);
224            } else {
225                return new ObjectRange((Comparable) get(fromIndex), (Comparable) get(toIndex), reverse);
226            }
227        }
228    
229        public String toString() {
230            return (reverse) ? "" + to + ".." + from : "" + from + ".." + to;
231        }
232    
233        public String inspect() {
234            String toText = InvokerHelper.inspect(to);
235            String fromText = InvokerHelper.inspect(from);
236            return (reverse) ? "" + toText + ".." + fromText : "" + fromText + ".." + toText;
237        }
238    
239        public boolean contains(Comparable value) {
240            int result = from.compareTo(value);
241            if (result == 0) {
242                return true;
243            }
244            return result < 0 && to.compareTo(value) >= 0;
245        }
246    
247        public void step(int step, Closure closure) {
248            if (reverse) {
249                step = -step;
250            }
251            if (step >= 0) {
252                Comparable value = from;
253                while (value.compareTo(to) <= 0) {
254                    closure.call(value);
255                    for (int i = 0; i < step; i++) {
256                        value = (Comparable) increment(value);
257                    }
258                }
259            } else {
260                step = -step;
261                Comparable value = to;
262                while (value.compareTo(from) >= 0) {
263                    closure.call(value);
264                    for (int i = 0; i < step; i++) {
265                        value = (Comparable) decrement(value);
266                    }
267                }
268            }
269        }
270    
271        public List step(int step) {
272            IteratorClosureAdapter adapter = new IteratorClosureAdapter(this);
273            step(step, adapter);
274            return adapter.asList();
275        }
276    
277        protected Object increment(Object value) {
278            return InvokerHelper.invokeMethod(value, "next", null);
279        }
280    
281        protected Object decrement(Object value) {
282            return InvokerHelper.invokeMethod(value, "previous", null);
283        }
284    
285        private static Comparable normaliseType(final Comparable operand) {
286            if (operand instanceof Character) {
287                return new Integer(((Character) operand).charValue());
288            } else if (operand instanceof String) {
289                final String string = (String) operand;
290    
291                if (string.length() == 1)
292                    return new Integer(string.charAt(0));
293                else
294                    return string;
295            } else {
296                return operand;
297            }
298        }
299    }