View Javadoc

1   /*
2    $Id: ObjectRange.java 4290 2006-12-01 20:28:08Z paulk $
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  import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
51  import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
52  
53  import java.util.AbstractList;
54  import java.util.Iterator;
55  import java.util.List;
56  import java.math.BigDecimal;
57  import java.math.BigInteger;
58  
59  /***
60   * Represents an inclusive list of objects from a value to a value using
61   * comparators
62   *
63   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
64   * @version $Revision: 4290 $
65   */
66  public class ObjectRange extends AbstractList implements Range {
67  
68      private Comparable from;
69      private Comparable to;
70      private int size;
71      private final boolean reverse;
72  
73      public ObjectRange(Comparable from, Comparable to) {
74          this.size = -1;
75          this.reverse = ScriptBytecodeAdapter.compareGreaterThan(from, to);
76          if (this.reverse) {
77              constructorHelper(to, from);
78          } else {
79              constructorHelper(from, to);
80          }
81      }
82  
83      public ObjectRange(Comparable from, Comparable to, boolean reverse) {
84          this.size = -1;
85          constructorHelper(from, to);
86  
87          this.reverse = reverse;
88      }
89  
90      private void constructorHelper(Comparable from, Comparable to) {
91          if (from == null) {
92              throw new IllegalArgumentException("Must specify a non-null value for the 'from' index in a Range");
93          }
94          if (to == null) {
95              throw new IllegalArgumentException("Must specify a non-null value for the 'to' index in a Range");
96          }
97          if (from.getClass() == to.getClass()) {
98              this.from = from;
99              this.to = to;
100         } else {
101             this.from = normaliseType(from);
102             this.to = normaliseType(to);
103         }
104         if (from instanceof String || to instanceof String) {
105             // this test depends deeply on the String.next implementation
106             // 009.next is 00:, not 010 
107             String start = from.toString();
108             String end = to.toString();
109             if (start.length() > end.length()) {
110                 throw new IllegalArgumentException("Incompatible Strings for Range: starting String is longer than ending string");
111             }
112             int length = Math.min(start.length(), end.length());
113             int i = 0;
114             for (i = 0; i < length; i++) {
115                 if (start.charAt(i) != end.charAt(i)) break;
116             }
117             if (i < length - 1) {
118                 throw new IllegalArgumentException("Incompatible Strings for Range: String#next() will not reach the expected value");
119             }
120 
121         }
122     }
123 
124     public int hashCode() {
125         /*** @todo should code this the Josh Bloch way */
126         return from.hashCode() ^ to.hashCode() + (reverse ? 1 : 0);
127     }
128 
129     public boolean equals(Object that) {
130         if (that instanceof ObjectRange) {
131             return equals((ObjectRange) that);
132         } else if (that instanceof List) {
133             return equals((List) that);
134         }
135         return false;
136     }
137 
138     public boolean equals(ObjectRange that) {
139         return this.reverse == that.reverse
140                 && DefaultTypeTransformation.compareEqual(this.from, that.from)
141                 && DefaultTypeTransformation.compareEqual(this.to, that.to);
142     }
143 
144     public boolean equals(List that) {
145         int size = size();
146         if (that.size() == size) {
147             for (int i = 0; i < size; i++) {
148                 if (!DefaultTypeTransformation.compareEqual(get(i), that.get(i))) {
149                     return false;
150                 }
151             }
152             return true;
153         }
154         return false;
155     }
156 
157     public Comparable getFrom() {
158         return from;
159     }
160 
161     public Comparable getTo() {
162         return to;
163     }
164 
165     public boolean isReverse() {
166         return reverse;
167     }
168 
169     public Object get(int index) {
170         if (index < 0) {
171             throw new IndexOutOfBoundsException("Index: " + index + " should not be negative");
172         }
173         if (index >= size()) {
174             throw new IndexOutOfBoundsException("Index: " + index + " is too big for range: " + this);
175         }
176         Object value = null;
177         if (reverse) {
178             value = to;
179 
180             for (int i = 0; i < index; i++) {
181                 value = decrement(value);
182             }
183         } else {
184             value = from;
185             for (int i = 0; i < index; i++) {
186                 value = increment(value);
187             }
188         }
189         return value;
190     }
191 
192     public Iterator iterator() {
193         return new Iterator() {
194             int index = 0;
195             Object value = (reverse) ? to : from;
196 
197             public boolean hasNext() {
198                 return index < size();
199             }
200 
201             public Object next() {
202                 if (index++ > 0) {
203                     if (index > size()) {
204                         value = null;
205                     } else {
206                         if (reverse) {
207                             value = decrement(value);
208                         } else {
209                             value = increment(value);
210                         }
211                     }
212                 }
213                 return value;
214             }
215 
216             public void remove() {
217                 ObjectRange.this.remove(index);
218             }
219         };
220     }
221 
222     public int size() {
223         if (size == -1) {
224             if (from instanceof Integer && to instanceof Integer) {
225                 // lets fast calculate the size
226                 size = 0;
227                 int fromNum = ((Integer) from).intValue();
228                 int toNum = ((Integer) to).intValue();
229                 size = toNum - fromNum + 1;
230             } else if (from instanceof BigDecimal || to instanceof BigDecimal) {
231                 // lets fast calculate the size
232                 size = 0;
233                 BigDecimal fromNum = new BigDecimal("" + from);
234                 BigDecimal toNum = new BigDecimal("" + to);
235                 BigInteger sizeNum = toNum.subtract(fromNum).add(new BigDecimal(1.0)).toBigInteger();
236                 size = sizeNum.intValue();
237             } else {
238                 // lets lazily calculate the size
239                 size = 0;
240                 Object value = from;
241                 while (to.compareTo(value) >= 0) {
242                     value = increment(value);
243                     size++;
244                 }
245             }
246         }
247         return size;
248     }
249 
250     public List subList(int fromIndex, int toIndex) {
251         if (fromIndex < 0) {
252             throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
253         }
254         int size = size();
255         if (toIndex > size) {
256             throw new IndexOutOfBoundsException("toIndex = " + toIndex);
257         }
258         if (fromIndex > toIndex) {
259             throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
260         }
261         if (--toIndex >= size) {
262             return new ObjectRange((Comparable) get(fromIndex), getTo(), reverse);
263         } else {
264             return new ObjectRange((Comparable) get(fromIndex), (Comparable) get(toIndex), reverse);
265         }
266     }
267 
268     public String toString() {
269         return (reverse) ? "" + to + ".." + from : "" + from + ".." + to;
270     }
271 
272     public String inspect() {
273         String toText = InvokerHelper.inspect(to);
274         String fromText = InvokerHelper.inspect(from);
275         return (reverse) ? "" + toText + ".." + fromText : "" + fromText + ".." + toText;
276     }
277 
278     public boolean contains(Object value) {
279         if (value instanceof Comparable) {
280             return contains((Comparable) value);
281         } else {
282             return super.contains(value);
283         }
284     }
285 
286     public boolean contains(Comparable value) {
287         int result = from.compareTo(value);
288         return result == 0 || result < 0 && to.compareTo(value) >= 0;
289     }
290 
291     public void step(int step, Closure closure) {
292         if (reverse) {
293             step = -step;
294         }
295         if (step >= 0) {
296             Comparable value = from;
297             while (value.compareTo(to) <= 0) {
298                 closure.call(value);
299                 for (int i = 0; i < step; i++) {
300                     value = (Comparable) increment(value);
301                 }
302             }
303         } else {
304             step = -step;
305             Comparable value = to;
306             while (value.compareTo(from) >= 0) {
307                 closure.call(value);
308                 for (int i = 0; i < step; i++) {
309                     value = (Comparable) decrement(value);
310                 }
311             }
312         }
313     }
314 
315     public List step(int step) {
316         IteratorClosureAdapter adapter = new IteratorClosureAdapter(this);
317         step(step, adapter);
318         return adapter.asList();
319     }
320 
321     protected Object increment(Object value) {
322         return InvokerHelper.invokeMethod(value, "next", null);
323     }
324 
325     protected Object decrement(Object value) {
326         return InvokerHelper.invokeMethod(value, "previous", null);
327     }
328 
329     private static Comparable normaliseType(final Comparable operand) {
330         if (operand instanceof Character) {
331             return new Integer(((Character) operand).charValue());
332         } else if (operand instanceof String) {
333             final String string = (String) operand;
334 
335             if (string.length() == 1)
336                 return new Integer(string.charAt(0));
337             else
338                 return string;
339         } else {
340             return operand;
341         }
342     }
343 }