View Javadoc

    1   /*
    2    $Id: ObjectRange.java,v 1.16 2005/08/26 09:13:19 blackdrag Exp $
    3
    4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
    5
    6    Redistribution and use of this software and associated documentation
    7    ("Software"), with or without modification, are permitted provided
    8    that the following conditions are met:
    9
    10   1. Redistributions of source code must retain copyright
    11      statements and notices.  Redistributions must also contain a
    12      copy of this document.
    13
    14   2. Redistributions in binary form must reproduce the
    15      above copyright notice, this list of conditions and the
    16      following disclaimer in the documentation and/or other
    17      materials provided with the distribution.
    18
    19   3. The name "groovy" must not be used to endorse or promote
    20      products derived from this Software without prior written
    21      permission of The Codehaus.  For written permission,
    22      please contact info@codehaus.org.
    23
    24   4. Products derived from this Software may not be called "groovy"
    25      nor may "groovy" appear in their names without prior written
    26      permission of The Codehaus. "groovy" is a registered
    27      trademark of The Codehaus.
    28
    29   5. Due credit should be given to The Codehaus -
    30      http://groovy.codehaus.org/
    31
    32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
    33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
    34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
    35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
    36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
    37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
    43   OF THE POSSIBILITY OF SUCH DAMAGE.
    44
    45   */
    46  package groovy.lang;
    47
    48  import org.codehaus.groovy.runtime.InvokerHelper;
    49  import org.codehaus.groovy.runtime.IteratorClosureAdapter;
    50
    51  import java.util.AbstractList;
    52  import java.util.Iterator;
    53  import java.util.List;
    54  import java.math.BigDecimal;
    55  import java.math.BigInteger;
    56
    57  /***
    58   * Represents an inclusive list of objects from a value to a value using
    59   * comparators
    60   *
    61   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
    62   * @version $Revision: 1.16 $
    63   */
    64  public class ObjectRange extends AbstractList implements Range {
    65
    66      private Comparable from;
    67      private Comparable to;
    68      private int size = -1;
    69      private final boolean reverse;
    70
    71      public ObjectRange(Comparable from, Comparable to) {
    72          this.reverse = InvokerHelper.compareGreaterThan(from, to);
    73          if (this.reverse) {
    74              constructorHelper(to, from);
    75          } else {
    76              constructorHelper(from, to);
    77          }
    78      }
    79
    80      public ObjectRange(Comparable from, Comparable to, boolean reverse) {
    81          constructorHelper(from, to);
    82
    83          this.reverse = reverse;
    84      }
    85
    86      private void constructorHelper(Comparable from, Comparable to) {
    87          if (from == null) {
    88              throw new IllegalArgumentException("Must specify a non-null value for the 'from' index in a Range");
    89          }
    90          if (to == null) {
    91              throw new IllegalArgumentException("Must specify a non-null value for the 'to' index in a Range");
    92          }
    93          if (from.getClass() == to.getClass()) {
    94              this.from = from;
    95              this.to = to;
    96          } else {
    97              this.from = normaliseType(from);
    98              this.to = normaliseType(to);
    99          }
    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 }