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 }