+ * In practice, calls {#link naive_and}
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap and(Iterator
+ * In practice, calls {#link naive_and}
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap and(RoaringBitmap... bitmaps) {
+ return naive_and(bitmaps);
+ }
+
+ /**
+ * Calls naive_or.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ @Deprecated
+ public static RoaringBitmap horizontal_or(Iterator
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #or(RoaringBitmap...)
+ */
+ public static RoaringBitmap horizontal_or(List
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #or(RoaringBitmap...)
+ */
+ public static RoaringBitmap horizontal_or(RoaringBitmap... bitmaps) {
+ RoaringBitmap answer = new RoaringBitmap();
+ if (bitmaps.length == 0) {
+ return answer;
+ }
+ PriorityQueue
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #xor(RoaringBitmap...)
+ */
+ public static RoaringBitmap horizontal_xor(RoaringBitmap... bitmaps) {
+ RoaringBitmap answer = new RoaringBitmap();
+ if (bitmaps.length == 0) {
+ return answer;
+ }
+ PriorityQueue
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap naive_and(Iterator
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap naive_and(RoaringBitmap... bitmaps) {
+ if (bitmaps.length == 0) {
+ return new RoaringBitmap();
+ }
+ RoaringBitmap answer = bitmaps[0].clone();
+ for (int k = 1; k < bitmaps.length; ++k) {
+ answer.and(bitmaps[k]);
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall OR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap naive_or(Iterator
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap naive_or(RoaringBitmap... bitmaps) {
+ RoaringBitmap answer = new RoaringBitmap();
+ for (int k = 0; k < bitmaps.length; ++k) {
+ answer.naivelazyor(bitmaps[k]);
+ }
+ answer.repairAfterLazy();
+ return answer;
+ }
+
+ /**
+ * Compute overall XOR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap naive_xor(Iterator
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap naive_xor(RoaringBitmap... bitmaps) {
+ RoaringBitmap answer = new RoaringBitmap();
+ for (int k = 0; k < bitmaps.length; ++k) {
+ answer.xor(bitmaps[k]);
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall OR between bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap or(Iterator
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #horizontal_or(RoaringBitmap...)
+ */
+ public static RoaringBitmap priorityqueue_or(Iterator
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #horizontal_or(RoaringBitmap...)
+ */
+ public static RoaringBitmap priorityqueue_or(RoaringBitmap... bitmaps) {
+ if (bitmaps.length == 0) {
+ return new RoaringBitmap();
+ }
+ // we buffer the call to getSizeInBytes(), hence the code complexity
+ final RoaringBitmap[] buffer = Arrays.copyOf(bitmaps, bitmaps.length);
+ final long[] sizes = new long[buffer.length];
+ final boolean[] istmp = new boolean[buffer.length];
+ for (int k = 0; k < sizes.length; ++k) {
+ sizes[k] = buffer[k].getLongSizeInBytes();
+ }
+ PriorityQueue
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #horizontal_xor(RoaringBitmap...)
+ */
+ public static RoaringBitmap priorityqueue_xor(RoaringBitmap... bitmaps) {
+ // TODO: This code could be faster, see priorityqueue_or
+ if (bitmaps.length == 0) {
+ return new RoaringBitmap();
+ }
+
+ PriorityQueue
+ * * Usage:
+ *
+ * Internally, this is computed as a 64-bit counter.
+ *
+ * @return estimated memory usage.
+ */
+ public int getSizeInBytes();
+
+ /**
+ * Estimate of the memory usage of this data structure. Provides
+ * full 64-bit number.
+ *
+ * @return estimated memory usage.
+ */
+ public long getLongSizeInBytes();
+
+ /**
+ * Checks whether the bitmap is empty.
+ *
+ * @return true if this bitmap contains no set bit
+ */
+ public boolean isEmpty();
+
+ /**
+ * Create a new bitmap of the same class, containing at most maxcardinality integers.
+ *
+ * @param x maximal cardinality
+ * @return a new bitmap with cardinality no more than maxcardinality
+ */
+ public ImmutableBitmapDataProvider limit(int x);
+
+ /**
+ * Rank returns the number of integers that are smaller or equal to x (Rank(infinity) would be
+ * GetCardinality()).
+ *
+ * The value is internally computed as a 64-bit number.
+ *
+ * @param x upper limit
+ * @return the rank
+ */
+ public int rank(int x);
+
+ /**
+ * Same as "rank" but produces a full 64-bit value.
+ *
+ * @param x upper limit
+ * @return the rank
+ */
+ public long rankLong(int x);
+
+ /**
+ * Return the jth value stored in this bitmap.
+ *
+ * @param j index of the value
+ * @return the value
+ */
+ public int select(int j);
+
+ /**
+ * Serialize this bitmap.
+ *
+ * The current bitmap is not modified.
+ *
+ * @param out the DataOutput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public void serialize(DataOutput out) throws IOException;
+
+ /**
+ * Report the number of bytes required to serialize this bitmap. This is the number of bytes
+ * written out when using the serialize method. When using the writeExternal method, the count
+ * will be higher due to the overhead of Java serialization.
+ *
+ * @return the size in bytes
+ */
+ public int serializedSizeInBytes();
+
+ /**
+ * Return the set values as an array. The integer values are in sorted order.
+ *
+ * @return array representing the set values.
+ */
+ public int[] toArray();
+
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntConsumer.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntConsumer.java
new file mode 100644
index 000000000..47ff1e27d
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntConsumer.java
@@ -0,0 +1,29 @@
+package com.fr.third.bitmap.roaringbitmap;
+
+/**
+ * An IntConsumer receives the int values contained in a data structure.
+ * Each value is visited once.
+ *
+ * Usage:
+ *
+ *
+ * For better performance, consider the {@link RoaringBitmap#forEach} method.
+ *
+ * @author Borislav Ivanov
+ **/
+public class IntIteratorFlyweight implements PeekableIntIterator {
+
+ private int hs;
+
+ private PeekableShortIterator iter;
+
+ private ArrayContainerShortIterator arrIter = new ArrayContainerShortIterator();
+
+ private BitmapContainerShortIterator bitmapIter = new BitmapContainerShortIterator();
+
+ private RunContainerShortIterator runIter = new RunContainerShortIterator();
+
+ private int pos;
+
+ private RoaringBitmap roaringBitmap = null;
+
+ /**
+ * Creates an instance that is not ready for iteration. You must first call
+ * {@link #wrap(RoaringBitmap)}.
+ */
+ public IntIteratorFlyweight() {
+
+ }
+
+ /**
+ * Creates an instance that is ready for iteration.
+ *
+ * @param r bitmap to be iterated over
+ */
+ public IntIteratorFlyweight(RoaringBitmap r) {
+ wrap(r);
+ }
+
+ @Override
+ public PeekableIntIterator clone() {
+ try {
+ IntIteratorFlyweight x = (IntIteratorFlyweight) super.clone();
+ x.iter = this.iter.clone();
+ return x;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return pos < this.roaringBitmap.highLowContainer.size();
+ }
+
+ @Override
+ public int next() {
+ int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ ++pos;
+ nextContainer();
+ }
+ return x;
+ }
+
+ private void nextContainer() {
+ if (pos < this.roaringBitmap.highLowContainer.size()) {
+
+ Container container = this.roaringBitmap.highLowContainer.getContainerAtIndex(pos);
+
+ if (container instanceof BitmapContainer) {
+ bitmapIter.wrap(((BitmapContainer) container).bitmap);
+ iter = bitmapIter;
+ } else if (container instanceof ArrayContainer) {
+ arrIter.wrap((ArrayContainer) container);
+ iter = arrIter;
+ } else {
+ runIter.wrap((RunContainer) container);
+ iter = runIter;
+ }
+ hs = Util.toIntUnsigned(this.roaringBitmap.highLowContainer.getKeyAtIndex(pos)) << 16;
+ }
+ }
+
+ /**
+ * Prepares a bitmap for iteration
+ *
+ * @param r bitmap to be iterated over
+ */
+ public void wrap(RoaringBitmap r) {
+ this.hs = 0;
+ this.pos = 0;
+ this.roaringBitmap = r;
+ this.nextContainer();
+ }
+
+ @Override
+ public void advanceIfNeeded(final int minval) {
+ while (hasNext() && ((hs >>> 16) < (minval >>> 16))) {
+ ++pos;
+ nextContainer();
+ }
+ if (hasNext() && ((hs >>> 16) == (minval >>> 16))) {
+ iter.advanceIfNeeded(Util.lowbits(minval));
+ if (!iter.hasNext()) {
+ ++pos;
+ nextContainer();
+ }
+ }
+ }
+
+ @Override
+ public int peekNext() {
+ return Util.toIntUnsigned(iter.peekNext()) | hs;
+ }
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/PeekableIntIterator.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/PeekableIntIterator.java
new file mode 100644
index 000000000..906f23b73
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/PeekableIntIterator.java
@@ -0,0 +1,33 @@
+package com.fr.third.bitmap.roaringbitmap;
+
+
+/**
+ * Simple extension to the IntIterator interface.
+ * It allows you to "skip" values using the advanceIfNeeded
+ * method, and to look at the value without advancing (peekNext).
+ */
+public interface PeekableIntIterator extends IntIterator {
+ /**
+ * If needed, advance as long as the next value is smaller than minval
+ *
+ * @param minval threshold
+ */
+ public void advanceIfNeeded(int minval);
+
+ /**
+ * Look at the next value without advancing
+ *
+ * @return next value
+ */
+ public int peekNext();
+
+ /**
+ * Creates a copy of the iterator.
+ *
+ * @return a clone of the current iterator
+ */
+ @Override
+ PeekableIntIterator clone();
+}
+
+
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/PeekableShortIterator.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/PeekableShortIterator.java
new file mode 100644
index 000000000..278f0e20a
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/PeekableShortIterator.java
@@ -0,0 +1,34 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+package com.fr.third.bitmap.roaringbitmap;
+
+
+/**
+ * Simple extension to the ShortIterator interface
+ */
+public interface PeekableShortIterator extends ShortIterator {
+ /**
+ * If needed, advance as long as the next value is smaller than minval (as an unsigned
+ * short)
+ *
+ * @param minval threshold
+ */
+ public void advanceIfNeeded(short minval);
+
+ /**
+ * Look at the next value without advancing
+ *
+ * @return next value
+ */
+ public short peekNext();
+
+ /**
+ * Creates a copy of the iterator.
+ *
+ * @return a clone of the current iterator
+ */
+ @Override
+ PeekableShortIterator clone();
+}
+
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/ReverseIntIteratorFlyweight.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/ReverseIntIteratorFlyweight.java
new file mode 100644
index 000000000..0475cc78d
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/ReverseIntIteratorFlyweight.java
@@ -0,0 +1,110 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap;
+
+/**
+ * Fast iterator minimizing the stress on the garbage collector. You can create one reusable
+ * instance of this class and then {@link #wrap(RoaringBitmap)}
+ *
+ * This iterator enumerates the stored values in reverse (starting from the end).
+ *
+ * @author Borislav Ivanov
+ **/
+public class ReverseIntIteratorFlyweight implements IntIterator {
+
+ private int hs;
+
+ private ShortIterator iter;
+
+ private ReverseArrayContainerShortIterator arrIter = new ReverseArrayContainerShortIterator();
+
+ private ReverseBitmapContainerShortIterator bitmapIter =
+ new ReverseBitmapContainerShortIterator();
+
+ private ReverseRunContainerShortIterator runIter = new ReverseRunContainerShortIterator();
+
+ private short pos;
+
+ private RoaringBitmap roaringBitmap = null;
+
+
+ /**
+ * Creates an instance that is not ready for iteration. You must first call
+ * {@link #wrap(RoaringBitmap)}.
+ */
+ public ReverseIntIteratorFlyweight() {
+
+ }
+
+ /**
+ * Creates an instance that is ready for iteration.
+ *
+ * @param r bitmap to be iterated over
+ */
+ public ReverseIntIteratorFlyweight(RoaringBitmap r) {
+ wrap(r);
+ }
+
+ @Override
+ public IntIterator clone() {
+ try {
+ ReverseIntIteratorFlyweight x = (ReverseIntIteratorFlyweight) super.clone();
+ x.iter = this.iter.clone();
+ return x;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return pos >= 0;
+ }
+
+
+ @Override
+ public int next() {
+ final int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ --pos;
+ nextContainer();
+ }
+ return x;
+ }
+
+ private void nextContainer() {
+
+
+ if (pos >= 0) {
+
+ Container container = this.roaringBitmap.highLowContainer.getContainerAtIndex(pos);
+ if (container instanceof BitmapContainer) {
+ bitmapIter.wrap(((BitmapContainer) container).bitmap);
+ iter = bitmapIter;
+ } else if (container instanceof ArrayContainer) {
+ arrIter.wrap((ArrayContainer) container);
+ iter = arrIter;
+ } else {
+ runIter.wrap((RunContainer) container);
+ iter = runIter;
+ }
+ hs = Util.toIntUnsigned(this.roaringBitmap.highLowContainer.getKeyAtIndex(pos)) << 16;
+ }
+ }
+
+ /**
+ * Prepares a bitmap for iteration
+ *
+ * @param r bitmap to be iterated over
+ */
+ public void wrap(RoaringBitmap r) {
+ this.roaringBitmap = r;
+ this.hs = 0;
+ this.pos = (short) (this.roaringBitmap.highLowContainer.size() - 1);
+ this.nextContainer();
+ }
+
+}
+
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RoaringArray.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RoaringArray.java
new file mode 100644
index 000000000..eebb99d77
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RoaringArray.java
@@ -0,0 +1,582 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap;
+
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Arrays;
+
+
+/**
+ * Specialized array to store the containers used by a RoaringBitmap. This is not meant to be used
+ * by end users.
+ */
+public final class RoaringArray implements Cloneable, Externalizable {
+ protected static final short SERIAL_COOKIE_NO_RUNCONTAINER = 12346;
+ protected static final short SERIAL_COOKIE = 12347;
+ protected static final int NO_OFFSET_THRESHOLD = 4;
+ static final int INITIAL_CAPACITY = 4;
+ // bumped serialVersionUID with runcontainers, so default serialization
+ // will not work...
+ private static final long serialVersionUID = 8L;
+ short[] keys = null;
+
+ Container[] values = null;
+
+ int size = 0;
+
+ protected RoaringArray() {
+ this.keys = new short[INITIAL_CAPACITY];
+ this.values = new Container[INITIAL_CAPACITY];
+ }
+
+ /**
+ * Find the smallest integer index larger than pos such that array[index].key>=x. If none can
+ * be found, return size. Based on code by O. Kaser.
+ *
+ * @param x minimal value
+ * @param pos index to exceed
+ * @return the smallest index greater than pos such that array[index].key is at least as large as
+ * min, or size if it is not possible.
+ */
+ protected int advanceUntil(short x, int pos) {
+ int lower = pos + 1;
+
+ // special handling for a possibly common sequential case
+ if (lower >= size || Util.toIntUnsigned(keys[lower]) >= Util.toIntUnsigned(x)) {
+ return lower;
+ }
+
+ int spansize = 1; // could set larger
+ // bootstrap an upper limit
+
+ while (lower + spansize < size
+ && Util.toIntUnsigned(keys[lower + spansize]) < Util.toIntUnsigned(x)) {
+ spansize *= 2; // hoping for compiler will reduce to shift
+ }
+ int upper = (lower + spansize < size) ? lower + spansize : size - 1;
+
+ // maybe we are lucky (could be common case when the seek ahead
+ // expected to be small and sequential will otherwise make us look bad)
+ if (keys[upper] == x) {
+ return upper;
+ }
+
+ if (Util.toIntUnsigned(keys[upper]) < Util.toIntUnsigned(x)) {// means array has no item key >=
+ // x
+ return size;
+ }
+
+ // we know that the next-smallest span was too small
+ lower += (spansize / 2);
+
+ // else begin binary search
+ // invariant: array[lower]
+ * The current bitmap is not modified.
+ *
+ * @param out the DataOutput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public void serialize(DataOutput out) throws IOException {
+ int startOffset = 0;
+ boolean hasrun = hasRunContainer();
+ if (hasrun) {
+ out.writeInt(Integer.reverseBytes(SERIAL_COOKIE | ((size - 1) << 16)));
+ byte[] bitmapOfRunContainers = new byte[(size + 7) / 8];
+ for (int i = 0; i < size; ++i) {
+ if (this.values[i] instanceof RunContainer) {
+ bitmapOfRunContainers[i / 8] |= (1 << (i % 8));
+ }
+ }
+ out.write(bitmapOfRunContainers);
+ if (this.size < NO_OFFSET_THRESHOLD) {
+ startOffset = 4 + 4 * this.size + bitmapOfRunContainers.length;
+ } else {
+ startOffset = 4 + 8 * this.size + bitmapOfRunContainers.length;
+ }
+ } else { // backwards compatibility
+ out.writeInt(Integer.reverseBytes(SERIAL_COOKIE_NO_RUNCONTAINER));
+ out.writeInt(Integer.reverseBytes(size));
+ startOffset = 4 + 4 + 4 * this.size + 4 * this.size;
+ }
+ for (int k = 0; k < size; ++k) {
+ out.writeShort(Short.reverseBytes(this.keys[k]));
+ out.writeShort(Short.reverseBytes((short) (this.values[k].getCardinality() - 1)));
+ }
+ if ((!hasrun) || (this.size >= NO_OFFSET_THRESHOLD)) {
+ // writing the containers offsets
+ for (int k = 0; k < this.size; k++) {
+ out.writeInt(Integer.reverseBytes(startOffset));
+ startOffset = startOffset + this.values[k].getArraySizeInBytes();
+ }
+ }
+ for (int k = 0; k < size; ++k) {
+ values[k].writeArray(out);
+ }
+ }
+
+ /**
+ * Report the number of bytes required for serialization.
+ *
+ * @return the size in bytes
+ */
+ public int serializedSizeInBytes() {
+ int count = headerSize();
+ for (int k = 0; k < size; ++k) {
+ count += values[k].getArraySizeInBytes();
+ }
+ return count;
+ }
+
+ protected void setContainerAtIndex(int i, Container c) {
+ this.values[i] = c;
+ }
+
+ protected int size() {
+ return this.size;
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput out) throws IOException {
+ serialize(out);
+ }
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RoaringBitmap.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RoaringBitmap.java
new file mode 100644
index 000000000..e300cb8b8
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RoaringBitmap.java
@@ -0,0 +1,2205 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap;
+
+import com.fr.third.bitmap.roaringbitmap.buffer.ImmutableRoaringBitmap;
+import com.fr.third.bitmap.roaringbitmap.buffer.MappeableContainerPointer;
+import com.fr.third.bitmap.roaringbitmap.buffer.MutableRoaringBitmap;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.util.Iterator;
+
+
+/**
+ * RoaringBitmap, a compressed alternative to the BitSet.
+ *
+ *
+ * Integers are added in unsigned sorted order. That is, they are treated as unsigned integers (see
+ * Java 8's Integer.toUnsignedLong function).
+ *
+ * Bitmaps are limited to a maximum of Integer.MAX_VALUE entries. Trying to create larger bitmaps
+ * could result in undefined behaviors.
+ */
+
+
+public class RoaringBitmap implements Cloneable, Serializable, Iterable
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ * @see FastAggregation#and(RoaringBitmap...)
+ */
+ public static RoaringBitmap and(final RoaringBitmap x1, final RoaringBitmap x2) {
+ final RoaringBitmap answer = new RoaringBitmap();
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+ int pos1 = 0, pos2 = 0;
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ if (s1 == s2) {
+ final Container c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ final Container c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ final Container c = c1.and(c2);
+ if (c.getCardinality() > 0) {
+ answer.highLowContainer.append(s1, c);
+ }
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1 = x1.highLowContainer.advanceUntil(s2, pos1);
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Cardinality of Bitwise AND (intersection) operation. The provided bitmaps are *not* modified.
+ * This operation is thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return as if you did and(x2,x2).getCardinality()
+ * @see FastAggregation#and(RoaringBitmap...)
+ */
+ public static int andCardinality(final RoaringBitmap x1, final RoaringBitmap x2) {
+ int answer = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+ int pos1 = 0, pos2 = 0;
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ if (s1 == s2) {
+ final Container c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ final Container c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ // TODO: could be made faster if we did not have to materialize container
+ answer += c1.andCardinality(c2);
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1 = x1.highLowContainer.advanceUntil(s2, pos1);
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Bitwise ANDNOT (difference) operation. The provided bitmaps are *not* modified. This operation
+ * is thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ */
+ public static RoaringBitmap andNot(final RoaringBitmap x1, final RoaringBitmap x2) {
+ final RoaringBitmap answer = new RoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ if (s1 == s2) {
+ final Container c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ final Container c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ final Container c = c1.andNot(c2);
+ if (c.getCardinality() > 0) {
+ answer.highLowContainer.append(s1, c);
+ }
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ final int nextPos1 = x1.highLowContainer.advanceUntil(s2, pos1);
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1, nextPos1);
+ pos1 = nextPos1;
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ if (pos2 == length2) {
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1, length1);
+ }
+ return answer;
+ }
+
+ /**
+ * Generate a bitmap with the specified values set to true. The provided integers values don't
+ * have to be in sorted order, but it may be preferable to sort them from a performance point of
+ * view.
+ *
+ * @param dat set values
+ * @return a new bitmap
+ */
+ public static RoaringBitmap bitmapOf(final int... dat) {
+ final RoaringBitmap ans = new RoaringBitmap();
+ ans.add(dat);
+ return ans;
+ }
+
+ /**
+ * Complements the bits in the given range, from rangeStart (inclusive) rangeEnd (exclusive). The
+ * given bitmap is unchanged.
+ *
+ * @param bm bitmap being negated
+ * @param rangeStart inclusive beginning of range, in [0, 0xffffffff]
+ * @param rangeEnd exclusive ending of range, in [0, 0xffffffff + 1]
+ * @return a new Bitmap
+ */
+ public static RoaringBitmap flip(RoaringBitmap bm, final long rangeStart, final long rangeEnd) {
+ rangeSanityCheck(rangeStart, rangeEnd);
+ if (rangeStart >= rangeEnd) {
+ return bm.clone();
+ }
+ RoaringBitmap answer = new RoaringBitmap();
+ final int hbStart = Util.toIntUnsigned(Util.highbits(rangeStart));
+ final int lbStart = Util.toIntUnsigned(Util.lowbits(rangeStart));
+ final int hbLast = Util.toIntUnsigned(Util.highbits(rangeEnd - 1));
+ final int lbLast = Util.toIntUnsigned(Util.lowbits(rangeEnd - 1));
+
+ // copy the containers before the active area
+ answer.highLowContainer.appendCopiesUntil(bm.highLowContainer, (short) hbStart);
+
+ for (int hb = hbStart; hb <= hbLast; ++hb) {
+ final int containerStart = (hb == hbStart) ? lbStart : 0;
+ final int containerLast = (hb == hbLast) ? lbLast : Util.maxLowBitAsInteger();
+
+ final int i = bm.highLowContainer.getIndex((short) hb);
+ final int j = answer.highLowContainer.getIndex((short) hb);
+ assert j < 0;
+
+ if (i >= 0) {
+ Container c =
+ bm.highLowContainer.getContainerAtIndex(i).not(containerStart, containerLast + 1);
+ if (c.getCardinality() > 0) {
+ answer.highLowContainer.insertNewKeyValueAt(-j - 1, (short) hb, c);
+ }
+
+ } else { // *think* the range of ones must never be
+ // empty.
+ answer.highLowContainer.insertNewKeyValueAt(-j - 1, (short) hb,
+ Container.rangeOfOnes(containerStart, containerLast + 1));
+ }
+ }
+ // copy the containers after the active area.
+ answer.highLowContainer.appendCopiesAfter(bm.highLowContainer, (short) hbLast);
+ return answer;
+ }
+
+ /**
+ * Complements the bits in the given range, from rangeStart (inclusive) rangeEnd (exclusive). The
+ * given bitmap is unchanged.
+ *
+ * @param rb bitmap being negated
+ * @param rangeStart inclusive beginning of range, in [0, 0xffffffff]
+ * @param rangeEnd exclusive ending of range, in [0, 0xffffffff + 1]
+ * @return a new Bitmap
+ * @deprecated use the version where longs specify the range
+ */
+ @Deprecated
+ public static RoaringBitmap flip(RoaringBitmap rb, final int rangeStart, final int rangeEnd) {
+ if (rangeStart >= 0) {
+ return flip(rb, (long) rangeStart, (long) rangeEnd);
+ }
+ // rangeStart being -ve and rangeEnd being positive is not expected)
+ // so assume both -ve
+ return flip(rb, rangeStart & 0xFFFFFFFFL, rangeEnd & 0xFFFFFFFFL);
+ }
+
+
+ /**
+ * Checks whether the two bitmaps intersect. This can be much faster than calling "and" and
+ * checking the cardinality of the result.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return true if they intersect
+ */
+ public static boolean intersects(final RoaringBitmap x1, final RoaringBitmap x2) {
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+ int pos1 = 0, pos2 = 0;
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ if (s1 == s2) {
+ final Container c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ final Container c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ if (c1.intersects(c2)) {
+ return true;
+ }
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1 = x1.highLowContainer.advanceUntil(s2, pos1);
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ return false;
+ }
+
+
+ // important: inputs should not have been computed lazily
+ protected static RoaringBitmap lazyor(final RoaringBitmap x1, final RoaringBitmap x2) {
+ final RoaringBitmap answer = new RoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ answer.highLowContainer.append(s1, x1.highLowContainer.getContainerAtIndex(pos1)
+ .lazyOR(x2.highLowContainer.getContainerAtIndex(pos2)));
+ pos1++;
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1);
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ } else { // s1 > s2
+ answer.highLowContainer.appendCopy(x2.highLowContainer, pos2);
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ answer.highLowContainer.appendCopy(x2.highLowContainer, pos2, length2);
+ } else if (pos2 == length2) {
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1, length1);
+ }
+ return answer;
+ }
+
+ // important: inputs should not be reused
+ protected static RoaringBitmap lazyorfromlazyinputs(final RoaringBitmap x1,
+ final RoaringBitmap x2) {
+ final RoaringBitmap answer = new RoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ Container c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ Container c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ if ((c2 instanceof BitmapContainer) && (!(c1 instanceof BitmapContainer))) {
+ Container tmp = c1;
+ c1 = c2;
+ c2 = tmp;
+ }
+ answer.highLowContainer.append(s1, c1.lazyIOR(c2));
+ pos1++;
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ Container c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ answer.highLowContainer.append(s1, c1);
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ } else { // s1 > s2
+ Container c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ answer.highLowContainer.append(s2, c2);
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ answer.highLowContainer.append(x2.highLowContainer, pos2, length2);
+ } else if (pos2 == length2) {
+ answer.highLowContainer.append(x1.highLowContainer, pos1, length1);
+ }
+ return answer;
+ }
+
+
+ /**
+ * Compute overall OR between bitmaps.
+ *
+ * (Effectively calls {@link FastAggregation#or})
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap or(Iterator
+ * (Effectively calls {@link FastAggregation#or})
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static RoaringBitmap or(RoaringBitmap... bitmaps) {
+ return FastAggregation.or(bitmaps);
+ }
+
+ /**
+ * Bitwise OR (union) operation. The provided bitmaps are *not* modified. This operation is
+ * thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ * @see FastAggregation#or(RoaringBitmap...)
+ * @see FastAggregation#horizontal_or(RoaringBitmap...)
+ */
+ public static RoaringBitmap or(final RoaringBitmap x1, final RoaringBitmap x2) {
+ final RoaringBitmap answer = new RoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ answer.highLowContainer.append(s1, x1.highLowContainer.getContainerAtIndex(pos1)
+ .or(x2.highLowContainer.getContainerAtIndex(pos2)));
+ pos1++;
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1);
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ } else { // s1 > s2
+ answer.highLowContainer.appendCopy(x2.highLowContainer, pos2);
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ answer.highLowContainer.appendCopy(x2.highLowContainer, pos2, length2);
+ } else if (pos2 == length2) {
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1, length1);
+ }
+ return answer;
+ }
+
+ /**
+ * Cardinality of the bitwise OR (union) operation. The provided bitmaps are *not* modified. This
+ * operation is thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return cardinality of the union
+ * @see FastAggregation#or(RoaringBitmap...)
+ * @see FastAggregation#horizontal_or(RoaringBitmap...)
+ */
+ public static int orCardinality(final RoaringBitmap x1, final RoaringBitmap x2) {
+ // we use the fact that the cardinality of the bitmaps is known so that
+ // the union is just the total cardinality minus the intersection
+ return x1.getCardinality() + x2.getCardinality() - andCardinality(x1, x2);
+ }
+
+
+ /**
+ * Generate a new bitmap with all integers in [rangeStart,rangeEnd) removed.
+ *
+ * @param rb initial bitmap (will not be modified)
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new bitmap
+ */
+ public static RoaringBitmap remove(RoaringBitmap rb, final long rangeStart, final long rangeEnd) {
+ rangeSanityCheck(rangeStart, rangeEnd);
+ if (rangeStart >= rangeEnd) {
+ return rb.clone(); // empty range
+ }
+
+
+ final int hbStart = Util.toIntUnsigned(Util.highbits(rangeStart));
+ final int lbStart = Util.toIntUnsigned(Util.lowbits(rangeStart));
+ final int hbLast = Util.toIntUnsigned(Util.highbits(rangeEnd - 1));
+ final int lbLast = Util.toIntUnsigned(Util.lowbits(rangeEnd - 1));
+ RoaringBitmap answer = new RoaringBitmap();
+ answer.highLowContainer.appendCopiesUntil(rb.highLowContainer, (short) hbStart);
+
+ if (hbStart == hbLast) {
+ final int i = rb.highLowContainer.getIndex((short) hbStart);
+ if (i >= 0) {
+ final Container c = rb.highLowContainer.getContainerAtIndex(i).remove(lbStart, lbLast + 1);
+ if (c.getCardinality() > 0) {
+ answer.highLowContainer.append((short) hbStart, c);
+ }
+ }
+ answer.highLowContainer.appendCopiesAfter(rb.highLowContainer, (short) hbLast);
+ return answer;
+ }
+ int ifirst = rb.highLowContainer.getIndex((short) hbStart);
+ int ilast = rb.highLowContainer.getIndex((short) hbLast);
+ if ((ifirst >= 0) && (lbStart != 0)) {
+ final Container c = rb.highLowContainer.getContainerAtIndex(ifirst).remove(lbStart,
+ Util.maxLowBitAsInteger() + 1);
+ if (c.getCardinality() > 0) {
+ answer.highLowContainer.append((short) hbStart, c);
+ }
+ }
+ if ((ilast >= 0) && (lbLast != Util.maxLowBitAsInteger())) {
+ final Container c = rb.highLowContainer.getContainerAtIndex(ilast).remove(0, lbLast + 1);
+ if (c.getCardinality() > 0) {
+ answer.highLowContainer.append((short) hbLast, c);
+ }
+ }
+ answer.highLowContainer.appendCopiesAfter(rb.highLowContainer, (short) hbLast);
+ return answer;
+ }
+
+ /**
+ * Generate a new bitmap with all integers in [rangeStart,rangeEnd) removed.
+ *
+ * @param rb initial bitmap (will not be modified)
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new bitmap
+ * @deprecated use the version where longs specify the range
+ */
+ @Deprecated
+ public static RoaringBitmap remove(RoaringBitmap rb, final int rangeStart, final int rangeEnd) {
+ if (rangeStart >= 0) {
+ return remove(rb, (long) rangeStart, (long) rangeEnd);
+ }
+ // rangeStart being -ve and rangeEnd being positive is not expected)
+ // so assume both -ve
+ return remove(rb, rangeStart & 0xFFFFFFFFL, rangeEnd & 0xFFFFFFFFL);
+ }
+
+
+ /**
+ * Bitwise XOR (symmetric difference) operation. The provided bitmaps are *not* modified. This
+ * operation is thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ * @see FastAggregation#xor(RoaringBitmap...)
+ * @see FastAggregation#horizontal_xor(RoaringBitmap...)
+ */
+ public static RoaringBitmap xor(final RoaringBitmap x1, final RoaringBitmap x2) {
+ final RoaringBitmap answer = new RoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ final Container c = x1.highLowContainer.getContainerAtIndex(pos1)
+ .xor(x2.highLowContainer.getContainerAtIndex(pos2));
+ if (c.getCardinality() > 0) {
+ answer.highLowContainer.append(s1, c);
+ }
+ pos1++;
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1);
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ } else { // s1 > s2
+ answer.highLowContainer.appendCopy(x2.highLowContainer, pos2);
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ answer.highLowContainer.appendCopy(x2.highLowContainer, pos2, length2);
+ } else if (pos2 == length2) {
+ answer.highLowContainer.appendCopy(x1.highLowContainer, pos1, length1);
+ }
+
+ return answer;
+ }
+
+ /**
+ * Computes AND between input bitmaps in the given range, from rangeStart (inclusive) to rangeEnd
+ * (exclusive)
+ *
+ * @param bitmaps input bitmaps, these are not modified
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new result bitmap
+ */
+ public static RoaringBitmap and(@SuppressWarnings("rawtypes") final Iterator bitmaps,
+ final long rangeStart, final long rangeEnd) {
+ rangeSanityCheck(rangeStart, rangeEnd);
+
+ Iterator
+ * The current bitmap is overwritten.
+ *
+ * @param in the DataInput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public void deserialize(DataInput in) throws IOException {
+ this.highLowContainer.deserialize(in);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof RoaringBitmap) {
+ final RoaringBitmap srb = (RoaringBitmap) o;
+ return srb.highLowContainer.equals(this.highLowContainer);
+ }
+ return false;
+ }
+
+ /**
+ * Add the value if it is not already present, otherwise remove it.
+ *
+ * @param x integer value
+ */
+ public void flip(final int x) {
+ final short hb = Util.highbits(x);
+ final int i = highLowContainer.getIndex(hb);
+ if (i >= 0) {
+ Container c = highLowContainer.getContainerAtIndex(i).flip(Util.lowbits(x));
+ if (c.getCardinality() > 0) {
+ highLowContainer.setContainerAtIndex(i, c);
+ } else {
+ highLowContainer.removeAtIndex(i);
+ }
+ } else {
+ final ArrayContainer newac = new ArrayContainer();
+ highLowContainer.insertNewKeyValueAt(-i - 1, hb, newac.add(Util.lowbits(x)));
+ }
+ }
+
+ /**
+ * Modifies the current bitmap by complementing the bits in the given range, from rangeStart
+ * (inclusive) rangeEnd (exclusive).
+ *
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ */
+ public void flip(final long rangeStart, final long rangeEnd) {
+ rangeSanityCheck(rangeStart, rangeEnd);
+ if (rangeStart >= rangeEnd) {
+ return; // empty range
+ }
+
+ final int hbStart = Util.toIntUnsigned(Util.highbits(rangeStart));
+ final int lbStart = Util.toIntUnsigned(Util.lowbits(rangeStart));
+ final int hbLast = Util.toIntUnsigned(Util.highbits(rangeEnd - 1));
+ final int lbLast = Util.toIntUnsigned(Util.lowbits(rangeEnd - 1));
+
+ // TODO:this can be accelerated considerably
+ for (int hb = hbStart; hb <= hbLast; ++hb) {
+ // first container may contain partial range
+ final int containerStart = (hb == hbStart) ? lbStart : 0;
+ // last container may contain partial range
+ final int containerLast = (hb == hbLast) ? lbLast : Util.maxLowBitAsInteger();
+ final int i = highLowContainer.getIndex((short) hb);
+
+ if (i >= 0) {
+ final Container c =
+ highLowContainer.getContainerAtIndex(i).inot(containerStart, containerLast + 1);
+ if (c.getCardinality() > 0) {
+ highLowContainer.setContainerAtIndex(i, c);
+ } else {
+ highLowContainer.removeAtIndex(i);
+ }
+ } else {
+ highLowContainer.insertNewKeyValueAt(-i - 1, (short) hb,
+ Container.rangeOfOnes(containerStart, containerLast + 1));
+ }
+ }
+ }
+
+ /**
+ * Modifies the current bitmap by complementing the bits in the given range, from rangeStart
+ * (inclusive) rangeEnd (exclusive).
+ *
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @deprecated use the version where longs specify the range
+ */
+ @Deprecated
+ public void flip(final int rangeStart, final int rangeEnd) {
+ if (rangeStart >= 0) {
+ flip((long) rangeStart, (long) rangeEnd);
+ } else {
+ // rangeStart being -ve and rangeEnd being positive is not expected)
+ // so assume both -ve
+ flip(rangeStart & 0xFFFFFFFFL, rangeEnd & 0xFFFFFFFFL);
+ }
+ }
+
+ /**
+ * Returns the number of distinct integers added to the bitmap (e.g., number of bits set).
+ *
+ * @return the cardinality
+ */
+ @Override
+ public long getLongCardinality() {
+ long size = 0;
+ for (int i = 0; i < this.highLowContainer.size(); i++) {
+ size += this.highLowContainer.getContainerAtIndex(i).getCardinality();
+ }
+ return size;
+ }
+
+ @Override
+ public int getCardinality() {
+ return (int) getLongCardinality();
+ }
+
+ @Override
+ public void forEach(IntConsumer ic) {
+ for (int i = 0; i < this.highLowContainer.size(); i++) {
+ this.highLowContainer.getContainerAtIndex(i).forEach(this.highLowContainer.keys[i], ic);
+ }
+ }
+
+ /**
+ * Return a low-level container pointer that can be used to access the underlying data structure.
+ *
+ * @return container pointer
+ */
+ public ContainerPointer getContainerPointer() {
+ return this.highLowContainer.getContainerPointer();
+ }
+
+ /**
+ * For better performance, consider the Use the {@link #forEach forEach} method.
+ *
+ * @return a custom iterator over set bits, the bits are traversed in ascending sorted order
+ */
+ @Override
+ public PeekableIntIterator getIntIterator() {
+ return new RoaringIntIterator();
+ }
+
+ /**
+ * @return a custom iterator over set bits, the bits are traversed in descending sorted order
+ */
+ @Override
+ public IntIterator getReverseIntIterator() {
+ return new RoaringReverseIntIterator();
+ }
+
+ /**
+ * Estimate of the memory usage of this data structure. This can be expected to be within 1% of
+ * the true memory usage.
+ *
+ * @return estimated memory usage.
+ */
+ @Override
+ public long getLongSizeInBytes() {
+ long size = 8;
+ for (int i = 0; i < this.highLowContainer.size(); i++) {
+ final Container c = this.highLowContainer.getContainerAtIndex(i);
+ size += 2 + c.getSizeInBytes();
+ }
+ return size;
+ }
+
+ @Override
+ public int getSizeInBytes() {
+ return (int) getLongSizeInBytes();
+ }
+
+ @Override
+ public int hashCode() {
+ return highLowContainer.hashCode();
+ }
+
+ /**
+ * Check whether this bitmap has had its runs compressed.
+ *
+ * @return whether this bitmap has run compression
+ */
+ public boolean hasRunCompression() {
+ for (int i = 0; i < this.highLowContainer.size(); i++) {
+ Container c = this.highLowContainer.getContainerAtIndex(i);
+ if (c instanceof RunContainer) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the bitmap is empty.
+ *
+ * @return true if this bitmap contains no set bit
+ */
+ @Override
+ public boolean isEmpty() {
+ return highLowContainer.size() == 0;
+ }
+
+ /**
+ * iterate over the positions of the true values.
+ *
+ * @return the iterator
+ */
+ @Override
+ public Iterator
+ * Consider calling {@link #runOptimize} before serialization to improve compression.
+ *
+ * The current bitmap is not modified.
+ *
+ * Advanced example: To serialize your bitmap to a ByteBuffer, you can do the following.
+ *
+ *
+ * Note: Java's data structures are in big endian format. Roaring serializes to a little endian
+ * format, so the bytes are flipped by the library during serialization to ensure that what is
+ * stored is in little endian---despite Java's big endianness. You can defeat this process by
+ * reflipping the bytes again in a custom DataOutput which could lead to serialized Roaring
+ * objects with an incorrect byte order.
+ *
+ * @param out the DataOutput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ @Override
+ public void serialize(DataOutput out) throws IOException {
+ this.highLowContainer.serialize(out);
+ }
+
+ /**
+ * Report the number of bytes required to serialize this bitmap. This is the number of bytes
+ * written out when using the serialize method. When using the writeExternal method, the count
+ * will be higher due to the overhead of Java serialization.
+ *
+ * @return the size in bytes
+ */
+ @Override
+ public int serializedSizeInBytes() {
+ return this.highLowContainer.serializedSizeInBytes();
+ }
+
+ /**
+ * Return the set values as an array, if the cardinality is smaller than 2147483648.
+ * The integer values are in sorted order.
+ *
+ * @return array representing the set values.
+ */
+ @Override
+ public int[] toArray() {
+ final int[] array = new int[(int) this.getCardinality()];
+ int pos = 0, pos2 = 0;
+ while (pos < this.highLowContainer.size()) {
+ final int hs = this.highLowContainer.getKeyAtIndex(pos) << 16;
+ Container c = this.highLowContainer.getContainerAtIndex(pos++);
+ c.fillLeastSignificant16bits(array, pos2, hs);
+ pos2 += c.getCardinality();
+ }
+ return array;
+ }
+
+
+ /**
+ * Convert (copies) to a mutable roaring bitmap.
+ *
+ * @return a copy of this bitmap as a MutableRoaringBitmap
+ */
+ public MutableRoaringBitmap toMutableRoaringBitmap() {
+ return new MutableRoaringBitmap(this);
+ }
+
+ /**
+ * A string describing the bitmap.
+ *
+ * @return the string
+ */
+ @Override
+ public String toString() {
+ final StringBuilder answer = new StringBuilder();
+ final IntIterator i = this.getIntIterator();
+ answer.append("{");
+ if (i.hasNext()) {
+ answer.append(i.next() & 0xFFFFFFFFL);
+ }
+ while (i.hasNext()) {
+ answer.append(",");
+ // to avoid using too much memory, we limit the size
+ if (answer.length() > 0x80000) {
+ answer.append("...");
+ break;
+ }
+ answer.append(i.next() & 0xFFFFFFFFL);
+
+ }
+ answer.append("}");
+ return answer.toString();
+ }
+
+ /**
+ * Recover allocated but unused memory.
+ */
+ public void trim() {
+ for (int i = 0; i < this.highLowContainer.size(); i++) {
+ this.highLowContainer.getContainerAtIndex(i).trim();
+ }
+ }
+
+
+ @Override
+ public void writeExternal(ObjectOutput out) throws IOException {
+ this.highLowContainer.writeExternal(out);
+ }
+
+ /**
+ * In-place bitwise XOR (symmetric difference) operation. The current bitmap is modified.
+ *
+ * @param x2 other bitmap
+ */
+ public void xor(final RoaringBitmap x2) {
+ int pos1 = 0, pos2 = 0;
+ int length1 = highLowContainer.size();
+ final int length2 = x2.highLowContainer.size();
+
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ final Container c = highLowContainer.getContainerAtIndex(pos1)
+ .ixor(x2.highLowContainer.getContainerAtIndex(pos2));
+ if (c.getCardinality() > 0) {
+ this.highLowContainer.setContainerAtIndex(pos1, c);
+ pos1++;
+ } else {
+ highLowContainer.removeAtIndex(pos1);
+ --length1;
+ }
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = highLowContainer.getKeyAtIndex(pos1);
+ } else { // s1 > s2
+ highLowContainer.insertNewKeyValueAt(pos1, s2,
+ x2.highLowContainer.getContainerAtIndex(pos2).clone());
+ pos1++;
+ length1++;
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ highLowContainer.appendCopy(x2.highLowContainer, pos2, length2);
+ }
+ }
+
+ private final class RoaringIntIterator implements PeekableIntIterator {
+ private int hs = 0;
+
+ private PeekableShortIterator iter;
+
+ private int pos = 0;
+
+ private RoaringIntIterator() {
+ nextContainer();
+ }
+
+ @Override
+ public PeekableIntIterator clone() {
+ try {
+ RoaringIntIterator x = (RoaringIntIterator) super.clone();
+ x.iter = this.iter.clone();
+ return x;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return pos < RoaringBitmap.this.highLowContainer.size();
+ }
+
+ @Override
+ public int next() {
+ final int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ ++pos;
+ nextContainer();
+ }
+ return x;
+ }
+
+ private void nextContainer() {
+ if (pos < RoaringBitmap.this.highLowContainer.size()) {
+ iter = RoaringBitmap.this.highLowContainer.getContainerAtIndex(pos).getShortIterator();
+ hs = RoaringBitmap.this.highLowContainer.getKeyAtIndex(pos) << 16;
+ }
+ }
+
+ @Override
+ public void advanceIfNeeded(int minval) {
+ while (hasNext() && ((hs >>> 16) < (minval >>> 16))) {
+ ++pos;
+ nextContainer();
+ }
+ if (hasNext() && ((hs >>> 16) == (minval >>> 16))) {
+ iter.advanceIfNeeded(Util.lowbits(minval));
+ if (!iter.hasNext()) {
+ ++pos;
+ nextContainer();
+ }
+ }
+ }
+
+ @Override
+ public int peekNext() {
+ return Util.toIntUnsigned(iter.peekNext()) | hs;
+ }
+
+
+ }
+
+ private final class RoaringReverseIntIterator implements IntIterator {
+
+ int hs = 0;
+
+ ShortIterator iter;
+
+ // don't need an int because we go to 0, not Short.MAX_VALUE, and signed shorts underflow well
+ // below zero
+ short pos = (short) (RoaringBitmap.this.highLowContainer.size() - 1);
+
+ private RoaringReverseIntIterator() {
+ nextContainer();
+ }
+
+ @Override
+ public IntIterator clone() {
+ try {
+ RoaringReverseIntIterator clone = (RoaringReverseIntIterator) super.clone();
+ clone.iter = this.iter.clone();
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return pos >= 0;
+ }
+
+ @Override
+ public int next() {
+ final int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ --pos;
+ nextContainer();
+ }
+ return x;
+ }
+
+ private void nextContainer() {
+ if (pos >= 0) {
+ iter =
+ RoaringBitmap.this.highLowContainer.getContainerAtIndex(pos).getReverseShortIterator();
+ hs = RoaringBitmap.this.highLowContainer.getKeyAtIndex(pos) << 16;
+ }
+ }
+
+ }
+
+
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RunContainer.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RunContainer.java
new file mode 100644
index 000000000..c078b52a8
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/RunContainer.java
@@ -0,0 +1,2518 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+package com.fr.third.bitmap.roaringbitmap;
+
+import com.fr.third.bitmap.roaringbitmap.buffer.MappeableContainer;
+import com.fr.third.bitmap.roaringbitmap.buffer.MappeableRunContainer;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+import java.util.Iterator;
+
+
+/**
+ * This container takes the form of runs of consecutive values (effectively, run-length encoding).
+ *
+ * Adding and removing content from this container might make it wasteful so regular calls to
+ * "runOptimize" might be warranted.
+ */
+public final class RunContainer extends Container implements Cloneable {
+ private static final int DEFAULT_INIT_SIZE = 4;
+ private static final boolean ENABLE_GALLOPING_AND = false;
+
+ private static final long serialVersionUID = 1L;
+ int nbrruns = 0;// how many runs, this number should fit in 16 bits.
+ private short[] valueslength;// we interleave values and lengths, so
+
+ /**
+ * Create a container with default capacity
+ */
+ public RunContainer() {
+ this(DEFAULT_INIT_SIZE);
+ }
+
+ protected RunContainer(ArrayContainer arr, int nbrRuns) {
+ this.nbrruns = nbrRuns;
+ valueslength = new short[2 * nbrRuns];
+ if (nbrRuns == 0) {
+ return;
+ }
+
+ int prevVal = -2;
+ int runLen = 0;
+ int runCount = 0;
+
+ for (int i = 0; i < arr.cardinality; i++) {
+ int curVal = Util.toIntUnsigned(arr.content[i]);
+ if (curVal == prevVal + 1) {
+ ++runLen;
+ } else {
+ if (runCount > 0) {
+ setLength(runCount - 1, (short) runLen);
+ }
+ setValue(runCount, (short) curVal);
+ runLen = 0;
+ ++runCount;
+ }
+ prevVal = curVal;
+ }
+ setLength(runCount - 1, (short) runLen);
+ }
+
+ // convert a bitmap container to a run container somewhat efficiently.
+ protected RunContainer(BitmapContainer bc, int nbrRuns) {
+ this.nbrruns = nbrRuns;
+ valueslength = new short[2 * nbrRuns];
+ if (nbrRuns == 0) {
+ return;
+ }
+
+ int longCtr = 0; // index of current long in bitmap
+ long curWord = bc.bitmap[0]; // its value
+ int runCount = 0;
+ while (true) {
+ // potentially multiword advance to first 1 bit
+ while (curWord == 0L && longCtr < bc.bitmap.length - 1) {
+ curWord = bc.bitmap[++longCtr];
+ }
+
+ if (curWord == 0L) {
+ // wrap up, no more runs
+ return;
+ }
+ int localRunStart = Long.numberOfTrailingZeros(curWord);
+ int runStart = localRunStart + 64 * longCtr;
+ // stuff 1s into number's LSBs
+ long curWordWith1s = curWord | (curWord - 1);
+
+ // find the next 0, potentially in a later word
+ int runEnd = 0;
+ while (curWordWith1s == -1L && longCtr < bc.bitmap.length - 1) {
+ curWordWith1s = bc.bitmap[++longCtr];
+ }
+
+ if (curWordWith1s == -1L) {
+ // a final unterminated run of 1s (32 of them)
+ runEnd = 64 + longCtr * 64;
+ setValue(runCount, (short) runStart);
+ setLength(runCount, (short) (runEnd - runStart - 1));
+ return;
+ }
+ int localRunEnd = Long.numberOfTrailingZeros(~curWordWith1s);
+ runEnd = localRunEnd + longCtr * 64;
+ setValue(runCount, (short) runStart);
+ setLength(runCount, (short) (runEnd - runStart - 1));
+ runCount++;
+ // now, zero out everything right of runEnd.
+ curWord = curWordWith1s & (curWordWith1s + 1);
+ // We've lathered and rinsed, so repeat...
+ }
+ }
+ // that if you have the values 11,12,13,14,15, you store that as 11,4 where 4 means that beyond 11
+ // itself, there are
+ // 4 contiguous values that follows.
+ // Other example: e.g., 1, 10, 20,0, 31,2 would be a concise representation of 1, 2, ..., 11, 20,
+ // 31, 32, 33
+
+ /**
+ * Create an array container with specified capacity
+ *
+ * @param capacity The capacity of the container
+ */
+ public RunContainer(final int capacity) {
+ valueslength = new short[2 * capacity];
+ }
+
+
+ private RunContainer(int nbrruns, short[] valueslength) {
+ this.nbrruns = nbrruns;
+ this.valueslength = Arrays.copyOf(valueslength, valueslength.length);
+ }
+
+
+ /**
+ * Creates a new non-mappeable container from a mappeable one. This copies the data.
+ *
+ * @param bc the original container
+ */
+ public RunContainer(MappeableRunContainer bc) {
+ this.nbrruns = bc.numberOfRuns();
+ this.valueslength = bc.toShortArray();
+ }
+
+ /**
+ * Construct a new RunContainer backed by the provided array. Note that if you modify the
+ * RunContainer a new array may be produced.
+ *
+ * @param array array where the data is stored
+ * @param numRuns number of runs (each using 2 shorts in the buffer)
+ */
+ public RunContainer(final short[] array, final int numRuns) {
+ if (array.length < 2 * numRuns) {
+ throw new RuntimeException("Mismatch between buffer and numRuns");
+ }
+ this.nbrruns = numRuns;
+ this.valueslength = array;
+ }
+
+ private static int branchyUnsignedInterleavedBinarySearch(final short[] array, final int begin,
+ final int end, final short k) {
+ int ikey = Util.toIntUnsigned(k);
+ int low = begin;
+ int high = end - 1;
+ while (low <= high) {
+ final int middleIndex = (low + high) >>> 1;
+ final int middleValue = Util.toIntUnsigned(array[2 * middleIndex]);
+ if (middleValue < ikey) {
+ low = middleIndex + 1;
+ } else if (middleValue > ikey) {
+ high = middleIndex - 1;
+ } else {
+ return middleIndex;
+ }
+ }
+ return -(low + 1);
+ }
+
+ // starts with binary search and finishes with a sequential search
+ private static int hybridUnsignedInterleavedBinarySearch(final short[] array, final int begin,
+ final int end, final short k) {
+ int ikey = Util.toIntUnsigned(k);
+ int low = begin;
+ int high = end - 1;
+ // 16 in the next line matches the size of a cache line
+ while (low + 16 <= high) {
+ final int middleIndex = (low + high) >>> 1;
+ final int middleValue = Util.toIntUnsigned(array[2 * middleIndex]);
+ if (middleValue < ikey) {
+ low = middleIndex + 1;
+ } else if (middleValue > ikey) {
+ high = middleIndex - 1;
+ } else {
+ return middleIndex;
+ }
+ }
+ // we finish the job with a sequential search
+ int x = low;
+ for (; x <= high; ++x) {
+ final int val = Util.toIntUnsigned(array[2 * x]);
+ if (val >= ikey) {
+ if (val == ikey) {
+ return x;
+ }
+ break;
+ }
+ }
+ return -(x + 1);
+ }
+
+ protected static int serializedSizeInBytes(int numberOfRuns) {
+ return 2 + 2 * 2 * numberOfRuns; // each run requires 2 2-byte entries.
+ }
+
+ private static int unsignedInterleavedBinarySearch(final short[] array, final int begin,
+ final int end, final short k) {
+ if (Util.USE_HYBRID_BINSEARCH) {
+ return hybridUnsignedInterleavedBinarySearch(array, begin, end, k);
+ } else {
+ return branchyUnsignedInterleavedBinarySearch(array, begin, end, k);
+ }
+
+ }
+
+ @Override
+ public Container add(int begin, int end) {
+ RunContainer rc = (RunContainer) clone();
+ return rc.iadd(begin, end);
+ }
+
+ @Override
+ public Container add(short k) {
+ // TODO: it might be better and simpler to do return
+ // toBitmapOrArrayContainer(getCardinality()).add(k)
+ // but note that some unit tests use this method to build up test runcontainers without calling
+ // runOptimize
+ int index = unsignedInterleavedBinarySearch(valueslength, 0, nbrruns, k);
+ if (index >= 0) {
+ return this;// already there
+ }
+ index = -index - 2;// points to preceding value, possibly -1
+ if (index >= 0) {// possible match
+ int offset = Util.toIntUnsigned(k) - Util.toIntUnsigned(getValue(index));
+ int le = Util.toIntUnsigned(getLength(index));
+ if (offset <= le) {
+ return this;
+ }
+ if (offset == le + 1) {
+ // we may need to fuse
+ if (index + 1 < nbrruns) {
+ if (Util.toIntUnsigned(getValue(index + 1)) == Util.toIntUnsigned(k) + 1) {
+ // indeed fusion is needed
+ setLength(index,
+ (short) (getValue(index + 1) + getLength(index + 1) - getValue(index)));
+ recoverRoomAtIndex(index + 1);
+ return this;
+ }
+ }
+ incrementLength(index);
+ return this;
+ }
+ if (index + 1 < nbrruns) {
+ // we may need to fuse
+ if (Util.toIntUnsigned(getValue(index + 1)) == Util.toIntUnsigned(k) + 1) {
+ // indeed fusion is needed
+ setValue(index + 1, k);
+ setLength(index + 1, (short) (getLength(index + 1) + 1));
+ return this;
+ }
+ }
+ }
+ if (index == -1) {
+ // we may need to extend the first run
+ if (0 < nbrruns) {
+ if (getValue(0) == k + 1) {
+ incrementLength(0);
+ decrementValue(0);
+ return this;
+ }
+ }
+ }
+ makeRoomAtIndex(index + 1);
+ setValue(index + 1, k);
+ setLength(index + 1, (short) 0);
+ return this;
+ }
+
+ @Override
+ public Container and(ArrayContainer x) {
+ ArrayContainer ac = new ArrayContainer(x.cardinality);
+ if (this.nbrruns == 0) {
+ return ac;
+ }
+ int rlepos = 0;
+ int arraypos = 0;
+
+ int rleval = Util.toIntUnsigned(this.getValue(rlepos));
+ int rlelength = Util.toIntUnsigned(this.getLength(rlepos));
+ while (arraypos < x.cardinality) {
+ int arrayval = Util.toIntUnsigned(x.content[arraypos]);
+ while (rleval + rlelength < arrayval) {// this will frequently be false
+ ++rlepos;
+ if (rlepos == this.nbrruns) {
+ return ac;// we are done
+ }
+ rleval = Util.toIntUnsigned(this.getValue(rlepos));
+ rlelength = Util.toIntUnsigned(this.getLength(rlepos));
+ }
+ if (rleval > arrayval) {
+ arraypos = Util.advanceUntil(x.content, arraypos, x.cardinality, (short) rleval);
+ } else {
+ ac.content[ac.cardinality] = (short) arrayval;
+ ac.cardinality++;
+ arraypos++;
+ }
+ }
+ return ac;
+ }
+
+
+ @Override
+ public Container and(BitmapContainer x) {
+ // could be implemented as return toBitmapOrArrayContainer().iand(x);
+ int card = this.getCardinality();
+ if (card <= ArrayContainer.DEFAULT_MAX_SIZE) {
+ // result can only be an array (assuming that we never make a RunContainer)
+ if (card > x.cardinality) {
+ card = x.cardinality;
+ }
+ ArrayContainer answer = new ArrayContainer(card);
+ answer.cardinality = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int runStart = Util.toIntUnsigned(this.getValue(rlepos));
+ int runEnd = runStart + Util.toIntUnsigned(this.getLength(rlepos));
+ for (int runValue = runStart; runValue <= runEnd; ++runValue) {
+ if (x.contains((short) runValue)) {// it looks like contains() should be cheap enough if
+ // accessed sequentially
+ answer.content[answer.cardinality++] = (short) runValue;
+ }
+ }
+ }
+ return answer;
+ }
+ // we expect the answer to be a bitmap (if we are lucky)
+ BitmapContainer answer = x.clone();
+ int start = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int end = Util.toIntUnsigned(this.getValue(rlepos));
+ Util.resetBitmapRange(answer.bitmap, start, end); // had been x.bitmap
+ start = end + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ Util.resetBitmapRange(answer.bitmap, start, Util.maxLowBitAsInteger() + 1); // had been x.bitmap
+ answer.computeCardinality();
+ if (answer.getCardinality() > ArrayContainer.DEFAULT_MAX_SIZE) {
+ return answer;
+ } else {
+ return answer.toArrayContainer();
+ }
+ }
+
+ @Override
+ public Container and(RunContainer x) {
+ RunContainer answer = new RunContainer(new short[2 * (this.nbrruns + x.nbrruns)], 0);
+ int rlepos = 0;
+ int xrlepos = 0;
+ int start = Util.toIntUnsigned(this.getValue(rlepos));
+ int end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ int xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ int xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ while ((rlepos < this.nbrruns) && (xrlepos < x.nbrruns)) {
+ if (end <= xstart) {
+ if (ENABLE_GALLOPING_AND) {
+ rlepos = skipAhead(this, rlepos, xstart); // skip over runs until we have end > xstart (or
+ // rlepos is advanced beyond end)
+ } else {
+ ++rlepos;
+ }
+
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ } else if (xend <= start) {
+ // exit the second run
+ if (ENABLE_GALLOPING_AND) {
+ xrlepos = skipAhead(x, xrlepos, start);
+ } else {
+ ++xrlepos;
+ }
+
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else {// they overlap
+ final int lateststart = start > xstart ? start : xstart;
+ int earliestend;
+ if (end == xend) {// improbable
+ earliestend = end;
+ rlepos++;
+ xrlepos++;
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else if (end < xend) {
+ earliestend = end;
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+
+ } else {// end > xend
+ earliestend = xend;
+ xrlepos++;
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ }
+ answer.valueslength[2 * answer.nbrruns] = (short) lateststart;
+ answer.valueslength[2 * answer.nbrruns + 1] = (short) (earliestend - lateststart - 1);
+ answer.nbrruns++;
+ }
+ }
+ return answer.toEfficientContainer(); // subsequent trim() may be required to avoid wasted
+ // space.
+ }
+
+ @Override
+ public int andCardinality(ArrayContainer x) {
+ if (this.nbrruns == 0) {
+ return x.cardinality;
+ }
+ int rlepos = 0;
+ int arraypos = 0;
+ int andCardinality = 0;
+ int rleval = Util.toIntUnsigned(this.getValue(rlepos));
+ int rlelength = Util.toIntUnsigned(this.getLength(rlepos));
+ while (arraypos < x.cardinality) {
+ int arrayval = Util.toIntUnsigned(x.content[arraypos]);
+ while (rleval + rlelength < arrayval) {// this will frequently be false
+ ++rlepos;
+ if (rlepos == this.nbrruns) {
+ return andCardinality;// we are done
+ }
+ rleval = Util.toIntUnsigned(this.getValue(rlepos));
+ rlelength = Util.toIntUnsigned(this.getLength(rlepos));
+ }
+ if (rleval > arrayval) {
+ arraypos = Util.advanceUntil(x.content, arraypos, x.cardinality, this.getValue(rlepos));
+ } else {
+ andCardinality++;
+ arraypos++;
+ }
+ }
+ return andCardinality;
+ }
+
+
+ @Override
+ public int andCardinality(BitmapContainer x) {
+ // could be implemented as return toBitmapOrArrayContainer().iand(x);
+ int cardinality = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int runStart = Util.toIntUnsigned(this.getValue(rlepos));
+ int runEnd = runStart + Util.toIntUnsigned(this.getLength(rlepos));
+ for (int runValue = runStart; runValue <= runEnd; ++runValue) {
+ if (x.contains((short) runValue)) {// it looks like contains() should be cheap enough if
+ // accessed sequentially
+ cardinality++;
+ }
+ }
+ }
+ return cardinality;
+ }
+
+ @Override
+ public int andCardinality(RunContainer x) {
+ int cardinality = 0;
+ int rlepos = 0;
+ int xrlepos = 0;
+ int start = Util.toIntUnsigned(this.getValue(rlepos));
+ int end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ int xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ int xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ while ((rlepos < this.nbrruns) && (xrlepos < x.nbrruns)) {
+ if (end <= xstart) {
+ if (ENABLE_GALLOPING_AND) {
+ rlepos = skipAhead(this, rlepos, xstart); // skip over runs until we have end > xstart (or
+ // rlepos is advanced beyond end)
+ } else {
+ ++rlepos;
+ }
+
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ } else if (xend <= start) {
+ // exit the second run
+ if (ENABLE_GALLOPING_AND) {
+ xrlepos = skipAhead(x, xrlepos, start);
+ } else {
+ ++xrlepos;
+ }
+
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else {// they overlap
+ final int lateststart = start > xstart ? start : xstart;
+ int earliestend;
+ if (end == xend) {// improbable
+ earliestend = end;
+ rlepos++;
+ xrlepos++;
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else if (end < xend) {
+ earliestend = end;
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+
+ } else {// end > xend
+ earliestend = xend;
+ xrlepos++;
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ }
+ // earliestend - lateststart are all values that are true.
+ cardinality += (short) (earliestend - lateststart);
+ }
+ }
+ return cardinality;
+ }
+
+ @Override
+ public Container andNot(ArrayContainer x) {
+ // when x is small, we guess that the result will still be a run container
+ final int arbitrary_threshold = 32; // this is arbitrary
+ if (x.getCardinality() < arbitrary_threshold) {
+ return lazyandNot(x).toEfficientContainer();
+ }
+ // otherwise we generate either an array or bitmap container
+ final int card = getCardinality();
+ if (card <= ArrayContainer.DEFAULT_MAX_SIZE) {
+ // if the cardinality is small, we construct the solution in place
+ ArrayContainer ac = new ArrayContainer(card);
+ ac.cardinality =
+ Util.unsignedDifference(this.getShortIterator(), x.getShortIterator(), ac.content);
+ return ac;
+ }
+ // otherwise, we generate a bitmap
+ return toBitmapOrArrayContainer(card).iandNot(x);
+ }
+
+ @Override
+ public Container andNot(BitmapContainer x) {
+ // could be implemented as toTemporaryBitmap().iandNot(x);
+ int card = this.getCardinality();
+ if (card <= ArrayContainer.DEFAULT_MAX_SIZE) {
+ // result can only be an array (assuming that we never make a RunContainer)
+ ArrayContainer answer = new ArrayContainer(card);
+ answer.cardinality = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int runStart = Util.toIntUnsigned(this.getValue(rlepos));
+ int runEnd = runStart + Util.toIntUnsigned(this.getLength(rlepos));
+ for (int runValue = runStart; runValue <= runEnd; ++runValue) {
+ if (!x.contains((short) runValue)) {// it looks like contains() should be cheap enough if
+ // accessed sequentially
+ answer.content[answer.cardinality++] = (short) runValue;
+ }
+ }
+ }
+ return answer;
+ }
+ // we expect the answer to be a bitmap (if we are lucky)
+ BitmapContainer answer = x.clone();
+ int lastPos = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int start = Util.toIntUnsigned(this.getValue(rlepos));
+ int end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ Util.resetBitmapRange(answer.bitmap, lastPos, start);
+ Util.flipBitmapRange(answer.bitmap, start, end);
+ lastPos = end;
+ }
+ Util.resetBitmapRange(answer.bitmap, lastPos, answer.bitmap.length * 64);
+ answer.computeCardinality();
+ if (answer.getCardinality() > ArrayContainer.DEFAULT_MAX_SIZE) {
+ return answer;
+ } else {
+ return answer.toArrayContainer();
+ }
+ }
+
+ @Override
+ public Container andNot(RunContainer x) {
+ RunContainer answer = new RunContainer(new short[2 * (this.nbrruns + x.nbrruns)], 0);
+ int rlepos = 0;
+ int xrlepos = 0;
+ int start = Util.toIntUnsigned(this.getValue(rlepos));
+ int end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ int xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ int xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ while ((rlepos < this.nbrruns) && (xrlepos < x.nbrruns)) {
+ if (end <= xstart) {
+ // output the first run
+ answer.valueslength[2 * answer.nbrruns] = (short) start;
+ answer.valueslength[2 * answer.nbrruns + 1] = (short) (end - start - 1);
+ answer.nbrruns++;
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ } else if (xend <= start) {
+ // exit the second run
+ xrlepos++;
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else {
+ if (start < xstart) {
+ answer.valueslength[2 * answer.nbrruns] = (short) start;
+ answer.valueslength[2 * answer.nbrruns + 1] = (short) (xstart - start - 1);
+ answer.nbrruns++;
+ }
+ if (xend < end) {
+ start = xend;
+ } else {
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ }
+ }
+ }
+ if (rlepos < this.nbrruns) {
+ answer.valueslength[2 * answer.nbrruns] = (short) start;
+ answer.valueslength[2 * answer.nbrruns + 1] = (short) (end - start - 1);
+ answer.nbrruns++;
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ System.arraycopy(this.valueslength, 2 * rlepos, answer.valueslength, 2 * answer.nbrruns,
+ 2 * (this.nbrruns - rlepos));
+ answer.nbrruns = answer.nbrruns + this.nbrruns - rlepos;
+ }
+ }
+ return answer.toEfficientContainer();
+ }
+
+ // Append a value length with all values until a given value
+ private void appendValueLength(int value, int index) {
+ int previousValue = Util.toIntUnsigned(getValue(index));
+ int length = Util.toIntUnsigned(getLength(index));
+ int offset = value - previousValue;
+ if (offset > length) {
+ setLength(index, (short) offset);
+ }
+ }
+
+ // To check if a value length can be prepended with a given value
+ private boolean canPrependValueLength(int value, int index) {
+ if (index < this.nbrruns) {
+ int nextValue = Util.toIntUnsigned(getValue(index));
+ return nextValue == value + 1;
+ }
+ return false;
+ }
+
+ @Override
+ public void clear() {
+ nbrruns = 0;
+ }
+
+ @Override
+ public Container clone() {
+ return new RunContainer(nbrruns, valueslength);
+ }
+
+ // To set the last value of a value length
+ private void closeValueLength(int value, int index) {
+ int initialValue = Util.toIntUnsigned(getValue(index));
+ setLength(index, (short) (value - initialValue));
+ }
+
+ @Override
+ public boolean contains(short x) {
+ int index = unsignedInterleavedBinarySearch(valueslength, 0, nbrruns, x);
+ if (index >= 0) {
+ return true;
+ }
+ index = -index - 2; // points to preceding value, possibly -1
+ if (index != -1) {// possible match
+ int offset = Util.toIntUnsigned(x) - Util.toIntUnsigned(getValue(index));
+ int le = Util.toIntUnsigned(getLength(index));
+ return offset <= le;
+ }
+ return false;
+ }
+
+
+ // a very cheap check... if you have more than 4096, then you should use a bitmap container.
+ // this function avoids computing the cardinality
+ private Container convertToLazyBitmapIfNeeded() {
+ // when nbrruns exceed ArrayContainer.DEFAULT_MAX_SIZE, then we know it should be stored as a
+ // bitmap, always
+ if (this.nbrruns > ArrayContainer.DEFAULT_MAX_SIZE) {
+ BitmapContainer answer = new BitmapContainer();
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int start = Util.toIntUnsigned(this.getValue(rlepos));
+ int end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ Util.setBitmapRange(answer.bitmap, start, end);
+ }
+ answer.cardinality = -1;
+ return answer;
+ }
+ return this;
+ }
+
+
+ // Push all values length to the end of the array (resize array if needed)
+ private void copyToOffset(int offset) {
+ final int minCapacity = 2 * (offset + nbrruns);
+ if (valueslength.length < minCapacity) {
+ // expensive case where we need to reallocate
+ int newCapacity = valueslength.length;
+ while (newCapacity < minCapacity) {
+ newCapacity = (newCapacity == 0) ? DEFAULT_INIT_SIZE
+ : newCapacity < 64 ? newCapacity * 2
+ : newCapacity < 1024 ? newCapacity * 3 / 2 : newCapacity * 5 / 4;
+ }
+ short[] newvalueslength = new short[newCapacity];
+ copyValuesLength(this.valueslength, 0, newvalueslength, offset, nbrruns);
+ this.valueslength = newvalueslength;
+ } else {
+ // efficient case where we just copy
+ copyValuesLength(this.valueslength, 0, this.valueslength, offset, nbrruns);
+ }
+ }
+
+ private void copyValuesLength(short[] src, int srcIndex, short[] dst, int dstIndex, int length) {
+ System.arraycopy(src, 2 * srcIndex, dst, 2 * dstIndex, 2 * length);
+ }
+
+ private void decrementLength(int index) {
+ valueslength[2 * index + 1]--;// caller is responsible to ensure that value is non-zero
+ }
+
+
+ private void decrementValue(int index) {
+ valueslength[2 * index]--;
+ }
+
+ @Override
+ public void deserialize(DataInput in) throws IOException {
+ nbrruns = Short.reverseBytes(in.readShort());
+ if (valueslength.length < 2 * nbrruns) {
+ valueslength = new short[2 * nbrruns];
+ }
+ for (int k = 0; k < 2 * nbrruns; ++k) {
+ this.valueslength[k] = Short.reverseBytes(in.readShort());
+ }
+ }
+
+ // not actually used anywhere, but potentially useful
+ protected void ensureCapacity(int minNbRuns) {
+ final int minCapacity = 2 * minNbRuns;
+ if (valueslength.length < minCapacity) {
+ int newCapacity = valueslength.length;
+ while (newCapacity < minCapacity) {
+ newCapacity = (newCapacity == 0) ? DEFAULT_INIT_SIZE
+ : newCapacity < 64 ? newCapacity * 2
+ : newCapacity < 1024 ? newCapacity * 3 / 2 : newCapacity * 5 / 4;
+ }
+ short[] nv = new short[newCapacity];
+ copyValuesLength(valueslength, 0, nv, 0, nbrruns);
+ valueslength = nv;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof RunContainer) {
+ RunContainer srb = (RunContainer) o;
+ if (srb.nbrruns != this.nbrruns) {
+ return false;
+ }
+ for (int i = 0; i < nbrruns; ++i) {
+ if (this.getValue(i) != srb.getValue(i)) {
+ return false;
+ }
+ if (this.getLength(i) != srb.getLength(i)) {
+ return false;
+ }
+ }
+ return true;
+ } else if (o instanceof Container) {
+ if (((Container) o).getCardinality() != this.getCardinality()) {
+ return false; // should be a frequent branch if they differ
+ }
+ // next bit could be optimized if needed:
+ ShortIterator me = this.getShortIterator();
+ ShortIterator you = ((Container) o).getShortIterator();
+ while (me.hasNext()) {
+ if (me.next() != you.next()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void fillLeastSignificant16bits(int[] x, int i, int mask) {
+ int pos = i;
+ for (int k = 0; k < this.nbrruns; ++k) {
+ final int limit = Util.toIntUnsigned(this.getLength(k));
+ final int base = Util.toIntUnsigned(this.getValue(k));
+ for (int le = 0; le <= limit; ++le) {
+ x[pos++] = (base + le) | mask;
+ }
+ }
+ }
+
+ @Override
+ public Container flip(short x) {
+ if (this.contains(x)) {
+ return this.remove(x);
+ } else {
+ return this.add(x);
+ }
+ }
+
+ @Override
+ protected int getArraySizeInBytes() {
+ return 2 + 4 * this.nbrruns; // "array" includes its size
+ }
+
+
+ @Override
+ public int getCardinality() {
+ int sum = nbrruns;// lengths are returned -1
+ for (int k = 0; k < nbrruns; ++k) {
+ sum = sum + Util.toIntUnsigned(getLength(k))/* + 1 */;
+ }
+ return sum;
+ }
+
+ short getLength(int index) {
+ return valueslength[2 * index + 1];
+ }
+
+ @Override
+ public ShortIterator getReverseShortIterator() {
+ return new ReverseRunContainerShortIterator(this);
+ }
+
+ @Override
+ public PeekableShortIterator getShortIterator() {
+ return new RunContainerShortIterator(this);
+ }
+
+ @Override
+ public int getSizeInBytes() {
+ return this.nbrruns * 4 + 4;
+ }
+
+ short getValue(int index) {
+ return valueslength[2 * index];
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (int k = 0; k < nbrruns * 2; ++k) {
+ hash += 31 * hash + valueslength[k];
+ }
+ return hash;
+ }
+
+ @Override
+ public Container iadd(int begin, int end) {
+ // TODO: it might be better and simpler to do return
+ // toBitmapOrArrayContainer(getCardinality()).iadd(begin,end)
+ if (end == begin) {
+ return this;
+ }
+ if ((begin > end) || (end > (1 << 16))) {
+ throw new IllegalArgumentException("Invalid range [" + begin + "," + end + ")");
+ }
+
+ if (begin == end - 1) {
+ add((short) begin);
+ return this;
+ }
+
+ int bIndex = unsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns, (short) begin);
+ int eIndex =
+ unsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns, (short) (end - 1));
+
+ if (bIndex >= 0 && eIndex >= 0) {
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+
+ } else if (bIndex >= 0 && eIndex < 0) {
+ eIndex = -eIndex - 2;
+
+ if (canPrependValueLength(end - 1, eIndex + 1)) {
+ mergeValuesLength(bIndex, eIndex + 1);
+ return this;
+ }
+
+ appendValueLength(end - 1, eIndex);
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+
+ } else if (bIndex < 0 && eIndex >= 0) {
+ bIndex = -bIndex - 2;
+
+ if (bIndex >= 0) {
+ if (valueLengthContains(begin - 1, bIndex)) {
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+ }
+ }
+ prependValueLength(begin, bIndex + 1);
+ mergeValuesLength(bIndex + 1, eIndex);
+ return this;
+
+ } else {
+ bIndex = -bIndex - 2;
+ eIndex = -eIndex - 2;
+
+ if (eIndex >= 0) {
+ if (bIndex >= 0) {
+ if (!valueLengthContains(begin - 1, bIndex)) {
+ if (bIndex == eIndex) {
+ if (canPrependValueLength(end - 1, eIndex + 1)) {
+ prependValueLength(begin, eIndex + 1);
+ return this;
+ }
+ makeRoomAtIndex(eIndex + 1);
+ setValue(eIndex + 1, (short) begin);
+ setLength(eIndex + 1, (short) (end - 1 - begin));
+ return this;
+
+ } else {
+ bIndex++;
+ prependValueLength(begin, bIndex);
+ }
+ }
+ } else {
+ bIndex = 0;
+ prependValueLength(begin, bIndex);
+ }
+
+ if (canPrependValueLength(end - 1, eIndex + 1)) {
+ mergeValuesLength(bIndex, eIndex + 1);
+ return this;
+ }
+
+ appendValueLength(end - 1, eIndex);
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+
+ } else {
+ if (canPrependValueLength(end - 1, 0)) {
+ prependValueLength(begin, 0);
+ } else {
+ makeRoomAtIndex(0);
+ setValue(0, (short) begin);
+ setLength(0, (short) (end - 1 - begin));
+ }
+ return this;
+ }
+ }
+ }
+
+ @Override
+ public Container iand(ArrayContainer x) {
+ return and(x);
+ }
+
+ @Override
+ public Container iand(BitmapContainer x) {
+ return and(x);
+ }
+
+
+ @Override
+ public Container iand(RunContainer x) {
+ return and(x);
+ }
+
+
+ @Override
+ public Container iandNot(ArrayContainer x) {
+ return andNot(x);
+ }
+
+ @Override
+ public Container iandNot(BitmapContainer x) {
+ return andNot(x);
+ }
+
+ @Override
+ public Container iandNot(RunContainer x) {
+ return andNot(x);
+ }
+
+ protected Container ilazyor(ArrayContainer x) {
+ if (isFull()) {
+ return this; // this can sometimes solve a lot of computation!
+ }
+ return ilazyorToRun(x);
+ }
+
+ private Container ilazyorToRun(ArrayContainer x) {
+ if (isFull()) {
+ return this.clone();
+ }
+ final int nbrruns = this.nbrruns;
+ final int offset = Math.max(nbrruns, x.getCardinality());
+ copyToOffset(offset);
+ int rlepos = 0;
+ this.nbrruns = 0;
+ PeekableShortIterator i = x.getShortIterator();
+ while (i.hasNext() && (rlepos < nbrruns)) {
+ if (Util.compareUnsigned(getValue(rlepos + offset), i.peekNext()) <= 0) {
+ smartAppend(getValue(rlepos + offset), getLength(rlepos + offset));
+ rlepos++;
+ } else {
+ smartAppend(i.next());
+ }
+ }
+ if (i.hasNext()) {
+ /*
+ * if(this.nbrruns>0) { // this might be useful if the run container has just one very large
+ * run int lastval = Util.toIntUnsigned(getValue(nbrruns + offset - 1)) +
+ * Util.toIntUnsigned(getLength(nbrruns + offset - 1)) + 1; i.advanceIfNeeded((short)
+ * lastval); }
+ */
+ while (i.hasNext()) {
+ smartAppend(i.next());
+ }
+ } else {
+ while (rlepos < nbrruns) {
+ smartAppend(getValue(rlepos + offset), getLength(rlepos + offset));
+ rlepos++;
+ }
+ }
+ return convertToLazyBitmapIfNeeded();
+ }
+
+ private void increaseCapacity() {
+ int newCapacity = (valueslength.length == 0) ? DEFAULT_INIT_SIZE
+ : valueslength.length < 64 ? valueslength.length * 2
+ : valueslength.length < 1024 ? valueslength.length * 3 / 2
+ : valueslength.length * 5 / 4;
+ short[] nv = new short[newCapacity];
+ System.arraycopy(valueslength, 0, nv, 0, 2 * nbrruns);
+ valueslength = nv;
+ }
+
+
+ private void incrementLength(int index) {
+ valueslength[2 * index + 1]++;
+ }
+
+
+ private void incrementValue(int index) {
+ valueslength[2 * index]++;
+ }
+
+ // To set the first value of a value length
+ private void initValueLength(int value, int index) {
+ int initialValue = Util.toIntUnsigned(getValue(index));
+ int length = Util.toIntUnsigned(getLength(index));
+ setValue(index, (short) (value));
+ setLength(index, (short) (length - (value - initialValue)));
+ }
+
+ @Override
+ public Container inot(int rangeStart, int rangeEnd) {
+ if (rangeEnd <= rangeStart) {
+ return this;
+ }
+
+ // TODO: write special case code for rangeStart=0; rangeEnd=65535
+ // a "sliding" effect where each range records the gap adjacent it
+ // can probably be quite fast. Probably have 2 cases: start with a
+ // 0 run vs start with a 1 run. If you both start and end with 0s,
+ // you will require room for expansion.
+
+ // the +1 below is needed in case the valueslength.length is odd
+ if (valueslength.length <= 2 * nbrruns + 1) {
+ // no room for expansion
+ // analyze whether this is a case that will require expansion (that we cannot do)
+ // this is a bit costly now (4 "contains" checks)
+
+ boolean lastValueBeforeRange = false;
+ boolean firstValueInRange = false;
+ boolean lastValueInRange = false;
+ boolean firstValuePastRange = false;
+
+ // contains is based on a binary search and is hopefully fairly fast.
+ // however, one binary search could *usually* suffice to find both
+ // lastValueBeforeRange AND firstValueInRange. ditto for
+ // lastVaueInRange and firstValuePastRange
+
+ // find the start of the range
+ if (rangeStart > 0) {
+ lastValueBeforeRange = contains((short) (rangeStart - 1));
+ }
+ firstValueInRange = contains((short) rangeStart);
+
+ if (lastValueBeforeRange == firstValueInRange) {
+ // expansion is required if also lastValueInRange==firstValuePastRange
+
+ // tougher to optimize out, but possible.
+ lastValueInRange = contains((short) (rangeEnd - 1));
+ if (rangeEnd != 65536) {
+ firstValuePastRange = contains((short) rangeEnd);
+ }
+
+ // there is definitely one more run after the operation.
+ if (lastValueInRange == firstValuePastRange) {
+ return not(rangeStart, rangeEnd); // can't do in-place: true space limit
+ }
+ }
+ }
+ // either no expansion required, or we have room to handle any required expansion for it.
+
+ // remaining code is just a minor variation on not()
+ int myNbrRuns = nbrruns;
+
+ RunContainer ans = this; // copy on top of self.
+ int k = 0;
+ ans.nbrruns = 0; // losing this.nbrruns, which is stashed in myNbrRuns.
+
+ // could try using unsignedInterleavedBinarySearch(valueslength, 0, nbrruns, rangeStart) instead
+ // of sequential scan
+ // to find the starting location
+
+ for (; (k < myNbrRuns) && (Util.toIntUnsigned(this.getValue(k)) < rangeStart); ++k) {
+ // since it is atop self, there is no copying needed
+ // ans.valueslength[2 * k] = this.valueslength[2 * k];
+ // ans.valueslength[2 * k + 1] = this.valueslength[2 * k + 1];
+ ans.nbrruns++;
+ }
+ // We will work left to right, with a read pointer that always stays
+ // left of the write pointer. However, we need to give the read pointer a head start.
+ // use local variables so we are always reading 1 location ahead.
+
+ short bufferedValue = 0, bufferedLength = 0; // 65535 start and 65535 length would be illegal,
+ // could use as sentinel
+ short nextValue = 0, nextLength = 0;
+ if (k < myNbrRuns) { // prime the readahead variables
+ bufferedValue = getValue(k);
+ bufferedLength = getLength(k);
+ }
+
+ ans.smartAppendExclusive((short) rangeStart, (short) (rangeEnd - rangeStart - 1));
+
+ for (; k < myNbrRuns; ++k) {
+ if (ans.nbrruns > k + 1) {
+ throw new RuntimeException(
+ "internal error in inot, writer has overtaken reader!! " + k + " " + ans.nbrruns);
+ }
+ if (k + 1 < myNbrRuns) {
+ nextValue = getValue(k + 1); // readahead for next iteration
+ nextLength = getLength(k + 1);
+ }
+ ans.smartAppendExclusive(bufferedValue, bufferedLength);
+ bufferedValue = nextValue;
+ bufferedLength = nextLength;
+ }
+ // the number of runs can increase by one, meaning (rarely) a bitmap will become better
+ // or the cardinality can decrease by a lot, making an array better
+ return ans.toEfficientContainer();
+ }
+
+ @Override
+ public boolean intersects(ArrayContainer x) {
+ if (this.nbrruns == 0) {
+ return false;
+ }
+ int rlepos = 0;
+ int arraypos = 0;
+ int rleval = Util.toIntUnsigned(this.getValue(rlepos));
+ int rlelength = Util.toIntUnsigned(this.getLength(rlepos));
+ while (arraypos < x.cardinality) {
+ int arrayval = Util.toIntUnsigned(x.content[arraypos]);
+ while (rleval + rlelength < arrayval) {// this will frequently be false
+ ++rlepos;
+ if (rlepos == this.nbrruns) {
+ return false;
+ }
+ rleval = Util.toIntUnsigned(this.getValue(rlepos));
+ rlelength = Util.toIntUnsigned(this.getLength(rlepos));
+ }
+ if (rleval > arrayval) {
+ arraypos = Util.advanceUntil(x.content, arraypos, x.cardinality, this.getValue(rlepos));
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean intersects(BitmapContainer x) {
+ // TODO: this is probably not optimally fast
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int runStart = Util.toIntUnsigned(this.getValue(rlepos));
+ int runEnd = runStart + Util.toIntUnsigned(this.getLength(rlepos));
+ for (int runValue = runStart; runValue <= runEnd; ++runValue) {
+ if (x.contains((short) runValue)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean intersects(RunContainer x) {
+ int rlepos = 0;
+ int xrlepos = 0;
+ int start = Util.toIntUnsigned(this.getValue(rlepos));
+ int end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ int xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ int xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ while ((rlepos < this.nbrruns) && (xrlepos < x.nbrruns)) {
+ if (end <= xstart) {
+ if (ENABLE_GALLOPING_AND) {
+ rlepos = skipAhead(this, rlepos, xstart); // skip over runs until we have end > xstart (or
+ // rlepos is advanced beyond end)
+ } else {
+ ++rlepos;
+ }
+
+ if (rlepos < this.nbrruns) {
+ start = Util.toIntUnsigned(this.getValue(rlepos));
+ end = start + Util.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ } else if (xend <= start) {
+ // exit the second run
+ if (ENABLE_GALLOPING_AND) {
+ xrlepos = skipAhead(x, xrlepos, start);
+ } else {
+ ++xrlepos;
+ }
+
+ if (xrlepos < x.nbrruns) {
+ xstart = Util.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + Util.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else {// they overlap
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Container ior(ArrayContainer x) {
+ if (isFull()) {
+ return this;
+ }
+ final int nbrruns = this.nbrruns;
+ final int offset = Math.max(nbrruns, x.getCardinality());
+ copyToOffset(offset);
+ int rlepos = 0;
+ this.nbrruns = 0;
+ PeekableShortIterator i = x.getShortIterator();
+ while (i.hasNext() && (rlepos < nbrruns)) {
+ if (Util.compareUnsigned(getValue(rlepos + offset), i.peekNext()) <= 0) {
+ smartAppend(getValue(rlepos + offset), getLength(rlepos + offset));
+ rlepos++;
+ } else {
+ smartAppend(i.next());
+ }
+ }
+ if (i.hasNext()) {
+ /*
+ * if(this.nbrruns>0) { // this might be useful if the run container has just one very large
+ * run int lastval = Util.toIntUnsigned(getValue(nbrruns + offset - 1)) +
+ * Util.toIntUnsigned(getLength(nbrruns + offset - 1)) + 1; i.advanceIfNeeded((short)
+ * lastval); }
+ */
+ while (i.hasNext()) {
+ smartAppend(i.next());
+ }
+ } else {
+ while (rlepos < nbrruns) {
+ smartAppend(getValue(rlepos + offset), getLength(rlepos + offset));
+ rlepos++;
+ }
+ }
+ return toEfficientContainer();
+ }
+
+ @Override
+ public Container ior(BitmapContainer x) {
+ if (isFull()) {
+ return this;
+ }
+ return or(x);
+ }
+
+ @Override
+ public Container ior(RunContainer x) {
+ if (isFull()) {
+ return this;
+ }
+
+ final int nbrruns = this.nbrruns;
+ final int xnbrruns = x.nbrruns;
+ final int offset = Math.max(nbrruns, xnbrruns);
+
+ // Push all values length to the end of the array (resize array if needed)
+ copyToOffset(offset);
+ // Aggregate and store the result at the beginning of the array
+ this.nbrruns = 0;
+ int rlepos = 0;
+ int xrlepos = 0;
+
+ // Add values length (smaller first)
+ while ((rlepos < nbrruns) && (xrlepos < xnbrruns)) {
+ final short value = this.getValue(offset + rlepos);
+ final short xvalue = x.getValue(xrlepos);
+ final short length = this.getLength(offset + rlepos);
+ final short xlength = x.getLength(xrlepos);
+
+ if (Util.compareUnsigned(value, xvalue) <= 0) {
+ this.smartAppend(value, length);
+ ++rlepos;
+ } else {
+ this.smartAppend(xvalue, xlength);
+ ++xrlepos;
+ }
+ }
+
+ while (rlepos < nbrruns) {
+ this.smartAppend(this.getValue(offset + rlepos), this.getLength(offset + rlepos));
+ ++rlepos;
+ }
+
+ while (xrlepos < xnbrruns) {
+ this.smartAppend(x.getValue(xrlepos), x.getLength(xrlepos));
+ ++xrlepos;
+ }
+ return this.toBitmapIfNeeded();
+ }
+
+ @Override
+ public Container iremove(int begin, int end) {
+ // TODO: it might be better and simpler to do return
+ // toBitmapOrArrayContainer(getCardinality()).iremove(begin,end)
+ if (end == begin) {
+ return this;
+ }
+ if ((begin > end) || (end > (1 << 16))) {
+ throw new IllegalArgumentException("Invalid range [" + begin + "," + end + ")");
+ }
+ if (begin == end - 1) {
+ remove((short) begin);
+ return this;
+ }
+
+ int bIndex = unsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns, (short) begin);
+ int eIndex =
+ unsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns, (short) (end - 1));
+
+ // note, eIndex is looking for (end-1)
+
+ if (bIndex >= 0) { // beginning marks beginning of a run
+ if (eIndex < 0) {
+ eIndex = -eIndex - 2;
+ }
+ // eIndex could be a run that begins exactly at "end"
+ // or it might be an earlier run
+
+ // if the end is before the first run, we'd have eIndex==-1. But bIndex makes this impossible.
+
+ if (valueLengthContains(end, eIndex)) {
+ initValueLength(end, eIndex); // there is something left in the run
+ recoverRoomsInRange(bIndex - 1, eIndex - 1);
+ } else {
+ recoverRoomsInRange(bIndex - 1, eIndex); // nothing left in the run
+ }
+
+ } else if (bIndex < 0 && eIndex >= 0) {
+ // start does not coincide to a run start, but end does.
+ bIndex = -bIndex - 2;
+
+ if (bIndex >= 0) {
+ if (valueLengthContains(begin, bIndex)) {
+ closeValueLength(begin - 1, bIndex);
+ }
+ }
+
+ // last run is one shorter
+ if (getLength(eIndex) == 0) {// special case where we remove last run
+ recoverRoomsInRange(eIndex, eIndex + 1);
+ } else {
+ incrementValue(eIndex);
+ decrementLength(eIndex);
+ }
+ recoverRoomsInRange(bIndex, eIndex - 1);
+
+ } else {
+ bIndex = -bIndex - 2;
+ eIndex = -eIndex - 2;
+
+ if (eIndex >= 0) { // end-1 is not before first run.
+ if (bIndex >= 0) { // nor is begin
+ if (bIndex == eIndex) { // all removal nested properly between
+ // one run start and the next
+ if (valueLengthContains(begin, bIndex)) {
+ if (valueLengthContains(end, eIndex)) {
+ // proper nesting within a run, generates 2 sub-runs
+ makeRoomAtIndex(bIndex);
+ closeValueLength(begin - 1, bIndex);
+ initValueLength(end, bIndex + 1);
+ return this;
+ }
+ // removed area extends beyond run.
+ closeValueLength(begin - 1, bIndex);
+ }
+ } else { // begin in one run area, end in a later one.
+ if (valueLengthContains(begin, bIndex)) {
+ closeValueLength(begin - 1, bIndex);
+ // this cannot leave the bIndex run empty.
+ }
+ if (valueLengthContains(end, eIndex)) {
+ // there is additional stuff in the eIndex run
+ initValueLength(end, eIndex);
+ eIndex--;
+ } else {
+ // run ends at or before the range being removed, can deleteById it
+ }
+ recoverRoomsInRange(bIndex, eIndex);
+ }
+
+ } else {
+ // removed range begins before the first run
+ if (valueLengthContains(end, eIndex)) { // had been end-1
+ initValueLength(end, eIndex);
+ recoverRoomsInRange(bIndex, eIndex - 1);
+ } else { // removed range includes all the last run
+ recoverRoomsInRange(bIndex, eIndex);
+ }
+ }
+
+ } else {
+ // eIndex == -1: whole range is before first run, nothing to deleteById...
+ }
+
+ }
+ return this;
+ }
+
+ protected boolean isFull() {
+ return (this.nbrruns == 1) && (this.getValue(0) == 0) && (this.getLength(0) == -1);
+ }
+
+ @Override
+ public Iterator
+ * In practice, calls {#link naive_and}
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap and(ImmutableRoaringBitmap... bitmaps) {
+ return naive_and(bitmaps);
+ }
+
+ /**
+ * Compute the AND aggregate.
+ *
+ * In practice, calls {#link naive_and}
+ *
+ * @param bitmaps input bitmaps (ImmutableRoaringBitmap or MutableRoaringBitmap)
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap and(@SuppressWarnings("rawtypes") Iterator bitmaps) {
+ return naive_and(bitmaps);
+ }
+
+ /**
+ * Compute the AND aggregate.
+ *
+ * In practice, calls {#link naive_and}
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap and(MutableRoaringBitmap... bitmaps) {
+ return and(convertToImmutable(bitmaps));
+ }
+
+ /**
+ * Convenience method converting one type of iterator into another, to avoid unnecessary warnings.
+ *
+ * @param i input bitmaps
+ * @return an iterator over the provided iterator, with a different type
+ */
+ public static Iterator
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #or(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap horizontal_or(ImmutableRoaringBitmap... bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ if (bitmaps.length == 0) {
+ return answer;
+ }
+ PriorityQueue
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #or(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap horizontal_or(MutableRoaringBitmap... bitmaps) {
+ return horizontal_or(convertToImmutable(bitmaps));
+ }
+
+ /**
+ * Minimizes memory usage while computing the xor aggregate on a moderate number of bitmaps.
+ *
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #xor(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap horizontal_xor(ImmutableRoaringBitmap... bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ if (bitmaps.length == 0) {
+ return answer;
+ }
+ PriorityQueue
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #xor(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap horizontal_xor(MutableRoaringBitmap... bitmaps) {
+ return horizontal_xor(convertToImmutable(bitmaps));
+ }
+
+ /**
+ * Compute overall AND between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_and(ImmutableRoaringBitmap... bitmaps) {
+ MutableRoaringBitmap answer;
+
+ if (bitmaps.length > 0) {
+ answer = (bitmaps[0]).toMutableRoaringBitmap();
+ for (int k = 1; k < bitmaps.length; ++k) {
+ answer = ImmutableRoaringBitmap.and(answer, bitmaps[k]);
+ }
+ } else {
+ answer = new MutableRoaringBitmap();
+ }
+
+ return answer;
+ }
+
+ /**
+ * Compute overall AND between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps (ImmutableRoaringBitmap or MutableRoaringBitmap)
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_and(@SuppressWarnings("rawtypes") Iterator bitmaps) {
+ if (!bitmaps.hasNext()) {
+ return new MutableRoaringBitmap();
+ }
+ MutableRoaringBitmap answer =
+ ((ImmutableRoaringBitmap) bitmaps.next()).toMutableRoaringBitmap();
+ while (bitmaps.hasNext()) {
+ answer.and((ImmutableRoaringBitmap) bitmaps.next());
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall AND between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_and(MutableRoaringBitmap... bitmaps) {
+ if (bitmaps.length == 0) {
+ return new MutableRoaringBitmap();
+ }
+ MutableRoaringBitmap answer = bitmaps[0].clone();
+ for (int k = 1; k < bitmaps.length; ++k) {
+ answer.and(bitmaps[k]);
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall OR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_or(ImmutableRoaringBitmap... bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ for (int k = 0; k < bitmaps.length; ++k) {
+ answer.naivelazyor(bitmaps[k]);
+ }
+ answer.repairAfterLazy();
+ return answer;
+ }
+
+ /**
+ * Compute overall OR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps (ImmutableRoaringBitmap or MutableRoaringBitmap)
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_or(@SuppressWarnings("rawtypes") Iterator bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ while (bitmaps.hasNext()) {
+ answer.naivelazyor((ImmutableRoaringBitmap) bitmaps.next());
+ }
+ answer.repairAfterLazy();
+ return answer;
+ }
+
+ /**
+ * Compute overall OR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_or(MutableRoaringBitmap... bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ for (int k = 0; k < bitmaps.length; ++k) {
+ answer.lazyor(bitmaps[k]);
+ }
+ answer.repairAfterLazy();
+ return answer;
+ }
+
+ /**
+ * Compute overall XOR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_xor(ImmutableRoaringBitmap... bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ for (int k = 0; k < bitmaps.length; ++k) {
+ answer.xor(bitmaps[k]);
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall XOR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps (ImmutableRoaringBitmap or MutableRoaringBitmap)
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_xor(@SuppressWarnings("rawtypes") Iterator bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ while (bitmaps.hasNext()) {
+ answer.xor((ImmutableRoaringBitmap) bitmaps.next());
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall XOR between bitmaps two-by-two.
+ *
+ * This function runs in linear time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap naive_xor(MutableRoaringBitmap... bitmaps) {
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ for (int k = 0; k < bitmaps.length; ++k) {
+ answer.xor(bitmaps[k]);
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall OR between bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap or(ImmutableRoaringBitmap... bitmaps) {
+ return naive_or(bitmaps);
+ }
+
+ /**
+ * Compute overall OR between bitmaps.
+ *
+ * @param bitmaps input bitmaps (ImmutableRoaringBitmap or MutableRoaringBitmap)
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap or(@SuppressWarnings("rawtypes") Iterator bitmaps) {
+ return naive_or(bitmaps);
+ }
+
+ /**
+ * Compute overall OR between bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap or(MutableRoaringBitmap... bitmaps) {
+ return naive_or(bitmaps);
+ }
+
+ /**
+ * Uses a priority queue to compute the or aggregate.
+ *
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #horizontal_or(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap priorityqueue_or(ImmutableRoaringBitmap... bitmaps) {
+ if (bitmaps.length == 0) {
+ return new MutableRoaringBitmap();
+ } else if (bitmaps.length == 1) {
+ return bitmaps[0].toMutableRoaringBitmap();
+ }
+ // we buffer the call to getLongSizeInBytes(), hence the code complexity
+ final ImmutableRoaringBitmap[] buffer = Arrays.copyOf(bitmaps, bitmaps.length);
+ final int[] sizes = new int[buffer.length];
+ final boolean[] istmp = new boolean[buffer.length];
+ for (int k = 0; k < sizes.length; ++k) {
+ sizes[k] = buffer[k].serializedSizeInBytes();
+ }
+ PriorityQueue
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #horizontal_or(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap priorityqueue_or(
+ @SuppressWarnings("rawtypes") Iterator bitmaps) {
+ if (!bitmaps.hasNext()) {
+ return new MutableRoaringBitmap();
+ }
+ // we buffer the call to getLongSizeInBytes(), hence the code complexity
+ ArrayList
+ * This function runs in linearithmic (O(n log n)) time with respect to the number of bitmaps.
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ * @see #horizontal_xor(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap priorityqueue_xor(ImmutableRoaringBitmap... bitmaps) {
+ // code could be faster, see priorityqueue_or
+ if (bitmaps.length < 2) {
+ throw new IllegalArgumentException("Expecting at least 2 bitmaps");
+ }
+ final PriorityQueue
+ * For better performance, consider the {@link ImmutableRoaringBitmap#forEach} method.
+ *
+ * @author Borislav Ivanov
+ **/
+public class BufferIntIteratorFlyweight implements PeekableIntIterator {
+
+ private int hs;
+
+ private PeekableShortIterator iter;
+
+ private MappeableArrayContainerShortIterator arrIter = new MappeableArrayContainerShortIterator();
+
+ private MappeableBitmapContainerShortIterator bitmapIter =
+ new MappeableBitmapContainerShortIterator();
+
+ private MappeableRunContainerShortIterator runIter = new MappeableRunContainerShortIterator();
+
+
+ private int pos;
+
+ private ImmutableRoaringBitmap roaringBitmap = null;
+
+ /**
+ * Creates an instance that is not ready for iteration. You must first call
+ * {@link #wrap(ImmutableRoaringBitmap)}.
+ */
+ public BufferIntIteratorFlyweight() {
+
+ }
+
+ /**
+ * Creates an instance that is ready for iteration.
+ *
+ * @param r bitmap to be iterated over
+ */
+ public BufferIntIteratorFlyweight(ImmutableRoaringBitmap r) {
+ wrap(r);
+ }
+
+ @Override
+ public PeekableIntIterator clone() {
+ try {
+ BufferIntIteratorFlyweight x = (BufferIntIteratorFlyweight) super.clone();
+ x.iter = this.iter.clone();
+ return x;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return pos < this.roaringBitmap.highLowContainer.size();
+ }
+
+ @Override
+ public int next() {
+ int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ ++pos;
+ nextContainer();
+ }
+ return x;
+ }
+
+ private void nextContainer() {
+ if (pos < this.roaringBitmap.highLowContainer.size()) {
+
+ MappeableContainer container = this.roaringBitmap.highLowContainer.getContainerAtIndex(pos);
+
+ if (container instanceof MappeableBitmapContainer) {
+ bitmapIter.wrap((MappeableBitmapContainer) container);
+ iter = bitmapIter;
+ } else if (container instanceof MappeableRunContainer) {
+ runIter.wrap((MappeableRunContainer) container);
+ iter = runIter;
+ } else {
+ arrIter.wrap((MappeableArrayContainer) container);
+ iter = arrIter;
+ }
+
+ hs = BufferUtil.toIntUnsigned(this.roaringBitmap.highLowContainer.getKeyAtIndex(pos)) << 16;
+ }
+ }
+
+ /**
+ * Prepares a bitmap for iteration
+ *
+ * @param r bitmap to be iterated over
+ */
+ public void wrap(ImmutableRoaringBitmap r) {
+ this.hs = 0;
+ this.pos = 0;
+ this.roaringBitmap = r;
+ this.nextContainer();
+ }
+
+ @Override
+ public void advanceIfNeeded(int minval) {
+ while (hasNext() && ((hs >>> 16) < (minval >>> 16))) {
+ ++pos;
+ nextContainer();
+ }
+ if (hasNext() && ((hs >>> 16) == (minval >>> 16))) {
+ iter.advanceIfNeeded(BufferUtil.lowbits(minval));
+ if (!iter.hasNext()) {
+ ++pos;
+ nextContainer();
+ }
+ }
+ }
+
+ @Override
+ public int peekNext() {
+ return BufferUtil.toIntUnsigned(iter.peekNext()) | hs;
+ }
+
+
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/BufferReverseIntIteratorFlyweight.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/BufferReverseIntIteratorFlyweight.java
new file mode 100644
index 000000000..677aba388
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/BufferReverseIntIteratorFlyweight.java
@@ -0,0 +1,116 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap.buffer;
+
+import com.fr.third.bitmap.roaringbitmap.IntIterator;
+import com.fr.third.bitmap.roaringbitmap.ShortIterator;
+
+/**
+ * Fast iterator minimizing the stress on the garbage collector. You can create one reusable
+ * instance of this class and then {@link #wrap(ImmutableRoaringBitmap)}
+ *
+ * This iterator enumerates the stored values in reverse (starting from the end).
+ *
+ * @author Borislav Ivanov
+ **/
+public class BufferReverseIntIteratorFlyweight implements IntIterator {
+
+ private int hs;
+
+ private ShortIterator iter;
+
+ private ReverseMappeableArrayContainerShortIterator arrIter =
+ new ReverseMappeableArrayContainerShortIterator();
+
+ private ReverseMappeableBitmapContainerShortIterator bitmapIter =
+ new ReverseMappeableBitmapContainerShortIterator();
+
+ private ReverseMappeableRunContainerShortIterator runIter =
+ new ReverseMappeableRunContainerShortIterator();
+
+ private short pos;
+
+ private ImmutableRoaringBitmap roaringBitmap = null;
+
+
+ /**
+ * Creates an instance that is not ready for iteration. You must first call
+ * {@link #wrap(ImmutableRoaringBitmap)}.
+ */
+ public BufferReverseIntIteratorFlyweight() {
+
+ }
+
+ /**
+ * Creates an instance that is ready for iteration.
+ *
+ * @param r bitmap to be iterated over
+ */
+ public BufferReverseIntIteratorFlyweight(ImmutableRoaringBitmap r) {
+ wrap(r);
+ }
+
+ @Override
+ public IntIterator clone() {
+ try {
+ BufferReverseIntIteratorFlyweight x = (BufferReverseIntIteratorFlyweight) super.clone();
+ x.iter = this.iter.clone();
+ return x;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return pos >= 0;
+ }
+
+
+ @Override
+ public int next() {
+ final int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ --pos;
+ nextContainer();
+ }
+ return x;
+ }
+
+ private void nextContainer() {
+
+ if (pos >= 0) {
+
+ MappeableContainer container = this.roaringBitmap.highLowContainer.getContainerAtIndex(pos);
+
+ if (container instanceof MappeableBitmapContainer) {
+ bitmapIter.wrap((MappeableBitmapContainer) container);
+ iter = bitmapIter;
+ } else if (container instanceof MappeableRunContainer) {
+ runIter.wrap((MappeableRunContainer) container);
+ iter = runIter;
+ } else {
+ arrIter.wrap((MappeableArrayContainer) container);
+ iter = arrIter;
+ }
+
+ hs = BufferUtil.toIntUnsigned(this.roaringBitmap.highLowContainer.getKeyAtIndex(pos)) << 16;
+ }
+ }
+
+ /**
+ * Prepares a bitmap for iteration
+ *
+ * @param r bitmap to be iterated over
+ */
+ public void wrap(ImmutableRoaringBitmap r) {
+ this.roaringBitmap = r;
+ this.hs = 0;
+ this.pos = (short) (this.roaringBitmap.highLowContainer.size() - 1);
+ this.nextContainer();
+ }
+
+}
+
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/BufferUtil.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/BufferUtil.java
new file mode 100644
index 000000000..7fd96590a
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/BufferUtil.java
@@ -0,0 +1,825 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap.buffer;
+
+import com.fr.third.bitmap.roaringbitmap.Util;
+
+import java.nio.Buffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Various useful methods for roaring bitmaps.
+ *
+ * This class is similar to Util but meant to be used with memory mapping.
+ */
+public final class BufferUtil {
+
+
+ /**
+ * Private constructor to prevent instantiation of utility class
+ */
+ private BufferUtil() {
+ }
+
+ /**
+ * Find the smallest integer larger than pos such that array[pos]>= min. If none can be found,
+ * return length. Based on code by O. Kaser.
+ *
+ * @param array container where we search
+ * @param pos initial position
+ * @param min minimal threshold
+ * @param length how big should the array consider to be
+ * @return x greater than pos such that array[pos] is at least as large as min, pos is is equal to
+ * length if it is not possible.
+ */
+ protected static int advanceUntil(ShortBuffer array, int pos, int length, short min) {
+ int lower = pos + 1;
+
+ // special handling for a possibly common sequential case
+ if (lower >= length || toIntUnsigned(array.get(lower)) >= toIntUnsigned(min)) {
+ return lower;
+ }
+
+ int spansize = 1; // could set larger
+ // bootstrap an upper limit
+
+ while (lower + spansize < length
+ && toIntUnsigned(array.get(lower + spansize)) < toIntUnsigned(min)) {
+ spansize *= 2; // hoping for compiler will reduce to
+ }
+ // shift
+ int upper = (lower + spansize < length) ? lower + spansize : length - 1;
+
+ // maybe we are lucky (could be common case when the seek ahead
+ // expected
+ // to be small and sequential will otherwise make us look bad)
+ if (array.get(upper) == min) {
+ return upper;
+ }
+
+ if (toIntUnsigned(array.get(upper)) < toIntUnsigned(min)) {// means
+ // array
+ // has no
+ // item
+ // >= min
+ // pos = array.length;
+ return length;
+ }
+
+ // we know that the next-smallest span was too small
+ lower += (spansize / 2);
+
+ // else begin binary search
+ // invariant: array[lower]
+ * The current bitmap is not modified.
+ *
+ * @param out the DataOutput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ @Override
+ public void serialize(DataOutput out) throws IOException {
+ if (buffer.hasArray()) {
+ out.write(buffer.array(), buffer.arrayOffset(), buffer.limit());
+ } else {
+ ByteBuffer tmp = buffer.duplicate();
+ tmp.position(0);
+ WritableByteChannel channel = Channels.newChannel((OutputStream) out);
+ channel.write(tmp);
+ }
+ }
+
+ /**
+ * @return the size that the data structure occupies on disk
+ */
+ @Override
+ public int serializedSizeInBytes() {
+ return buffer.limit();
+ }
+
+ @Override
+ public int size() {
+ return this.size;
+ }
+
+ private int unsignedBinarySearch(short k) {
+ return branchyUnsignedBinarySearch(k);
+ }
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/ImmutableRoaringBitmap.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/ImmutableRoaringBitmap.java
new file mode 100644
index 000000000..d631a142d
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/ImmutableRoaringBitmap.java
@@ -0,0 +1,1320 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap.buffer;
+
+import com.fr.third.bitmap.roaringbitmap.ImmutableBitmapDataProvider;
+import com.fr.third.bitmap.roaringbitmap.IntConsumer;
+import com.fr.third.bitmap.roaringbitmap.IntIterator;
+import com.fr.third.bitmap.roaringbitmap.PeekableIntIterator;
+import com.fr.third.bitmap.roaringbitmap.PeekableShortIterator;
+import com.fr.third.bitmap.roaringbitmap.RoaringBitmap;
+import com.fr.third.bitmap.roaringbitmap.ShortIterator;
+import com.fr.third.bitmap.roaringbitmap.Util;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+
+/**
+ * ImmutableRoaringBitmap provides a compressed immutable (cannot be modified) bitmap. It is meant
+ * to be used with MutableRoaringBitmap, a derived class that adds methods
+ * to modify the bitmap.
+ *
+ *
+ * It can also be constructed from a ByteBuffer (useful for memory mapping).
+ *
+ * Objects of this class may reside almost entirely in memory-map files.
+ *
+ * @see MutableRoaringBitmap
+ */
+public class ImmutableRoaringBitmap
+ implements Iterable
+ * It is not necessary that limit() on the input ByteBuffer indicates the end of the serialized
+ * data.
+ *
+ * After creating this ImmutableRoaringBitmap, you can advance to the rest of the data (if there
+ * is more) by setting b.position(b.position() + bitmap.serializedSizeInBytes());
+ *
+ * Note that the input ByteBuffer is effectively copied (with the slice operation) so you should
+ * expect the provided ByteBuffer to remain unchanged.
+ *
+ * @param b data source
+ */
+ public ImmutableRoaringBitmap(final ByteBuffer b) {
+ highLowContainer = new ImmutableRoaringArray(b);
+ }
+
+ /**
+ * Computes AND between input bitmaps in the given range, from rangeStart (inclusive) to rangeEnd
+ * (exclusive)
+ *
+ * @param bitmaps input bitmaps, these are not modified
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new result bitmap
+ */
+ public static MutableRoaringBitmap and(@SuppressWarnings("rawtypes") final Iterator bitmaps,
+ final long rangeStart, final long rangeEnd) {
+ MutableRoaringBitmap.rangeSanityCheck(rangeStart, rangeEnd);
+ Iterator
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ * @see BufferFastAggregation#and(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap and(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2) {
+ final MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ if (s1 == s2) {
+ final MappeableContainer c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ final MappeableContainer c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ final MappeableContainer c = c1.and(c2);
+ if (c.getCardinality() > 0) {
+ answer.getMappeableRoaringArray().append(s1, c);
+ }
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1 = x1.highLowContainer.advanceUntil(s2, pos1);
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Cardinality of Bitwise AND (intersection) operation. The provided bitmaps are *not* modified.
+ * This operation is thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return as if you did and(x2,x2).getCardinality()
+ * @see BufferFastAggregation#and(ImmutableRoaringBitmap...)
+ */
+ public static int andCardinality(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2) {
+ int answer = 0;
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ if (s1 == s2) {
+ final MappeableContainer c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ final MappeableContainer c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ answer += c1.andCardinality(c2);
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1 = x1.highLowContainer.advanceUntil(s2, pos1);
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Bitwise ANDNOT (difference) operation for the given range, rangeStart (inclusive) and rangeEnd
+ * (exclusive). The provided bitmaps are *not* modified. This operation is thread-safe as long as
+ * the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @param rangeStart beginning of the range (inclusive)
+ * @param rangeEnd end of range (exclusive)
+ * @return result of the operation
+ */
+ public static MutableRoaringBitmap andNot(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2, long rangeStart, long rangeEnd) {
+ MutableRoaringBitmap.rangeSanityCheck(rangeStart, rangeEnd);
+ MutableRoaringBitmap rb1 = selectRangeWithoutCopy(x1, rangeStart, rangeEnd);
+ MutableRoaringBitmap rb2 = selectRangeWithoutCopy(x2, rangeStart, rangeEnd);
+ return andNot(rb1, rb2);
+ }
+
+ /**
+ * Bitwise ANDNOT (difference) operation for the given range, rangeStart (inclusive) and rangeEnd
+ * (exclusive). The provided bitmaps are *not* modified. This operation is thread-safe as long as
+ * the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @param rangeStart beginning of the range (inclusive)
+ * @param rangeEnd end of range (exclusive)
+ * @return result of the operation
+ * @deprecated use the version where longs specify the range. Negative values for range
+ * endpoints are not allowed.
+ */
+ @Deprecated
+ public static MutableRoaringBitmap andNot(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2,
+ final int rangeStart, final int rangeEnd) {
+ return andNot(x1, x2, (long) rangeStart, (long) rangeEnd);
+ }
+
+ /**
+ * Bitwise ANDNOT (difference) operation. The provided bitmaps are *not* modified. This operation
+ * is thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ */
+ public static MutableRoaringBitmap andNot(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2) {
+ final MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ if (s1 == s2) {
+ final MappeableContainer c1 = x1.highLowContainer.getContainerAtIndex(pos1);
+ final MappeableContainer c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ final MappeableContainer c = c1.andNot(c2);
+ if (c.getCardinality() > 0) {
+ answer.getMappeableRoaringArray().append(s1, c);
+ }
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ final int nextPos1 = x1.highLowContainer.advanceUntil(s2, pos1);
+ answer.getMappeableRoaringArray().appendCopy(x1.highLowContainer, pos1, nextPos1);
+ pos1 = nextPos1;
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ if (pos2 == length2) {
+ answer.getMappeableRoaringArray().appendCopy(x1.highLowContainer, pos1, length1);
+ }
+ return answer;
+ }
+
+ /**
+ * Generate a bitmap with the specified values set to true. The provided integers values don't
+ * have to be in sorted order, but it may be preferable to sort them from a performance point of
+ * view.
+ *
+ * This function is equivalent to :
+ *
+ *
+ * (Effectively calls {@link BufferFastAggregation#or})
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap or(ImmutableRoaringBitmap... bitmaps) {
+ return BufferFastAggregation.or(bitmaps);
+ }
+
+ /**
+ * Bitwise OR (union) operation. The provided bitmaps are *not* modified. This operation is
+ * thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ * @see BufferFastAggregation#or(ImmutableRoaringBitmap...)
+ * @see BufferFastAggregation#horizontal_or(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap or(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2) {
+ final MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ MappeableContainerPointer i1 = x1.highLowContainer.getContainerPointer();
+ MappeableContainerPointer i2 = x2.highLowContainer.getContainerPointer();
+ main:
+ if (i1.hasContainer() && i2.hasContainer()) {
+ while (true) {
+ if (i1.key() == i2.key()) {
+ answer.getMappeableRoaringArray().append(i1.key(),
+ i1.getContainer().or(i2.getContainer()));
+ i1.advance();
+ i2.advance();
+ if (!i1.hasContainer() || !i2.hasContainer()) {
+ break main;
+ }
+ } else if (Util.compareUnsigned(i1.key(), i2.key()) < 0) { // i1.key() < i2.key()
+ answer.getMappeableRoaringArray().appendCopy(i1.key(), i1.getContainer());
+ i1.advance();
+ if (!i1.hasContainer()) {
+ break main;
+ }
+ } else { // i1.key() > i2.key()
+ answer.getMappeableRoaringArray().appendCopy(i2.key(), i2.getContainer());
+ i2.advance();
+ if (!i2.hasContainer()) {
+ break main;
+ }
+ }
+ }
+ }
+ if (!i1.hasContainer()) {
+ while (i2.hasContainer()) {
+ answer.getMappeableRoaringArray().appendCopy(i2.key(), i2.getContainer());
+ i2.advance();
+ }
+ } else if (!i2.hasContainer()) {
+ while (i1.hasContainer()) {
+ answer.getMappeableRoaringArray().appendCopy(i1.key(), i1.getContainer());
+ i1.advance();
+ }
+ }
+ return answer;
+ }
+
+ /**
+ * Compute overall OR between bitmaps.
+ *
+ * (Effectively calls {@link BufferFastAggregation#or})
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap or(@SuppressWarnings("rawtypes") Iterator bitmaps) {
+ return BufferFastAggregation.or(bitmaps);
+ }
+
+ /**
+ * Computes OR between input bitmaps in the given range, from rangeStart (inclusive) to rangeEnd
+ * (exclusive)
+ *
+ * @param bitmaps input bitmaps, these are not modified
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new result bitmap
+ */
+ public static MutableRoaringBitmap or(@SuppressWarnings("rawtypes") final Iterator bitmaps,
+ final long rangeStart, final long rangeEnd) {
+ MutableRoaringBitmap.rangeSanityCheck(rangeStart, rangeEnd);
+ Iterator
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return cardinality of the union
+ * @see BufferFastAggregation#or(ImmutableRoaringBitmap...)
+ * @see BufferFastAggregation#horizontal_or(ImmutableRoaringBitmap...)
+ */
+ public static int orCardinality(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2) {
+ // we use the fact that the cardinality of the bitmaps is known so that
+ // the union is just the total cardinality minus the intersection
+ return x1.getCardinality() + x2.getCardinality() - andCardinality(x1, x2);
+ }
+
+ /**
+ * Computes XOR between input bitmaps in the given range, from rangeStart (inclusive) to rangeEnd
+ * (exclusive)
+ *
+ * @param bitmaps input bitmaps, these are not modified
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new result bitmap
+ */
+ public static MutableRoaringBitmap xor(@SuppressWarnings("rawtypes") final Iterator bitmaps,
+ final long rangeStart, final long rangeEnd) {
+ Iterator
+ * If you have more than 2 bitmaps, consider using the FastAggregation class.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ * @see BufferFastAggregation#xor(ImmutableRoaringBitmap...)
+ * @see BufferFastAggregation#horizontal_xor(ImmutableRoaringBitmap...)
+ */
+ public static MutableRoaringBitmap xor(final ImmutableRoaringBitmap x1,
+ final ImmutableRoaringBitmap x2) {
+ final MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ MappeableContainerPointer i1 = x1.highLowContainer.getContainerPointer();
+ MappeableContainerPointer i2 = x2.highLowContainer.getContainerPointer();
+ main:
+ if (i1.hasContainer() && i2.hasContainer()) {
+ while (true) {
+ if (i1.key() == i2.key()) {
+ final MappeableContainer c = i1.getContainer().xor(i2.getContainer());
+ if (c.getCardinality() > 0) {
+ answer.getMappeableRoaringArray().append(i1.key(), c);
+ }
+ i1.advance();
+ i2.advance();
+ if (!i1.hasContainer() || !i2.hasContainer()) {
+ break main;
+ }
+ } else if (Util.compareUnsigned(i1.key(), i2.key()) < 0) { // i1.key() < i2.key()
+ answer.getMappeableRoaringArray().appendCopy(i1.key(), i1.getContainer());
+ i1.advance();
+ if (!i1.hasContainer()) {
+ break main;
+ }
+ } else { // i1.key() < i2.key()
+ answer.getMappeableRoaringArray().appendCopy(i2.key(), i2.getContainer());
+ i2.advance();
+ if (!i2.hasContainer()) {
+ break main;
+ }
+ }
+ }
+ }
+ if (!i1.hasContainer()) {
+ while (i2.hasContainer()) {
+ answer.getMappeableRoaringArray().appendCopy(i2.key(), i2.getContainer());
+ i2.advance();
+ }
+ } else if (!i2.hasContainer()) {
+ while (i1.hasContainer()) {
+ answer.getMappeableRoaringArray().appendCopy(i1.key(), i1.getContainer());
+ i1.advance();
+ }
+ }
+
+ return answer;
+ }
+
+ @Override
+ public ImmutableRoaringBitmap clone() {
+ try {
+ final ImmutableRoaringBitmap x = (ImmutableRoaringBitmap) super.clone();
+ x.highLowContainer = highLowContainer.clone();
+ return x;
+ } catch (final CloneNotSupportedException e) {
+ throw new RuntimeException("shouldn't happen with clone", e);
+ }
+ }
+
+ /**
+ * Checks whether the value in included, which is equivalent to checking if the corresponding bit
+ * is set (get in BitSet class).
+ *
+ * @param x integer value
+ * @return whether the integer value is included.
+ */
+ @Override
+ public boolean contains(final int x) {
+ final short hb = BufferUtil.highbits(x);
+ final MappeableContainer c = highLowContainer.getContainer(hb);
+ return c != null && c.contains(BufferUtil.lowbits(x));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof ImmutableRoaringBitmap) {
+ if (this.highLowContainer.size() != ((ImmutableRoaringBitmap) o).highLowContainer.size()) {
+ return false;
+ }
+ MappeableContainerPointer mp1 = this.highLowContainer.getContainerPointer();
+ MappeableContainerPointer mp2 =
+ ((ImmutableRoaringBitmap) o).highLowContainer.getContainerPointer();
+ while (mp1.hasContainer()) {
+ if (mp1.key() != mp2.key()) {
+ return false;
+ }
+ if (mp1.getCardinality() != mp2.getCardinality()) {
+ return false;
+ }
+ if (!mp1.getContainer().equals(mp2.getContainer())) {
+ return false;
+ }
+ mp1.advance();
+ mp2.advance();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the number of distinct integers added to the bitmap (e.g., number of bits set).
+ *
+ * @return the cardinality
+ */
+ @Override
+ public long getLongCardinality() {
+ long size = 0;
+ for (int i = 0; i < this.highLowContainer.size(); ++i) {
+ size += this.highLowContainer.getCardinality(i);
+ }
+ return size;
+ }
+
+ @Override
+ public int getCardinality() {
+ return (int) getLongCardinality();
+ }
+
+ @Override
+ public void forEach(IntConsumer ic) {
+ for (int i = 0; i < this.highLowContainer.size(); i++) {
+ highLowContainer.getContainerAtIndex(i).forEach(highLowContainer.getKeyAtIndex(i), ic);
+ }
+ }
+
+ /**
+ * Return a low-level container pointer that can be used to access the underlying data structure.
+ *
+ * @return container pointer
+ */
+ public MappeableContainerPointer getContainerPointer() {
+ return this.highLowContainer.getContainerPointer();
+ }
+
+ /**
+ * For better performance, consider the Use the {@link #forEach forEach} method.
+ *
+ * @return a custom iterator over set bits, the bits are traversed in ascending sorted order
+ */
+ @Override
+ public PeekableIntIterator getIntIterator() {
+ return new ImmutableRoaringIntIterator();
+ }
+
+ /**
+ * @return a custom iterator over set bits, the bits are traversed in descending sorted order
+ */
+ @Override
+ public IntIterator getReverseIntIterator() {
+ return new ImmutableRoaringReverseIntIterator();
+ }
+
+ /**
+ * Estimate of the memory usage of this data structure. This can be expected to be within 1% of
+ * the true memory usage. If exact measures are needed, we recommend using dedicated libraries
+ * such as SizeOf.
+ *
+ * When the bitmap is constructed from a ByteBuffer from a memory-mapped file, this estimate is
+ * invalid: we can expect the actual memory usage to be significantly (e.g., 10x) less.
+ *
+ * @return estimated memory usage.
+ */
+ @Override
+ public long getLongSizeInBytes() {
+ long size = 4;
+ for (int i = 0; i < this.highLowContainer.size(); ++i) {
+ if (this.highLowContainer.getContainerAtIndex(i) instanceof MappeableRunContainer) {
+ MappeableRunContainer thisRunContainer =
+ (MappeableRunContainer) this.highLowContainer.getContainerAtIndex(i);
+ size += 4 + BufferUtil.getSizeInBytesFromCardinalityEtc(0, thisRunContainer.nbrruns, true);
+ } else {
+ size += 4 + BufferUtil
+ .getSizeInBytesFromCardinalityEtc(this.highLowContainer.getCardinality(i), 0, false);
+ }
+ }
+ return size;
+ }
+
+ @Override
+ public int getSizeInBytes() {
+ return (int) getLongSizeInBytes();
+ }
+
+ @Override
+ public int hashCode() {
+ return highLowContainer.hashCode();
+ }
+
+ /**
+ * Check whether this bitmap has had its runs compressed.
+ *
+ * @return whether this bitmap has run compression
+ */
+ public boolean hasRunCompression() {
+ return this.highLowContainer.hasRunCompression();
+ }
+
+ /**
+ * Checks whether the bitmap is empty.
+ *
+ * @return true if this bitmap contains no set bit
+ */
+ @Override
+ public boolean isEmpty() {
+ return highLowContainer.size() == 0;
+ }
+
+ /**
+ * iterate over the positions of the true values.
+ *
+ * @return the iterator
+ */
+ @Override
+ public Iterator
+ * Consider calling {@link MutableRoaringBitmap#runOptimize} before serialization to improve
+ * compression if this is a MutableRoaringBitmap instance.
+ *
+ * The current bitmap is not modified.
+ *
+ * Advanced example: To serialize your bitmap to a ByteBuffer, you can do the following.
+ *
+ *
+ * Note: Java's data structures are in big endian format. Roaring serializes to a little endian
+ * format, so the bytes are flipped by the library during serialization to ensure that what is
+ * stored is in little endian---despite Java's big endianness. You can defeat this process by
+ * reflipping the bytes again in a custom DataOutput which could lead to serialized Roaring
+ * objects with an incorrect byte order.
+ *
+ * @param out the DataOutput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ @Override
+ public void serialize(DataOutput out) throws IOException {
+ this.highLowContainer.serialize(out);
+ }
+
+ /**
+ * Report the number of bytes required for serialization. This count will match the bytes written
+ * when calling the serialize method.
+ *
+ * @return the size in bytes
+ */
+ @Override
+ public int serializedSizeInBytes() {
+ return this.highLowContainer.serializedSizeInBytes();
+ }
+
+ /**
+ * Return the set values as an array if the cardinality is less
+ * than 2147483648. The integer values are in sorted order.
+ *
+ * @return array representing the set values.
+ */
+ @Override
+ public int[] toArray() {
+ final int[] array = new int[(int) this.getCardinality()];
+ int pos = 0, pos2 = 0;
+ while (pos < this.highLowContainer.size()) {
+ final int hs = BufferUtil.toIntUnsigned(this.highLowContainer.getKeyAtIndex(pos)) << 16;
+ final MappeableContainer c = this.highLowContainer.getContainerAtIndex(pos++);
+ c.fillLeastSignificant16bits(array, pos2, hs);
+ pos2 += c.getCardinality();
+ }
+ return array;
+ }
+
+ /**
+ * Copies the content of this bitmap to a bitmap that can be modified.
+ *
+ * @return a mutable bitmap.
+ */
+ public MutableRoaringBitmap toMutableRoaringBitmap() {
+ MutableRoaringBitmap c = new MutableRoaringBitmap();
+ MappeableContainerPointer mcp = highLowContainer.getContainerPointer();
+ while (mcp.hasContainer()) {
+ c.getMappeableRoaringArray().appendCopy(mcp.key(), mcp.getContainer());
+ mcp.advance();
+ }
+ return c;
+ }
+
+ /**
+ * Copies this bitmap to a mutable RoaringBitmap.
+ *
+ * @return a copy of this bitmap as a RoaringBitmap.
+ */
+ public RoaringBitmap toRoaringBitmap() {
+ return new RoaringBitmap(this);
+ }
+
+ /**
+ * A string describing the bitmap.
+ *
+ * @return the string
+ */
+ @Override
+ public String toString() {
+ final StringBuilder answer = new StringBuilder();
+ final IntIterator i = this.getIntIterator();
+ answer.append("{");
+ if (i.hasNext()) {
+ answer.append(i.next() & 0xFFFFFFFFL);
+ }
+ while (i.hasNext()) {
+ answer.append(",");
+ // to avoid using too much memory, we limit the size
+ if (answer.length() > 0x80000) {
+ answer.append("...");
+ break;
+ }
+ answer.append(i.next() & 0xFFFFFFFFL);
+ }
+ answer.append("}");
+ return answer.toString();
+ }
+
+ private final class ImmutableRoaringIntIterator implements PeekableIntIterator {
+ private MappeableContainerPointer cp =
+ ImmutableRoaringBitmap.this.highLowContainer.getContainerPointer();
+
+ private int hs = 0;
+
+ private PeekableShortIterator iter;
+
+ private boolean ok;
+
+ public ImmutableRoaringIntIterator() {
+ nextContainer();
+ }
+
+ @Override
+ public PeekableIntIterator clone() {
+ try {
+ ImmutableRoaringIntIterator x = (ImmutableRoaringIntIterator) super.clone();
+ x.iter = this.iter.clone();
+ x.cp = this.cp.clone();
+ return x;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return ok;
+ }
+
+ @Override
+ public int next() {
+ int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ cp.advance();
+ nextContainer();
+ }
+ return x;
+ }
+
+
+ private void nextContainer() {
+ ok = cp.hasContainer();
+ if (ok) {
+ iter = cp.getContainer().getShortIterator();
+ hs = BufferUtil.toIntUnsigned(cp.key()) << 16;
+ }
+ }
+
+ @Override
+ public void advanceIfNeeded(int minval) {
+ while (hasNext() && ((hs >>> 16) < (minval >>> 16))) {
+ cp.advance();
+ nextContainer();
+ }
+ if (ok && ((hs >>> 16) == (minval >>> 16))) {
+ iter.advanceIfNeeded(BufferUtil.lowbits(minval));
+ if (!iter.hasNext()) {
+ cp.advance();
+ nextContainer();
+ }
+ }
+ }
+
+ @Override
+ public int peekNext() {
+ return BufferUtil.toIntUnsigned(iter.peekNext()) | hs;
+ }
+
+
+ }
+
+ private final class ImmutableRoaringReverseIntIterator implements IntIterator {
+ private MappeableContainerPointer cp = ImmutableRoaringBitmap.this.highLowContainer
+ .getContainerPointer(ImmutableRoaringBitmap.this.highLowContainer.size() - 1);
+
+ private int hs = 0;
+
+ private ShortIterator iter;
+
+ private boolean ok;
+
+ public ImmutableRoaringReverseIntIterator() {
+ nextContainer();
+ }
+
+ @Override
+ public IntIterator clone() {
+ try {
+ ImmutableRoaringReverseIntIterator x = (ImmutableRoaringReverseIntIterator) super.clone();
+ x.iter = this.iter.clone();
+ x.cp = this.cp.clone();
+ return x;
+ } catch (CloneNotSupportedException e) {
+ return null;// will not happen
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return ok;
+ }
+
+ @Override
+ public int next() {
+ int x = iter.nextAsInt() | hs;
+ if (!iter.hasNext()) {
+ cp.previous();
+ nextContainer();
+ }
+ return x;
+ }
+
+
+ private void nextContainer() {
+ ok = cp.hasContainer();
+ if (ok) {
+ iter = cp.getContainer().getReverseShortIterator();
+ hs = BufferUtil.toIntUnsigned(cp.key()) << 16;
+ }
+ }
+
+
+ }
+
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/MappeableArrayContainer.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/MappeableArrayContainer.java
new file mode 100644
index 000000000..5a68357b1
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/MappeableArrayContainer.java
@@ -0,0 +1,1688 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap.buffer;
+
+import com.fr.third.bitmap.roaringbitmap.ArrayContainer;
+import com.fr.third.bitmap.roaringbitmap.Container;
+import com.fr.third.bitmap.roaringbitmap.IntConsumer;
+import com.fr.third.bitmap.roaringbitmap.PeekableShortIterator;
+import com.fr.third.bitmap.roaringbitmap.ShortIterator;
+import com.fr.third.bitmap.roaringbitmap.Util;
+
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.nio.ShortBuffer;
+import java.util.Arrays;
+import java.util.Iterator;
+
+/**
+ * Simple container made of an array of 16-bit integers. Unlike ArrayContainer,
+ * this class uses a ShortBuffer to store data.
+ */
+public final class MappeableArrayContainer extends MappeableContainer implements Cloneable {
+ protected static final int DEFAULT_MAX_SIZE = 4096; // containers with DEFAULT_MAX_SZE or less
+ private static final int DEFAULT_INIT_SIZE = 4;
+ private static final int ARRAY_LAZY_LOWERBOUND = 1024;
+ // integers should be ArrayContainers
+ private static final long serialVersionUID = 1L;
+ protected int cardinality = 0;
+ protected ShortBuffer content;
+
+ /**
+ * Create an array container with default capacity
+ */
+ public MappeableArrayContainer() {
+ this(DEFAULT_INIT_SIZE);
+ }
+
+
+ /**
+ * Creates a new container from a non-mappeable one. This copies the data.
+ *
+ * @param bc the original container
+ */
+ public MappeableArrayContainer(ArrayContainer bc) {
+ this.cardinality = bc.getCardinality();
+ this.content = bc.toShortBuffer();
+ }
+
+
+ /**
+ * Create an array container with specified capacity
+ *
+ * @param capacity The capacity of the container
+ */
+ public MappeableArrayContainer(final int capacity) {
+ content = ShortBuffer.allocate(capacity);
+ }
+
+ /**
+ * Create an array container with a run of ones from firstOfRun to lastOfRun, exclusive. Caller is
+ * responsible for making sure the range is small enough that ArrayContainer is appropriate.
+ *
+ * @param firstOfRun first index
+ * @param lastOfRun last index (range is exclusive)
+ */
+ public MappeableArrayContainer(final int firstOfRun, final int lastOfRun) {
+ // TODO: this can be optimized for performance
+ final int valuesInRange = lastOfRun - firstOfRun;
+ content = ShortBuffer.allocate(valuesInRange);
+ short[] sarray = content.array();
+ for (int i = 0; i < valuesInRange; ++i) {
+ sarray[i] = (short) (firstOfRun + i);
+ }
+ cardinality = valuesInRange;
+ }
+
+
+ private MappeableArrayContainer(int newCard, ShortBuffer newContent) {
+ this.cardinality = newCard;
+ ShortBuffer tmp = newContent.duplicate();// for thread-safety
+ this.content = ShortBuffer.allocate(Math.max(newCard, tmp.limit()));
+ tmp.rewind();
+ this.content.put(tmp);
+ }
+
+ /**
+ * Construct a new ArrayContainer backed by the provided ShortBuffer. Note that if you modify the
+ * ArrayContainer a new ShortBuffer may be produced.
+ *
+ * @param array ShortBuffer where the data is stored
+ * @param cardinality cardinality (number of values stored)
+ */
+ public MappeableArrayContainer(final ShortBuffer array, final int cardinality) {
+ if (array.limit() != cardinality) {
+ throw new RuntimeException("Mismatch between buffer and cardinality");
+ }
+ this.cardinality = cardinality;
+ this.content = array;
+ }
+
+ protected static int getArraySizeInBytes(int cardinality) {
+ return cardinality * 2;
+ }
+
+ protected static int serializedSizeInBytes(int cardinality) {
+ return cardinality * 2 + 2;
+ }
+
+ @Override
+ public MappeableContainer add(int begin, int end) {
+ // TODO: may need to convert to a RunContainer
+ if (end == begin) {
+ return clone();
+ }
+ if ((begin > end) || (end > (1 << 16))) {
+ throw new IllegalArgumentException("Invalid range [" + begin + "," + end + ")");
+ }
+ int indexstart = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) begin);
+ if (indexstart < 0) {
+ indexstart = -indexstart - 1;
+ }
+ int indexend = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) (end - 1));
+ if (indexend < 0) {
+ indexend = -indexend - 1;
+ } else {
+ indexend++;
+ }
+ int rangelength = end - begin;
+ int newcardinality = indexstart + (cardinality - indexend) + rangelength;
+ if (newcardinality > DEFAULT_MAX_SIZE) {
+ MappeableBitmapContainer a = this.toBitmapContainer();
+ return a.iadd(begin, end);
+ }
+ MappeableArrayContainer answer = new MappeableArrayContainer(newcardinality, content);
+ if (!BufferUtil.isBackedBySimpleArray(answer.content)) {
+ throw new RuntimeException("Should not happen. Internal bug.");
+ }
+ BufferUtil.arraycopy(content, indexend, answer.content, indexstart + rangelength,
+ cardinality - indexend);
+ short[] answerarray = answer.content.array();
+ for (int k = 0; k < rangelength; ++k) {
+ answerarray[k + indexstart] = (short) (begin + k);
+ }
+ answer.cardinality = newcardinality;
+ return answer;
+ }
+
+ /**
+ * running time is in O(n) time if insert is not in order.
+ */
+ @Override
+ // not thread-safe
+ public MappeableContainer add(final short x) {
+ if (BufferUtil.isBackedBySimpleArray(this.content)) {
+ short[] sarray = content.array();
+
+ int loc = Util.unsignedBinarySearch(sarray, 0, cardinality, x);
+ if (loc < 0) {
+ // Transform the ArrayContainer to a BitmapContainer
+ // when cardinality exceeds DEFAULT_MAX_SIZE
+ if (cardinality >= DEFAULT_MAX_SIZE) {
+ final MappeableBitmapContainer a = this.toBitmapContainer();
+ a.add(x);
+ return a;
+ }
+ if (cardinality >= sarray.length) {
+ increaseCapacity();
+ sarray = content.array();
+ }
+ // insertion : shift the elements > x by one
+ // position to
+ // the right
+ // and put x in it's appropriate place
+ System.arraycopy(sarray, -loc - 1, sarray, -loc, cardinality + loc + 1);
+ sarray[-loc - 1] = x;
+ ++cardinality;
+ }
+ } else {
+
+ final int loc = BufferUtil.unsignedBinarySearch(content, 0, cardinality, x);
+ if (loc < 0) {
+ // Transform the ArrayContainer to a BitmapContainer
+ // when cardinality exceeds DEFAULT_MAX_SIZE
+ if (cardinality >= DEFAULT_MAX_SIZE) {
+ final MappeableBitmapContainer a = this.toBitmapContainer();
+ a.add(x);
+ return a;
+ }
+ if (cardinality >= this.content.limit()) {
+ increaseCapacity();
+ }
+ // insertion : shift the elements > x by one
+ // position to
+ // the right
+ // and put x in it's appropriate place
+ for (int k = cardinality; k > -loc - 1; --k) {
+ content.put(k, content.get(k - 1));
+ }
+ content.put(-loc - 1, x);
+
+ ++cardinality;
+ }
+ }
+ return this;
+ }
+
+ private int advance(ShortIterator it) {
+ if (it.hasNext()) {
+ return BufferUtil.toIntUnsigned(it.next());
+ } else {
+ return -1;
+ }
+ }
+
+
+ @Override
+ public MappeableArrayContainer and(final MappeableArrayContainer value2) {
+
+ MappeableArrayContainer value1 = this;
+ final int desiredCapacity = Math.min(value1.getCardinality(), value2.getCardinality());
+ MappeableArrayContainer answer = new MappeableArrayContainer(desiredCapacity);
+ if (BufferUtil.isBackedBySimpleArray(this.content)
+ && BufferUtil.isBackedBySimpleArray(value2.content)) {
+ answer.cardinality = Util.unsignedIntersect2by2(value1.content.array(),
+ value1.getCardinality(), value2.content.array(), value2.getCardinality(),
+ answer.content.array());
+ } else {
+ answer.cardinality = BufferUtil.unsignedIntersect2by2(value1.content, value1.getCardinality(),
+ value2.content, value2.getCardinality(), answer.content.array());
+ }
+ return answer;
+ }
+
+ @Override
+ public MappeableContainer and(MappeableBitmapContainer x) {
+ return x.and(this);
+ }
+
+ @Override
+ public MappeableContainer and(final MappeableRunContainer value2) {
+ return value2.and(this);
+ }
+
+
+ @Override
+ public MappeableArrayContainer andNot(final MappeableArrayContainer value2) {
+ final MappeableArrayContainer value1 = this;
+ final int desiredCapacity = value1.getCardinality();
+ final MappeableArrayContainer answer = new MappeableArrayContainer(desiredCapacity);
+ if (BufferUtil.isBackedBySimpleArray(value1.content)
+ && BufferUtil.isBackedBySimpleArray(value2.content)) {
+ answer.cardinality =
+ Util.unsignedDifference(value1.content.array(), value1.getCardinality(),
+ value2.content.array(), value2.getCardinality(), answer.content.array());
+ } else {
+ answer.cardinality = BufferUtil.unsignedDifference(value1.content, value1.getCardinality(),
+ value2.content, value2.getCardinality(), answer.content.array());
+ }
+ return answer;
+ }
+
+ @Override
+ public MappeableArrayContainer andNot(MappeableBitmapContainer value2) {
+
+ final MappeableArrayContainer answer = new MappeableArrayContainer(content.limit());
+ int pos = 0;
+ short[] sarray = answer.content.array();
+ if (BufferUtil.isBackedBySimpleArray(this.content)) {
+ short[] c = content.array();
+ for (int k = 0; k < cardinality; ++k) {
+ short v = c[k];
+ if (!value2.contains(v)) {
+ sarray[pos++] = v;
+ }
+ }
+ } else {
+ for (int k = 0; k < cardinality; ++k) {
+ short v = this.content.get(k);
+ if (!value2.contains(v)) {
+ sarray[pos++] = v;
+ }
+ }
+ }
+ answer.cardinality = pos;
+ return answer;
+ }
+
+ @Override
+ public MappeableContainer andNot(final MappeableRunContainer x) {
+ int writeLocation = 0;
+ int runStart, runEnd; // the current or upcoming run.
+ if (x.nbrruns == 0) {
+ return clone();
+ }
+
+ ShortBuffer buffer = ShortBuffer.allocate(cardinality);
+
+ runStart = BufferUtil.toIntUnsigned(x.getValue(0));
+ runEnd = runStart + BufferUtil.toIntUnsigned(x.getLength(0));
+ int whichRun = 0;
+
+ short val;
+ for (int i = 0; i < cardinality; ++i) {
+ val = content.get(i);
+ int valInt = BufferUtil.toIntUnsigned(val);
+ if (valInt < runStart) {
+ buffer.put(writeLocation++, val);
+ } else if (valInt <= runEnd) {
+ ; // don't want item
+ } else {
+ // greater than this run, need to do an advanceUntil on runs
+ // done sequentially for now (no galloping attempts).
+ do {
+ if (whichRun + 1 < x.nbrruns) {
+ whichRun++;
+ runStart = BufferUtil.toIntUnsigned(x.getValue(whichRun));
+ runEnd = runStart + BufferUtil.toIntUnsigned(x.getLength(whichRun));
+ } else {
+ runStart = runEnd = (1 << 16) + 1; // infinity....
+ }
+ } while (valInt > runEnd);
+ --i; // need to re-process this val
+ }
+ }
+ return new MappeableArrayContainer(writeLocation, buffer);
+ }
+
+ @Override
+ public void clear() {
+ cardinality = 0;
+ }
+
+ @Override
+ public MappeableArrayContainer clone() {
+ return new MappeableArrayContainer(this.cardinality, this.content);
+ }
+
+ @Override
+ public boolean contains(final short x) {
+ return BufferUtil.unsignedBinarySearch(content, 0, cardinality, x) >= 0;
+ }
+
+ // in order
+ // not thread-safe
+ private void emit(short val) {
+ if (cardinality == content.limit()) {
+ increaseCapacity(true);
+ }
+ content.put(cardinality++, val);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MappeableArrayContainer) {
+ final MappeableArrayContainer srb = (MappeableArrayContainer) o;
+ if (srb.cardinality != this.cardinality) {
+ return false;
+ }
+ if (BufferUtil.isBackedBySimpleArray(this.content)
+ && BufferUtil.isBackedBySimpleArray(srb.content)) {
+ short[] t = this.content.array();
+ short[] sr = srb.content.array();
+
+ for (int i = 0; i < this.cardinality; ++i) {
+ if (t[i] != sr[i]) {
+ return false;
+ }
+ }
+
+ } else {
+ for (int i = 0; i < this.cardinality; ++i) {
+ if (this.content.get(i) != srb.content.get(i)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ } else if (o instanceof MappeableRunContainer) {
+ return o.equals(this);
+ }
+ return false;
+ }
+
+
+ @Override
+ public void fillLeastSignificant16bits(int[] x, int i, int mask) {
+ if (BufferUtil.isBackedBySimpleArray(this.content)) {
+ short[] c = this.content.array();
+ for (int k = 0; k < this.cardinality; ++k) {
+ x[k + i] = BufferUtil.toIntUnsigned(c[k]) | mask;
+ }
+
+ } else {
+ for (int k = 0; k < this.cardinality; ++k) {
+ x[k + i] = BufferUtil.toIntUnsigned(this.content.get(k)) | mask;
+ }
+ }
+ }
+
+ @Override
+ // not thread-safe
+ public MappeableContainer flip(short x) {
+ if (BufferUtil.isBackedBySimpleArray(this.content)) {
+ short[] sarray = content.array();
+ int loc = Util.unsignedBinarySearch(sarray, 0, cardinality, x);
+ if (loc < 0) {
+ // Transform the ArrayContainer to a BitmapContainer
+ // when cardinality = DEFAULT_MAX_SIZE
+ if (cardinality >= DEFAULT_MAX_SIZE) {
+ MappeableBitmapContainer a = this.toBitmapContainer();
+ a.add(x);
+ return a;
+ }
+ if (cardinality >= sarray.length) {
+ increaseCapacity();
+ sarray = content.array();
+ }
+ // insertion : shift the elements > x by one position to
+ // the right
+ // and put x in it's appropriate place
+ System.arraycopy(sarray, -loc - 1, sarray, -loc, cardinality + loc + 1);
+ sarray[-loc - 1] = x;
+ ++cardinality;
+ } else {
+ System.arraycopy(sarray, loc + 1, sarray, loc, cardinality - loc - 1);
+ --cardinality;
+ }
+ return this;
+
+ } else {
+ int loc = BufferUtil.unsignedBinarySearch(content, 0, cardinality, x);
+ if (loc < 0) {
+ // Transform the ArrayContainer to a BitmapContainer
+ // when cardinality = DEFAULT_MAX_SIZE
+ if (cardinality >= DEFAULT_MAX_SIZE) {
+ MappeableBitmapContainer a = this.toBitmapContainer();
+ a.add(x);
+ return a;
+ }
+ if (cardinality >= content.limit()) {
+ increaseCapacity();
+ }
+ // insertion : shift the elements > x by one position to
+ // the right
+ // and put x in it's appropriate place
+ for (int k = cardinality; k > -loc - 1; --k) {
+ content.put(k, content.get(k - 1));
+ }
+ content.put(-loc - 1, x);
+ ++cardinality;
+ } else {
+ for (int k = loc + 1; k < cardinality; --k) {
+ content.put(k - 1, content.get(k));
+ }
+ --cardinality;
+ }
+ return this;
+ }
+ }
+
+ @Override
+ protected int getArraySizeInBytes() {
+ return getArraySizeInBytes(cardinality);
+ }
+
+ @Override
+ public int getCardinality() {
+ return cardinality;
+ }
+
+ @Override
+ public ShortIterator getReverseShortIterator() {
+ if (this.isArrayBacked()) {
+ return new RawReverseArrayContainerShortIterator(this);
+ }
+ return new ReverseMappeableArrayContainerShortIterator(this);
+ }
+
+ @Override
+ public PeekableShortIterator getShortIterator() {
+ if (this.isArrayBacked()) {
+ return new RawArrayContainerShortIterator(this);
+ }
+ return new MappeableArrayContainerShortIterator(this);
+ }
+
+ @Override
+ public int getSizeInBytes() {
+ return this.cardinality * 2;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (int k = 0; k < cardinality; ++k) {
+ hash += 31 * hash + content.get(k);
+ }
+ return hash;
+ }
+
+ @Override
+ // not thread-safe
+ public MappeableContainer iadd(int begin, int end) {
+ // TODO: may need to convert to a RunContainer
+ if (end == begin) {
+ return this;
+ }
+ if ((begin > end) || (end > (1 << 16))) {
+ throw new IllegalArgumentException("Invalid range [" + begin + "," + end + ")");
+ }
+ int indexstart = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) begin);
+ if (indexstart < 0) {
+ indexstart = -indexstart - 1;
+ }
+ int indexend = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) (end - 1));
+ if (indexend < 0) {
+ indexend = -indexend - 1;
+ } else {
+ indexend++;
+ }
+ int rangelength = end - begin;
+ int newcardinality = indexstart + (cardinality - indexend) + rangelength;
+ if (newcardinality > DEFAULT_MAX_SIZE) {
+ MappeableBitmapContainer a = this.toBitmapContainer();
+ return a.iadd(begin, end);
+ }
+ if (newcardinality >= this.content.limit()) {
+ increaseCapacity(newcardinality);
+ }
+ BufferUtil.arraycopy(content, indexend, content, indexstart + rangelength,
+ cardinality - indexend);
+ if (BufferUtil.isBackedBySimpleArray(content)) {
+ short[] contentarray = content.array();
+ for (int k = 0; k < rangelength; ++k) {
+ contentarray[k + indexstart] = (short) (begin + k);
+ }
+ } else {
+ for (int k = 0; k < rangelength; ++k) {
+ content.put(k + indexstart, (short) (begin + k));
+ }
+ }
+ cardinality = newcardinality;
+ return this;
+ }
+
+ @Override
+ public MappeableArrayContainer iand(final MappeableArrayContainer value2) {
+ final MappeableArrayContainer value1 = this;
+ if (!BufferUtil.isBackedBySimpleArray(value1.content)) {
+ throw new RuntimeException("Should not happen. Internal bug.");
+ }
+ value1.cardinality = BufferUtil.unsignedIntersect2by2(value1.content, value1.getCardinality(),
+ value2.content, value2.getCardinality(), value1.content.array());
+ return this;
+ }
+
+
+ @Override
+ public MappeableContainer iand(MappeableBitmapContainer value2) {
+ int pos = 0;
+ for (int k = 0; k < cardinality; ++k) {
+ short v = this.content.get(k);
+ if (value2.contains(v)) {
+ this.content.put(pos++, v);
+ }
+ }
+ cardinality = pos;
+ return this;
+ }
+
+
+ // Note it is never inplace, may wish to fix
+ @Override
+ public MappeableContainer iand(final MappeableRunContainer value2) {
+ return value2.and(this);
+ }
+
+ @Override
+ public MappeableArrayContainer iandNot(final MappeableArrayContainer value2) {
+ if (!BufferUtil.isBackedBySimpleArray(this.content)) {
+ throw new RuntimeException("Should not happen. Internal bug.");
+ }
+ if (BufferUtil.isBackedBySimpleArray(value2.content)) {
+ this.cardinality =
+ Util.unsignedDifference(this.content.array(), this.getCardinality(),
+ value2.content.array(), value2.getCardinality(), this.content.array());
+ } else {
+ this.cardinality = BufferUtil.unsignedDifference(this.content, this.getCardinality(),
+ value2.content, value2.getCardinality(), this.content.array());
+ }
+
+ return this;
+ }
+
+ @Override
+ public MappeableArrayContainer iandNot(MappeableBitmapContainer value2) {
+ if (!BufferUtil.isBackedBySimpleArray(this.content)) {
+ throw new RuntimeException("Should not happen. Internal bug.");
+ }
+ short[] c = this.content.array();
+ int pos = 0;
+ for (int k = 0; k < cardinality; ++k) {
+ short v = c[k];
+ if (!value2.contains(v)) {
+ c[pos++] = v;
+ }
+ }
+ this.cardinality = pos;
+ return this;
+ }
+
+ @Override
+ public MappeableContainer iandNot(final MappeableRunContainer value2) { // not inplace, revisit?
+ return andNot(value2);
+ }
+
+ private void increaseCapacity() {
+ increaseCapacity(false);
+ }
+
+ // temporarily allow an illegally large size, as long as the operation creating
+ // the illegal container does not return it.
+ // not thread safe!
+ private void increaseCapacity(boolean allowIllegalSize) {
+ int len = this.content.limit();
+ int newCapacity = (len == 0) ? DEFAULT_INIT_SIZE
+ : len < 64 ? len * 2 : this.content.limit() < 1067 ? len * 3 / 2 : len * 5 / 4;
+ // do not allocate more than we will ever need
+ if (newCapacity > MappeableArrayContainer.DEFAULT_MAX_SIZE && !allowIllegalSize) {
+ newCapacity = MappeableArrayContainer.DEFAULT_MAX_SIZE;
+ }
+ // if we are within 1/16th of the max., go to max right away to avoid further reallocations
+ if (newCapacity > MappeableArrayContainer.DEFAULT_MAX_SIZE
+ - MappeableArrayContainer.DEFAULT_MAX_SIZE / 16 && !allowIllegalSize) {
+ newCapacity = MappeableArrayContainer.DEFAULT_MAX_SIZE;
+ }
+ final ShortBuffer newContent = ShortBuffer.allocate(newCapacity);
+ this.content.rewind();
+ newContent.put(this.content);
+ this.content = newContent;
+ }
+
+
+ // not thread safe!
+ private void increaseCapacity(int min) {
+ int len = this.content.limit();
+ int newCapacity = (len == 0) ? DEFAULT_INIT_SIZE
+ : len < 64 ? len * 2 : len < 1024 ? len * 3 / 2 : len * 5 / 4;
+ if (newCapacity < min) {
+ newCapacity = min;
+ }
+ if (newCapacity > MappeableArrayContainer.DEFAULT_MAX_SIZE) {
+ newCapacity = MappeableArrayContainer.DEFAULT_MAX_SIZE;
+ }
+ if (newCapacity > MappeableArrayContainer.DEFAULT_MAX_SIZE
+ - MappeableArrayContainer.DEFAULT_MAX_SIZE / 16) {
+ newCapacity = MappeableArrayContainer.DEFAULT_MAX_SIZE;
+ }
+ final ShortBuffer newContent = ShortBuffer.allocate(newCapacity);
+ this.content.rewind();
+ newContent.put(this.content);
+ this.content = newContent;
+ }
+
+ @Override
+ // not thread safe! (duh!)
+ public MappeableContainer inot(final int firstOfRange, final int lastOfRange) {
+ // TODO: may need to convert to a RunContainer
+ // TODO: this can be optimized for performance
+ // determine the span of array indices to be affected
+ int startIndex = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) firstOfRange);
+ if (startIndex < 0) {
+ startIndex = -startIndex - 1;
+ }
+ int lastIndex =
+ BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) (lastOfRange - 1));
+ if (lastIndex < 0) {
+ lastIndex = -lastIndex - 1 - 1;
+ }
+ final int currentValuesInRange = lastIndex - startIndex + 1;
+ final int spanToBeFlipped = lastOfRange - firstOfRange;
+ final int newValuesInRange = spanToBeFlipped - currentValuesInRange;
+ final ShortBuffer buffer = ShortBuffer.allocate(newValuesInRange);
+ final int cardinalityChange = newValuesInRange - currentValuesInRange;
+ final int newCardinality = cardinality + cardinalityChange;
+
+ if (cardinalityChange > 0) { // expansion, right shifting needed
+ if (newCardinality > content.limit()) {
+ // so big we need a bitmap?
+ if (newCardinality > DEFAULT_MAX_SIZE) {
+ return toBitmapContainer().inot(firstOfRange, lastOfRange);
+ }
+ final ShortBuffer co = ShortBuffer.allocate(newCardinality);
+ content.rewind();
+ co.put(content);
+ content = co;
+ }
+ // slide right the contents after the range
+ for (int pos = cardinality - 1; pos > lastIndex; --pos) {
+ content.put(pos + cardinalityChange, content.get(pos));
+ }
+ negateRange(buffer, startIndex, lastIndex, firstOfRange, lastOfRange);
+ } else { // no expansion needed
+ negateRange(buffer, startIndex, lastIndex, firstOfRange, lastOfRange);
+ if (cardinalityChange < 0) {
+ // Leave array oversize
+ for (int i = startIndex + newValuesInRange; i < newCardinality; ++i) {
+ content.put(i, content.get(i - cardinalityChange));
+ }
+ }
+ }
+ cardinality = newCardinality;
+ return this;
+ }
+
+ @Override
+ public boolean intersects(MappeableArrayContainer value2) {
+ MappeableArrayContainer value1 = this;
+ return BufferUtil.unsignedIntersects(value1.content, value1.getCardinality(), value2.content,
+ value2.getCardinality());
+ }
+
+ @Override
+ public boolean intersects(MappeableBitmapContainer x) {
+ return x.intersects(this);
+ }
+
+ @Override
+ public boolean intersects(MappeableRunContainer x) {
+ return x.intersects(this);
+ }
+
+ @Override
+ public MappeableContainer ior(final MappeableArrayContainer value2) {
+ return this.or(value2);
+ }
+
+ @Override
+ public MappeableContainer ior(MappeableBitmapContainer x) {
+ return x.or(this);
+ }
+
+ @Override
+ public MappeableContainer ior(final MappeableRunContainer value2) {
+ // not inplace
+ return value2.or(this);
+ }
+
+ @Override
+ public MappeableContainer iremove(int begin, int end) {
+ if (end == begin) {
+ return this;
+ }
+ if ((begin > end) || (end > (1 << 16))) {
+ throw new IllegalArgumentException("Invalid range [" + begin + "," + end + ")");
+ }
+ int indexstart = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) begin);
+ if (indexstart < 0) {
+ indexstart = -indexstart - 1;
+ }
+ int indexend = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short) (end - 1));
+ if (indexend < 0) {
+ indexend = -indexend - 1;
+ } else {
+ indexend++;
+ }
+ int rangelength = indexend - indexstart;
+ BufferUtil.arraycopy(content, indexstart + rangelength, content, indexstart,
+ cardinality - indexstart - rangelength);
+ cardinality -= rangelength;
+ return this;
+ }
+
+ @Override
+ protected boolean isArrayBacked() {
+ return BufferUtil.isBackedBySimpleArray(this.content);
+ }
+
+
+ @Override
+ public Iterator
+ *
+ * Adding and removing content from this container might make it wasteful so regular calls to
+ * "runOptimize" might be warranted.
+ */
+public final class MappeableRunContainer extends MappeableContainer implements Cloneable {
+ private static final int DEFAULT_INIT_SIZE = 4;
+ private static final long serialVersionUID = 1L;
+ protected ShortBuffer valueslength;
+ protected int nbrruns = 0;// how many runs, this number should fit in 16 bits.
+
+ /**
+ * Create a container with default capacity
+ */
+ public MappeableRunContainer() {
+ this(DEFAULT_INIT_SIZE);
+ }
+
+ /**
+ * Create an array container with specified capacity
+ *
+ * @param capacity The capacity of the container
+ */
+ public MappeableRunContainer(final int capacity) {
+ valueslength = ShortBuffer.allocate(2 * capacity);
+ }
+
+
+ private MappeableRunContainer(int nbrruns, final ShortBuffer valueslength) {
+ this.nbrruns = nbrruns;
+ ShortBuffer tmp = valueslength.duplicate();// for thread safety
+ this.valueslength = ShortBuffer.allocate(Math.max(2 * nbrruns, tmp.limit()));
+ tmp.rewind();
+ this.valueslength.put(tmp); // may copy more than it needs to??
+ }
+
+ protected MappeableRunContainer(MappeableArrayContainer arr, int nbrRuns) {
+ this.nbrruns = nbrRuns;
+ valueslength = ShortBuffer.allocate(2 * nbrRuns);
+ short[] vl = valueslength.array();
+ if (nbrRuns == 0) {
+ return;
+ }
+
+ int prevVal = -2;
+ int runLen = 0;
+ int runCount = 0;
+ if (BufferUtil.isBackedBySimpleArray(arr.content)) {
+ short[] a = arr.content.array();
+ for (int i = 0; i < arr.cardinality; i++) {
+ int curVal = BufferUtil.toIntUnsigned(a[i]);
+ if (curVal == prevVal + 1) {
+ ++runLen;
+ } else {
+ if (runCount > 0) {
+ vl[2 * (runCount - 1) + 1] = (short) runLen;
+ }
+ // setLength(runCount - 1, (short) runLen);
+ vl[2 * runCount] = (short) curVal;
+ // setValue(runCount, (short) curVal);
+ runLen = 0;
+ ++runCount;
+ }
+ prevVal = curVal;
+ }
+
+ } else {
+ for (int i = 0; i < arr.cardinality; i++) {
+ int curVal = BufferUtil.toIntUnsigned(arr.content.get(i));
+ if (curVal == prevVal + 1) {
+ ++runLen;
+ } else {
+ if (runCount > 0) {
+ vl[2 * (runCount - 1) + 1] = (short) runLen;
+ }
+ // setLength(runCount - 1, (short) runLen);
+ vl[2 * runCount] = (short) curVal;
+ // setValue(runCount, (short) curVal);
+ runLen = 0;
+ ++runCount;
+ }
+ prevVal = curVal;
+ }
+ }
+ // setLength(runCount-1, (short) runLen);
+ vl[2 * (runCount - 1) + 1] = (short) runLen;
+ }
+
+ // convert a bitmap container to a run container somewhat efficiently.
+ protected MappeableRunContainer(MappeableBitmapContainer bc, int nbrRuns) {
+ this.nbrruns = nbrRuns;
+ valueslength = ShortBuffer.allocate(2 * nbrRuns);
+ if (!BufferUtil.isBackedBySimpleArray(valueslength)) {
+ throw new RuntimeException("Unexpected internal error.");
+ }
+ short[] vl = valueslength.array();
+ if (nbrRuns == 0) {
+ return;
+ }
+ if (bc.isArrayBacked()) {
+ long[] b = bc.bitmap.array();
+ int longCtr = 0; // index of current long in bitmap
+ long curWord = b[0]; // its value
+ int runCount = 0;
+ final int len = bc.bitmap.limit();
+ while (true) {
+ // potentially multiword advance to first 1 bit
+ while (curWord == 0L && longCtr < len - 1) {
+ curWord = b[++longCtr];
+ }
+
+ if (curWord == 0L) {
+ // wrap up, no more runs
+ return;
+ }
+ int localRunStart = Long.numberOfTrailingZeros(curWord);
+ int runStart = localRunStart + 64 * longCtr;
+ // stuff 1s into number's LSBs
+ long curWordWith1s = curWord | (curWord - 1);
+
+ // find the next 0, potentially in a later word
+ int runEnd = 0;
+ while (curWordWith1s == -1L && longCtr < len - 1) {
+ curWordWith1s = b[++longCtr];
+ }
+
+ if (curWordWith1s == -1L) {
+ // a final unterminated run of 1s (32 of them)
+ runEnd = 64 + longCtr * 64;
+ // setValue(runCount, (short) runStart);
+ vl[2 * runCount] = (short) runStart;
+ // setLength(runCount, (short) (runEnd-runStart-1));
+ vl[2 * runCount + 1] = (short) (runEnd - runStart - 1);
+ return;
+ }
+ int localRunEnd = Long.numberOfTrailingZeros(~curWordWith1s);
+ runEnd = localRunEnd + longCtr * 64;
+ // setValue(runCount, (short) runStart);
+ vl[2 * runCount] = (short) runStart;
+ // setLength(runCount, (short) (runEnd-runStart-1));
+ vl[2 * runCount + 1] = (short) (runEnd - runStart - 1);
+ runCount++;
+ // now, zero out everything right of runEnd.
+ curWord = curWordWith1s & (curWordWith1s + 1);
+ // We've lathered and rinsed, so repeat...
+ }
+ } else {
+ int longCtr = 0; // index of current long in bitmap
+ long curWord = bc.bitmap.get(0); // its value
+ int runCount = 0;
+ final int len = bc.bitmap.limit();
+ while (true) {
+ // potentially multiword advance to first 1 bit
+ while (curWord == 0L && longCtr < len - 1) {
+ curWord = bc.bitmap.get(++longCtr);
+ }
+
+ if (curWord == 0L) {
+ // wrap up, no more runs
+ return;
+ }
+ int localRunStart = Long.numberOfTrailingZeros(curWord);
+ int runStart = localRunStart + 64 * longCtr;
+ // stuff 1s into number's LSBs
+ long curWordWith1s = curWord | (curWord - 1);
+
+ // find the next 0, potentially in a later word
+ int runEnd = 0;
+ while (curWordWith1s == -1L && longCtr < len - 1) {
+ curWordWith1s = bc.bitmap.get(++longCtr);
+ }
+
+ if (curWordWith1s == -1L) {
+ // a final unterminated run of 1s (32 of them)
+ runEnd = 64 + longCtr * 64;
+ // setValue(runCount, (short) runStart);
+ vl[2 * runCount] = (short) runStart;
+ // setLength(runCount, (short) (runEnd-runStart-1));
+ vl[2 * runCount + 1] = (short) (runEnd - runStart - 1);
+ return;
+ }
+ int localRunEnd = Long.numberOfTrailingZeros(~curWordWith1s);
+ runEnd = localRunEnd + longCtr * 64;
+ // setValue(runCount, (short) runStart);
+ vl[2 * runCount] = (short) runStart;
+ // setLength(runCount, (short) (runEnd-runStart-1));
+ vl[2 * runCount + 1] = (short) (runEnd - runStart - 1);
+ runCount++;
+ // now, zero out everything right of runEnd.
+
+ curWord = curWordWith1s & (curWordWith1s + 1);
+ // We've lathered and rinsed, so repeat...
+ }
+
+ }
+ }
+
+ /**
+ * Creates a new container from a non-mappeable one. This copies the data.
+ *
+ * @param bc the original container
+ */
+ public MappeableRunContainer(RunContainer bc) {
+ this.nbrruns = bc.numberOfRuns();
+ this.valueslength = bc.toShortBuffer();
+ }
+
+
+ /**
+ * Construct a new RunContainer backed by the provided ShortBuffer. Note that if you modify the
+ * RunContainer a new ShortBuffer may be produced.
+ *
+ * @param array ShortBuffer where the data is stored
+ * @param numRuns number of runs (each using 2 shorts in the buffer)
+ */
+ public MappeableRunContainer(final ShortBuffer array, final int numRuns) {
+ if (array.limit() < 2 * numRuns) {
+ throw new RuntimeException("Mismatch between buffer and numRuns");
+ }
+ this.nbrruns = numRuns;
+ this.valueslength = array;
+ }
+
+ private static int branchyBufferedUnsignedInterleavedBinarySearch(final ShortBuffer sb,
+ final int begin, final int end, final short k) {
+ int ikey = BufferUtil.toIntUnsigned(k);
+ int low = begin;
+ int high = end - 1;
+ while (low <= high) {
+ final int middleIndex = (low + high) >>> 1;
+ final int middleValue = BufferUtil.toIntUnsigned(sb.get(2 * middleIndex));
+ if (middleValue < ikey) {
+ low = middleIndex + 1;
+ } else if (middleValue > ikey) {
+ high = middleIndex - 1;
+ } else {
+ return middleIndex;
+ }
+ }
+ return -(low + 1);
+ }
+
+ private static int bufferedUnsignedInterleavedBinarySearch(final ShortBuffer sb, final int begin,
+ final int end, final short k) {
+ return branchyBufferedUnsignedInterleavedBinarySearch(sb, begin, end, k);
+ }
+
+ protected static int getArraySizeInBytes(int nbrruns) {
+ return 2 + 4 * nbrruns;
+ }
+
+ static short getLength(short[] vl, int index) {
+ return vl[2 * index + 1];
+ }
+
+ static short getValue(short[] vl, int index) {
+ return vl[2 * index];
+ }
+
+ protected static int serializedSizeInBytes(int numberOfRuns) {
+ return 2 + 2 * 2 * numberOfRuns; // each run requires 2 2-byte entries.
+ }
+
+ @Override
+ public MappeableContainer add(int begin, int end) {
+ MappeableRunContainer rc = (MappeableRunContainer) clone();
+ return rc.iadd(begin, end);
+ }
+
+ @Override
+ // not thread-safe
+ public MappeableContainer add(short k) {
+ // TODO: it might be better and simpler to do return
+ // toBitmapOrArrayContainer(getCardinality()).add(k)
+ int index = bufferedUnsignedInterleavedBinarySearch(valueslength, 0, nbrruns, k);
+ if (index >= 0) {
+ return this;// already there
+ }
+ index = -index - 2;// points to preceding value, possibly -1
+ if (index >= 0) {// possible match
+ int offset = BufferUtil.toIntUnsigned(k) - BufferUtil.toIntUnsigned(getValue(index));
+ int le = BufferUtil.toIntUnsigned(getLength(index));
+ if (offset <= le) {
+ return this;
+ }
+ if (offset == le + 1) {
+ // we may need to fuse
+ if (index + 1 < nbrruns) {
+ if (BufferUtil.toIntUnsigned(getValue(index + 1)) == BufferUtil.toIntUnsigned(k) + 1) {
+ // indeed fusion is needed
+ setLength(index,
+ (short) (getValue(index + 1) + getLength(index + 1) - getValue(index)));
+ recoverRoomAtIndex(index + 1);
+ return this;
+ }
+ }
+ incrementLength(index);
+ return this;
+ }
+ if (index + 1 < nbrruns) {
+ // we may need to fuse
+ if (BufferUtil.toIntUnsigned(getValue(index + 1)) == BufferUtil.toIntUnsigned(k) + 1) {
+ // indeed fusion is needed
+ setValue(index + 1, k);
+ setLength(index + 1, (short) (getLength(index + 1) + 1));
+ return this;
+ }
+ }
+ }
+ if (index == -1) {
+ // we may need to extend the first run
+ if (0 < nbrruns) {
+ if (getValue(0) == k + 1) {
+ incrementLength(0);
+ decrementValue(0);
+ return this;
+ }
+ }
+ }
+ makeRoomAtIndex(index + 1);
+ setValue(index + 1, k);
+ setLength(index + 1, (short) 0);
+ return this;
+ }
+
+
+ @Override
+ public MappeableContainer and(MappeableArrayContainer x) {
+ MappeableArrayContainer ac = new MappeableArrayContainer(x.cardinality);
+ if (this.nbrruns == 0) {
+ return ac;
+ }
+ int rlepos = 0;
+ int arraypos = 0;
+
+ int rleval = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int rlelength = BufferUtil.toIntUnsigned(this.getLength(rlepos));
+ while (arraypos < x.cardinality) {
+ int arrayval = BufferUtil.toIntUnsigned(x.content.get(arraypos));
+ while (rleval + rlelength < arrayval) {// this will frequently be false
+ ++rlepos;
+ if (rlepos == this.nbrruns) {
+ return ac;// we are done
+ }
+ rleval = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ rlelength = BufferUtil.toIntUnsigned(this.getLength(rlepos));
+ }
+ if (rleval > arrayval) {
+ arraypos =
+ BufferUtil.advanceUntil(x.content, arraypos, x.cardinality, (short) rleval);
+ } else {
+ ac.content.put(ac.cardinality, (short) arrayval);
+ ac.cardinality++;
+ arraypos++;
+ }
+ }
+ return ac;
+ }
+
+ @Override
+ public MappeableContainer and(MappeableBitmapContainer x) {
+ int card = this.getCardinality();
+ if (card <= MappeableArrayContainer.DEFAULT_MAX_SIZE) {
+ // result can only be an array (assuming that we never make a RunContainer)
+ if (card > x.cardinality) {
+ card = x.cardinality;
+ }
+ MappeableArrayContainer answer = new MappeableArrayContainer(card);
+ answer.cardinality = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int runStart = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int runEnd = runStart + BufferUtil.toIntUnsigned(this.getLength(rlepos));
+ for (int runValue = runStart; runValue <= runEnd; ++runValue) {
+ if (x.contains((short) runValue)) {
+ answer.content.put(answer.cardinality++, (short) runValue);
+ }
+ }
+ }
+ return answer;
+ }
+ // we expect the answer to be a bitmap (if we are lucky)
+
+ MappeableBitmapContainer answer = x.clone();
+ int start = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int end = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ BufferUtil.resetBitmapRange(answer.bitmap, start, end);
+ start = end + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ BufferUtil.resetBitmapRange(answer.bitmap, start, BufferUtil.maxLowBitAsInteger() + 1);
+ answer.computeCardinality();
+ if (answer.getCardinality() > MappeableArrayContainer.DEFAULT_MAX_SIZE) {
+ return answer;
+ } else {
+ return answer.toArrayContainer();
+ }
+
+ }
+
+ @Override
+ public MappeableContainer and(MappeableRunContainer x) {
+ MappeableRunContainer answer =
+ new MappeableRunContainer(ShortBuffer.allocate(2 * (this.nbrruns + x.nbrruns)), 0);
+ short[] vl = answer.valueslength.array();
+ int rlepos = 0;
+ int xrlepos = 0;
+ int start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ int xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ int xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ while ((rlepos < this.nbrruns) && (xrlepos < x.nbrruns)) {
+ if (end <= xstart) {
+ // exit the first run
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ } else if (xend <= start) {
+ // exit the second run
+ xrlepos++;
+ if (xrlepos < x.nbrruns) {
+ xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else {// they overlap
+ final int lateststart = start > xstart ? start : xstart;
+ int earliestend;
+ if (end == xend) {// improbable
+ earliestend = end;
+ rlepos++;
+ xrlepos++;
+ if (rlepos < this.nbrruns) {
+ start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ if (xrlepos < x.nbrruns) {
+ xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else if (end < xend) {
+ earliestend = end;
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+
+ } else {// end > xend
+ earliestend = xend;
+ xrlepos++;
+ if (xrlepos < x.nbrruns) {
+ xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ }
+ vl[2 * answer.nbrruns] = (short) lateststart;
+ vl[2 * answer.nbrruns + 1] = (short) (earliestend - lateststart - 1);
+ answer.nbrruns++;
+ }
+ }
+ return answer;
+ }
+
+ @Override
+ public MappeableContainer andNot(MappeableArrayContainer x) {
+ // when x is small, we guess that the result will still be a run container
+ final int arbitrary_threshold = 32; // this is arbitrary
+ if (x.getCardinality() < arbitrary_threshold) {
+ return lazyandNot(x).toEfficientContainer();
+ }
+ // otherwise we generate either an array or bitmap container
+ final int card = getCardinality();
+ if (card <= MappeableArrayContainer.DEFAULT_MAX_SIZE) {
+ // if the cardinality is small, we construct the solution in place
+ MappeableArrayContainer ac = new MappeableArrayContainer(card);
+ ac.cardinality = Util.unsignedDifference(this.getShortIterator(),
+ x.getShortIterator(), ac.content.array());
+ return ac;
+ }
+ // otherwise, we generate a bitmap
+ return toBitmapOrArrayContainer(card).iandNot(x);
+ }
+
+ @Override
+ public MappeableContainer andNot(MappeableBitmapContainer x) {
+ int card = this.getCardinality();
+ if (card <= MappeableArrayContainer.DEFAULT_MAX_SIZE) {
+ // result can only be an array (assuming that we never make a RunContainer)
+ MappeableArrayContainer answer = new MappeableArrayContainer(card);
+ answer.cardinality = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int runStart = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int runEnd = runStart + BufferUtil.toIntUnsigned(this.getLength(rlepos));
+ for (int runValue = runStart; runValue <= runEnd; ++runValue) {
+ if (!x.contains((short) runValue)) {
+ answer.content.put(answer.cardinality++, (short) runValue);
+ }
+ }
+ }
+ return answer;
+ }
+ // we expect the answer to be a bitmap (if we are lucky)
+ MappeableBitmapContainer answer = x.clone();
+ int lastPos = 0;
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ BufferUtil.resetBitmapRange(answer.bitmap, lastPos, start);
+ BufferUtil.flipBitmapRange(answer.bitmap, start, end);
+ lastPos = end;
+ }
+ BufferUtil.resetBitmapRange(answer.bitmap, lastPos, answer.bitmap.capacity() * 64);
+ answer.computeCardinality();
+ if (answer.getCardinality() > MappeableArrayContainer.DEFAULT_MAX_SIZE) {
+ return answer;
+ } else {
+ return answer.toArrayContainer();
+ }
+ }
+
+ @Override
+ public MappeableContainer andNot(MappeableRunContainer x) {
+ MappeableRunContainer answer =
+ new MappeableRunContainer(ShortBuffer.allocate(2 * (this.nbrruns + x.nbrruns)), 0);
+ short[] vl = answer.valueslength.array();
+ int rlepos = 0;
+ int xrlepos = 0;
+ int start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ int xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ int xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ while ((rlepos < this.nbrruns) && (xrlepos < x.nbrruns)) {
+ if (end <= xstart) {
+ // output the first run
+ vl[2 * answer.nbrruns] = (short) start;
+ vl[2 * answer.nbrruns + 1] = (short) (end - start - 1);
+ answer.nbrruns++;
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ } else if (xend <= start) {
+ // exit the second run
+ xrlepos++;
+ if (xrlepos < x.nbrruns) {
+ xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else {
+ if (start < xstart) {
+ vl[2 * answer.nbrruns] = (short) start;
+ vl[2 * answer.nbrruns + 1] = (short) (xstart - start - 1);
+ answer.nbrruns++;
+ }
+ if (xend < end) {
+ start = xend;
+ } else {
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ }
+ }
+ }
+ if (rlepos < this.nbrruns) {
+ vl[2 * answer.nbrruns] = (short) start;
+ vl[2 * answer.nbrruns + 1] = (short) (end - start - 1);
+ answer.nbrruns++;
+ rlepos++;
+ for (; rlepos < this.nbrruns; ++rlepos) {
+ vl[2 * answer.nbrruns] = this.valueslength.get(2 * rlepos);
+ vl[2 * answer.nbrruns + 1] = this.valueslength.get(2 * rlepos + 1);
+ answer.nbrruns++;
+ }
+ // next bit would be faster but not thread-safe because of the "position"
+ // if(rlepos < this.nbrruns) {
+ // this.valueslength.position(2 * rlepos);
+ // this.valueslength.get(vl, 2 * answer.nbrruns, 2*(this.nbrruns-rlepos ));
+ // answer.nbrruns = answer.nbrruns + this.nbrruns - rlepos;
+ // }
+ }
+ return answer;
+ }
+
+ // Append a value length with all values until a given value
+ private void appendValueLength(int value, int index) {
+ int previousValue = BufferUtil.toIntUnsigned(getValue(index));
+ int length = BufferUtil.toIntUnsigned(getLength(index));
+ int offset = value - previousValue;
+ if (offset > length) {
+ setLength(index, (short) offset);
+ }
+ }
+
+
+ // To check if a value length can be prepended with a given value
+ private boolean canPrependValueLength(int value, int index) {
+ if (index < this.nbrruns) {
+ int nextValue = BufferUtil.toIntUnsigned(getValue(index));
+ if (nextValue == value + 1) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ @Override
+ public void clear() {
+ nbrruns = 0;
+ }
+
+
+ @Override
+ public MappeableContainer clone() {
+ return new MappeableRunContainer(nbrruns, valueslength);
+ }
+
+
+ // To set the last value of a value length
+ private void closeValueLength(int value, int index) {
+ int initialValue = BufferUtil.toIntUnsigned(getValue(index));
+ setLength(index, (short) (value - initialValue));
+ }
+
+ @Override
+ public boolean contains(short x) {
+ int index = bufferedUnsignedInterleavedBinarySearch(valueslength, 0, nbrruns, x);
+ if (index >= 0) {
+ return true;
+ }
+ index = -index - 2; // points to preceding value, possibly -1
+ if (index != -1) {// possible match
+ int offset = BufferUtil.toIntUnsigned(x) - BufferUtil.toIntUnsigned(getValue(index));
+ int le = BufferUtil.toIntUnsigned(getLength(index));
+ if (offset <= le) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // a very cheap check... if you have more than 4096, then you should use a bitmap container.
+ // this function avoids computing the cardinality
+ private MappeableContainer convertToLazyBitmapIfNeeded() {
+ // when nbrruns exceed MappeableArrayContainer.DEFAULT_MAX_SIZE, then we know it should be
+ // stored as a bitmap, always
+ if (this.nbrruns > MappeableArrayContainer.DEFAULT_MAX_SIZE) {
+ MappeableBitmapContainer answer = new MappeableBitmapContainer();
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ BufferUtil.setBitmapRange(answer.bitmap, start, end);
+ }
+ answer.cardinality = -1;
+ return answer;
+ }
+ return this;
+ }
+
+ // Push all values length to the end of the array (resize array if needed)
+ private void copyToOffset(int offset) {
+ final int minCapacity = 2 * (offset + nbrruns);
+ if (valueslength.capacity() < minCapacity) {
+ // expensive case where we need to reallocate
+ int newCapacity = valueslength.capacity();
+ while (newCapacity < minCapacity) {
+ newCapacity = (newCapacity == 0) ? DEFAULT_INIT_SIZE
+ : newCapacity < 64 ? newCapacity * 2
+ : newCapacity < 1024 ? newCapacity * 3 / 2 : newCapacity * 5 / 4;
+ }
+ ShortBuffer newvalueslength = ShortBuffer.allocate(newCapacity);
+ copyValuesLength(this.valueslength, 0, newvalueslength, offset, nbrruns);
+ this.valueslength = newvalueslength;
+ } else {
+ // efficient case where we just copy
+ copyValuesLength(this.valueslength, 0, this.valueslength, offset, nbrruns);
+ }
+ }
+
+ private void copyValuesLength(ShortBuffer src, int srcIndex, ShortBuffer dst, int dstIndex,
+ int length) {
+ if (BufferUtil.isBackedBySimpleArray(src) && BufferUtil.isBackedBySimpleArray(dst)) {
+ // common case.
+ System.arraycopy(src.array(), 2 * srcIndex, dst.array(), 2 * dstIndex, 2 * length);
+ return;
+ }
+ // source and destination may overlap
+ // consider specialized code for various cases, rather than using a second buffer
+ ShortBuffer temp = ShortBuffer.allocate(2 * length);
+ for (int i = 0; i < 2 * length; ++i) {
+ temp.put(src.get(2 * srcIndex + i));
+ }
+ temp.flip();
+ for (int i = 0; i < 2 * length; ++i) {
+ dst.put(2 * dstIndex + i, temp.get());
+ }
+ }
+
+ private void decrementLength(int index) {
+ // caller is responsible to ensure that value is non-zero
+ valueslength.put(2 * index + 1, (short) (valueslength.get(2 * index + 1) - 1));
+ }
+
+
+ private void decrementValue(int index) {
+ valueslength.put(2 * index, (short) (valueslength.get(2 * index) - 1));
+ }
+
+ // not thread safe!
+ // not actually used anywhere, but potentially useful
+ protected void ensureCapacity(int minNbRuns) {
+ final int minCapacity = 2 * minNbRuns;
+ if (valueslength.capacity() < minCapacity) {
+ int newCapacity = valueslength.capacity();
+ while (newCapacity < minCapacity) {
+ newCapacity = (newCapacity == 0) ? DEFAULT_INIT_SIZE
+ : newCapacity < 64 ? newCapacity * 2
+ : newCapacity < 1024 ? newCapacity * 3 / 2 : newCapacity * 5 / 4;
+ }
+ final ShortBuffer nv = ShortBuffer.allocate(newCapacity);
+ valueslength.rewind();
+ nv.put(valueslength);
+ valueslength = nv;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof MappeableRunContainer) {
+ MappeableRunContainer srb = (MappeableRunContainer) o;
+ if (srb.nbrruns != this.nbrruns) {
+ return false;
+ }
+ for (int i = 0; i < nbrruns; ++i) {
+ if (this.getValue(i) != srb.getValue(i)) {
+ return false;
+ }
+ if (this.getLength(i) != srb.getLength(i)) {
+ return false;
+ }
+ }
+ return true;
+ } else if (o instanceof MappeableContainer) {
+ if (((MappeableContainer) o).getCardinality() != this.getCardinality()) {
+ return false; // should be a frequent branch if they differ
+ }
+ // next bit could be optimized if needed:
+ ShortIterator me = this.getShortIterator();
+ ShortIterator you = ((MappeableContainer) o).getShortIterator();
+ while (me.hasNext()) {
+ if (me.next() != you.next()) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void fillLeastSignificant16bits(int[] x, int i, int mask) {
+ int pos = i;
+ for (int k = 0; k < this.nbrruns; ++k) {
+ final int limit = BufferUtil.toIntUnsigned(this.getLength(k));
+ final int base = BufferUtil.toIntUnsigned(this.getValue(k));
+ for (int le = 0; le <= limit; ++le) {
+ x[pos++] = (base + le) | mask;
+ }
+ }
+ }
+
+
+ @Override
+ public MappeableContainer flip(short x) {
+ if (this.contains(x)) {
+ return this.remove(x);
+ } else {
+ return this.add(x);
+ }
+ }
+
+ @Override
+ protected int getArraySizeInBytes() {
+ return 2 + 4 * this.nbrruns; // "array" includes its size
+ }
+
+ @Override
+ public int getCardinality() {
+ int sum = nbrruns; // lengths are stored -1
+ if (isArrayBacked()) {
+ short[] vl = valueslength.array();
+ for (int k = 0; k < nbrruns; ++k) {
+ sum = sum + BufferUtil.toIntUnsigned(vl[2 * k + 1])/* + 1 */;
+ }
+ } else {
+ for (int k = 0; k < nbrruns; ++k) {
+ sum = sum + BufferUtil.toIntUnsigned(getLength(k))/* + 1 */;
+ }
+ }
+ return sum;
+ }
+
+ short getLength(int index) {
+ return valueslength.get(2 * index + 1);
+ }
+
+ @Override
+ public ShortIterator getReverseShortIterator() {
+ if (isArrayBacked()) {
+ return new RawReverseMappeableRunContainerShortIterator(this);
+ }
+ return new ReverseMappeableRunContainerShortIterator(this);
+ }
+
+ @Override
+ public PeekableShortIterator getShortIterator() {
+ if (isArrayBacked()) {
+ return new RawMappeableRunContainerShortIterator(this);
+ }
+ return new MappeableRunContainerShortIterator(this);
+ }
+
+ @Override
+ public int getSizeInBytes() {
+ return this.nbrruns * 4 + 4; // not sure about how exact it will be
+ }
+
+ short getValue(int index) {
+ return valueslength.get(2 * index);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ for (int k = 0; k < nbrruns * 2; ++k) {
+ hash += 31 * hash + valueslength.get(k);
+ }
+ return hash;
+ }
+
+ @Override
+ // not thread-safe
+ public MappeableContainer iadd(int begin, int end) {
+ // TODO: it might be better and simpler to do return
+ // toBitmapOrArrayContainer(getCardinality()).iadd(begin,end)
+ if (end == begin) {
+ return this;
+ }
+ if ((begin > end) || (end > (1 << 16))) {
+ throw new IllegalArgumentException("Invalid range [" + begin + "," + end + ")");
+ }
+ if (begin == end - 1) {
+ add((short) begin);
+ return this;
+ }
+
+ int bIndex =
+ bufferedUnsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns, (short) begin);
+ int eIndex = bufferedUnsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns,
+ (short) (end - 1));
+
+ if (bIndex >= 0 && eIndex >= 0) {
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+
+ } else if (bIndex >= 0 && eIndex < 0) {
+ eIndex = -eIndex - 2;
+
+ if (canPrependValueLength(end - 1, eIndex + 1)) {
+ mergeValuesLength(bIndex, eIndex + 1);
+ return this;
+ }
+
+ appendValueLength(end - 1, eIndex);
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+
+ } else if (bIndex < 0 && eIndex >= 0) {
+ bIndex = -bIndex - 2;
+
+ if (bIndex >= 0) {
+ if (valueLengthContains(begin - 1, bIndex)) {
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+ }
+ }
+ prependValueLength(begin, bIndex + 1);
+ mergeValuesLength(bIndex + 1, eIndex);
+ return this;
+
+ } else {
+ bIndex = -bIndex - 2;
+ eIndex = -eIndex - 2;
+
+ if (eIndex >= 0) {
+ if (bIndex >= 0) {
+ if (!valueLengthContains(begin - 1, bIndex)) {
+ if (bIndex == eIndex) {
+ if (canPrependValueLength(end - 1, eIndex + 1)) {
+ prependValueLength(begin, eIndex + 1);
+ return this;
+ }
+ makeRoomAtIndex(eIndex + 1);
+ setValue(eIndex + 1, (short) begin);
+ setLength(eIndex + 1, (short) (end - 1 - begin));
+ return this;
+
+ } else {
+ bIndex++;
+ prependValueLength(begin, bIndex);
+ }
+ }
+ } else {
+ bIndex = 0;
+ prependValueLength(begin, bIndex);
+ }
+
+ if (canPrependValueLength(end - 1, eIndex + 1)) {
+ mergeValuesLength(bIndex, eIndex + 1);
+ return this;
+ }
+
+ appendValueLength(end - 1, eIndex);
+ mergeValuesLength(bIndex, eIndex);
+ return this;
+
+ } else {
+ if (canPrependValueLength(end - 1, 0)) {
+ prependValueLength(begin, 0);
+ } else {
+ makeRoomAtIndex(0);
+ setValue(0, (short) begin);
+ setLength(0, (short) (end - 1 - begin));
+ }
+ return this;
+ }
+ }
+ }
+
+
+ @Override
+ public MappeableContainer iand(MappeableArrayContainer x) {
+ return and(x);
+ }
+
+ @Override
+ public MappeableContainer iand(MappeableBitmapContainer x) {
+ return and(x);
+ }
+
+
+ @Override
+ public MappeableContainer iand(MappeableRunContainer x) {
+ return and(x);
+ }
+
+ @Override
+ public MappeableContainer iandNot(MappeableArrayContainer x) {
+ return andNot(x);
+ }
+
+ @Override
+ public MappeableContainer iandNot(MappeableBitmapContainer x) {
+ return andNot(x);
+ }
+
+ @Override
+ public MappeableContainer iandNot(MappeableRunContainer x) {
+ return andNot(x);
+ }
+
+ protected MappeableContainer ilazyor(MappeableArrayContainer x) {
+ if (isFull()) {
+ return this; // this can sometimes solve a lot of computation!
+ }
+ return ilazyorToRun(x);
+ }
+
+
+ private MappeableContainer ilazyorToRun(MappeableArrayContainer x) {
+ if (isFull()) {
+ return this.clone();
+ }
+ final int nbrruns = this.nbrruns;
+ final int offset = Math.max(nbrruns, x.getCardinality());
+ copyToOffset(offset);
+ short[] vl = valueslength.array();
+ int rlepos = 0;
+ this.nbrruns = 0;
+ PeekableShortIterator i = (PeekableShortIterator) x.getShortIterator();
+ while (i.hasNext() && (rlepos < nbrruns)) {
+ if (BufferUtil.compareUnsigned(getValue(vl, rlepos + offset), i.peekNext()) <= 0) {
+ smartAppend(vl, getValue(vl, rlepos + offset), getLength(vl, rlepos + offset));
+ rlepos++;
+ } else {
+ smartAppend(vl, i.next());
+ }
+ }
+ if (i.hasNext()) {
+ /*
+ * if(this.nbrruns>0) { // this might be useful if the run container has just one very large
+ * run int lastval = BufferUtil.toIntUnsigned(getValue(vl,nbrruns + offset - 1)) +
+ * BufferUtil.toIntUnsigned(getLength(vl,nbrruns + offset - 1)) + 1; i.advanceIfNeeded((short)
+ * lastval); }
+ */
+ while (i.hasNext()) {
+ smartAppend(vl, i.next());
+ }
+ } else {
+ while (rlepos < nbrruns) {
+ smartAppend(vl, getValue(vl, rlepos + offset), getLength(vl, rlepos + offset));
+ rlepos++;
+ }
+ }
+ return convertToLazyBitmapIfNeeded();
+ }
+
+ // not thread safe!
+ private void increaseCapacity() {
+ int newCapacity = (valueslength.capacity() == 0) ? DEFAULT_INIT_SIZE
+ : valueslength.capacity() < 64 ? valueslength.capacity() * 2
+ : valueslength.capacity() < 1024 ? valueslength.capacity() * 3 / 2
+ : valueslength.capacity() * 5 / 4;
+
+ final ShortBuffer nv = ShortBuffer.allocate(newCapacity);
+ valueslength.rewind();
+ nv.put(valueslength);
+ valueslength = nv;
+ }
+
+ private void incrementLength(int index) {
+ valueslength.put(2 * index + 1, (short) (1 + valueslength.get(2 * index + 1)));
+ }
+
+ private void incrementValue(int index) {
+ valueslength.put(2 * index, (short) (1 + valueslength.get(2 * index)));
+ }
+
+ // To set the first value of a value length
+ private void initValueLength(int value, int index) {
+ int initialValue = BufferUtil.toIntUnsigned(getValue(index));
+ int length = BufferUtil.toIntUnsigned(getLength(index));
+ setValue(index, (short) (value));
+ setLength(index, (short) (length - (value - initialValue)));
+ }
+
+
+ @Override
+ public MappeableContainer inot(int rangeStart, int rangeEnd) {
+ if (rangeEnd <= rangeStart) {
+ return this;
+ }
+ short[] vl = this.valueslength.array();
+
+ // TODO: write special case code for rangeStart=0; rangeEnd=65535
+ // a "sliding" effect where each range records the gap adjacent it
+ // can probably be quite fast. Probably have 2 cases: start with a
+ // 0 run vs start with a 1 run. If you both start and end with 0s,
+ // you will require room for expansion.
+
+ // the +1 below is needed in case the valueslength.length is odd
+ if (vl.length <= 2 * nbrruns + 1) {
+ // no room for expansion
+ // analyze whether this is a case that will require expansion (that we cannot do)
+ // this is a bit costly now (4 "contains" checks)
+
+ boolean lastValueBeforeRange = false;
+ boolean firstValueInRange = false;
+ boolean lastValueInRange = false;
+ boolean firstValuePastRange = false;
+
+ // contains is based on a binary search and is hopefully fairly fast.
+ // however, one binary search could *usually* suffice to find both
+ // lastValueBeforeRange AND firstValueInRange. ditto for
+ // lastVaueInRange and firstValuePastRange
+
+ // find the start of the range
+ if (rangeStart > 0) {
+ lastValueBeforeRange = contains((short) (rangeStart - 1));
+ }
+ firstValueInRange = contains((short) rangeStart);
+
+ if (lastValueBeforeRange == firstValueInRange) {
+ // expansion is required if also lastValueInRange==firstValuePastRange
+
+ // tougher to optimize out, but possible.
+ lastValueInRange = contains((short) (rangeEnd - 1));
+ if (rangeEnd != 65536) {
+ firstValuePastRange = contains((short) rangeEnd);
+ }
+
+ // there is definitely one more run after the operation.
+ if (lastValueInRange == firstValuePastRange) {
+ return not(rangeStart, rangeEnd); // can't do in-place: true space limit
+ }
+ }
+ }
+ // either no expansion required, or we have room to handle any required expansion for it.
+
+ // remaining code is just a minor variation on not()
+ int myNbrRuns = nbrruns;
+
+ MappeableRunContainer ans = this; // copy on top of self.
+ int k = 0;
+ ans.nbrruns = 0; // losing this.nbrruns, which is stashed in myNbrRuns.
+
+ // could try using unsignedInterleavedBinarySearch(valueslength, 0, nbrruns, rangeStart) instead
+ // of sequential scan
+ // to find the starting location
+
+ for (; k < myNbrRuns && BufferUtil.toIntUnsigned(this.getValue(k)) < rangeStart; ++k) {
+ // since it is atop self, there is no copying needed
+ // ans.valueslength[2 * k] = this.valueslength[2 * k];
+ // ans.valueslength[2 * k + 1] = this.valueslength[2 * k + 1];
+ ans.nbrruns++;
+ }
+ // We will work left to right, with a read pointer that always stays
+ // left of the write pointer. However, we need to give the read pointer a head start.
+ // use local variables so we are always reading 1 location ahead.
+
+ short bufferedValue = 0, bufferedLength = 0; // 65535 start and 65535 length would be illegal,
+ // could use as sentinel
+ short nextValue = 0, nextLength = 0;
+ if (k < myNbrRuns) { // prime the readahead variables
+ bufferedValue = vl[2 * k];// getValue(k);
+ bufferedLength = vl[2 * k + 1];// getLength(k);
+ }
+
+ ans.smartAppendExclusive(vl, (short) rangeStart, (short) (rangeEnd - rangeStart - 1));
+
+ for (; k < myNbrRuns; ++k) {
+ if (ans.nbrruns > k + 1) {
+ throw new RuntimeException(
+ "internal error in inot, writer has overtaken reader!! " + k + " " + ans.nbrruns);
+ }
+ if (k + 1 < myNbrRuns) {
+ nextValue = vl[2 * (k + 1)];// getValue(k+1); // readahead for next iteration
+ nextLength = vl[2 * (k + 1) + 1];// getLength(k+1);
+ }
+ ans.smartAppendExclusive(vl, bufferedValue, bufferedLength);
+ bufferedValue = nextValue;
+ bufferedLength = nextLength;
+ }
+ // the number of runs can increase by one, meaning (rarely) a bitmap will become better
+ // or the cardinality can decrease by a lot, making an array better
+ return ans.toEfficientContainer();
+ }
+
+ @Override
+ public boolean intersects(MappeableArrayContainer x) {
+ if (this.nbrruns == 0) {
+ return false;
+ }
+ int rlepos = 0;
+ int arraypos = 0;
+
+ int rleval = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int rlelength = BufferUtil.toIntUnsigned(this.getLength(rlepos));
+ while (arraypos < x.cardinality) {
+ int arrayval = BufferUtil.toIntUnsigned(x.content.get(arraypos));
+ while (rleval + rlelength < arrayval) {// this will frequently be false
+ ++rlepos;
+ if (rlepos == this.nbrruns) {
+ return false;
+ }
+ rleval = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ rlelength = BufferUtil.toIntUnsigned(this.getLength(rlepos));
+ }
+ if (rleval > arrayval) {
+ arraypos =
+ BufferUtil.advanceUntil(x.content, arraypos, x.cardinality, this.getValue(rlepos));
+ } else {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean intersects(MappeableBitmapContainer x) {
+ // possibly inefficient
+ for (int rlepos = 0; rlepos < this.nbrruns; ++rlepos) {
+ int runStart = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int runEnd = runStart + BufferUtil.toIntUnsigned(this.getLength(rlepos));
+ for (int runValue = runStart; runValue <= runEnd; ++runValue) {
+ if (x.contains((short) runValue)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean intersects(MappeableRunContainer x) {
+ int rlepos = 0;
+ int xrlepos = 0;
+ int start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ int end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ int xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ int xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ while ((rlepos < this.nbrruns) && (xrlepos < x.nbrruns)) {
+ if (end <= xstart) {
+ // exit the first run
+ rlepos++;
+ if (rlepos < this.nbrruns) {
+ start = BufferUtil.toIntUnsigned(this.getValue(rlepos));
+ end = start + BufferUtil.toIntUnsigned(this.getLength(rlepos)) + 1;
+ }
+ } else if (xend <= start) {
+ // exit the second run
+ xrlepos++;
+ if (xrlepos < x.nbrruns) {
+ xstart = BufferUtil.toIntUnsigned(x.getValue(xrlepos));
+ xend = xstart + BufferUtil.toIntUnsigned(x.getLength(xrlepos)) + 1;
+ }
+ } else {// they overlap
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public MappeableContainer ior(MappeableArrayContainer x) {
+ if (isFull()) {
+ return this;
+ }
+ final int nbrruns = this.nbrruns;
+ final int offset = Math.max(nbrruns, x.getCardinality());
+ copyToOffset(offset);
+ short[] vl = this.valueslength.array();
+ int rlepos = 0;
+ this.nbrruns = 0;
+ PeekableShortIterator i = (PeekableShortIterator) x.getShortIterator();
+ while (i.hasNext() && (rlepos < nbrruns)) {
+ if (BufferUtil.compareUnsigned(getValue(vl, rlepos + offset), i.peekNext()) <= 0) {
+ smartAppend(vl, getValue(vl, rlepos + offset), getLength(vl, rlepos + offset));
+ rlepos++;
+ } else {
+ smartAppend(vl, i.next());
+ }
+ }
+ if (i.hasNext()) {
+ /*
+ * if(this.nbrruns>0) { // this might be useful if the run container has just one very large
+ * run int lastval = BufferUtil.toIntUnsigned(getValue(nbrruns + offset - 1)) +
+ * BufferUtil.toIntUnsigned(getLength(nbrruns + offset - 1)) + 1; i.advanceIfNeeded((short)
+ * lastval); }
+ */
+ while (i.hasNext()) {
+ smartAppend(vl, i.next());
+ }
+ } else {
+ while (rlepos < nbrruns) {
+ smartAppend(vl, getValue(vl, rlepos + offset), getLength(vl, rlepos + offset));
+ rlepos++;
+ }
+ }
+ return toEfficientContainer();
+ }
+
+ @Override
+ public MappeableContainer ior(MappeableBitmapContainer x) {
+ if (isFull()) {
+ return this;
+ }
+ return or(x);
+ }
+
+ @Override
+ public MappeableContainer ior(MappeableRunContainer x) {
+ if (isFull()) {
+ return this;
+ }
+
+ final int nbrruns = this.nbrruns;
+ final int xnbrruns = x.nbrruns;
+ final int offset = Math.max(nbrruns, xnbrruns);
+
+ // Push all values length to the end of the array (resize array if needed)
+ copyToOffset(offset);
+
+ // Aggregate and store the result at the beginning of the array
+ this.nbrruns = 0;
+ int rlepos = 0;
+ int xrlepos = 0;
+ short[] vl = this.valueslength.array();
+
+ // Add values length (smaller first)
+ while ((rlepos < nbrruns) && (xrlepos < xnbrruns)) {
+ final short value = getValue(vl, offset + rlepos);
+ final short xvalue = x.getValue(xrlepos);
+ final short length = getLength(vl, offset + rlepos);
+ final short xlength = x.getLength(xrlepos);
+
+ if (BufferUtil.compareUnsigned(value, xvalue) <= 0) {
+ this.smartAppend(vl, value, length);
+ ++rlepos;
+ } else {
+ this.smartAppend(vl, xvalue, xlength);
+ ++xrlepos;
+ }
+ }
+ while (rlepos < nbrruns) {
+ this.smartAppend(vl, getValue(vl, offset + rlepos), getLength(vl, offset + rlepos));
+ ++rlepos;
+ }
+ while (xrlepos < xnbrruns) {
+ this.smartAppend(vl, x.getValue(xrlepos), x.getLength(xrlepos));
+ ++xrlepos;
+ }
+ return this.toBitmapIfNeeded();
+ }
+
+ @Override
+ // not thread-safe
+ public MappeableContainer iremove(int begin, int end) {
+ // TODO: it might be better and simpler to do return
+ // toBitmapOrArrayContainer(getCardinality()).iremove(begin,end)
+ if (end == begin) {
+ return this;
+ }
+ if ((begin > end) || (end > (1 << 16))) {
+ throw new IllegalArgumentException("Invalid range [" + begin + "," + end + ")");
+ }
+ if (begin == end - 1) {
+ remove((short) begin);
+ return this;
+ }
+
+ int bIndex =
+ bufferedUnsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns, (short) begin);
+ int eIndex = bufferedUnsignedInterleavedBinarySearch(this.valueslength, 0, this.nbrruns,
+ (short) (end - 1));
+
+ if (bIndex >= 0) {
+ if (eIndex < 0) {
+ eIndex = -eIndex - 2;
+ }
+
+ if (valueLengthContains(end, eIndex)) {
+ initValueLength(end, eIndex);
+ recoverRoomsInRange(bIndex - 1, eIndex - 1);
+ } else {
+ recoverRoomsInRange(bIndex - 1, eIndex);
+ }
+
+ } else if (bIndex < 0 && eIndex >= 0) {
+ bIndex = -bIndex - 2;
+
+ if (bIndex >= 0) {
+ if (valueLengthContains(begin, bIndex)) {
+ closeValueLength(begin - 1, bIndex);
+ }
+ }
+ // last run is one shorter
+ if (getLength(eIndex) == 0) {// special case where we remove last run
+ recoverRoomsInRange(eIndex, eIndex + 1);
+ } else {
+ incrementValue(eIndex);
+ decrementLength(eIndex);
+ }
+ recoverRoomsInRange(bIndex, eIndex - 1);
+
+ } else {
+ bIndex = -bIndex - 2;
+ eIndex = -eIndex - 2;
+
+ if (eIndex >= 0) {
+ if (bIndex >= 0) {
+ if (bIndex == eIndex) {
+ if (valueLengthContains(begin, bIndex)) {
+ if (valueLengthContains(end, eIndex)) {
+ makeRoomAtIndex(bIndex);
+ closeValueLength(begin - 1, bIndex);
+ initValueLength(end, bIndex + 1);
+ return this;
+ }
+ closeValueLength(begin - 1, bIndex);
+ }
+ } else {
+ if (valueLengthContains(begin, bIndex)) {
+ closeValueLength(begin - 1, bIndex);
+ }
+ if (valueLengthContains(end, eIndex)) {
+ initValueLength(end, eIndex);
+ eIndex--;
+ }
+ recoverRoomsInRange(bIndex, eIndex);
+ }
+
+ } else {
+ if (valueLengthContains(end, eIndex)) { // was end-1
+ initValueLength(end, eIndex);
+ recoverRoomsInRange(bIndex, eIndex - 1);
+ } else {
+ recoverRoomsInRange(bIndex, eIndex);
+ }
+ }
+
+ }
+
+ }
+ return this;
+ }
+
+ @Override
+ protected boolean isArrayBacked() {
+ return BufferUtil.isBackedBySimpleArray(this.valueslength);
+ }
+
+ protected boolean isFull() {
+ return (this.nbrruns == 1) && (this.getValue(0) == 0) && (this.getLength(0) == -1);
+ }
+
+ @Override
+ public Iterator
+ * Objects of this class reside in RAM.
+ */
+public final class MutableRoaringArray implements Cloneable, Externalizable, PointableRoaringArray {
+
+ protected static final int INITIAL_CAPACITY = 4;
+
+ protected static final short SERIAL_COOKIE_NO_RUNCONTAINER = 12346;
+ protected static final short SERIAL_COOKIE = 12347;
+
+ protected static final int NO_OFFSET_THRESHOLD = 4;
+
+ private static final long serialVersionUID = 5L; // TODO: OFK was 4L, not sure
+ protected boolean mayHaveRunContainers = false; // does not necessarily have them, after
+ // optimization
+
+
+ short[] keys = null;
+ MappeableContainer[] values = null;
+
+ int size = 0;
+
+ protected MutableRoaringArray() {
+ this.keys = new short[INITIAL_CAPACITY];
+ this.values = new MappeableContainer[INITIAL_CAPACITY];
+ }
+
+
+ @Override
+ public int advanceUntil(short x, int pos) {
+ int lower = pos + 1;
+
+ // special handling for a possibly common sequential case
+ if (lower >= size || BufferUtil.toIntUnsigned(keys[lower]) >= BufferUtil.toIntUnsigned(x)) {
+ return lower;
+ }
+
+ int spansize = 1; // could set larger
+ // bootstrap an upper limit
+
+ while (lower + spansize < size
+ && BufferUtil.toIntUnsigned(keys[lower + spansize]) < BufferUtil.toIntUnsigned(x)) {
+ spansize *= 2; // hoping for compiler will reduce to shift
+ }
+ int upper = (lower + spansize < size) ? lower + spansize : size - 1;
+
+ // maybe we are lucky (could be common case when the seek ahead
+ // expected to be small and sequential will otherwise make us look bad)
+ if (keys[upper] == x) {
+ return upper;
+ }
+
+ if (BufferUtil.toIntUnsigned(keys[upper]) < BufferUtil.toIntUnsigned(x)) {// means array has no
+ // item key >= x
+ return size;
+ }
+
+ // we know that the next-smallest span was too small
+ lower += (spansize / 2);
+
+ // else begin binary search
+ // invariant: array[lower]
+ * The current bitmap is not modified.
+ *
+ * @param out the DataOutput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ @Override
+ public void serialize(DataOutput out) throws IOException {
+ int startOffset = 0;
+ boolean hasrun = hasRunCompression();
+ if (hasrun) {
+ out.writeInt(Integer.reverseBytes(SERIAL_COOKIE | ((this.size - 1) << 16)));
+ byte[] bitmapOfRunContainers = new byte[(size + 7) / 8];
+ for (int i = 0; i < size; ++i) {
+ if (this.values[i] instanceof MappeableRunContainer) {
+ bitmapOfRunContainers[i / 8] |= (1 << (i % 8));
+ }
+ }
+ out.write(bitmapOfRunContainers);
+ if (this.size < NO_OFFSET_THRESHOLD) {
+ startOffset = 4 + 4 * this.size + bitmapOfRunContainers.length;
+ } else {
+ startOffset = 4 + 8 * this.size + bitmapOfRunContainers.length;
+ }
+ } else { // backwards compatibilility
+ out.writeInt(Integer.reverseBytes(SERIAL_COOKIE_NO_RUNCONTAINER));
+ out.writeInt(Integer.reverseBytes(size));
+ startOffset = 4 + 4 + this.size * 4 + this.size * 4;
+ }
+ for (int k = 0; k < size; ++k) {
+ out.writeShort(Short.reverseBytes(this.keys[k]));
+ out.writeShort(Short.reverseBytes((short) (this.values[k].getCardinality() - 1)));
+ }
+ if ((!hasrun) || (this.size >= NO_OFFSET_THRESHOLD)) {
+ for (int k = 0; k < this.size; k++) {
+ out.writeInt(Integer.reverseBytes(startOffset));
+ startOffset = startOffset + values[k].getArraySizeInBytes();
+ }
+ }
+ for (int k = 0; k < size; ++k) {
+ values[k].writeArray(out);
+ }
+
+ }
+
+ /**
+ * Report the number of bytes required for serialization.
+ *
+ * @return the size in bytes
+ */
+ @Override
+ public int serializedSizeInBytes() {
+ int count = headerSize();
+ // for each container, we store cardinality (16 bits), key (16 bits) and location offset (32
+ // bits).
+ for (int k = 0; k < this.size; ++k) {
+ count += values[k].getArraySizeInBytes();
+ }
+ return count;
+ }
+
+ protected void setContainerAtIndex(int i, MappeableContainer c) {
+ this.values[i] = c;
+ }
+
+ @Override
+ public int size() {
+ return this.size;
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput out) throws IOException {
+ serialize(out);
+ }
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/MutableRoaringBitmap.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/MutableRoaringBitmap.java
new file mode 100644
index 000000000..902e421a7
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/MutableRoaringBitmap.java
@@ -0,0 +1,1420 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap.buffer;
+
+import com.fr.third.bitmap.roaringbitmap.BitmapDataProvider;
+import com.fr.third.bitmap.roaringbitmap.ContainerPointer;
+import com.fr.third.bitmap.roaringbitmap.RoaringBitmap;
+import com.fr.third.bitmap.roaringbitmap.ShortIterator;
+import com.fr.third.bitmap.roaringbitmap.Util;
+
+import java.io.DataInput;
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.io.Serializable;
+import java.util.Iterator;
+
+/**
+ * MutableRoaringBitmap, a compressed alternative to the BitSet. It is similar to
+ * RoaringBitmap, but it differs in that it can interact with
+ * ImmutableRoaringBitmap objects.
+ *
+ * A MutableRoaringBitmap is an instance of an ImmutableRoaringBitmap (where methods like
+ * "serialize" are implemented). That is, they both share the same core (immutable) methods, but a
+ * MutableRoaringBitmap adds methods that allow you to modify the object. This design allows us to
+ * use MutableRoaringBitmap as ImmutableRoaringBitmap instances when needed.
+ *
+ * A MutableRoaringBitmap can be used much like an RoaringBitmap instance, and
+ * they serialize to the same output. The RoaringBitmap instance will be faster since it does not
+ * carry the overhead of a ByteBuffer back-end, but the MutableRoaringBitmap can be used as an
+ * ImmutableRoaringBitmap instance. Thus, if you use ImmutableRoaringBitmap, you probably need to
+ * use MutableRoaringBitmap instances as well; if you do not use ImmutableRoaringBitmap, you
+ * probably want to use only RoaringBitmap instances.
+ *
+ *
+ * (Effectively calls {@link BufferFastAggregation#or})
+ *
+ * @param bitmaps input bitmaps
+ * @return aggregated bitmap
+ */
+ public static MutableRoaringBitmap or(ImmutableRoaringBitmap... bitmaps) {
+ return BufferFastAggregation.or(bitmaps);
+ }
+
+ /**
+ * Bitwise OR (union) operation. The provided bitmaps are *not* modified. This operation is
+ * thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ */
+ public static MutableRoaringBitmap or(final MutableRoaringBitmap x1,
+ final MutableRoaringBitmap x2) {
+ final MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ answer.getMappeableRoaringArray().append(s1, x1.highLowContainer.getContainerAtIndex(pos1)
+ .or(x2.highLowContainer.getContainerAtIndex(pos2)));
+ pos1++;
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ answer.getMappeableRoaringArray().appendCopy(x1.highLowContainer.getKeyAtIndex(pos1),
+ x1.highLowContainer.getContainerAtIndex(pos1));
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ } else { // s1 > s2
+ answer.getMappeableRoaringArray().appendCopy(x2.highLowContainer.getKeyAtIndex(pos2),
+ x2.highLowContainer.getContainerAtIndex(pos2));
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ answer.getMappeableRoaringArray().appendCopy(x2.highLowContainer, pos2, length2);
+ } else if (pos2 == length2) {
+ answer.getMappeableRoaringArray().appendCopy(x1.highLowContainer, pos1, length1);
+ }
+ return answer;
+ }
+
+ /**
+ * Generate a new bitmap with all integers in [rangeStart,rangeEnd) removed.
+ *
+ * @param rb initial bitmap (will not be modified)
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new bitmap
+ */
+ public static MutableRoaringBitmap remove(MutableRoaringBitmap rb, final long rangeStart,
+ final long rangeEnd) {
+ rangeSanityCheck(rangeStart, rangeEnd);
+ if (rangeStart >= rangeEnd) {
+ return rb.clone(); // empty range
+ }
+ final int hbStart = BufferUtil.toIntUnsigned(BufferUtil.highbits(rangeStart));
+ final int lbStart = BufferUtil.toIntUnsigned(BufferUtil.lowbits(rangeStart));
+ final int hbLast = BufferUtil.toIntUnsigned(BufferUtil.highbits(rangeEnd - 1));
+ final int lbLast = BufferUtil.toIntUnsigned(BufferUtil.lowbits(rangeEnd - 1));
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ ((MutableRoaringArray) answer.highLowContainer).appendCopiesUntil(rb.highLowContainer,
+ (short) hbStart);
+
+ if (hbStart == hbLast) {
+ final int i = rb.highLowContainer.getIndex((short) hbStart);
+ if (i >= 0) {
+ final MappeableContainer c =
+ rb.highLowContainer.getContainerAtIndex(i).remove(lbStart, lbLast + 1);
+ if (c.getCardinality() > 0) {
+ ((MutableRoaringArray) answer.highLowContainer).append((short) hbStart, c);
+ }
+ }
+ ((MutableRoaringArray) answer.highLowContainer).appendCopiesAfter(rb.highLowContainer,
+ (short) hbLast);
+ return answer;
+ }
+ int ifirst = rb.highLowContainer.getIndex((short) hbStart);
+ int ilast = rb.highLowContainer.getIndex((short) hbLast);
+ if ((ifirst >= 0) && (lbStart != 0)) {
+ final MappeableContainer c = rb.highLowContainer.getContainerAtIndex(ifirst).remove(lbStart,
+ BufferUtil.maxLowBitAsInteger() + 1);
+ if (c.getCardinality() > 0) {
+ ((MutableRoaringArray) answer.highLowContainer).append((short) hbStart, c);
+ }
+ }
+ if ((ilast >= 0) && (lbLast != BufferUtil.maxLowBitAsInteger())) {
+ final MappeableContainer c =
+ rb.highLowContainer.getContainerAtIndex(ilast).remove(0, lbLast + 1);
+ if (c.getCardinality() > 0) {
+ ((MutableRoaringArray) answer.highLowContainer).append((short) hbLast, c);
+ }
+ }
+ ((MutableRoaringArray) answer.highLowContainer).appendCopiesAfter(rb.highLowContainer,
+ (short) hbLast);
+ return answer;
+ }
+
+ /**
+ * Generate a new bitmap with all integers in [rangeStart,rangeEnd) removed.
+ *
+ * @param rb initial bitmap (will not be modified)
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return new bitmap
+ * @deprecated use the version where longs specify the range
+ */
+ @Deprecated
+ public static MutableRoaringBitmap remove(MutableRoaringBitmap rb,
+ final int rangeStart, final int rangeEnd) {
+ if (rangeStart >= 0) {
+ return remove(rb, (long) rangeStart, (long) rangeEnd);
+ }
+ // rangeStart being -ve and rangeEnd being positive is not expected)
+ // so assume both -ve
+ return remove(rb, rangeStart & 0xFFFFFFFFL, rangeEnd & 0xFFFFFFFFL);
+ }
+
+ /**
+ * Bitwise XOR (symmetric difference) operation. The provided bitmaps are *not* modified. This
+ * operation is thread-safe as long as the provided bitmaps remain unchanged.
+ *
+ * @param x1 first bitmap
+ * @param x2 other bitmap
+ * @return result of the operation
+ */
+ public static MutableRoaringBitmap xor(final MutableRoaringBitmap x1,
+ final MutableRoaringBitmap x2) {
+ final MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ int pos1 = 0, pos2 = 0;
+ final int length1 = x1.highLowContainer.size(), length2 = x2.highLowContainer.size();
+
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ final MappeableContainer c = x1.highLowContainer.getContainerAtIndex(pos1)
+ .xor(x2.highLowContainer.getContainerAtIndex(pos2));
+ if (c.getCardinality() > 0) {
+ answer.getMappeableRoaringArray().append(s1, c);
+ }
+ pos1++;
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ answer.getMappeableRoaringArray().appendCopy(x1.highLowContainer.getKeyAtIndex(pos1),
+ x1.highLowContainer.getContainerAtIndex(pos1));
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = x1.highLowContainer.getKeyAtIndex(pos1);
+ } else if (s1 > s2) {
+ answer.getMappeableRoaringArray().appendCopy(x2.highLowContainer.getKeyAtIndex(pos2),
+ x2.highLowContainer.getContainerAtIndex(pos2));
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ answer.getMappeableRoaringArray().appendCopy(x2.highLowContainer, pos2, length2);
+ } else if (pos2 == length2) {
+ answer.getMappeableRoaringArray().appendCopy(x1.highLowContainer, pos1, length1);
+ }
+
+ return answer;
+ }
+
+ /**
+ * Assume that one wants to store "cardinality" integers in [0, universe_size),
+ * this function returns an upper bound on the serialized size in bytes.
+ *
+ * This function is identical to RoaringBitmap.maximumSerializedSize.
+ *
+ * @param cardinality maximal cardinality
+ * @param universe_size maximal value
+ * @return upper bound on the serialized size in bytes of the bitmap
+ */
+ public static long maximumSerializedSize(int cardinality, int universe_size) {
+ return RoaringBitmap.maximumSerializedSize(cardinality, universe_size);
+ }
+
+ /**
+ * Set all the specified values to true. This can be expected to be slightly
+ * faster than calling "add" repeatedly. The provided integers values don't
+ * have to be in sorted order, but it may be preferable to sort them from a performance point of
+ * view.
+ *
+ * @param dat set values
+ */
+ public void add(final int... dat) {
+ MutableRoaringArray mra = (MutableRoaringArray) highLowContainer;
+ MappeableContainer currentcont = null;
+ short currenthb = 0;
+ int currentcontainerindex = 0;
+ int j = 0;
+ if (j < dat.length) {
+ int val = dat[j];
+ currenthb = BufferUtil.highbits(val);
+ currentcontainerindex = highLowContainer.getIndex(currenthb);
+ if (currentcontainerindex >= 0) {
+ currentcont = highLowContainer.getContainerAtIndex(currentcontainerindex);
+ MappeableContainer newcont = currentcont.add(BufferUtil.lowbits(val));
+ if (newcont != currentcont) {
+ mra.setContainerAtIndex(currentcontainerindex, newcont);
+ currentcont = newcont;
+ }
+ } else {
+ currentcontainerindex = -currentcontainerindex - 1;
+ final MappeableArrayContainer newac = new MappeableArrayContainer();
+ currentcont = newac.add(BufferUtil.lowbits(val));
+ mra.insertNewKeyValueAt(currentcontainerindex, currenthb, currentcont);
+ }
+ j++;
+ }
+ for (; j < dat.length; ++j) {
+ int val = dat[j];
+ short newhb = BufferUtil.highbits(val);
+ if (currenthb == newhb) {// easy case
+ // this could be quite frequent
+ MappeableContainer newcont = currentcont.add(BufferUtil.lowbits(val));
+ if (newcont != currentcont) {
+ mra.setContainerAtIndex(currentcontainerindex, newcont);
+ currentcont = newcont;
+ }
+ } else {
+ currenthb = newhb;
+ currentcontainerindex = highLowContainer.getIndex(currenthb);
+ if (currentcontainerindex >= 0) {
+ currentcont = highLowContainer.getContainerAtIndex(currentcontainerindex);
+ MappeableContainer newcont = currentcont.add(BufferUtil.lowbits(val));
+ if (newcont != currentcont) {
+ mra.setContainerAtIndex(currentcontainerindex, newcont);
+ currentcont = newcont;
+ }
+ } else {
+ currentcontainerindex = -currentcontainerindex - 1;
+ final MappeableArrayContainer newac = new MappeableArrayContainer();
+ currentcont = newac.add(BufferUtil.lowbits(val));
+ mra.insertNewKeyValueAt(currentcontainerindex, currenthb, currentcont);
+ }
+ }
+ }
+ }
+
+ /**
+ * Add the value to the container (set the value to "true"), whether it already appears or not.
+ *
+ * @param x integer value
+ */
+ @Override
+ public void add(final int x) {
+ final short hb = BufferUtil.highbits(x);
+ final int i = highLowContainer.getIndex(hb);
+ if (i >= 0) {
+ getMappeableRoaringArray().setContainerAtIndex(i,
+ highLowContainer.getContainerAtIndex(i).add(BufferUtil.lowbits(x)));
+ } else {
+ final MappeableArrayContainer newac = new MappeableArrayContainer();
+ getMappeableRoaringArray().insertNewKeyValueAt(-i - 1, hb, newac.add(BufferUtil.lowbits(x)));
+ }
+ }
+
+ /**
+ * Add to the current bitmap all integers in [rangeStart,rangeEnd).
+ *
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ */
+ public void add(final long rangeStart, final long rangeEnd) {
+ rangeSanityCheck(rangeStart, rangeEnd);
+ if (rangeStart >= rangeEnd) {
+ return; // empty range
+ }
+
+ final int hbStart = BufferUtil.toIntUnsigned(BufferUtil.highbits(rangeStart));
+ final int lbStart = BufferUtil.toIntUnsigned(BufferUtil.lowbits(rangeStart));
+ final int hbLast = BufferUtil.toIntUnsigned(BufferUtil.highbits(rangeEnd - 1));
+ final int lbLast = BufferUtil.toIntUnsigned(BufferUtil.lowbits(rangeEnd - 1));
+ for (int hb = hbStart; hb <= hbLast; ++hb) {
+
+ // first container may contain partial range
+ final int containerStart = (hb == hbStart) ? lbStart : 0;
+ // last container may contain partial range
+ final int containerLast = (hb == hbLast) ? lbLast : BufferUtil.maxLowBitAsInteger();
+ final int i = highLowContainer.getIndex((short) hb);
+
+ if (i >= 0) {
+ final MappeableContainer c =
+ highLowContainer.getContainerAtIndex(i).iadd(containerStart, containerLast + 1);
+ ((MutableRoaringArray) highLowContainer).setContainerAtIndex(i, c);
+ } else {
+ ((MutableRoaringArray) highLowContainer).insertNewKeyValueAt(-i - 1, (short) hb,
+ MappeableContainer.rangeOfOnes(containerStart, containerLast + 1));
+ }
+ }
+ }
+
+ /**
+ * Add to the current bitmap all integers in [rangeStart,rangeEnd).
+ *
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @deprecated use the version where longs specify the range
+ */
+ @Deprecated
+ public void add(final int rangeStart, final int rangeEnd) {
+ if (rangeStart >= 0) {
+ add((long) rangeStart, (long) rangeEnd);
+ }
+ // rangeStart being -ve and rangeEnd being positive is not expected)
+ // so assume both -ve
+ add(rangeStart & 0xFFFFFFFFL, rangeEnd & 0xFFFFFFFFL);
+ }
+
+ /**
+ * In-place bitwise AND (intersection) operation. The current bitmap is modified.
+ *
+ * @param array other bitmap
+ */
+ public void and(final ImmutableRoaringBitmap array) {
+ int pos1 = 0, pos2 = 0, intersectionSize = 0;
+ final int length1 = highLowContainer.size(), length2 = array.highLowContainer.size();
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = array.highLowContainer.getKeyAtIndex(pos2);
+ if (s1 == s2) {
+ final MappeableContainer c1 = highLowContainer.getContainerAtIndex(pos1);
+ final MappeableContainer c2 = array.highLowContainer.getContainerAtIndex(pos2);
+ final MappeableContainer c = c1.iand(c2);
+ if (c.getCardinality() > 0) {
+ getMappeableRoaringArray().replaceKeyAndContainerAtIndex(intersectionSize++, s1, c);
+ }
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1 = highLowContainer.advanceUntil(s2, pos1);
+ } else { // s1 > s2
+ pos2 = array.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ getMappeableRoaringArray().resize(intersectionSize);
+ }
+
+ /**
+ * In-place bitwise ANDNOT (difference) operation. The current bitmap is modified.
+ *
+ * @param x2 other bitmap
+ */
+ public void andNot(final ImmutableRoaringBitmap x2) {
+ int pos1 = 0, pos2 = 0, intersectionSize = 0;
+ final int length1 = highLowContainer.size(), length2 = x2.highLowContainer.size();
+
+ while (pos1 < length1 && pos2 < length2) {
+ final short s1 = highLowContainer.getKeyAtIndex(pos1);
+ final short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ if (s1 == s2) {
+ final MappeableContainer c1 = highLowContainer.getContainerAtIndex(pos1);
+ final MappeableContainer c2 = x2.highLowContainer.getContainerAtIndex(pos2);
+ final MappeableContainer c = c1.iandNot(c2);
+ if (c.getCardinality() > 0) {
+ getMappeableRoaringArray().replaceKeyAndContainerAtIndex(intersectionSize++, s1, c);
+ }
+ ++pos1;
+ ++pos2;
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ if (pos1 != intersectionSize) {
+ final MappeableContainer c1 = highLowContainer.getContainerAtIndex(pos1);
+ getMappeableRoaringArray().replaceKeyAndContainerAtIndex(intersectionSize, s1, c1);
+ }
+ ++intersectionSize;
+ ++pos1;
+ } else { // s1 > s2
+ pos2 = x2.highLowContainer.advanceUntil(s1, pos2);
+ }
+ }
+ if (pos1 < length1) {
+ getMappeableRoaringArray().copyRange(pos1, length1, intersectionSize);
+ intersectionSize += length1 - pos1;
+ }
+ getMappeableRoaringArray().resize(intersectionSize);
+ }
+
+ /**
+ * Add the value to the container (set the value to "true"), whether it already appears or not.
+ *
+ * @param x integer value
+ * @return true if the added int wasn't already contained in the bitmap. False otherwise.
+ */
+ public boolean checkedAdd(final int x) {
+ final short hb = BufferUtil.highbits(x);
+ final int i = highLowContainer.getIndex(hb);
+ if (i >= 0) {
+ MappeableContainer C = highLowContainer.getContainerAtIndex(i);
+ int oldcard = C.getCardinality();
+ C = C.add(BufferUtil.lowbits(x));
+ getMappeableRoaringArray().setContainerAtIndex(i, C);
+ return C.getCardinality() > oldcard;
+ } else {
+ final MappeableArrayContainer newac = new MappeableArrayContainer();
+ getMappeableRoaringArray().insertNewKeyValueAt(-i - 1, hb, newac.add(BufferUtil.lowbits(x)));
+ return true;
+ }
+ }
+
+ /**
+ * If present remove the specified integer (effectively, sets its bit value to false)
+ *
+ * @param x integer value representing the index in a bitmap
+ * @return true if the unset bit was already in the bitmap
+ */
+ public boolean checkedRemove(final int x) {
+ final short hb = BufferUtil.highbits(x);
+ final int i = highLowContainer.getIndex(hb);
+ if (i < 0) {
+ return false;
+ }
+ MappeableContainer C = highLowContainer.getContainerAtIndex(i);
+ int oldcard = C.getCardinality();
+ C.remove(BufferUtil.lowbits(x));
+ int newcard = C.getCardinality();
+ if (newcard == oldcard) {
+ return false;
+ }
+ if (newcard > 0) {
+ ((MutableRoaringArray) highLowContainer).setContainerAtIndex(i, C);
+ } else {
+ ((MutableRoaringArray) highLowContainer).removeAtIndex(i);
+ }
+ return true;
+ }
+
+ /**
+ * reset to an empty bitmap; result occupies as much space a newly created bitmap.
+ */
+ public void clear() {
+ highLowContainer = new MutableRoaringArray(); // lose references
+ }
+
+ @Override
+ public MutableRoaringBitmap clone() {
+ final MutableRoaringBitmap x = (MutableRoaringBitmap) super.clone();
+ x.highLowContainer = highLowContainer.clone();
+ return x;
+
+ }
+
+ /**
+ * Deserialize the bitmap (retrieve from the input stream). The current bitmap is overwritten.
+ *
+ * @param in the DataInput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public void deserialize(DataInput in) throws IOException {
+ getMappeableRoaringArray().deserialize(in);
+ }
+
+ /**
+ * Add the value if it is not already present, otherwise remove it.
+ *
+ * @param x integer value
+ */
+ public void flip(final int x) {
+ final short hb = BufferUtil.highbits(x);
+ final int i = highLowContainer.getIndex(hb);
+ if (i >= 0) {
+ MappeableContainer c = highLowContainer.getContainerAtIndex(i);
+ c = c.flip(BufferUtil.lowbits(x));
+ if (c.getCardinality() > 0) {
+ ((MutableRoaringArray) highLowContainer).setContainerAtIndex(i, c);
+ } else {
+ ((MutableRoaringArray) highLowContainer).removeAtIndex(i);
+ }
+ } else {
+ final MappeableArrayContainer newac = new MappeableArrayContainer();
+ ((MutableRoaringArray) highLowContainer).insertNewKeyValueAt(-i - 1, hb,
+ newac.add(BufferUtil.lowbits(x)));
+ }
+ }
+
+ /**
+ * Modifies the current bitmap by complementing the bits in the given range, from rangeStart
+ * (inclusive) rangeEnd (exclusive).
+ *
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ */
+ public void flip(final long rangeStart, final long rangeEnd) {
+ rangeSanityCheck(rangeStart, rangeEnd);
+ if (rangeStart >= rangeEnd) {
+ return; // empty range
+ }
+
+ final int hbStart = BufferUtil.toIntUnsigned(BufferUtil.highbits(rangeStart));
+ final int lbStart = BufferUtil.toIntUnsigned(BufferUtil.lowbits(rangeStart));
+ final int hbLast = BufferUtil.toIntUnsigned(BufferUtil.highbits(rangeEnd - 1));
+ final int lbLast = BufferUtil.toIntUnsigned(BufferUtil.lowbits(rangeEnd - 1));
+
+ for (int hb = hbStart; hb <= hbLast; ++hb) {
+ // first container may contain partial range
+ final int containerStart = (hb == hbStart) ? lbStart : 0;
+ // last container may contain partial range
+ final int containerLast = (hb == hbLast) ? lbLast : BufferUtil.maxLowBitAsInteger();
+ final int i = highLowContainer.getIndex((short) hb);
+
+ if (i >= 0) {
+ final MappeableContainer c =
+ highLowContainer.getContainerAtIndex(i).inot(containerStart, containerLast + 1);
+ if (c.getCardinality() > 0) {
+ getMappeableRoaringArray().setContainerAtIndex(i, c);
+ } else {
+ getMappeableRoaringArray().removeAtIndex(i);
+ }
+ } else {
+ getMappeableRoaringArray().insertNewKeyValueAt(-i - 1, (short) hb,
+ MappeableContainer.rangeOfOnes(containerStart, containerLast + 1));
+ }
+ }
+ }
+
+ /**
+ * Modifies the current bitmap by complementing the bits in the given range, from rangeStart
+ * (inclusive) rangeEnd (exclusive).
+ *
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @deprecated use the version where longs specify the range
+ */
+ @Deprecated
+ public void flip(final int rangeStart, final int rangeEnd) {
+ if (rangeStart >= 0) {
+ flip((long) rangeStart, (long) rangeEnd);
+ } else {
+ // rangeStart being -ve and rangeEnd being positive is not expected)
+ // so assume both -ve
+ flip(rangeStart & 0xFFFFFFFFL, rangeEnd & 0xFFFFFFFFL);
+ }
+ }
+
+ /**
+ * @return a mutable copy of this bitmap
+ */
+ public MutableRoaringArray getMappeableRoaringArray() {
+ return (MutableRoaringArray) highLowContainer;
+ }
+
+ @Override
+ public int hashCode() {
+ return highLowContainer.hashCode();
+ }
+
+ /**
+ * iterate over the positions of the true values.
+ *
+ * @return the iterator
+ */
+ @Override
+ public Iterator
+ *
+ * This function is equivalent to :
+ *
+ *
+ * The current bitmap is not modified.
+ *
+ * @param out the DataOutput stream
+ * @throws IOException Signals that an I/O exception has occurred.
+ */
+ public void serialize(DataOutput out) throws IOException;
+
+ /**
+ * @return the size that the data structure occupies on disk
+ */
+ public int serializedSizeInBytes();
+
+
+ /**
+ * @return number of keys
+ */
+ int size();
+}
diff --git a/fine-third-default/readme.md b/fine-third-default/readme.md
new file mode 100644
index 000000000..7df79386d
--- /dev/null
+++ b/fine-third-default/readme.md
@@ -0,0 +1,3 @@
+用于针对性的修改原3rd中的class
+
+1.修改javax.xml.stream.XMLEntityReaderImpl 阅读特殊字符出现的问题 责任人-Bryant 2019/08/01
\ No newline at end of file
diff --git a/fine-third-default/src/com/fr/third/javax/xml/stream/XMLEntityReaderImpl.java b/fine-third-default/src/com/fr/third/javax/xml/stream/XMLEntityReaderImpl.java
new file mode 100644
index 000000000..90649414f
--- /dev/null
+++ b/fine-third-default/src/com/fr/third/javax/xml/stream/XMLEntityReaderImpl.java
@@ -0,0 +1,1133 @@
+package com.fr.third.javax.xml.stream;
+
+import com.fr.third.javax.xml.stream.xerces.xni.parser.*;
+import java.util.*;
+import com.fr.third.javax.xml.stream.xerces.xni.*;
+import com.fr.third.javax.xml.stream.xerces.impl.io.*;
+import com.fr.third.javax.xml.stream.xerces.util.*;
+import java.io.*;
+
+import static com.fr.third.javax.xml.stream.xerces.util.XMLChar.isInvalid;
+
+public class XMLEntityReaderImpl extends XMLEntityReader
+{
+ protected Entity.ScannedEntity fCurrentEntity;
+ protected XMLEntityManager fEntityManager;
+ private static final boolean DEBUG_ENCODINGS = false;
+ private Vector listeners;
+ public static final boolean[] validContent;
+ public static final boolean[] validNames;
+ private static final boolean DEBUG_BUFFER = false;
+ private static final boolean DEBUG_SKIP_STRING = false;
+ protected SymbolTable fSymbolTable;
+ protected XMLErrorReporter fErrorReporter;
+ int[] whiteSpaceLookup;
+ int whiteSpaceLen;
+ boolean whiteSpaceInfoNeeded;
+ char[] scannedName;
+ protected boolean fAllowJavaEncodings;
+ protected static final String SYMBOL_TABLE = "http://apache.org/xml/properties/internal/symbol-table";
+ protected static final String ERROR_REPORTER = "http://apache.org/xml/properties/internal/error-reporter";
+ protected static final String ALLOW_JAVA_ENCODINGS = "http://apache.org/xml/features/allow-java-encodings";
+ protected PropertyManager fPropertyManager;
+ boolean isExternal;
+
+ static {
+ validContent = new boolean[127];
+ validNames = new boolean[127];
+ for (char i = ' '; i < '\u007f'; ++i) {
+ XMLEntityReaderImpl.validContent[i] = true;
+ }
+ XMLEntityReaderImpl.validContent[9] = true;
+ XMLEntityReaderImpl.validContent[38] = false;
+ XMLEntityReaderImpl.validContent[60] = false;
+ for (int j = 65; j <= 90; ++j) {
+ XMLEntityReaderImpl.validNames[j] = true;
+ }
+ for (int j = 97; j <= 122; ++j) {
+ XMLEntityReaderImpl.validNames[j] = true;
+ }
+ for (int j = 48; j <= 57; ++j) {
+ XMLEntityReaderImpl.validNames[j] = true;
+ }
+ XMLEntityReaderImpl.validNames[45] = true;
+ XMLEntityReaderImpl.validNames[46] = true;
+ XMLEntityReaderImpl.validNames[58] = true;
+ XMLEntityReaderImpl.validNames[95] = true;
+ }
+
+ public XMLEntityReaderImpl(final XMLEntityManager entityManager) {
+ this.fCurrentEntity = null;
+ this.listeners = new Vector();
+ this.fSymbolTable = null;
+ this.fErrorReporter = null;
+ this.whiteSpaceLookup = new int[100];
+ this.whiteSpaceLen = 0;
+ this.whiteSpaceInfoNeeded = true;
+ this.scannedName = null;
+ this.fPropertyManager = null;
+ this.isExternal = false;
+ this.fEntityManager = entityManager;
+ }
+
+ public XMLEntityReaderImpl(final PropertyManager propertyManager, final XMLEntityManager entityManager) {
+ this.fCurrentEntity = null;
+ this.listeners = new Vector();
+ this.fSymbolTable = null;
+ this.fErrorReporter = null;
+ this.whiteSpaceLookup = new int[100];
+ this.whiteSpaceLen = 0;
+ this.whiteSpaceInfoNeeded = true;
+ this.scannedName = null;
+ this.fPropertyManager = null;
+ this.isExternal = false;
+ this.fEntityManager = entityManager;
+ this.reset(propertyManager);
+ }
+
+ public void reset(final PropertyManager propertyManager) {
+ this.fSymbolTable = (SymbolTable)propertyManager.getProperty("http://apache.org/xml/properties/internal/symbol-table");
+ this.fErrorReporter = (XMLErrorReporter)propertyManager.getProperty("http://apache.org/xml/properties/internal/error-reporter");
+ this.fCurrentEntity = null;
+ this.whiteSpaceLen = 0;
+ this.whiteSpaceInfoNeeded = true;
+ this.scannedName = null;
+ this.listeners.clear();
+ }
+
+ public void reset(final XMLComponentManager componentManager) throws XMLConfigurationException {
+ try {
+ this.fAllowJavaEncodings = componentManager.getFeature("http://apache.org/xml/features/allow-java-encodings");
+ }
+ catch (XMLConfigurationException e) {
+ this.fAllowJavaEncodings = false;
+ }
+ this.fSymbolTable = (SymbolTable)componentManager.getProperty("http://apache.org/xml/properties/internal/symbol-table");
+ this.fErrorReporter = (XMLErrorReporter)componentManager.getProperty("http://apache.org/xml/properties/internal/error-reporter");
+ }
+
+ public void setCurrentEntity(final Entity.ScannedEntity scannedEntity) {
+ this.fCurrentEntity = scannedEntity;
+ if (this.fCurrentEntity != null) {
+ this.isExternal = this.fCurrentEntity.isExternal();
+ }
+ }
+
+ public Entity.ScannedEntity getCurrentEntity() {
+ return this.fCurrentEntity;
+ }
+
+ public String getBaseSystemId() {
+ return (this.fCurrentEntity != null && this.fCurrentEntity.entityLocation != null) ? this.fCurrentEntity.entityLocation.getExpandedSystemId() : null;
+ }
+
+ public int getLineNumber() {
+ return (this.fCurrentEntity != null) ? this.fCurrentEntity.lineNumber : -1;
+ }
+
+ public int getColumnNumber() {
+ return (this.fCurrentEntity != null) ? this.fCurrentEntity.columnNumber : -1;
+ }
+
+ public int getCharacterOffset() {
+ return (this.fCurrentEntity != null) ? (this.fCurrentEntity.fTotalCountTillLastLoad + this.fCurrentEntity.position) : -1;
+ }
+
+ public String getExpandedSystemId() {
+ return (this.fCurrentEntity != null && this.fCurrentEntity.entityLocation != null) ? this.fCurrentEntity.entityLocation.getExpandedSystemId() : null;
+ }
+
+ public String getLiteralSystemId() {
+ return (this.fCurrentEntity != null && this.fCurrentEntity.entityLocation != null) ? this.fCurrentEntity.entityLocation.getLiteralSystemId() : null;
+ }
+
+ public String getPublicId() {
+ return (this.fCurrentEntity != null && this.fCurrentEntity.entityLocation != null) ? this.fCurrentEntity.entityLocation.getPublicId() : null;
+ }
+
+ public void setVersion(final String version) {
+ this.fCurrentEntity.version = version;
+ }
+
+ public String getVersion() {
+ return this.fCurrentEntity.version;
+ }
+
+ public String getEncoding() {
+ return this.fCurrentEntity.encoding;
+ }
+
+ public void setEncoding(final String encoding) throws IOException {
+ if (this.fCurrentEntity.stream != null && (this.fCurrentEntity.encoding == null || !this.fCurrentEntity.encoding.equals(encoding))) {
+ if (this.fCurrentEntity.encoding != null && this.fCurrentEntity.encoding.startsWith("UTF-16")) {
+ final String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
+ if (ENCODING.equals("UTF-16")) {
+ return;
+ }
+ if (ENCODING.equals("ISO-10646-UCS-4")) {
+ if (this.fCurrentEntity.encoding.equals("UTF-16BE")) {
+ this.fCurrentEntity.reader = new UCSReader(this.fCurrentEntity.stream, (short)8);
+ }
+ else {
+ this.fCurrentEntity.reader = new UCSReader(this.fCurrentEntity.stream, (short)4);
+ }
+ return;
+ }
+ if (ENCODING.equals("ISO-10646-UCS-2")) {
+ if (this.fCurrentEntity.encoding.equals("UTF-16BE")) {
+ this.fCurrentEntity.reader = new UCSReader(this.fCurrentEntity.stream, (short)2);
+ }
+ else {
+ this.fCurrentEntity.reader = new UCSReader(this.fCurrentEntity.stream, (short)1);
+ }
+ return;
+ }
+ }
+ this.fCurrentEntity.reader = this.createReader(this.fCurrentEntity.stream, encoding, null);
+ this.fCurrentEntity.encoding = encoding;
+ }
+ }
+
+ public boolean isExternal() {
+ return this.fCurrentEntity.isExternal();
+ }
+
+ public int getChar(final int relative) throws IOException {
+ if (this.arrangeCapacity(relative + 1, false)) {
+ return this.fCurrentEntity.ch[this.fCurrentEntity.position + relative];
+ }
+ return -1;
+ }
+
+ public int peekChar() throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ final int c = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (this.isExternal) {
+ return (c != 13) ? c : 10;
+ }
+ return c;
+ }
+
+ public int scanChar() throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ int c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if (c == 10 || (c == 13 && this.isExternal)) {
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ ++fCurrentEntity.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(1);
+ this.fCurrentEntity.ch[0] = (char)c;
+ this.load(1, false);
+ }
+ if (c == 13 && this.isExternal) {
+ if (this.fCurrentEntity.ch[this.fCurrentEntity.position++] != '\n') {
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ --fCurrentEntity2.position;
+ }
+ c = 10;
+ }
+ }
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ ++fCurrentEntity3.columnNumber;
+ return c;
+ }
+
+ public String scanNmtoken() throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ int offset = this.fCurrentEntity.position;
+ boolean vc = false;
+ while (true) {
+ final char c = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (c < '\u007f') {
+ vc = XMLEntityReaderImpl.validNames[c];
+ }
+ else {
+ vc = XMLChar.isName(c);
+ }
+ if (!vc) {
+ break;
+ }
+ if (++this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ continue;
+ }
+ final int length = this.fCurrentEntity.position - offset;
+ this.invokeListeners(length);
+ if (length == this.fCurrentEntity.fBufferSize) {
+ final char[] tmp = new char[this.fCurrentEntity.fBufferSize * 2];
+ System.arraycopy(this.fCurrentEntity.ch, offset, tmp, 0, length);
+ this.fCurrentEntity.ch = tmp;
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ fCurrentEntity.fBufferSize *= 2;
+ }
+ else {
+ System.arraycopy(this.fCurrentEntity.ch, offset, this.fCurrentEntity.ch, 0, length);
+ }
+ offset = 0;
+ if (this.load(length, false)) {
+ break;
+ }
+ }
+ final int length = this.fCurrentEntity.position - offset;
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ fCurrentEntity2.columnNumber += length;
+ String symbol = null;
+ if (length > 0) {
+ symbol = this.fSymbolTable.addSymbol(this.fCurrentEntity.ch, offset, length);
+ }
+ return symbol;
+ }
+
+ public String scanName() throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ int offset = this.fCurrentEntity.position;
+ if (XMLChar.isNameStart(this.fCurrentEntity.ch[offset])) {
+ if (++this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(1);
+ this.fCurrentEntity.ch[0] = this.fCurrentEntity.ch[offset];
+ offset = 0;
+ if (this.load(1, false)) {
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ ++fCurrentEntity.columnNumber;
+ final String symbol = this.fSymbolTable.addSymbol(this.fCurrentEntity.ch, 0, 1);
+ this.scannedName = this.fSymbolTable.getCharArray();
+ return symbol;
+ }
+ }
+ boolean vc = false;
+ while (true) {
+ final char c = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (c < '\u007f') {
+ vc = XMLEntityReaderImpl.validNames[c];
+ }
+ else {
+ vc = XMLChar.isName(c);
+ }
+ if (!vc) {
+ break;
+ }
+ if (++this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ continue;
+ }
+ final int length = this.fCurrentEntity.position - offset;
+ this.invokeListeners(length);
+ if (length == this.fCurrentEntity.fBufferSize) {
+ final char[] tmp = new char[this.fCurrentEntity.fBufferSize * 2];
+ System.arraycopy(this.fCurrentEntity.ch, offset, tmp, 0, length);
+ this.fCurrentEntity.ch = tmp;
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ fCurrentEntity2.fBufferSize *= 2;
+ }
+ else {
+ System.arraycopy(this.fCurrentEntity.ch, offset, this.fCurrentEntity.ch, 0, length);
+ }
+ offset = 0;
+ if (this.load(length, false)) {
+ break;
+ }
+ }
+ }
+ final int length2 = this.fCurrentEntity.position - offset;
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ fCurrentEntity3.columnNumber += length2;
+ String symbol2 = null;
+ if (length2 > 0) {
+ symbol2 = this.fSymbolTable.addSymbol(this.fCurrentEntity.ch, offset, length2);
+ this.scannedName = this.fSymbolTable.getCharArray();
+ }
+ return symbol2;
+ }
+
+ public boolean scanQName(final QName qname) throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ int offset = this.fCurrentEntity.position;
+ if (XMLChar.isNameStart(this.fCurrentEntity.ch[offset])) {
+ if (++this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(1);
+ this.fCurrentEntity.ch[0] = this.fCurrentEntity.ch[offset];
+ offset = 0;
+ if (this.load(1, false)) {
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ ++fCurrentEntity.columnNumber;
+ final String name = this.fSymbolTable.addSymbol(this.fCurrentEntity.ch, 0, 1);
+ qname.setValues(null, name, name, null);
+ qname.characters = this.fSymbolTable.getCharArray();
+ return true;
+ }
+ }
+ int index = -1;
+ boolean vc = false;
+ while (true) {
+ final char c = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (c < '\u007f') {
+ vc = XMLEntityReaderImpl.validNames[c];
+ }
+ else {
+ vc = XMLChar.isName(c);
+ }
+ if (!vc) {
+ break;
+ }
+ if (c == ':') {
+ if (index != -1) {
+ break;
+ }
+ index = this.fCurrentEntity.position;
+ }
+ if (++this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ continue;
+ }
+ final int length = this.fCurrentEntity.position - offset;
+ this.invokeListeners(length);
+ if (length == this.fCurrentEntity.fBufferSize) {
+ final char[] tmp = new char[this.fCurrentEntity.fBufferSize * 2];
+ System.arraycopy(this.fCurrentEntity.ch, offset, tmp, 0, length);
+ this.fCurrentEntity.ch = tmp;
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ fCurrentEntity2.fBufferSize *= 2;
+ }
+ else {
+ System.arraycopy(this.fCurrentEntity.ch, offset, this.fCurrentEntity.ch, 0, length);
+ }
+ if (index != -1) {
+ index -= offset;
+ }
+ offset = 0;
+ if (this.load(length, false)) {
+ break;
+ }
+ }
+ final int length2 = this.fCurrentEntity.position - offset;
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ fCurrentEntity3.columnNumber += length2;
+ if (length2 > 0) {
+ String prefix = null;
+ String localpart = null;
+ final String rawname = this.fSymbolTable.addSymbol(this.fCurrentEntity.ch, offset, length2);
+ qname.characters = this.fSymbolTable.getCharArray();
+ if (index != -1) {
+ final int prefixLength = index - offset;
+ prefix = this.fSymbolTable.addSymbol(this.fCurrentEntity.ch, offset, prefixLength);
+ final int len = length2 - prefixLength - 1;
+ localpart = this.fSymbolTable.addSymbol(this.fCurrentEntity.ch, index + 1, len);
+ }
+ else {
+ localpart = rawname;
+ }
+ qname.setValues(prefix, localpart, rawname, null);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public int scanContent(final XMLString content) throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ else if (this.fCurrentEntity.position == this.fCurrentEntity.count - 1) {
+ this.invokeListeners(0);
+ this.fCurrentEntity.ch[0] = this.fCurrentEntity.ch[this.fCurrentEntity.count - 1];
+ this.load(1, false);
+ this.fCurrentEntity.position = 0;
+ }
+ int offset = this.fCurrentEntity.position;
+ int c = this.fCurrentEntity.ch[offset];
+ int newlines = 0;
+ if (c == 10 || (c == 13 && this.isExternal)) {
+ do {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if (c == 13 && this.isExternal) {
+ ++newlines;
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ ++fCurrentEntity.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ offset = 0;
+ this.invokeListeners(newlines);
+ this.fCurrentEntity.position = newlines;
+ if (this.load(newlines, false)) {
+ break;
+ }
+ }
+ if (this.fCurrentEntity.ch[this.fCurrentEntity.position] == '\n') {
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ ++fCurrentEntity2.position;
+ ++offset;
+ }
+ else {
+ ++newlines;
+ }
+ }
+ else {
+ if (c != 10) {
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ --fCurrentEntity3.position;
+ break;
+ }
+ ++newlines;
+ final Entity.ScannedEntity fCurrentEntity4 = this.fCurrentEntity;
+ ++fCurrentEntity4.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ continue;
+ }
+ offset = 0;
+ this.invokeListeners(newlines);
+ this.fCurrentEntity.position = newlines;
+ if (this.load(newlines, false)) {
+ break;
+ }
+ continue;
+ }
+ } while (this.fCurrentEntity.position < this.fCurrentEntity.count - 1);
+ for (int i = offset; i < this.fCurrentEntity.position; ++i) {
+ this.fCurrentEntity.ch[i] = '\n';
+ }
+ final int length = this.fCurrentEntity.position - offset;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count - 1) {
+ content.setValues(this.fCurrentEntity.ch, offset, length);
+ return -1;
+ }
+ }
+ boolean vc = false;
+ while (this.fCurrentEntity.position < this.fCurrentEntity.count) {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if (c < 127) {
+ vc = XMLEntityReaderImpl.validContent[c];
+ }
+ else {
+ vc = XMLChar.isContent(c);
+ }
+ if (!vc) {
+ final Entity.ScannedEntity fCurrentEntity5 = this.fCurrentEntity;
+ --fCurrentEntity5.position;
+ break;
+ }
+ }
+ final int length2 = this.fCurrentEntity.position - offset;
+ final Entity.ScannedEntity fCurrentEntity6 = this.fCurrentEntity;
+ fCurrentEntity6.columnNumber += length2 - newlines;
+ content.setValues(this.fCurrentEntity.ch, offset, length2);
+ if (this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (c == 13 && this.isExternal) {
+ c = 10;
+ }
+ }
+ else {
+ c = -1;
+ }
+ return c;
+ }
+
+ public int scanLiteral(final int quote, final XMLString content) throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ else if (this.fCurrentEntity.position == this.fCurrentEntity.count - 1) {
+ this.invokeListeners(0);
+ this.fCurrentEntity.ch[0] = this.fCurrentEntity.ch[this.fCurrentEntity.count - 1];
+ this.load(1, false);
+ this.fCurrentEntity.position = 0;
+ }
+ int offset = this.fCurrentEntity.position;
+ int c = this.fCurrentEntity.ch[offset];
+ int newlines = 0;
+ if (this.whiteSpaceInfoNeeded) {
+ this.whiteSpaceLen = 0;
+ }
+ if (c == 10 || (c == 13 && this.isExternal)) {
+ do {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if (c == 13 && this.isExternal) {
+ ++newlines;
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ ++fCurrentEntity.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(newlines);
+ offset = 0;
+ this.fCurrentEntity.position = newlines;
+ if (this.load(newlines, false)) {
+ break;
+ }
+ }
+ if (this.fCurrentEntity.ch[this.fCurrentEntity.position] == '\n') {
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ ++fCurrentEntity2.position;
+ ++offset;
+ }
+ else {
+ ++newlines;
+ }
+ }
+ else {
+ if (c != 10) {
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ --fCurrentEntity3.position;
+ break;
+ }
+ ++newlines;
+ final Entity.ScannedEntity fCurrentEntity4 = this.fCurrentEntity;
+ ++fCurrentEntity4.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ continue;
+ }
+ offset = 0;
+ this.invokeListeners(newlines);
+ this.fCurrentEntity.position = newlines;
+ if (this.load(newlines, false)) {
+ break;
+ }
+ continue;
+ }
+ } while (this.fCurrentEntity.position < this.fCurrentEntity.count - 1);
+ int i;
+ for (i = 0, i = offset; i < this.fCurrentEntity.position; ++i) {
+ this.fCurrentEntity.ch[i] = '\n';
+ this.whiteSpaceLookup[this.whiteSpaceLen++] = i;
+ }
+ final int length = this.fCurrentEntity.position - offset;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count - 1) {
+ content.setValues(this.fCurrentEntity.ch, offset, length);
+ return -1;
+ }
+ }
+ boolean vc = true;
+ while (this.fCurrentEntity.position < this.fCurrentEntity.count) {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if ((c == quote && (!this.fCurrentEntity.literal || this.isExternal)) || c == 37) {
+ final Entity.ScannedEntity fCurrentEntity5 = this.fCurrentEntity;
+ --fCurrentEntity5.position;
+ break;
+ }
+ if (c < 127) {
+ vc = XMLEntityReaderImpl.validContent[c];
+ }
+ else {
+ vc = XMLChar.isContent(c);
+ }
+ if (!vc) {
+ final Entity.ScannedEntity fCurrentEntity6 = this.fCurrentEntity;
+ --fCurrentEntity6.position;
+ break;
+ }
+ if (!this.whiteSpaceInfoNeeded || (c != 32 && c != 9)) {
+ continue;
+ }
+ if (this.whiteSpaceLen < this.whiteSpaceLookup.length) {
+ this.whiteSpaceLookup[this.whiteSpaceLen++] = this.fCurrentEntity.position - 1;
+ }
+ else {
+ final int[] tmp = new int[this.whiteSpaceLookup.length + 20];
+ System.arraycopy(this.whiteSpaceLookup, 0, tmp, 0, this.whiteSpaceLookup.length);
+ (this.whiteSpaceLookup = tmp)[this.whiteSpaceLen++] = this.fCurrentEntity.position - 1;
+ }
+ }
+ final int length = this.fCurrentEntity.position - offset;
+ final Entity.ScannedEntity fCurrentEntity7 = this.fCurrentEntity;
+ fCurrentEntity7.columnNumber += length - newlines;
+ content.setValues(this.fCurrentEntity.ch, offset, length);
+ if (this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (c == quote && this.fCurrentEntity.literal) {
+ c = -1;
+ }
+ }
+ else {
+ c = -1;
+ }
+ return c;
+ }
+
+ public boolean scanData(final String delimiter, final XMLStringBuffer buffer) throws IOException {
+ boolean done = false;
+ final int delimLen = delimiter.length();
+ final char charAt0 = delimiter.charAt(0);
+ do {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ else if (this.fCurrentEntity.position >= this.fCurrentEntity.count - delimLen) {
+ this.invokeListeners(this.fCurrentEntity.count - this.fCurrentEntity.position);
+ System.arraycopy(this.fCurrentEntity.ch, this.fCurrentEntity.position, this.fCurrentEntity.ch, 0, this.fCurrentEntity.count - this.fCurrentEntity.position);
+ this.load(this.fCurrentEntity.count - this.fCurrentEntity.position, false);
+ this.fCurrentEntity.position = 0;
+ }
+ if (this.fCurrentEntity.position >= this.fCurrentEntity.count - delimLen) {
+ this.invokeListeners(0);
+ final int length = this.fCurrentEntity.count - this.fCurrentEntity.position;
+ buffer.append(this.fCurrentEntity.ch, this.fCurrentEntity.position, length);
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ fCurrentEntity.columnNumber += this.fCurrentEntity.count;
+ this.fCurrentEntity.position = this.fCurrentEntity.count;
+ this.load(0, true);
+ return false;
+ }
+ int offset = this.fCurrentEntity.position;
+ int c = this.fCurrentEntity.ch[offset];
+ int newlines = 0;
+ if (c == 10 || (c == 13 && this.isExternal)) {
+ do {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if (c == 13 && this.isExternal) {
+ ++newlines;
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ ++fCurrentEntity2.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ offset = 0;
+ this.invokeListeners(newlines);
+ this.fCurrentEntity.position = newlines;
+ if (this.load(newlines, false)) {
+ break;
+ }
+ }
+ if (this.fCurrentEntity.ch[this.fCurrentEntity.position] == '\n') {
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ ++fCurrentEntity3.position;
+ ++offset;
+ }
+ else {
+ ++newlines;
+ }
+ }
+ else {
+ if (c != 10) {
+ final Entity.ScannedEntity fCurrentEntity4 = this.fCurrentEntity;
+ --fCurrentEntity4.position;
+ break;
+ }
+ ++newlines;
+ final Entity.ScannedEntity fCurrentEntity5 = this.fCurrentEntity;
+ ++fCurrentEntity5.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position != this.fCurrentEntity.count) {
+ continue;
+ }
+ offset = 0;
+ this.invokeListeners(newlines);
+ this.fCurrentEntity.position = newlines;
+ this.fCurrentEntity.count = newlines;
+ if (this.load(newlines, false)) {
+ break;
+ }
+ continue;
+ }
+ } while (this.fCurrentEntity.position < this.fCurrentEntity.count - 1);
+ for (int i = offset; i < this.fCurrentEntity.position; ++i) {
+ this.fCurrentEntity.ch[i] = '\n';
+ }
+ final int length2 = this.fCurrentEntity.position - offset;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count - 1) {
+ buffer.append(this.fCurrentEntity.ch, offset, length2);
+ return true;
+ }
+ }
+ Label_0949:
+ while (this.fCurrentEntity.position < this.fCurrentEntity.count) {
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if (c == charAt0) {
+ final int delimOffset = this.fCurrentEntity.position - 1;
+ for (int j = 1; j < delimLen; ++j) {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ final Entity.ScannedEntity fCurrentEntity6 = this.fCurrentEntity;
+ fCurrentEntity6.position -= j;
+ break Label_0949;
+ }
+ c = this.fCurrentEntity.ch[this.fCurrentEntity.position++];
+ if (delimiter.charAt(j) != c) {
+ final Entity.ScannedEntity fCurrentEntity7 = this.fCurrentEntity;
+ fCurrentEntity7.position -= j;
+ break;
+ }
+ }
+ if (this.fCurrentEntity.position == delimOffset + delimLen) {
+ done = true;
+ break;
+ }
+ }
+ else {
+ if (c == 10 || (this.isExternal && c == 13)) {
+ final Entity.ScannedEntity fCurrentEntity8 = this.fCurrentEntity;
+ --fCurrentEntity8.position;
+ break;
+ }
+ if (isInvalid(c)) {
+ if(XMLChar.isHighSurrogate(c)){
+ this.fCurrentEntity.position--;
+ if (this.fCurrentEntity.position - offset > 0){
+ int length2 = this.fCurrentEntity.position - offset;
+ final Entity.ScannedEntity fCurrentEntity10 = this.fCurrentEntity;
+ fCurrentEntity10.columnNumber += length2 - newlines;
+ buffer.append(this.fCurrentEntity.ch, offset, length2);
+ }
+ final int high = this.scanChar();
+ final int low = this.peekChar();
+ if (!XMLChar.isLowSurrogate(low)) {
+ return false;
+ }
+ int x = XMLChar.supplemental((char)high, (char)low);
+ if (isInvalid(x)) {
+ return false;
+ }
+ buffer.append((char)high);
+ buffer.append((char)low);
+ offset = ++this.fCurrentEntity.position;
+ break;
+ }
+ final Entity.ScannedEntity fCurrentEntity9 = this.fCurrentEntity;
+ --fCurrentEntity9.position;
+ final int length2 = this.fCurrentEntity.position - offset;
+ final Entity.ScannedEntity fCurrentEntity10 = this.fCurrentEntity;
+ fCurrentEntity10.columnNumber += length2 - newlines;
+ buffer.append(this.fCurrentEntity.ch, offset, length2);
+ return true;
+ }
+ continue;
+ }
+ }
+ int length2 = this.fCurrentEntity.position - offset;
+ if (length2 != 0){
+ final Entity.ScannedEntity fCurrentEntity11 = this.fCurrentEntity;
+ fCurrentEntity11.columnNumber += length2 - newlines;
+ if (done) {
+ length2 -= delimLen;
+ }
+ buffer.append(this.fCurrentEntity.ch, offset, length2);
+ }
+ } while (!done);
+ return !done;
+ }
+
+ public boolean skipChar(final int c) throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ final int cc = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (cc == c) {
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ ++fCurrentEntity.position;
+ if (c == 10) {
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ ++fCurrentEntity2.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ }
+ else {
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ ++fCurrentEntity3.columnNumber;
+ }
+ return true;
+ }
+ if (c == 10 && cc == 13 && this.isExternal) {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(1);
+ this.fCurrentEntity.ch[0] = (char)cc;
+ this.load(1, false);
+ }
+ final Entity.ScannedEntity fCurrentEntity4 = this.fCurrentEntity;
+ ++fCurrentEntity4.position;
+ if (this.fCurrentEntity.ch[this.fCurrentEntity.position] == '\n') {
+ final Entity.ScannedEntity fCurrentEntity5 = this.fCurrentEntity;
+ ++fCurrentEntity5.position;
+ }
+ final Entity.ScannedEntity fCurrentEntity6 = this.fCurrentEntity;
+ ++fCurrentEntity6.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isSpace(final char ch) {
+ return ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r';
+ }
+
+ public boolean skipSpaces() throws IOException {
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ }
+ if (this.fCurrentEntity == null) {
+ return false;
+ }
+ int c = this.fCurrentEntity.ch[this.fCurrentEntity.position];
+ if (XMLChar.isSpace(c)) {
+ do {
+ boolean entityChanged = false;
+ if (c == 10 || (this.isExternal && c == 13)) {
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ ++fCurrentEntity.lineNumber;
+ this.fCurrentEntity.columnNumber = 1;
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count - 1) {
+ this.invokeListeners(0);
+ this.fCurrentEntity.ch[0] = (char)c;
+ entityChanged = this.load(1, true);
+ if (!entityChanged) {
+ this.fCurrentEntity.position = 0;
+ }
+ else if (this.fCurrentEntity == null) {
+ return true;
+ }
+ }
+ if (c == 13 && this.isExternal && this.fCurrentEntity.ch[++this.fCurrentEntity.position] != '\n') {
+ final Entity.ScannedEntity fCurrentEntity2 = this.fCurrentEntity;
+ --fCurrentEntity2.position;
+ }
+ }
+ else {
+ final Entity.ScannedEntity fCurrentEntity3 = this.fCurrentEntity;
+ ++fCurrentEntity3.columnNumber;
+ }
+ if (!entityChanged) {
+ final Entity.ScannedEntity fCurrentEntity4 = this.fCurrentEntity;
+ ++fCurrentEntity4.position;
+ }
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.invokeListeners(0);
+ this.load(0, true);
+ if (this.fCurrentEntity == null) {
+ return true;
+ }
+ continue;
+ }
+ } while (XMLChar.isSpace(c = this.fCurrentEntity.ch[this.fCurrentEntity.position]));
+ return true;
+ }
+ return false;
+ }
+
+ public boolean arrangeCapacity(final int length) throws IOException {
+ return this.arrangeCapacity(length, false);
+ }
+
+ public boolean arrangeCapacity(final int length, final boolean changeEntity) throws IOException {
+ if (this.fCurrentEntity.count - this.fCurrentEntity.position >= length) {
+ return true;
+ }
+ boolean entityChanged = false;
+ while (this.fCurrentEntity.count - this.fCurrentEntity.position < length) {
+ if (this.fCurrentEntity.ch.length - this.fCurrentEntity.position < length) {
+ this.invokeListeners(0);
+ System.arraycopy(this.fCurrentEntity.ch, this.fCurrentEntity.position, this.fCurrentEntity.ch, 0, this.fCurrentEntity.count - this.fCurrentEntity.position);
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ fCurrentEntity.count -= this.fCurrentEntity.position;
+ this.fCurrentEntity.position = 0;
+ }
+ if (this.fCurrentEntity.count - this.fCurrentEntity.position < length) {
+ final int pos = this.fCurrentEntity.position;
+ this.invokeListeners(pos);
+ entityChanged = this.load(this.fCurrentEntity.count, changeEntity);
+ this.fCurrentEntity.position = pos;
+ if (entityChanged) {
+ break;
+ }
+ continue;
+ }
+ }
+ return this.fCurrentEntity.count - this.fCurrentEntity.position >= length;
+ }
+
+ public boolean skipString(final String s) throws IOException {
+ final int length = s.length();
+ if (this.arrangeCapacity(length, false)) {
+ final int beforeSkip = this.fCurrentEntity.position;
+ int afterSkip = this.fCurrentEntity.position + length - 1;
+ int i = length - 1;
+ while (s.charAt(i--) == this.fCurrentEntity.ch[afterSkip]) {
+ if (afterSkip-- == beforeSkip) {
+ this.fCurrentEntity.position += length;
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ fCurrentEntity.columnNumber += length;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean skipString(final char[] s) throws IOException {
+ final int length = s.length;
+ if (this.arrangeCapacity(length, false)) {
+ int beforeSkip = this.fCurrentEntity.position;
+ final int afterSkip = this.fCurrentEntity.position + length;
+ for (int i = 0; i < length; ++i) {
+ if (this.fCurrentEntity.ch[beforeSkip++] != s[i]) {
+ return false;
+ }
+ }
+ this.fCurrentEntity.position += length;
+ final Entity.ScannedEntity fCurrentEntity = this.fCurrentEntity;
+ fCurrentEntity.columnNumber += length;
+ return true;
+ }
+ return false;
+ }
+
+ final boolean load(final int offset, final boolean changeEntity) throws IOException {
+ this.fCurrentEntity.fTotalCountTillLastLoad += this.fCurrentEntity.fLastCount;
+ final int length = this.fCurrentEntity.mayReadChunks ? (this.fCurrentEntity.ch.length - offset) : 64;
+ final int count = this.fCurrentEntity.reader.read(this.fCurrentEntity.ch, offset, length);
+ boolean entityChanged = false;
+ if (count != -1) {
+ if (count != 0) {
+ this.fCurrentEntity.fLastCount = count;
+ this.fCurrentEntity.count = count + offset;
+ this.fCurrentEntity.position = offset;
+ }
+ }
+ else {
+ this.fCurrentEntity.count = offset;
+ this.fCurrentEntity.position = offset;
+ entityChanged = true;
+ if (changeEntity) {
+ this.fEntityManager.endEntity();
+ if (this.fCurrentEntity == null) {
+ return true;
+ }
+ if (this.fCurrentEntity.position == this.fCurrentEntity.count) {
+ this.load(0, true);
+ }
+ }
+ }
+ return entityChanged;
+ }
+
+ protected Reader createReader(final InputStream inputStream, String encoding, final Boolean isBigEndian) throws IOException {
+ if (encoding == null) {
+ encoding = "UTF-8";
+ }
+ final String ENCODING = encoding.toUpperCase(Locale.ENGLISH);
+ if (ENCODING.equals("UTF-8")) {
+ return new UTF8Reader(inputStream, this.fCurrentEntity.fBufferSize, this.fErrorReporter.getMessageFormatter("http://www.w3.org/TR/1998/REC-xml-19980210"), this.fErrorReporter.getLocale());
+ }
+ if (ENCODING.equals("US-ASCII")) {
+ return new ASCIIReader(inputStream, this.fCurrentEntity.fBufferSize, this.fErrorReporter.getMessageFormatter("http://www.w3.org/TR/1998/REC-xml-19980210"), this.fErrorReporter.getLocale());
+ }
+ if (ENCODING.equals("ISO-10646-UCS-4")) {
+ if (isBigEndian != null) {
+ final boolean isBE = isBigEndian;
+ if (isBE) {
+ return new UCSReader(inputStream, (short)8);
+ }
+ return new UCSReader(inputStream, (short)4);
+ }
+ else {
+ this.fErrorReporter.reportError("http://www.w3.org/TR/1998/REC-xml-19980210", "EncodingByteOrderUnsupported", new Object[] { encoding }, (short)2);
+ }
+ }
+ if (ENCODING.equals("ISO-10646-UCS-2")) {
+ if (isBigEndian != null) {
+ final boolean isBE = isBigEndian;
+ if (isBE) {
+ return new UCSReader(inputStream, (short)2);
+ }
+ return new UCSReader(inputStream, (short)1);
+ }
+ else {
+ this.fErrorReporter.reportError("http://www.w3.org/TR/1998/REC-xml-19980210", "EncodingByteOrderUnsupported", new Object[] { encoding }, (short)2);
+ }
+ }
+ final boolean validIANA = XMLChar.isValidIANAEncoding(encoding);
+ final boolean validJava = XMLChar.isValidJavaEncoding(encoding);
+ if (!validIANA || (this.fAllowJavaEncodings && !validJava)) {
+ this.fErrorReporter.reportError("http://www.w3.org/TR/1998/REC-xml-19980210", "EncodingDeclInvalid", new Object[] { encoding }, (short)2);
+ encoding = "ISO-8859-1";
+ }
+ String javaEncoding = EncodingMap.getIANA2JavaMapping(ENCODING);
+ if (javaEncoding == null) {
+ if (this.fAllowJavaEncodings) {
+ javaEncoding = encoding;
+ }
+ else {
+ this.fErrorReporter.reportError("http://www.w3.org/TR/1998/REC-xml-19980210", "EncodingDeclInvalid", new Object[] { encoding }, (short)2);
+ javaEncoding = "ISO8859_1";
+ }
+ }
+ return new InputStreamReader(inputStream, javaEncoding);
+ }
+
+ protected Object[] getEncodingName(final byte[] b4, final int count) {
+ if (count < 2) {
+ return new Object[] { "UTF-8", null };
+ }
+ final int b5 = b4[0] & 0xFF;
+ final int b6 = b4[1] & 0xFF;
+ if (b5 == 254 && b6 == 255) {
+ return new Object[] { "UTF-16BE", new Boolean(true) };
+ }
+ if (b5 == 255 && b6 == 254) {
+ return new Object[] { "UTF-16LE", new Boolean(false) };
+ }
+ if (count < 3) {
+ return new Object[] { "UTF-8", null };
+ }
+ final int b7 = b4[2] & 0xFF;
+ if (b5 == 239 && b6 == 187 && b7 == 191) {
+ return new Object[] { "UTF-8", null };
+ }
+ if (count < 4) {
+ return new Object[] { "UTF-8", null };
+ }
+ final int b8 = b4[3] & 0xFF;
+ if (b5 == 0 && b6 == 0 && b7 == 0 && b8 == 60) {
+ return new Object[] { "ISO-10646-UCS-4", new Boolean(true) };
+ }
+ if (b5 == 60 && b6 == 0 && b7 == 0 && b8 == 0) {
+ return new Object[] { "ISO-10646-UCS-4", new Boolean(false) };
+ }
+ if (b5 == 0 && b6 == 0 && b7 == 60 && b8 == 0) {
+ return new Object[] { "ISO-10646-UCS-4", null };
+ }
+ if (b5 == 0 && b6 == 60 && b7 == 0 && b8 == 0) {
+ return new Object[] { "ISO-10646-UCS-4", null };
+ }
+ if (b5 == 0 && b6 == 60 && b7 == 0 && b8 == 63) {
+ return new Object[] { "UTF-16BE", new Boolean(true) };
+ }
+ if (b5 == 60 && b6 == 0 && b7 == 63 && b8 == 0) {
+ return new Object[] { "UTF-16LE", new Boolean(false) };
+ }
+ if (b5 == 76 && b6 == 111 && b7 == 167 && b8 == 148) {
+ return new Object[] { "CP037", null };
+ }
+ return new Object[] { "UTF-8", null };
+ }
+
+ final void print() {
+ }
+
+ public void registerListener(final XMLBufferListener listener) {
+ if (!this.listeners.contains(listener)) {
+ this.listeners.add(listener);
+ }
+ }
+
+ private void invokeListeners(final int loadPos) {
+ for (int i = 0; i < this.listeners.size(); ++i) {
+ final XMLBufferListener listener = (XMLBufferListener) this.listeners.get(i);
+ listener.refresh(loadPos);
+ }
+ }
+}
+ * {@code
+ * bitmap.forEach(new IntConsumer() {
+ *
+ * {@literal @}Override
+ * public void accept(int value) {
+ * // do something here
+ *
+ * }});
+ * }
+ * }
+ *
+ *
+ * @param ic the consumer
+ */
+ public void forEach(IntConsumer ic);
+
+ /**
+ * For better performance, consider the Use the {@link #forEach forEach} method.
+ *
+ * @return a custom iterator over set bits, the bits are traversed in ascending sorted order
+ */
+ public PeekableIntIterator getIntIterator();
+
+ /**
+ * @return a custom iterator over set bits, the bits are traversed in descending sorted order
+ */
+ public IntIterator getReverseIntIterator();
+
+ /**
+ * Estimate of the memory usage of this data structure.
+ *
+ * {@code
+ * bitmap.forEach(new IntConsumer() {
+ *
+ * @Override
+ * public void accept(int value) {
+ * // do something here
+ *
+ * }});
+ * }
+ * }
+ *
+ */
+public interface IntConsumer {
+ /**
+ * Receives the integer
+ *
+ * @param value the integer value
+ */
+ void accept(int value);
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntIterator.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntIterator.java
new file mode 100644
index 000000000..ef86f6611
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntIterator.java
@@ -0,0 +1,28 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap;
+
+/**
+ * A simple iterator over integer values
+ */
+public interface IntIterator extends Cloneable {
+ /**
+ * Creates a copy of the iterator.
+ *
+ * @return a clone of the current iterator
+ */
+ IntIterator clone();
+
+ /**
+ * @return whether there is another value
+ */
+ boolean hasNext();
+
+ /**
+ * @return next integer value
+ */
+ int next();
+
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntIteratorFlyweight.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntIteratorFlyweight.java
new file mode 100644
index 000000000..36a91b168
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/IntIteratorFlyweight.java
@@ -0,0 +1,124 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap;
+
+/**
+ * Fast iterator minimizing the stress on the garbage collector. You can create one reusable
+ * instance of this class and then {@link #wrap(RoaringBitmap)}
+ *
+ * {@code
+ * import com.fr.swift.bitmap.roaringbitmap.*;
+ *
+ * //...
+ *
+ * RoaringBitmap rr = RoaringBitmap.bitmapOf(1,2,3,1000);
+ * RoaringBitmap rr2 = new RoaringBitmap();
+ * for(int k = 4000; k<4255;++k) rr2.add(k);
+ * RoaringBitmap rror = RoaringBitmap.or(rr, rr2);
+ *
+ * //...
+ * DataOutputStream wheretoserialize = ...
+ * rr.runOptimize(); // can help compression
+ * rr.serialize(wheretoserialize);
+ * }
+ *
+ *
+ * {@code
+ * //r is your bitmap
+ *
+ * r.runOptimize(); // might improve compression
+ * // next we create the ByteBuffer where the data will be stored
+ * ByteBuffer outbb = ByteBuffer.allocate(r.serializedSizeInBytes());
+ * // then we can serialize on a custom OutputStream
+ * mrb.serialize(new DataOutputStream(new OutputStream(){
+ * ByteBuffer mBB;
+ * OutputStream init(ByteBuffer mbb) {mBB=mbb; return this;}
+ * public void close() {}
+ * public void flush() {}
+ * public void write(int b) {
+ * mBB.put((byte) b);}
+ * public void write(byte[] b) {mBB.put(b);}
+ * public void write(byte[] b, int off, int l) {mBB.put(b,off,l);}
+ * }.init(outbb)));
+ * // outbuff will now contain a serialized version of your bitmap
+ * }
+ *
+ *
+ * {@code
+ * import com.fr.swift.bitmap.roaringbitmap.buffer.*;
+ *
+ * //...
+ *
+ * MutableRoaringBitmap rr1 = MutableRoaringBitmap.bitmapOf(1, 2, 3, 1000);
+ * MutableRoaringBitmap rr2 = MutableRoaringBitmap.bitmapOf( 2, 3, 1010);
+ * ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ * DataOutputStream dos = new DataOutputStream(bos);
+ * // could call "rr1.runOptimize()" and "rr2.runOptimize" if there
+ * // there were runs to compress
+ * rr1.serialize(dos);
+ * rr2.serialize(dos);
+ * dos.close();
+ * ByteBuffer bb = ByteBuffer.wrap(bos.toByteArray());
+ * ImmutableRoaringBitmap rrback1 = new ImmutableRoaringBitmap(bb);
+ * bb.position(bb.position() + rrback1.serializedSizeInBytes());
+ * ImmutableRoaringBitmap rrback2 = new ImmutableRoaringBitmap(bb);
+ * }
+ *
+ *
+ * {@code
+ * (ImmutableRoaringBitmap) MutableRoaringBitmap.bitmapOf(data)
+ * }
+ *
+ *
+ * @param data set values
+ * @return a new bitmap
+ */
+ public static ImmutableRoaringBitmap bitmapOf(final int... data) {
+ return MutableRoaringBitmap.bitmapOf(data);
+ }
+
+ /**
+ * Complements the bits in the given range, from rangeStart (inclusive) rangeEnd (exclusive). The
+ * given bitmap is unchanged.
+ *
+ * @param bm bitmap being negated
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return a new Bitmap
+ */
+ public static MutableRoaringBitmap flip(ImmutableRoaringBitmap bm, final long rangeStart,
+ final long rangeEnd) {
+ MutableRoaringBitmap.rangeSanityCheck(rangeStart, rangeEnd);
+ if (rangeStart >= rangeEnd) {
+ throw new RuntimeException("Invalid range " + rangeStart + " -- " + rangeEnd);
+ }
+
+ MutableRoaringBitmap answer = new MutableRoaringBitmap();
+ final short hbStart = BufferUtil.highbits(rangeStart);
+ final short lbStart = BufferUtil.lowbits(rangeStart);
+ final short hbLast = BufferUtil.highbits(rangeEnd - 1);
+ final short lbLast = BufferUtil.lowbits(rangeEnd - 1);
+
+ // copy the containers before the active area
+ answer.getMappeableRoaringArray().appendCopiesUntil(bm.highLowContainer, hbStart);
+
+ final int max = BufferUtil.toIntUnsigned(BufferUtil.maxLowBit());
+ for (short hb = hbStart; hb <= hbLast; ++hb) {
+ final int containerStart = (hb == hbStart) ? BufferUtil.toIntUnsigned(lbStart) : 0;
+ final int containerLast = (hb == hbLast) ? BufferUtil.toIntUnsigned(lbLast) : max;
+
+ final int i = bm.highLowContainer.getIndex(hb);
+ final int j = answer.getMappeableRoaringArray().getIndex(hb);
+ assert j < 0;
+
+ if (i >= 0) {
+ final MappeableContainer c =
+ bm.highLowContainer.getContainerAtIndex(i).not(containerStart, containerLast + 1);
+ if (c.getCardinality() > 0) {
+ answer.getMappeableRoaringArray().insertNewKeyValueAt(-j - 1, hb, c);
+ }
+
+ } else { // *think* the range of ones must never be
+ // empty.
+ answer.getMappeableRoaringArray().insertNewKeyValueAt(-j - 1, hb,
+ MappeableContainer.rangeOfOnes(containerStart, containerLast + 1));
+ }
+ }
+ // copy the containers after the active area.
+ answer.getMappeableRoaringArray().appendCopiesAfter(bm.highLowContainer, hbLast);
+
+ return answer;
+ }
+
+ /**
+ * Complements the bits in the given range, from rangeStart (inclusive) rangeEnd (exclusive). The
+ * given bitmap is unchanged.
+ *
+ * @param bm bitmap being negated
+ * @param rangeStart inclusive beginning of range
+ * @param rangeEnd exclusive ending of range
+ * @return a new Bitmap
+ * @deprecated use the version where longs specify the range
+ */
+ @Deprecated
+ public static MutableRoaringBitmap flip(ImmutableRoaringBitmap bm,
+ final int rangeStart, final int rangeEnd) {
+ if (rangeStart >= 0) {
+ return flip(bm, (long) rangeStart, (long) rangeEnd);
+ }
+ // rangeStart being -ve and rangeEnd being positive is not expected)
+ // so assume both -ve
+ return flip(bm, rangeStart & 0xFFFFFFFFL, rangeEnd & 0xFFFFFFFFL);
+ }
+
+ /**
+ * Return new iterator with only values from rangeStart (inclusive) to rangeEnd (exclusive)
+ *
+ * @param input bitmaps iterator
+ * @param rangeStart inclusive
+ * @param rangeEnd exclusive
+ * @return new iterator of bitmaps
+ */
+ private static Iterator
+ * {
+ * @code
+ * // r is your bitmap
+ *
+ * // r.runOptimize(); // might improve compression, only if you have a
+ * // MutableRoaringBitmap instance.
+ * // next we create the ByteBuffer where the data will be stored
+ * ByteBuffer outbb = ByteBuffer.allocate(r.serializedSizeInBytes());
+ * // then we can serialize on a custom OutputStream
+ * mrb.serialize(new DataOutputStream(new OutputStream() {
+ * ByteBuffer mBB;
+ *
+ * OutputStream init(ByteBuffer mbb) {
+ * mBB = mbb;
+ * return this;
+ * }
+ *
+ * public void close() {}
+ *
+ * public void flush() {}
+ *
+ * public void write(int b) {
+ * mBB.put((byte) b);
+ * }
+ *
+ * public void write(byte[] b) {
+ * mBB.put(b);
+ * }
+ *
+ * public void write(byte[] b, int off, int l) {
+ * mBB.put(b, off, l);
+ * }
+ * }.init(outbb)));
+ * // outbuff will now contain a serialized version of your bitmap
+ * }
+ *
+ *
+ * {@code
+ * import com.fr.swift.bitmap.roaringbitmap.buffer.*;
+ *
+ * //...
+ *
+ * MutableRoaringBitmap rr = MutableRoaringBitmap.bitmapOf(1,2,3,1000);
+ * MutableRoaringBitmap rr2 = new MutableRoaringBitmap();
+ * for(int k = 4000; k<4255;++k) rr2.add(k);
+ *
+ * RoaringBitmap rror = RoaringBitmap.or(rr, rr2);
+ *
+ * //...
+ * DataOutputStream wheretoserialize = ...
+ * rr.runOptimize(); // can help compression
+ * rr.serialize(wheretoserialize);
+ * }
+ *
+ *
+ * @see ImmutableRoaringBitmap
+ * @see RoaringBitmap
+ */
+public class MutableRoaringBitmap extends ImmutableRoaringBitmap
+ implements Cloneable, Serializable, Iterable
+ * {@code
+ * (ImmutableRoaringBitmap) bitmap
+ * }
+ *
+ *
+ * @return a cast of this object
+ */
+ public ImmutableRoaringBitmap toImmutableRoaringBitmap() {
+ return this;
+
+ }
+
+ /**
+ * Recover allocated but unused memory.
+ */
+ @Override
+ public void trim() {
+ for (int i = 0; i < this.highLowContainer.size(); i++) {
+ this.highLowContainer.getContainerAtIndex(i).trim();
+ }
+ }
+
+ @Override
+ public void writeExternal(ObjectOutput out) throws IOException {
+ getMappeableRoaringArray().writeExternal(out);
+ }
+
+ /**
+ * In-place bitwise XOR (symmetric difference) operation. The current bitmap is modified.
+ *
+ * @param x2 other bitmap
+ */
+ public void xor(final ImmutableRoaringBitmap x2) {
+ int pos1 = 0, pos2 = 0;
+ int length1 = highLowContainer.size();
+ final int length2 = x2.highLowContainer.size();
+
+ main:
+ if (pos1 < length1 && pos2 < length2) {
+ short s1 = highLowContainer.getKeyAtIndex(pos1);
+ short s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+
+ while (true) {
+ if (s1 == s2) {
+ final MappeableContainer c = highLowContainer.getContainerAtIndex(pos1)
+ .ixor(x2.highLowContainer.getContainerAtIndex(pos2));
+ if (c.getCardinality() > 0) {
+ this.getMappeableRoaringArray().setContainerAtIndex(pos1, c);
+ pos1++;
+ } else {
+ getMappeableRoaringArray().removeAtIndex(pos1);
+ --length1;
+ }
+ pos2++;
+ if ((pos1 == length1) || (pos2 == length2)) {
+ break main;
+ }
+ s1 = highLowContainer.getKeyAtIndex(pos1);
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ } else if (Util.compareUnsigned(s1, s2) < 0) { // s1 < s2
+ pos1++;
+ if (pos1 == length1) {
+ break main;
+ }
+ s1 = highLowContainer.getKeyAtIndex(pos1);
+ } else { // s1 > s2
+ getMappeableRoaringArray().insertNewKeyValueAt(pos1, s2,
+ x2.highLowContainer.getContainerAtIndex(pos2).clone());
+ pos1++;
+ length1++;
+ pos2++;
+ if (pos2 == length2) {
+ break main;
+ }
+ s2 = x2.highLowContainer.getKeyAtIndex(pos2);
+ }
+ }
+ }
+ if (pos1 == length1) {
+ getMappeableRoaringArray().appendCopy(x2.highLowContainer, pos2, length2);
+ }
+ }
+
+
+}
diff --git a/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/PointableRoaringArray.java b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/PointableRoaringArray.java
new file mode 100644
index 000000000..c9cd8fa7d
--- /dev/null
+++ b/fine-roaringbitmap/src/com/fr/third/bitmap/roaringbitmap/buffer/PointableRoaringArray.java
@@ -0,0 +1,103 @@
+/*
+ * (c) the authors Licensed under the Apache License, Version 2.0.
+ */
+
+package com.fr.third.bitmap.roaringbitmap.buffer;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * Generic interface for the array underlying roaring bitmap classes.
+ */
+public interface PointableRoaringArray extends Cloneable {
+ /**
+ * Find the smallest integer index larger than pos such that getKeyAtIndex(index)>=x. If none
+ * can be found, return size.
+ *
+ * @param x minimal value
+ * @param pos index to exceed
+ * @return the smallest index greater than pos such that getKeyAtIndex(index) is at least as large
+ * as min, or size if it is not possible.
+ */
+ int advanceUntil(short x, int pos);
+
+ /**
+ * Create an independent copy of the underlying array
+ *
+ * @return a copy
+ */
+ PointableRoaringArray clone();
+
+ /**
+ * Returns the cardinality of the container at the given index. This method is expected to be
+ * fast.
+ *
+ * @param i index
+ * @return the cardinality
+ */
+ public int getCardinality(int i);
+
+ /**
+ * @param x 16-bit key
+ * @return matching container
+ */
+ MappeableContainer getContainer(short x);
+
+ /**
+ * @param i index
+ * @return matching container
+ */
+ MappeableContainer getContainerAtIndex(int i);
+
+ /**
+ * @return a ContainerPointer to iterator over the array
+ */
+ MappeableContainerPointer getContainerPointer();
+
+ /**
+ * @param startIndex starting index
+ * @return a ContainerPointer to iterator over the array initially positioned at startIndex
+ */
+ MappeableContainerPointer getContainerPointer(int startIndex);
+
+ /**
+ * @param x 16-bit key
+ * @return corresponding index
+ */
+ int getIndex(short x);
+
+ /**
+ * @param i the index
+ * @return 16-bit key at the index
+ */
+ short getKeyAtIndex(int i);
+
+ /**
+ * Check whether this bitmap has had its runs compressed.
+ *
+ * @return whether this bitmap has run compression
+ */
+ public boolean hasRunCompression();
+
+ /**
+ * Serialize.
+ *