groovy.lang.IntRange Maven / Gradle / Ivy
/*
* 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;
/**
* Represents a list of Integer objects from a specified int up (or down) to and including
* a given to.
*
* This class is a copy of {@link ObjectRange} optimized for int
. If you make any
* changes to this class, you might consider making parallel changes to {@link ObjectRange}.
* 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.
*/
public class IntRange extends AbstractList implements Range {
/**
* Iterates through each number in an IntRange
.
*/
private class IntRangeIterator implements Iterator {
/**
* 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()) {
// TODO instead of returning null, do this: throw new NoSuchElementException();
return null;
}
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; from
is always less than or equal to to
.
* For inclusive aware ranges, the from
argument supplied to the constructor.
*/
private int from;
/**
* For non-inclusive aware ranges, the last number in the range; to
is always greater than or equal to from
.
* For inclusive aware ranges, the from
argument supplied to the constructor.
*/
private int to;
/**
* If false
, counts up from from
to to
. Otherwise, counts down
* from to
to from
. Not used for inclusive aware ranges.
*/
private boolean reverse;
/**
* If true
, to
is included in the range. Otherwise, the range stops
* before the to
value. Null for non-inclusive aware ranges.
*/
private Boolean inclusive;
/**
* Creates a new non-inclusive IntRange
. If from
is greater than
* to
, a reverse range is created with from
and to
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;
}
checkSize();
}
/**
* Creates a new non-inclusive aware IntRange
.
*
* @param from the first value in the range.
* @param to the last value in the range.
* @param reverse true
if the range should count from
* to
to from
.
* @throws IllegalArgumentException if from
is greater than to
.
*/
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 IntRange
.
*
* @param from the first value in the range.
* @param to the last value in the range.
* @param inclusive true
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();
}
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 that
is anything
* other than an {@link IntRange}.
*
* It is not necessary to override hashCode
, as
* {@link AbstractList#hashCode()} provides a suitable hash code.
*
* 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 true
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 true
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 iterator() {
return new IntRangeIterator();
}
@Override
public List 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(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 step(int step) {
final IteratorClosureAdapter adapter = new IteratorClosureAdapter(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;
}
}