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    }