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 com.google.common.annotations.Beta;
11 import com.google.common.collect.Collections2;
12 import com.google.common.collect.ImmutableRangeSet;
13 import com.google.common.primitives.UnsignedLong;
14 import java.util.NavigableSet;
15 import java.util.TreeSet;
16 import org.eclipse.jdt.annotation.NonNull;
17 import org.opendaylight.yangtools.concepts.Mutable;
20 public final class MutableUnsignedLongSet extends UnsignedLongSet implements Mutable {
21 MutableUnsignedLongSet(final TreeSet<Entry> ranges) {
25 public static @NonNull MutableUnsignedLongSet of() {
26 return new MutableUnsignedLongSet(new TreeSet<>());
29 public static @NonNull MutableUnsignedLongSet of(final long... ulongs) {
30 final var ret = MutableUnsignedLongSet.of();
31 for (long longBits : ulongs) {
38 public ImmutableUnsignedLongSet immutableCopy() {
39 return ImmutableUnsignedLongSet.copyOf(this);
42 public void add(final long longBits) {
43 addOne(trustedRanges(), longBits);
46 public void addAll(final UnsignedLongSet other) {
47 final var ranges = trustedRanges();
48 for (var range : other.trustedRanges()) {
49 if (range.lowerBits == range.upperBits) {
50 addOne(ranges, range.lowerBits);
52 addRange(ranges, range);
57 private static void addOne(final NavigableSet<Entry> ranges, final long longBits) {
58 final var range = Entry.of(longBits);
59 // We need Iterator.remove() to perform efficient merge below
60 final var headIt = ranges.headSet(range, true).descendingIterator();
61 if (headIt.hasNext()) {
62 final var head = headIt.next();
63 if (Long.compareUnsigned(head.upperBits, longBits) >= 0) {
64 // Already contained, this is a no-op
68 // Merge into head entry if possible
69 if (head.upperBits + 1 == longBits) {
70 head.upperBits = longBits;
72 // Potentially merge head entry and tail entry
73 final var tail = ranges.higher(range);
75 if (tail.lowerBits - 1 == longBits) {
76 // Expand tail, remove head
77 tail.lowerBits = head.lowerBits;
85 final var tail = ranges.higher(range);
87 // Merge into tail entry if possible
88 if (tail.lowerBits - 1 == longBits) {
89 tail.lowerBits = longBits;
94 // No luck, store a new entry
98 private static void addRange(final NavigableSet<Entry> ranges, final Entry range) {
99 // If the start of the range is already covered by an existing range, we can expand that
100 final var lower = ranges.floor(range);
101 if (lower != null && Long.compareUnsigned(lower.upperBits + 1, range.lowerBits) >= 0) {
102 expandLower(ranges, lower, range.upperBits);
106 // If the end of the range is already covered by an existing range, we can expand that
107 final var upper = ranges.floor(Entry.of(range.upperBits));
108 if (upper != null && Long.compareUnsigned(upper.lowerBits - 1, range.upperBits) <= 0) {
109 // Quick check: if we did not find a lower range at all, we might be expanding the entire span, in which
110 // case upper needs to become the first entry
112 ranges.headSet(upper, false).clear();
115 expandUpper(ranges, upper, range.lowerBits, range.upperBits);
120 ranges.add(range.copy());
123 private static void expandLower(final NavigableSet<Entry> ranges, final Entry entry, final long upperBits) {
124 if (Long.compareUnsigned(entry.upperBits, upperBits) >= 0) {
125 // Already contained, this is a no-op
129 // Acquire any ranges after floor and clean them up
130 final var tailIt = ranges.tailSet(entry, false).iterator();
131 final long nextLower = upperBits + 1;
132 while (tailIt.hasNext()) {
133 final var tail = tailIt.next();
134 if (Long.compareUnsigned(tail.lowerBits, nextLower) > 0) {
135 // There is gap, nothing more to cleanup
139 // We can merge this entry into floor...
142 if (Long.compareUnsigned(tail.upperBits, nextLower) >= 0) {
143 // ... but we need to expand floor accordingly and after that we are done
144 entry.upperBits = tail.upperBits;
149 // Expand floor to include this range and we are done
150 entry.upperBits = upperBits;
153 private static void expandUpper(final NavigableSet<Entry> ranges, final Entry entry, final long lowerBits,
154 final long upperBits) {
155 if (Long.compareUnsigned(entry.upperBits, upperBits) >= 0) {
156 // Upper end is already covered
157 entry.lowerBits = lowerBits;
161 // We are expanding the entry's upper boundary, we need to check if we need to coalesce following entries
162 long newUpper = upperBits;
163 final var tailIt = ranges.tailSet(entry, false).iterator();
164 if (tailIt.hasNext()) {
165 final var tail = tailIt.next();
166 if (Long.compareUnsigned(tail.lowerBits, newUpper + 1) <= 0) {
168 newUpper = tail.upperBits;
172 entry.lowerBits = lowerBits;
173 entry.upperBits = newUpper;
176 public ImmutableRangeSet<UnsignedLong> toRangeSet() {
177 return ImmutableRangeSet.copyOf(Collections2.transform(trustedRanges(), Entry::toUnsigned));