001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.units.collections;
006
007import java.util.Arrays;
008import java.util.Iterator;
009import java.util.NoSuchElementException;
010import java.util.Set;
011import java.util.stream.LongStream;
012
013/** A read-only set of unique primitive {@code long} values. */
014public class ReadOnlyPrimitiveLongSet implements Iterable<Long> {
015  private final long[] m_values;
016
017  /**
018   * Creates a new set from the given values. These values do not have to be unique.
019   *
020   * @param values the values that belong to the set.
021   */
022  @SuppressWarnings({"PMD.ForLoopCanBeForeach", "ForLoopReplaceableByForEach"})
023  public ReadOnlyPrimitiveLongSet(long... values) {
024    // initial size is the upper limit
025    long[] uniqueValues = new long[values.length];
026    int numUniqueValues = 0;
027    boolean seenZero = false;
028
029    // copy the set of unique values to our array
030    // using indexed for-loops to avoid allocations
031    copyLoop:
032    for (int i = 0; i < values.length; i++) {
033      long value = values[i];
034      if (value == 0 && !seenZero) {
035        // special case to support zero
036        seenZero = true;
037      } else {
038        for (int j = 0; j < uniqueValues.length; j++) {
039          long uniqueValue = uniqueValues[j];
040          if (uniqueValue == value) {
041            continue copyLoop;
042          }
043        }
044      }
045      uniqueValues[numUniqueValues] = value;
046      numUniqueValues++;
047    }
048
049    if (numUniqueValues == values.length) {
050      // all input values were unique, no need to truncate
051      m_values = uniqueValues;
052    } else {
053      // truncate the array to remove trailing empty space
054      m_values = Arrays.copyOf(uniqueValues, numUniqueValues);
055    }
056  }
057
058  /**
059   * Checks if the set contains a particular value.
060   *
061   * @param value the value to check for
062   * @return true if the value is in the set, false if not
063   */
064  public boolean contains(long value) {
065    for (long mValue : m_values) {
066      if (mValue == value) {
067        return true;
068      }
069    }
070    return false;
071  }
072
073  /**
074   * Retrieves the number of elements in the set.
075   *
076   * @return the number of elements in the set
077   */
078  public int size() {
079    return m_values.length;
080  }
081
082  /**
083   * Checks if the set is empty, i.e. contains no values.
084   *
085   * @return true if there are no values in the set, false otherwise.
086   */
087  public boolean isEmpty() {
088    return size() == 0;
089  }
090
091  /**
092   * Creates a stream of primitive long values for the set.
093   *
094   * @return a sequential Stream over the elements in this collection
095   * @see Set#stream()
096   */
097  public LongStream stream() {
098    return Arrays.stream(m_values);
099  }
100
101  /**
102   * Creates a new array that contains all of the values in the set.
103   *
104   * @return an array containing all the values in the set
105   */
106  public long[] toArray() {
107    return Arrays.copyOf(m_values, m_values.length);
108  }
109
110  @Override
111  public Iterator<Long> iterator() {
112    return new Iterator<>() {
113      @SuppressWarnings("PMD.RedundantFieldInitializer")
114      private int m_index = 0;
115
116      @Override
117      public boolean hasNext() {
118        return m_index < ReadOnlyPrimitiveLongSet.this.size();
119      }
120
121      @Override
122      public Long next() {
123        if (!hasNext()) {
124          throw new NoSuchElementException();
125        }
126
127        return ReadOnlyPrimitiveLongSet.this.m_values[m_index++];
128      }
129    };
130  }
131}