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