View Javadoc

1   /*
2    $Id: ObjectRange.java,v 1.15 2005/07/25 08:09:00 phk Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.lang;
47  
48  import org.codehaus.groovy.runtime.InvokerHelper;
49  import org.codehaus.groovy.runtime.IteratorClosureAdapter;
50  
51  import java.util.AbstractList;
52  import java.util.Iterator;
53  import java.util.List;
54  import java.math.BigDecimal;
55  import java.math.BigInteger;
56  
57  /***
58   * Represents an inclusive list of objects from a value to a value using
59   * comparators
60   *
61   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
62   * @version $Revision: 1.15 $
63   */
64  public class ObjectRange extends AbstractList implements Range {
65  
66      private Comparable from;
67      private Comparable to;
68      private int size = -1;
69      private final boolean reverse;
70  
71      public ObjectRange(Comparable from, Comparable to) {
72          this.reverse = InvokerHelper.compareGreaterThan(from, to);
73          if (this.reverse) {
74              constructorHelper(to, from);
75          } else {
76              constructorHelper(from, to);
77          }
78      }
79  
80      public ObjectRange(Comparable from, Comparable to, boolean reverse) {
81          constructorHelper(from, to);
82  
83          this.reverse = reverse;
84      }
85  
86      private void constructorHelper(Comparable from, Comparable to) {
87          if (from == null) {
88              throw new IllegalArgumentException("Must specify a non-null value for the 'from' index in a Range");
89          }
90          if (to == null) {
91              throw new IllegalArgumentException("Must specify a non-null value for the 'to' index in a Range");
92          }
93          if (from.getClass() == to.getClass()) {
94              this.from = from;
95              this.to = to;
96          } else {
97              this.from = normaliseType(from);
98              this.to = normaliseType(to);
99          }
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 }