/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package groovy.lang; import org.codehaus.groovy.runtime.IteratorClosureAdapter; import org.codehaus.groovy.runtime.RangeInfo; import java.math.BigInteger; import java.util.AbstractList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; /** * Represents a list of Integer objects starting at a specified {@code from} value up (or down) * to and potentially including a given {@code to} value. * <p> * Instances of this class may be either inclusive aware or non-inclusive aware. See the * relevant constructors for creating each type. Inclusive aware IntRange instances are * suitable for use with Groovy's range indexing - in particular if the from or to values * might be negative. This normally happens underneath the covers but is worth keeping * in mind if creating these ranges yourself explicitly. * <p> * Note: the design of this class might seem a little strange at first. It contains a Boolean * field, {@code inclusive}, which can be {@code true}, {@code false} or {@code null}. This * design is for backwards compatibility reasons. Groovy uses this class under the covers * to represent range indexing, e.g. {@code someList[x..y]} and {@code someString[x..<y]}. * In early versions of Groovy the ranges in these expressions were represented under the * covers by the {@code new IntRange(x, y)} and {@code new IntRange(x, y-1)}. This turns * out to be a lossy abstraction when x and/or y are negative values. Now the latter case * is represented by {@code new IntRange(false, x, y)}. * <p> * Note: This class is a copy of {@link ObjectRange} optimized for <code>int</code>. If you make any * changes to this class, you might consider making parallel changes to {@link ObjectRange}. */ public class IntRange extends AbstractList<Integer> implements Range<Integer> { /** * Iterates through each number in an <code>IntRange</code>. */ private class IntRangeIterator implements Iterator<Integer> { /** * Counts from 0 up to size - 1. */ private int index; /** * The number of values in the range. */ private int size = size(); /** * The next value to return. */ private int value = isReverse() ? getTo() : getFrom(); @Override public boolean hasNext() { return index < size; } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } if (index++ > 0) { if (isReverse()) { --value; } else { ++value; } } return value; } /** * Not supported. * * @throws java.lang.UnsupportedOperationException always */ @Override public void remove() { IntRange.this.remove(index); } } /** * For non-inclusive aware ranges, the first number in the range; <code>from</code> is always less than or equal to <code>to</code>. * For inclusive aware ranges, the <code>from</code> argument supplied to the constructor. */ private final int from; /** * For non-inclusive aware ranges, the last number in the range; <code>to</code> is always greater than or equal to <code>from</code>. * For inclusive aware ranges, the <code>from</code> argument supplied to the constructor. */ private final int to; /** * If <code>false</code>, counts up from <code>from</code> to <code>to</code>. Otherwise, counts down * from <code>to</code> to <code>from</code>. Not used for inclusive-aware ranges (inclusive = true|false). */ private final boolean reverse; /** * If <code>true</code> or null, <code>to</code> is included in the range. * If <code>false</code>, the range stops before the <code>to</code> value. * <p> * Null for non-inclusive-aware ranges (which are inclusive). * <p> * If true or false, the reverse flag is discarded. */ private final Boolean inclusive; /** * Creates a new non-inclusive aware <code>IntRange</code>. If <code>from</code> is greater than * <code>to</code>, a reverse range is created with <code>from</code> and <code>to</code> swapped. * * @param from the first number in the range. * @param to the last number in the range. * @throws IllegalArgumentException if the range would contain more than {@link Integer#MAX_VALUE} values. */ public IntRange(int from, int to) { this.inclusive = null; if (from > to) { this.from = to; this.to = from; this.reverse = true; } else { this.from = from; this.to = to; this.reverse = false; } checkSize(); } /** * Creates a new non-inclusive aware <code>IntRange</code>. * * @param from the first value in the range. * @param to the last value in the range. * @param reverse <code>true</code> if the range should count from * <code>to</code> to <code>from</code>. * @throws IllegalArgumentException if <code>from</code> is greater than <code>to</code>. */ protected IntRange(int from, int to, boolean reverse) { this.inclusive = null; if (from > to) { throw new IllegalArgumentException("'from' must be less than or equal to 'to'"); } this.from = from; this.to = to; this.reverse = reverse; checkSize(); } /** * Creates a new inclusive aware <code>IntRange</code>. * * @param from the first value in the range. * @param to the last value in the range. * @param inclusive <code>true</code> if the to value is included in the range. */ public IntRange(boolean inclusive, int from, int to) { this.from = from; this.to = to; this.inclusive = inclusive; this.reverse = false; // range may still be reversed, this value is ignored for inclusive-aware ranges checkSize(); } /** * Creates a new NumberRange with the same <code>from</code> and <code>to</code> as this * IntRange but with a step size of <code>stepSize</code>. * * @param stepSize the desired step size * @return a new NumberRange * @since 2.5 */ public <T extends Number & Comparable> NumberRange by(T stepSize) { return new NumberRange(NumberRange.comparableNumber((Number)from), NumberRange.comparableNumber((Number)to), stepSize, inclusive); } private void checkSize() { // size() in the Collection interface returns an integer, so ranges can have no more than Integer.MAX_VALUE elements final Long size = (long) to - from + 1; if (size > Integer.MAX_VALUE) { throw new IllegalArgumentException("A range must have no more than " + Integer.MAX_VALUE + " elements but attempted " + size + " elements"); } } /** * A method for determining from and to information when using this IntRange to index an aggregate object of the specified size. * Normally only used internally within Groovy but useful if adding range indexing support for your own aggregates. * * @param size the size of the aggregate being indexed * @return the calculated range information (with 1 added to the to value, ready for providing to subList */ public RangeInfo subListBorders(int size) { if (inclusive == null) { throw new IllegalStateException("Should not call subListBorders on a non-inclusive aware IntRange"); } int tempFrom = from; if (tempFrom < 0) { tempFrom += size; } int tempTo = to; if (tempTo < 0) { tempTo += size; } if (tempFrom > tempTo) { return new RangeInfo(inclusive ? tempTo : tempTo + 1, tempFrom + 1, true); } return new RangeInfo(tempFrom, inclusive ? tempTo + 1 : tempTo, false); } /** * Determines if this object is equal to another object. Delegates to * {@link AbstractList#equals(Object)} if <code>that</code> is anything * other than an {@link IntRange}. * <p> * It is not necessary to override <code>hashCode</code>, as * {@link AbstractList#hashCode()} provides a suitable hash code.<p> * <p> * Note that equals is generally handled by {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#equals(List, List)} * instead of this method. * * @param that the object to compare * @return <code>true</code> if the objects are equal */ public boolean equals(Object that) { return that instanceof IntRange ? equals((IntRange) that) : super.equals(that); } /** * Compares an {@link IntRange} to another {@link IntRange}. * * @param that the object to compare for equality * @return <code>true</code> if the ranges are equal */ public boolean equals(IntRange that) { return that != null && ((inclusive == null && reverse == that.reverse && from == that.from && to == that.to) || (inclusive != null && inclusive == that.inclusive && from == that.from && to == that.to)); } @Override public Integer getFrom() { if (inclusive == null || from <= to) { return from; } return inclusive ? to : to + 1; } @Override public Integer getTo() { if (inclusive == null) { return to; } if (from <= to) { return inclusive ? to : to - 1; } return from; } /** * Returns the inclusive flag. Null for non-inclusive aware ranges or non-null for inclusive aware ranges. */ public Boolean getInclusive() { return inclusive; } /** * Gets the 'from' value as a primitive integer. * * @return the 'from' value as a primitive integer. */ public int getFromInt() { return getFrom(); } /** * Gets the 'to' value as a primitive integer. * * @return the 'to' value as a primitive integer. */ public int getToInt() { return getTo(); } @Override public boolean isReverse() { return inclusive == null ? reverse : (from > to); } @Override public boolean containsWithinBounds(Object o) { return contains(o); } @Override public Integer get(int index) { if (index < 0) { throw new IndexOutOfBoundsException("Index: " + index + " should not be negative"); } if (index >= size()) { throw new IndexOutOfBoundsException("Index: " + index + " too big for range: " + this); } return isReverse() ? getTo() - index : index + getFrom(); } @Override public int size() { return getTo() - getFrom() + 1; } @Override public Iterator<Integer> iterator() { return new IntRangeIterator(); } @Override public List<Integer> subList(int fromIndex, int toIndex) { if (fromIndex < 0) { throw new IndexOutOfBoundsException("fromIndex = " + fromIndex); } if (toIndex > size()) { throw new IndexOutOfBoundsException("toIndex = " + toIndex); } if (fromIndex > toIndex) { throw new IllegalArgumentException("fromIndex(" + fromIndex + ") > toIndex(" + toIndex + ")"); } if (fromIndex == toIndex) { return new EmptyRange<Integer>(getFrom()); } return new IntRange(fromIndex + getFrom(), toIndex + getFrom() - 1, isReverse()); } public String toString() { return inclusive != null ? ("" + from + ".." + (inclusive ? "" : "<") + to) : (reverse ? "" + to + ".." + from : "" + from + ".." + to); } @Override public String inspect() { return toString(); } @Override public boolean contains(Object value) { if (value instanceof Integer) { return (Integer) value >= getFrom() && (Integer) value <= getTo(); } if (value instanceof BigInteger) { final BigInteger bigint = (BigInteger) value; return bigint.compareTo(BigInteger.valueOf(getFrom())) >= 0 && bigint.compareTo(BigInteger.valueOf(getTo())) <= 0; } return false; } @Override public boolean containsAll(Collection other) { if (other instanceof IntRange) { final IntRange range = (IntRange) other; return getFrom() <= range.getFrom() && range.getTo() <= getTo(); } return super.containsAll(other); } @Override public void step(int step, Closure closure) { if (step == 0) { if (!getFrom().equals(getTo())) { throw new GroovyRuntimeException("Infinite loop detected due to step size of 0"); } return; // from == to and step == 0, nothing to do, so return } if (isReverse()) { step = -step; } if (step > 0) { int value = getFrom(); while (value <= getTo()) { closure.call(value); if (((long) value + step) >= Integer.MAX_VALUE) { break; } value = value + step; } } else { int value = getTo(); while (value >= getFrom()) { closure.call(value); if (((long) value + step) <= Integer.MIN_VALUE) { break; } value = value + step; } } } @Override public List<Integer> step(int step) { final IteratorClosureAdapter<Integer> adapter = new IteratorClosureAdapter<Integer>(this); step(step, adapter); return adapter.asList(); } @Override public int hashCode(){ int hashCode; final int from = this.getFrom(); final int to = this.getTo(); hashCode = ((from+to+1)*(from+to))/2+to; return hashCode; } }