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