001 /*
002 $Id: ObjectRange.java 4290 2006-12-01 20:28:08Z paulk $
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 import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
051 import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
052
053 import java.util.AbstractList;
054 import java.util.Iterator;
055 import java.util.List;
056 import java.math.BigDecimal;
057 import java.math.BigInteger;
058
059 /**
060 * Represents an inclusive list of objects from a value to a value using
061 * comparators
062 *
063 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
064 * @version $Revision: 4290 $
065 */
066 public class ObjectRange extends AbstractList implements Range {
067
068 private Comparable from;
069 private Comparable to;
070 private int size;
071 private final boolean reverse;
072
073 public ObjectRange(Comparable from, Comparable to) {
074 this.size = -1;
075 this.reverse = ScriptBytecodeAdapter.compareGreaterThan(from, to);
076 if (this.reverse) {
077 constructorHelper(to, from);
078 } else {
079 constructorHelper(from, to);
080 }
081 }
082
083 public ObjectRange(Comparable from, Comparable to, boolean reverse) {
084 this.size = -1;
085 constructorHelper(from, to);
086
087 this.reverse = reverse;
088 }
089
090 private void constructorHelper(Comparable from, Comparable to) {
091 if (from == null) {
092 throw new IllegalArgumentException("Must specify a non-null value for the 'from' index in a Range");
093 }
094 if (to == null) {
095 throw new IllegalArgumentException("Must specify a non-null value for the 'to' index in a Range");
096 }
097 if (from.getClass() == to.getClass()) {
098 this.from = from;
099 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 }