View Javadoc

1   /*
2    $Id: ObjectRange.java,v 1.16 2005/08/26 09:13:19 blackdrag 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.16 $
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         if (from instanceof String || to instanceof String) {
101             // this test depends deeply on the String.next implementation
102             // 009.next is 00:, not 010 
103             String start = from.toString();
104             String end = to.toString();
105             if (start.length()>end.length()){
106                 throw new IllegalArgumentException("Incompatible Strings for Range: starting String is longer than ending string");
107             }
108             int length = Math.min(start.length(),end.length());
109             int i = 0;
110             for (i=0; i<length; i++) {
111                 if (start.charAt(i) != end.charAt(i)) break;
112             }
113             if (i<length-1) {
114                 throw new IllegalArgumentException("Incompatible Strings for Range: String#next() will not reach the expected value");
115             }
116             
117         }
118     }
119 
120     public int hashCode() {
121         /*** @todo should code this the Josh Bloch way */
122         return from.hashCode() ^ to.hashCode() + (reverse ? 1 : 0);
123     }
124 
125     public boolean equals(Object that) {
126         if (that instanceof ObjectRange) {
127             return equals((ObjectRange) that);
128         } else if (that instanceof List) {
129             return equals((List) that);
130         }
131         return false;
132     }
133 
134     public boolean equals(ObjectRange that) {
135         return this.reverse == that.reverse
136                 && InvokerHelper.compareEqual(this.from, that.from)
137                 && InvokerHelper.compareEqual(this.to, that.to);
138     }
139 
140     public boolean equals(List that) {
141         int size = size();
142         if (that.size() == size) {
143             for (int i = 0; i < size; i++) {
144                 if (!InvokerHelper.compareEqual(get(i), that.get(i))) {
145                     return false;
146                 }
147             }
148             return true;
149         }
150         return false;
151     }
152 
153     public Comparable getFrom() {
154         return from;
155     }
156 
157     public Comparable getTo() {
158         return to;
159     }
160 
161     public boolean isReverse() {
162         return reverse;
163     }
164 
165     public Object get(int index) {
166         if (index < 0) {
167             throw new IndexOutOfBoundsException("Index: " + index + " should not be negative");
168         }
169         if (index >= size()) {
170             throw new IndexOutOfBoundsException("Index: " + index + " is too big for range: " + this);
171         }
172         Object value = null;
173         if (reverse) {
174             value = to;
175 
176             for (int i = 0; i < index; i++) {
177                 value = decrement(value);
178             }
179         } else {
180             value = from;
181             for (int i = 0; i < index; i++) {
182                 value = increment(value);
183             }
184         }
185         return value;
186     }
187 
188     public Iterator iterator() {
189         return new Iterator() {
190             int index = 0;
191             Object value = (reverse) ? to : from;
192 
193             public boolean hasNext() {
194                 return index < size();
195             }
196 
197             public Object next() {
198                 if (index++ > 0) {
199                     if (index > size()) {
200                         value = null;
201                     } else {
202                         if (reverse) {
203                             value = decrement(value);
204                         } else {
205                             value = increment(value);
206                         }
207                     }
208                 }
209                 return value;
210             }
211 
212             public void remove() {
213                 ObjectRange.this.remove(index);
214             }
215         };
216     }
217 
218     public int size() {
219         if (size == -1) {
220             if (from instanceof Integer && to instanceof Integer) {
221                 // lets fast calculate the size
222                 size = 0;
223                 int fromNum = ((Integer) from).intValue();
224                 int toNum = ((Integer) to).intValue();
225                 size = toNum - fromNum + 1;
226             }
227             else if (from instanceof BigDecimal || to instanceof BigDecimal) {
228                 // lets fast calculate the size
229                 size = 0;
230                 BigDecimal fromNum = new BigDecimal("" + from);
231                 BigDecimal toNum = new BigDecimal("" + to);
232                 BigInteger sizeNum = toNum.subtract(fromNum).add(new BigDecimal(1.0)).toBigInteger();
233                 size = sizeNum.intValue();
234             }
235             else {
236                 // lets lazily calculate the size
237                 size = 0;
238                 Object value = from;
239                 while (to.compareTo(value) >= 0) {
240                     value = increment(value);
241                     size++;
242                 }
243             }
244         }
245         return size;
246     }
247 
248     public List subList(int fromIndex, int toIndex) {
249         if (fromIndex < 0) {
250             throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
251         }
252         int size = size();
253         if (toIndex > size) {
254             throw new IndexOutOfBoundsException("toIndex = " + toIndex);
255         }
256         if (fromIndex > toIndex) {
257             throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
258         }
259         if (--toIndex >= size) {
260             return new ObjectRange((Comparable) get(fromIndex), getTo(), reverse);
261         } else {
262             return new ObjectRange((Comparable) get(fromIndex), (Comparable) get(toIndex), reverse);
263         }
264     }
265 
266     public String toString() {
267         return (reverse) ? "" + to + ".." + from : "" + from + ".." + to;
268     }
269 
270     public String inspect() {
271         String toText = InvokerHelper.inspect(to);
272         String fromText = InvokerHelper.inspect(from);
273         return (reverse) ? "" + toText + ".." + fromText : "" + fromText + ".." + toText;
274     }
275 
276     public boolean contains(Comparable value) {
277         if (from instanceof BigDecimal || to instanceof BigDecimal) {
278             int result = (new BigDecimal("" + from)).compareTo(new BigDecimal("" + value));
279             if (result == 0) {
280                 return true;
281             }
282             return result < 0 && (new BigDecimal("" + to)).compareTo(new BigDecimal("" + value)) >= 0;
283         }
284         else {
285             int result = from.compareTo(value);
286             if (result == 0) {
287                 return true;
288             }
289             return result < 0 && to.compareTo(value) >= 0;
290         }
291     }
292 
293     public void step(int step, Closure closure) {
294         if (reverse) {
295             step = -step;
296         }
297         if (step >= 0) {
298             Comparable value = from;
299             while (value.compareTo(to) <= 0) {
300                 closure.call(value);
301                 for (int i = 0; i < step; i++) {
302                     value = (Comparable) increment(value);
303                 }
304             }
305         } else {
306             step = -step;
307             Comparable value = to;
308             while (value.compareTo(from) >= 0) {
309                 closure.call(value);
310                 for (int i = 0; i < step; i++) {
311                     value = (Comparable) decrement(value);
312                 }
313             }
314         }
315     }
316 
317     public List step(int step) {
318         IteratorClosureAdapter adapter = new IteratorClosureAdapter(this);
319         step(step, adapter);
320         return adapter.asList();
321     }
322 
323     protected Object increment(Object value) {
324         return InvokerHelper.invokeMethod(value, "next", null);
325     }
326 
327     protected Object decrement(Object value) {
328         return InvokerHelper.invokeMethod(value, "previous", null);
329     }
330 
331     private static Comparable normaliseType(final Comparable operand) {
332         if (operand instanceof Character) {
333             return new Integer(((Character) operand).charValue());
334         } else if (operand instanceof String) {
335             final String string = (String) operand;
336 
337             if (string.length() == 1)
338                 return new Integer(string.charAt(0));
339             else
340                 return string;
341         } else {
342             return operand;
343         }
344     }
345 }