001 /*
002 $Id: ObjectRange.java,v 1.15 2005/07/25 08:09:00 phk 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.15 $
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 }
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 }