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