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 }