001 /*
002 $Id: ObjectRange.java,v 1.14 2005/01/13 13:09:17 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
055 /**
056 * Represents an inclusive list of objects from a value to a value using
057 * comparators
058 *
059 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
060 * @version $Revision: 1.14 $
061 */
062 public class ObjectRange extends AbstractList implements Range {
063
064 private Comparable from;
065 private Comparable to;
066 private int size = -1;
067 private final boolean reverse;
068
069 public ObjectRange(Comparable from, Comparable to) {
070 this.reverse = InvokerHelper.compareGreaterThan(from, to);
071 if (this.reverse) {
072 constructorHelper(to, from);
073 } else {
074 constructorHelper(from, to);
075 }
076 }
077
078 public ObjectRange(Comparable from, Comparable to, boolean reverse) {
079 constructorHelper(from, to);
080
081 this.reverse = reverse;
082 }
083
084 private void constructorHelper(Comparable from, Comparable to) {
085 if (from == null) {
086 throw new IllegalArgumentException("Must specify a non-null value for the 'from' index in a Range");
087 }
088 if (to == null) {
089 throw new IllegalArgumentException("Must specify a non-null value for the 'to' index in a Range");
090 }
091 if (from.getClass() == to.getClass()) {
092 this.from = from;
093 this.to = to;
094 } else {
095 this.from = normaliseType(from);
096 this.to = normaliseType(to);
097 }
098 }
099
100 public int hashCode() {
101 /** @todo should code this the Josh Bloch way */
102 return from.hashCode() ^ to.hashCode() + (reverse ? 1 : 0);
103 }
104
105 public boolean equals(Object that) {
106 if (that instanceof ObjectRange) {
107 return equals((ObjectRange) that);
108 } else if (that instanceof List) {
109 return equals((List) that);
110 }
111 return false;
112 }
113
114 public boolean equals(ObjectRange that) {
115 return this.reverse == that.reverse
116 && InvokerHelper.compareEqual(this.from, that.from)
117 && InvokerHelper.compareEqual(this.to, that.to);
118 }
119
120 public boolean equals(List that) {
121 int size = size();
122 if (that.size() == size) {
123 for (int i = 0; i < size; i++) {
124 if (!InvokerHelper.compareEqual(get(i), that.get(i))) {
125 return false;
126 }
127 }
128 return true;
129 }
130 return false;
131 }
132
133 public Comparable getFrom() {
134 return from;
135 }
136
137 public Comparable getTo() {
138 return to;
139 }
140
141 public boolean isReverse() {
142 return reverse;
143 }
144
145 public Object get(int index) {
146 if (index < 0) {
147 throw new IndexOutOfBoundsException("Index: " + index + " should not be negative");
148 }
149 if (index >= size()) {
150 throw new IndexOutOfBoundsException("Index: " + index + " is too big for range: " + this);
151 }
152 Object value = null;
153 if (reverse) {
154 value = to;
155
156 for (int i = 0; i < index; i++) {
157 value = decrement(value);
158 }
159 } else {
160 value = from;
161 for (int i = 0; i < index; i++) {
162 value = increment(value);
163 }
164 }
165 return value;
166 }
167
168 public Iterator iterator() {
169 return new Iterator() {
170 int index = 0;
171 Object value = (reverse) ? to : from;
172
173 public boolean hasNext() {
174 return index < size();
175 }
176
177 public Object next() {
178 if (index++ > 0) {
179 if (index > size()) {
180 value = null;
181 } else {
182 if (reverse) {
183 value = decrement(value);
184 } else {
185 value = increment(value);
186 }
187 }
188 }
189 return value;
190 }
191
192 public void remove() {
193 ObjectRange.this.remove(index);
194 }
195 };
196 }
197
198 public int size() {
199 if (size == -1) {
200 // lets lazily calculate the size
201 size = 0;
202 Object value = from;
203 while (to.compareTo(value) >= 0) {
204 value = increment(value);
205 size++;
206 }
207 }
208 return size;
209 }
210
211 public List subList(int fromIndex, int toIndex) {
212 if (fromIndex < 0) {
213 throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
214 }
215 int size = size();
216 if (toIndex > size) {
217 throw new IndexOutOfBoundsException("toIndex = " + toIndex);
218 }
219 if (fromIndex > toIndex) {
220 throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")");
221 }
222 if (--toIndex >= size) {
223 return new ObjectRange((Comparable) get(fromIndex), getTo(), reverse);
224 } else {
225 return new ObjectRange((Comparable) get(fromIndex), (Comparable) get(toIndex), reverse);
226 }
227 }
228
229 public String toString() {
230 return (reverse) ? "" + to + ".." + from : "" + from + ".." + to;
231 }
232
233 public String inspect() {
234 String toText = InvokerHelper.inspect(to);
235 String fromText = InvokerHelper.inspect(from);
236 return (reverse) ? "" + toText + ".." + fromText : "" + fromText + ".." + toText;
237 }
238
239 public boolean contains(Comparable value) {
240 int result = from.compareTo(value);
241 if (result == 0) {
242 return true;
243 }
244 return result < 0 && to.compareTo(value) >= 0;
245 }
246
247 public void step(int step, Closure closure) {
248 if (reverse) {
249 step = -step;
250 }
251 if (step >= 0) {
252 Comparable value = from;
253 while (value.compareTo(to) <= 0) {
254 closure.call(value);
255 for (int i = 0; i < step; i++) {
256 value = (Comparable) increment(value);
257 }
258 }
259 } else {
260 step = -step;
261 Comparable value = to;
262 while (value.compareTo(from) >= 0) {
263 closure.call(value);
264 for (int i = 0; i < step; i++) {
265 value = (Comparable) decrement(value);
266 }
267 }
268 }
269 }
270
271 public List step(int step) {
272 IteratorClosureAdapter adapter = new IteratorClosureAdapter(this);
273 step(step, adapter);
274 return adapter.asList();
275 }
276
277 protected Object increment(Object value) {
278 return InvokerHelper.invokeMethod(value, "next", null);
279 }
280
281 protected Object decrement(Object value) {
282 return InvokerHelper.invokeMethod(value, "previous", null);
283 }
284
285 private static Comparable normaliseType(final Comparable operand) {
286 if (operand instanceof Character) {
287 return new Integer(((Character) operand).charValue());
288 } else if (operand instanceof String) {
289 final String string = (String) operand;
290
291 if (string.length() == 1)
292 return new Integer(string.charAt(0));
293 else
294 return string;
295 } else {
296 return operand;
297 }
298 }
299 }