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