2 * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.cluster.datastore.utils;
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.Beta;
14 import com.google.common.annotations.VisibleForTesting;
15 import com.google.common.base.MoreObjects;
16 import com.google.common.collect.BoundType;
17 import com.google.common.collect.Collections2;
18 import com.google.common.collect.Range;
19 import com.google.common.collect.RangeSet;
20 import com.google.common.primitives.UnsignedLong;
21 import java.io.DataInput;
22 import java.io.DataOutput;
23 import java.io.IOException;
24 import java.util.Collections;
25 import java.util.Iterator;
26 import java.util.NavigableSet;
27 import java.util.TreeSet;
28 import org.eclipse.jdt.annotation.NonNull;
29 import org.opendaylight.yangtools.concepts.Mutable;
30 import org.opendaylight.yangtools.concepts.WritableObjects;
33 * A class holding an equivalent of {@code Set<UnsignedLong>}. It is geared towards efficiently tracking ranges of
34 * objects, similar to what a {@link RangeSet} would do.
37 * Unlike a {@code RangeSet}, though, this class takes advantage of knowing that an unsigned long is a discrete unit
38 * and can be stored in a simple {@code long}.
40 * @author Robert Varga
42 abstract class UnsignedLongSet {
45 public static final class Entry implements Comparable<Entry>, Mutable {
46 // Note: mutable to allow efficient merges.
50 private Entry(final long lowerBits, final long upperBits) {
51 this.lowerBits = lowerBits;
52 this.upperBits = upperBits;
55 static Entry of(final long longBits) {
56 return of(longBits, longBits);
59 static Entry of(final long lowerBits, final long upperBits) {
60 return new Entry(lowerBits, upperBits);
63 static Entry of(final Range<UnsignedLong> range) {
64 verify(range.lowerBoundType() == BoundType.CLOSED && range.upperBoundType() == BoundType.OPEN,
65 "Unexpected range %s", range);
66 return of(range.lowerEndpoint().longValue(), range.upperEndpoint().longValue() - 1);
70 public UnsignedLong lower() {
71 return UnsignedLong.fromLongBits(lowerBits);
75 public UnsignedLong upper() {
76 return UnsignedLong.fromLongBits(upperBits);
79 boolean contains(final long longBits) {
80 return Long.compareUnsigned(lowerBits, longBits) <= 0 && Long.compareUnsigned(upperBits, longBits) >= 0;
84 return new Entry(lowerBits, upperBits);
87 // Provides compatibility with RangeSet<UnsignedLong> using [lower, upper + 1)
88 Range<UnsignedLong> toUnsigned() {
89 return Range.closedOpen(UnsignedLong.fromLongBits(lowerBits), UnsignedLong.fromLongBits(upperBits + 1));
92 // These two methods provide the same serialization format as the one we've used to serialize
93 // Range<UnsignedLong>
94 static @NonNull Entry readUnsigned(final DataInput in) throws IOException {
95 final byte hdr = WritableObjects.readLongHeader(in);
96 final long first = WritableObjects.readFirstLong(in, hdr);
97 final long second = WritableObjects.readSecondLong(in, hdr) - 1;
98 if (Long.compareUnsigned(first, second) > 0) {
99 throw new IOException("Lower endpoint " + Long.toUnsignedString(first) + " is greater than upper "
100 + "endpoint " + Long.toUnsignedString(second));
103 return new Entry(first, second);
106 void writeUnsigned(final @NonNull DataOutput out) throws IOException {
107 WritableObjects.writeLongs(out, lowerBits, upperBits + 1);
111 @SuppressWarnings("checkstyle:parameterName")
112 public int compareTo(final Entry o) {
113 return Long.compareUnsigned(lowerBits, o.lowerBits);
117 public int hashCode() {
118 return Long.hashCode(lowerBits) * 31 + Long.hashCode(upperBits);
122 public boolean equals(final Object obj) {
126 if (!(obj instanceof Entry)) {
129 final var other = (Entry) obj;
130 return lowerBits == other.lowerBits && upperBits == other.upperBits;
134 public String toString() {
135 return "[" + Long.toUnsignedString(lowerBits) + ".." + Long.toUnsignedString(upperBits) + "]";
139 // The idea is rather simple, we track a TreeSet of range entries, ordered by their lower bound. This means that
140 // for a contains() operation we just need the first headSet() entry. For insert operations we just update either
141 // the lower bound or the upper bound of an existing entry. When we do, we also look at prev/next entry and if they
142 // are contiguous with the updated entry, we adjust the entry once more and remove the prev/next entry.
143 private final @NonNull TreeSet<Entry> ranges;
145 UnsignedLongSet(final TreeSet<Entry> ranges) {
146 this.ranges = requireNonNull(ranges);
149 final void addImpl(final long longBits) {
150 final var range = Entry.of(longBits);
152 final var headIt = headIter(range);
153 if (headIt.hasNext()) {
154 final var head = headIt.next();
155 if (head.contains(longBits)) {
158 if (head.upperBits + 1 == longBits) {
159 head.upperBits = longBits;
160 final var tailSet = ranges.tailSet(range);
161 if (!tailSet.isEmpty()) {
162 final var tail = tailSet.first();
163 if (tail.lowerBits - 1 == longBits) {
164 tail.lowerBits = head.lowerBits;
172 final var tailSet = ranges.tailSet(range);
173 if (!tailSet.isEmpty()) {
174 final var tail = tailSet.first();
175 if (tail.lowerBits - 1 == longBits) {
176 tail.lowerBits = longBits;
184 public final boolean contains(final long longBits) {
185 final var headIt = headIter(Entry.of(longBits));
186 return headIt.hasNext() && headIt.next().contains(longBits);
189 public final boolean isEmpty() {
190 return ranges.isEmpty();
193 public final int size() {
194 return ranges.size();
197 public abstract @NonNull ImmutableUnsignedLongSet immutableCopy();
199 public final @NonNull MutableUnsignedLongSet mutableCopy() {
200 return new MutableUnsignedLongSet(copyRanges());
203 final @NonNull TreeSet<Entry> copyRanges() {
204 return new TreeSet<>(Collections2.transform(ranges, Entry::copy));
207 public final @NonNull NavigableSet<Entry> ranges() {
208 return Collections.unmodifiableNavigableSet(ranges);
211 final @NonNull NavigableSet<Entry> trustedRanges() {
216 public final int hashCode() {
217 return ranges.hashCode();
221 public final boolean equals(final Object obj) {
222 return obj == this || obj instanceof UnsignedLongSet && ranges.equals(((UnsignedLongSet) obj).ranges);
226 public final String toString() {
227 final var helper = MoreObjects.toStringHelper(this);
229 final int size = ranges.size();
234 helper.add("span", ranges.first());
237 helper.add("span", Entry.of(ranges.first().lowerBits, ranges.last().upperBits));
240 return helper.add("size", size).toString();
243 private Iterator<Entry> headIter(final Entry range) {
244 return ranges.headSet(range, true).descendingIterator();