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