001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2015-2016 ForgeRock AS.
015 */
016
017package org.forgerock.util;
018
019// Java SE
020import java.io.Serializable;
021import java.util.AbstractSet;
022import java.util.Iterator;
023import java.util.NoSuchElementException;
024
025/**
026 * Exposes a range of integer values as a set. Avoids the allocation of storage for all
027 * values. Order of elements is guaranteed to be in the order from the specified start and
028 * stop values.
029 * <p>
030 * If combination of start/stop/step values are not mathematically possible to represent
031 * as a set of values, it is represented by this implementation as an empty set.
032 */
033public class RangeSet extends AbstractSet<Integer> implements Cloneable, Serializable {
034
035    /** Establishes serialized object compatibility. */
036    private static final long serialVersionUID = 1L;
037
038    /** The start of the range, inclusive. */
039    private final int start;
040
041    /** The end of the range, inclusive. */
042    private final int stop;
043
044    /** TODO: Description. */
045    private final int step;
046
047    /**
048     * Constructs a range set for a sequence of numbers, starting at {@code 0} with
049     * the value to stop.  Equivalent to constructing the range set with:
050     * {@code RangeSet(0, stop, 1)}.
051     * @param stop the point at which to stop the range (exclusive).
052     */
053    public RangeSet(int stop) {
054        this(0, stop, 1);
055    }
056
057    /**
058     * Constructs a range set for the specified range of integers with a step of {@code 1}.
059     * Equivalent to constructing the range set with: {@code RangeSet(start, stop, 1)}.
060     *
061     * @param start the start of the range (inclusive).
062     * @param stop the point at which to stop the range (exclusive).
063     */
064    public RangeSet(int start, int stop) {
065        this(start, stop, 1);
066    }
067
068    /**
069     * Constructs a range set for the specified range of integers and increment.
070     *
071     * @param start the start of the range, inclusive.
072     * @param stop the point at which to stop the range (exclusive).
073     * @param step the step to increment for each value in the range.
074     * @throws IllegalArgumentException if {@code step} is {@code 0}.
075     */
076    public RangeSet(int start, int stop, int step) {
077        if (step == 0) {
078            throw new IllegalArgumentException();
079        }
080        this.start = start;
081        this.stop = stop;
082        this.step = step;
083    }
084
085    /**
086     * Returns the number of elements in this set.
087     */
088    @Override
089    public int size() {
090        int difference = (stop - start); // show all work
091        int count = (difference / step);
092        int remainder = Math.abs(difference % step);
093        return (count > 0 ? count + remainder : 0);
094    }
095
096    /**
097     * Returns {@code true} if this set contains no elements.
098     */
099    @Override
100    public boolean isEmpty() {
101        return (size() == 0);
102    }
103
104    /**
105     * Returns {@code true} if this set contains the specified element.
106     *
107     * @param o element whose presence in this set is to be tested.
108     * @return {@code true} if this set contains the specified element.
109     */
110    @Override
111    public boolean contains(Object o) {
112        boolean result = false;
113        if (o != null && o instanceof Integer && size() != 0) {
114            int contains = ((Number) o).intValue();
115            if ((step > 0 && contains >= start && contains < stop)
116                    || (step < 0 && contains >= start && contains > stop)) {
117                result = ((contains - start) % step == 0);
118            }
119        }
120        return result;
121    }
122
123    /**
124     * Returns an iterator over the elements in this set. The elements are returned in
125     * the order they are specified from start to stop.
126     */
127    @Override
128    public Iterator<Integer> iterator() {
129        return new Iterator<Integer>() {
130            int cursor = start;
131            @Override public boolean hasNext() {
132                boolean result;
133                if (step > 0) {
134                    result = (cursor < stop);
135                } else {
136                    result = (cursor > stop);
137                }
138                return result;
139            }
140            @Override public Integer next() {
141                if (!hasNext()) {
142                    throw new NoSuchElementException();
143                }
144                int result = cursor;
145                cursor += step;
146                return result;
147            }
148            @Override public void remove() {
149                throw new UnsupportedOperationException();
150            }
151        };
152    }
153}