001 /*
002 $Id: ObjectRange.java,v 1.16 2005/08/26 09:13:19 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 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.16 $
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 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 }