All Downloads are FREE. Search and download functionalities are using the official Maven repository.

uk.co.openkappa.roaringbitmap.buffer.MappeableArrayContainer Maven / Gradle / Ivy

There is a newer version: 0.1.7
Show newest version
/*
 * (c) the authors Licensed under the Apache License, Version 2.0.
 */

package org.roaringbitmap.buffer;

import org.roaringbitmap.*;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.Arrays;
import java.util.Iterator;

import static org.roaringbitmap.buffer.BufferUtil.toIntUnsigned;

/**
 * Simple container made of an array of 16-bit integers. Unlike org.roaringbitmap.ArrayContainer,
 * this class uses a ShortBuffer to store data.
 */
public final class MappeableArrayContainer extends MappeableContainer implements Cloneable {
  private static final int DEFAULT_INIT_SIZE = 4;
  private static final int ARRAY_LAZY_LOWERBOUND = 1024;
  protected static final int DEFAULT_MAX_SIZE = 4096; // containers with DEFAULT_MAX_SZE or less
                                                      // integers should be ArrayContainers

  private static final long serialVersionUID = 1L;

  protected static int getArraySizeInBytes(int cardinality) {
    return cardinality * 2;
  }

  protected static int serializedSizeInBytes(int cardinality) {
    return cardinality * 2 + 2;
  }

  protected int cardinality = 0;


  protected ShortBuffer content;



  /**
   * Create an array container with default capacity
   */
  public MappeableArrayContainer() {
    this(DEFAULT_INIT_SIZE);
  }

  public static MappeableArrayContainer empty() {
    return new MappeableArrayContainer();
  }

  /**
   * 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;
  }

  @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;
  }

  @Override
  public boolean isEmpty() {
    return cardinality == 0;
  }

  private int advance(ShortIterator it) {
    if (it.hasNext()) {
      return 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 = org.roaringbitmap.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 =
          org.roaringbitmap.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];
        sarray[pos] = v;
        pos += 1 - value2.bitValue(v);
      }
    } else {
      for (int k = 0; k < cardinality; ++k) {
        short v = this.content.get(k);
        sarray[pos] = v;
        pos += 1 - value2.bitValue(v);
      }
    }
    answer.cardinality = pos;
    return answer;
  }

  @Override
  public MappeableContainer andNot(final MappeableRunContainer x) {
    if (x.numberOfRuns() == 0) {
      return clone();
    } else if (x.isFull()) {
      return MappeableArrayContainer.empty();
    }
    int write = 0;
    int read = 0;
    MappeableArrayContainer answer = new MappeableArrayContainer(cardinality);
    for (int i = 0; i < x.numberOfRuns() && read < cardinality; ++i) {
      int runStart = BufferUtil.toIntUnsigned(x.getValue(i));
      int runEnd = runStart + BufferUtil.toIntUnsigned(x.getLength(i));
      if (BufferUtil.toIntUnsigned(content.get(read)) > runEnd) {
        continue;
      }
      int firstInRun = BufferUtil.iterateUntil(content, read, cardinality, runStart);
      int toWrite = firstInRun - read;
      BufferUtil.arraycopy(content, read, answer.content, write, toWrite);
      write += toWrite;

      read = BufferUtil.iterateUntil(content, firstInRun, cardinality, runEnd + 1);
    }
    BufferUtil.arraycopy(content, read, answer.content, write, cardinality - read);
    write += cardinality - read;
    answer.cardinality = write;
    return answer;

  }

  @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;
  }

  /**
   * Checks whether the container contains the value x.
   * 
   * @param buf underlying buffer
   * @param position starting position of the container in the ByteBuffer
   * @param x target value x
   * @param cardinality container cardinality
   * @return whether the container contains the value x
   */
  public static boolean contains(ByteBuffer buf, int position, final short x, int cardinality) {
    return BufferUtil.unsignedBinarySearch(buf, position, 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] = toIntUnsigned(c[k]) | mask;
      }

    } else {
      for (int k = 0; k < this.cardinality; ++k) {
        x[k + i] = 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 ContainerBatchIterator getBatchIterator() {
    return new ArrayBatchIterator(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);
    }
    /*
     * b - index of begin(indexstart), e - index of end(indexend), |--| is current sequential
     * indexes in content. Total 6 cases are possible, listed as below:
     * 
     * case-1) |--------|b-e case-2) |----b---|e case-3) |---b---e---| case-4) b|----e---| case-5)
     * b-e|------| case-6) b|-----|e
     * 
     * In case of old approach, we did (1a) Array.copyOf in increaseCapacity ( # of elements copied
     * -> cardinality), (1b) then we moved elements using System.arrayCopy ( # of elements copied ->
     * cardinality -indexend), (1c) then we set all elements from begin to end ( # of elements set
     * -> end - begin)
     * 
     * With new approach, (2a) we set all elements from begin to end ( # of elements set -> end-
     * begin), (2b) we only copy elements in current set which are not in range begin-end ( # of
     * elements copied -> cardinality - (end-begin) )
     * 
     * why is it faster? Logically we are doing less # of copies. Mathematically proof as below: ->
     * 2a is same as 1c, so we can avoid. Assume, 2b < (1a+1b), lets prove this assumption.
     * Substitute the values. (cardinality - (end-begin)) < ( 2*cardinality - indexend) , lowest
     * possible value of indexend is 0 and equation holds true , hightest possible value of indexend
     * is cardinality and equation holds true , hence "<" equation holds true always
     */
    if (newcardinality >= this.content.limit()) {
      ShortBuffer destination = ShortBuffer.allocate(newcardinality);
      BufferUtil.arraycopy(content, 0, destination, 0, indexstart);
      if (BufferUtil.isBackedBySimpleArray(content)) {
        short[] destinationarray = destination.array();
        for (int k = 0; k < rangelength; ++k) {
          destinationarray[k + indexstart] = (short) (begin + k);
        }
      } else {
        for (int k = 0; k < rangelength; ++k) {
          destination.put(k + indexstart, (short) (begin + k));
        }
      }
      BufferUtil.arraycopy(content, indexend, destination, indexstart + rangelength, cardinality
          - indexend);
      this.content = destination;
    } else {
      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);
      this.content.put(pos, v);
      pos += value2.bitValue(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 =
          org.roaringbitmap.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];
      c[pos] = v;
      pos += 1 - value2.bitValue(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) {
    final ShortBuffer newContent = ShortBuffer.allocate(calculateCapacity(min));
    this.content.rewind();
    newContent.put(this.content);
    this.content = newContent;
  }
  
  private int calculateCapacity(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;
    }
    return newCapacity;
  }

  @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) {
    final int totalCardinality = getCardinality() + value2.getCardinality();
    if (totalCardinality > DEFAULT_MAX_SIZE) {// it could be a bitmap!
      final MappeableBitmapContainer bc = new MappeableBitmapContainer();
      if (!BufferUtil.isBackedBySimpleArray(bc.bitmap)) {
        throw new RuntimeException("Should not happen. Internal bug.");
      }
      long[] bitArray = bc.bitmap.array();
      if (BufferUtil.isBackedBySimpleArray(value2.content)) {
        short[] sarray = value2.content.array();
        for (int k = 0; k < value2.cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      } else {
        for (int k = 0; k < value2.cardinality; ++k) {
          short v2 = value2.content.get(k);
          final int i = toIntUnsigned(v2) >>> 6;
          bitArray[i] |= (1L << v2);
        }
      }
      if (BufferUtil.isBackedBySimpleArray(content)) {
        short[] sarray = content.array();
        for (int k = 0; k < cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      } else {
        for (int k = 0; k < cardinality; ++k) {
          short v = content.get(k);
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      }
      bc.cardinality = 0;
      int len = bc.bitmap.limit();
      for (int index = 0; index < len; ++index) {
        bc.cardinality += Long.bitCount(bitArray[index]);
      }
      if (bc.cardinality <= DEFAULT_MAX_SIZE) {
        return bc.toArrayContainer();
      } else if (bc.isFull()) {
        return MappeableRunContainer.full();
      }
      return bc;
    }
    if (totalCardinality >= content.limit()) {
      int newCapacity = calculateCapacity(totalCardinality);
      ShortBuffer destination = ShortBuffer.allocate(newCapacity);

      if (BufferUtil.isBackedBySimpleArray(content)
          && BufferUtil.isBackedBySimpleArray(value2.content)) {
        cardinality =
            Util.unsignedUnion2by2(content.array(), 0, cardinality, value2.content.array(), 0,
                value2.cardinality, destination.array());
      } else {
        cardinality =
            BufferUtil.unsignedUnion2by2(content, 0, cardinality, value2.content, 0,
                value2.cardinality, destination.array());
      }
      this.content = destination;
    } else {
      BufferUtil.arraycopy(content, 0, content, value2.cardinality, cardinality);
      
      if (BufferUtil.isBackedBySimpleArray(content)
          && BufferUtil.isBackedBySimpleArray(value2.content)) {
        cardinality =
            Util.unsignedUnion2by2(content.array(), value2.cardinality, cardinality,
                value2.content.array(), 0, value2.cardinality, content.array());
      } else {
        cardinality =
            BufferUtil.unsignedUnion2by2(content, value2.cardinality, cardinality, value2.content,
                0, value2.cardinality, content.array());
      }
    }
    return this;
  }

  @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 iterator() {

    return new Iterator() {
      short pos = 0;

      @Override
      public boolean hasNext() {
        return pos < MappeableArrayContainer.this.cardinality;
      }

      @Override
      public Short next() {
        return MappeableArrayContainer.this.content.get(pos++);
      }

      @Override
      public void remove() {
        MappeableArrayContainer.this.removeAtIndex(pos - 1);
        pos--;
      }
    };
  }

  @Override
  public MappeableContainer ixor(final MappeableArrayContainer value2) {
    return this.xor(value2);
  }


  @Override
  public MappeableContainer ixor(MappeableBitmapContainer x) {
    return x.xor(this);
  }

  @Override
  public MappeableContainer ixor(final MappeableRunContainer value2) {
    return value2.xor(this);
  }

  @Override
  public MappeableContainer limit(int maxcardinality) {
    if (maxcardinality < this.getCardinality()) {
      return new MappeableArrayContainer(maxcardinality, this.content);
    } else {
      return clone();
    }
  }


  protected void loadData(final MappeableBitmapContainer bitmapContainer) {
    this.cardinality = bitmapContainer.cardinality;
    if (!BufferUtil.isBackedBySimpleArray(this.content)) {
      throw new RuntimeException("Should not happen. Internal bug.");
    }
    bitmapContainer.fillArray(content.array());
  }

  // for use in inot range known to be nonempty
  private void negateRange(final ShortBuffer buffer, final int startIndex, final int lastIndex,
      final int startRange, final int lastRange) {
    // compute the negation into buffer

    int outPos = 0;
    int inPos = startIndex; // value here always >= valInRange,
    // until it is exhausted
    // n.b., we can start initially exhausted.

    int valInRange = startRange;
    for (; valInRange < lastRange && inPos <= lastIndex; ++valInRange) {
      if ((short) valInRange != content.get(inPos)) {
        buffer.put(outPos++, (short) valInRange);
      } else {
        ++inPos;
      }
    }

    // if there are extra items (greater than the biggest
    // pre-existing one in range), buffer them
    for (; valInRange < lastRange; ++valInRange) {
      buffer.put(outPos++, (short) valInRange);
    }

    if (outPos != buffer.limit()) {
      throw new RuntimeException(
          "negateRange: outPos " + outPos + " whereas buffer.length=" + buffer.limit());
    }
    assert outPos == buffer.limit();
    // copy back from buffer...caller must ensure there is room
    int i = startIndex;
    int len = buffer.limit();
    for (int k = 0; k < len; ++k) {
      final short item = buffer.get(k);
      content.put(i++, item);
    }
  }

  // shares lots of code with inot; candidate for refactoring
  @Override
  public MappeableContainer not(final int firstOfRange, final int lastOfRange) {
    // TODO: may need to convert to a RunContainer
    // TODO: this can be optimized for performance
    if (firstOfRange >= lastOfRange) {
      return clone(); // empty range
    }

    // 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 - 2;
    }
    final int currentValuesInRange = lastIndex - startIndex + 1;
    final int spanToBeFlipped = lastOfRange - firstOfRange;
    final int newValuesInRange = spanToBeFlipped - currentValuesInRange;
    final int cardinalityChange = newValuesInRange - currentValuesInRange;
    final int newCardinality = cardinality + cardinalityChange;

    if (newCardinality > DEFAULT_MAX_SIZE) {
      return toBitmapContainer().not(firstOfRange, lastOfRange);
    }

    final MappeableArrayContainer answer = new MappeableArrayContainer(newCardinality);
    if (!BufferUtil.isBackedBySimpleArray(answer.content)) {
      throw new RuntimeException("Should not happen. Internal bug.");
    }
    short[] sarray = answer.content.array();

    for (int i = 0; i < startIndex; ++i) {
      // copy stuff before the active area
      sarray[i] = content.get(i);
    }

    int outPos = startIndex;
    int inPos = startIndex; // item at inPos always >= valInRange

    int valInRange = firstOfRange;
    for (; valInRange < lastOfRange && inPos <= lastIndex; ++valInRange) {
      if ((short) valInRange != content.get(inPos)) {
        sarray[outPos++] = (short) valInRange;
      } else {
        ++inPos;
      }
    }

    for (; valInRange < lastOfRange; ++valInRange) {
      answer.content.put(outPos++, (short) valInRange);
    }

    // content after the active range
    for (int i = lastIndex + 1; i < cardinality; ++i) {
      answer.content.put(outPos++, content.get(i));
    }
    answer.cardinality = newCardinality;
    return answer;
  }

  @Override
  int numberOfRuns() {
    if (cardinality == 0) {
      return 0; // should never happen
    }

    if (BufferUtil.isBackedBySimpleArray(content)) {
      short[] c = content.array();
      int numRuns = 1;
      int oldv = toIntUnsigned(c[0]);
      for (int i = 1; i < cardinality; i++) {
        int newv = toIntUnsigned(c[i]);
        if (oldv + 1 != newv) {
          ++numRuns;
        }
        oldv = newv;
      }
      return numRuns;
    } else {
      int numRuns = 1;
      int previous = toIntUnsigned(content.get(0));
      // we do not proceed like above for fear that calling "get" twice per loop would be too much
      for (int i = 1; i < cardinality; i++) {
        int val = toIntUnsigned(content.get(i));
        if (val != previous + 1) {
          ++numRuns;
        }
        previous = val;
      }
      return numRuns;
    }
  }


  @Override
  public MappeableContainer or(final MappeableArrayContainer value2) {
    final MappeableArrayContainer value1 = this;
    final int totalCardinality = value1.getCardinality() + value2.getCardinality();
    if (totalCardinality > DEFAULT_MAX_SIZE) {// it could be a bitmap!
      final MappeableBitmapContainer bc = new MappeableBitmapContainer();
      if (!BufferUtil.isBackedBySimpleArray(bc.bitmap)) {
        throw new RuntimeException("Should not happen. Internal bug.");
      }
      long[] bitArray = bc.bitmap.array();
      if (BufferUtil.isBackedBySimpleArray(value2.content)) {
        short[] sarray = value2.content.array();
        for (int k = 0; k < value2.cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      } else {
        for (int k = 0; k < value2.cardinality; ++k) {
          short v2 = value2.content.get(k);
          final int i = toIntUnsigned(v2) >>> 6;
          bitArray[i] |= (1L << v2);
        }
      }
      if (BufferUtil.isBackedBySimpleArray(this.content)) {
        short[] sarray = this.content.array();
        for (int k = 0; k < this.cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      } else {
        for (int k = 0; k < this.cardinality; ++k) {
          short v = this.content.get(k);
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      }
      bc.cardinality = 0;
      int len = bc.bitmap.limit();
      for (int index = 0; index < len; ++index) {
        bc.cardinality += Long.bitCount(bitArray[index]);
      }
      if (bc.cardinality <= DEFAULT_MAX_SIZE) {
        return bc.toArrayContainer();
      } else if (bc.isFull()) {
        return MappeableRunContainer.full();
      }
      return bc;
    }
    final MappeableArrayContainer answer = new MappeableArrayContainer(totalCardinality);
    if (BufferUtil.isBackedBySimpleArray(value1.content)
        && BufferUtil.isBackedBySimpleArray(value2.content)) {
      answer.cardinality =
          Util.unsignedUnion2by2(
                  value1.content.array(), 0, value1.getCardinality(),
                  value2.content.array(), 0, value2.getCardinality(),
                  answer.content.array()
          );
    } else {
      answer.cardinality =
              BufferUtil.unsignedUnion2by2(
                      value1.content, 0, value1.getCardinality(),
                      value2.content, 0, value2.getCardinality(),
                      answer.content.array()
              );
    }
    return answer;
  }

  protected MappeableContainer lazyor(final MappeableArrayContainer value2) {
    final MappeableArrayContainer value1 = this;
    final int totalCardinality = value1.getCardinality() + value2.getCardinality();
    if (totalCardinality > ARRAY_LAZY_LOWERBOUND) {// it could be a bitmap!
      final MappeableBitmapContainer bc = new MappeableBitmapContainer();
      if (!BufferUtil.isBackedBySimpleArray(bc.bitmap)) {
        throw new RuntimeException("Should not happen. Internal bug.");
      }
      long[] bitArray = bc.bitmap.array();
      if (BufferUtil.isBackedBySimpleArray(value2.content)) {
        short[] sarray = value2.content.array();
        for (int k = 0; k < value2.cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      } else {
        for (int k = 0; k < value2.cardinality; ++k) {
          short v2 = value2.content.get(k);
          final int i = toIntUnsigned(v2) >>> 6;
          bitArray[i] |= (1L << v2);
        }
      }
      if (BufferUtil.isBackedBySimpleArray(this.content)) {
        short[] sarray = this.content.array();
        for (int k = 0; k < this.cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      } else {
        for (int k = 0; k < this.cardinality; ++k) {
          short v = this.content.get(k);
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] |= (1L << v);
        }
      }
      bc.cardinality = -1;
      return bc;
    }
    final MappeableArrayContainer answer = new MappeableArrayContainer(totalCardinality);
    if (BufferUtil.isBackedBySimpleArray(value1.content)
        && BufferUtil.isBackedBySimpleArray(value2.content)) {
      answer.cardinality =
              Util.unsignedUnion2by2(
                      value1.content.array(), 0, value1.getCardinality(),
                      value2.content.array(), 0, value2.getCardinality(),
                      answer.content.array()
              );
    } else {
      answer.cardinality =
              BufferUtil.unsignedUnion2by2(
                      value1.content, 0, value1.getCardinality(),
                      value2.content, 0, value2.getCardinality(),
                      answer.content.array()
              );
    }
    return answer;
  }

  
  @Override
  public MappeableContainer or(MappeableBitmapContainer x) {
    return x.or(this);
  }

  @Override
  public MappeableContainer or(final MappeableRunContainer value2) {
    return value2.or(this);
  }

  protected MappeableContainer or(ShortIterator it) {
    return or(it, false);
  }

  /**
   * it must return items in (unsigned) sorted order. Possible candidate for Container interface?
   **/
  private MappeableContainer or(ShortIterator it, boolean exclusive) {
    MappeableArrayContainer ac = new MappeableArrayContainer();
    int myItPos = 0;
    ac.cardinality = 0;
    // do a merge. int -1 denotes end of input.
    int myHead = (myItPos == cardinality) ? -1 : toIntUnsigned(content.get(myItPos++));
    int hisHead = advance(it);

    while (myHead != -1 && hisHead != -1) {
      if (myHead < hisHead) {
        ac.emit((short) myHead);
        myHead = (myItPos == cardinality) ? -1 : toIntUnsigned(content.get(myItPos++));
      } else if (myHead > hisHead) {
        ac.emit((short) hisHead);
        hisHead = advance(it);
      } else {
        if (!exclusive) {
          ac.emit((short) hisHead);
        }
        hisHead = advance(it);
        myHead = (myItPos == cardinality) ? -1 : toIntUnsigned(content.get(myItPos++));
      }
    }

    while (myHead != -1) {
      ac.emit((short) myHead);
      myHead = (myItPos == cardinality) ? -1 : toIntUnsigned(content.get(myItPos++));
    }

    while (hisHead != -1) {
      ac.emit((short) hisHead);
      hisHead = advance(it);
    }

    if (ac.cardinality > DEFAULT_MAX_SIZE) {
      return ac.toBitmapContainer();
    } else {
      return ac;
    }
  }

  @Override
  public int rank(short lowbits) {
    int answer = BufferUtil.unsignedBinarySearch(content, 0, cardinality, lowbits);
    if (answer >= 0) {
      return answer + 1;
    } else {
      return -answer - 1;
    }
  }

  @Override
  public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    // little endian
    this.cardinality = 0xFFFF & Short.reverseBytes(in.readShort());
    if (this.content.limit() < this.cardinality) {
      this.content = ShortBuffer.allocate(this.cardinality);
    }
    for (int k = 0; k < this.cardinality; ++k) {
      this.content.put(k, Short.reverseBytes(in.readShort()));
    }
  }

  @Override
  public MappeableContainer remove(int begin, int end) {
    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 = indexend - indexstart;
    MappeableArrayContainer answer = clone();
    BufferUtil.arraycopy(content, indexstart + rangelength, answer.content, indexstart,
        cardinality - indexstart - rangelength);
    answer.cardinality = cardinality - rangelength;
    return answer;
  }
  
  protected void removeAtIndex(final int loc) {
    System.arraycopy(content.array(), loc + 1, content.array(), loc, cardinality - loc - 1);
    --cardinality;    
  }


  @Override
  public MappeableContainer remove(final short x) {
    if (BufferUtil.isBackedBySimpleArray(this.content)) {
      final int loc = Util.unsignedBinarySearch(content.array(), 0, cardinality, x);
      if (loc >= 0) {
        removeAtIndex(loc);
      }
      return this;
    } else {
      final int loc = BufferUtil.unsignedBinarySearch(content, 0, cardinality, x);
      if (loc >= 0) {
        // insertion
        for (int k = loc + 1; k < cardinality; --k) {
          content.put(k - 1, content.get(k));
        }
        --cardinality;
      }
      return this;

    }
  }

  @Override
  public MappeableContainer repairAfterLazy() {
    return this;
  }


  @Override
  public MappeableContainer runOptimize() {
    int numRuns = numberOfRuns();
    int sizeAsRunContainer = MappeableRunContainer.getArraySizeInBytes(numRuns);
    if (getArraySizeInBytes() > sizeAsRunContainer) {
      return new MappeableRunContainer(this, numRuns); // this could be
                                                       // maybe faster if
                                                       // initial
                                                       // container is a
                                                       // bitmap
    } else {
      return this;
    }
  }

  @Override
  public short select(int j) {
    return this.content.get(j);
  }

  @Override
  public int serializedSizeInBytes() {
    return serializedSizeInBytes(cardinality);
  }

  /**
   * Copies the data in a bitmap container.
   *
   * @return the bitmap container
   */
  @Override
  public MappeableBitmapContainer toBitmapContainer() {
    final MappeableBitmapContainer bc = new MappeableBitmapContainer();
    bc.loadData(this);
    return bc;
  }

  @Override
  public int first() {
    assertNonEmpty(cardinality == 0);
    return toIntUnsigned(this.select(0));
  }

  @Override
  public int last() {
    assertNonEmpty(cardinality == 0);
    return toIntUnsigned(select(cardinality - 1));
  }

  @Override
  public Container toContainer() {
    return new ArrayContainer(this);
  }

  /**
   * Create a copy of the content of this container as a short array. This creates a copy.
   * 
   * @return copy of the content as a short array
   */
  public short[] toShortArray() {
    short[] answer = new short[cardinality];
    content.rewind();
    content.get(answer);
    return answer;
  }

  @Override
  public String toString() {
    if (this.cardinality == 0) {
      return "{}";
    }
    final StringBuilder sb = new StringBuilder();
    sb.append("{");
    for (int i = 0; i < this.cardinality - 1; i++) {
      sb.append(toIntUnsigned(this.content.get(i)));
      sb.append(",");
    }
    sb.append(toIntUnsigned(this.content.get(this.cardinality - 1)));
    sb.append("}");
    return sb.toString();
  }

  @Override
  public void trim() {
    if (this.content.limit() == this.cardinality) {
      return;
    }
    if (BufferUtil.isBackedBySimpleArray(content)) {
      this.content = ShortBuffer.wrap(Arrays.copyOf(content.array(), cardinality));
    } else {
      final ShortBuffer co = ShortBuffer.allocate(this.cardinality);
      // can assume that new co is array backed
      short[] x = co.array();
      for (int k = 0; k < this.cardinality; ++k) {
        x[k] = this.content.get(k);
      }
      this.content = co;
    }
  }

  @Override
  protected void writeArray(DataOutput out) throws IOException {
    // little endian
    if (BufferUtil.isBackedBySimpleArray(content)) {
      short[] a = content.array();
      for (int k = 0; k < this.cardinality; ++k) {
        out.writeShort(Short.reverseBytes(a[k]));
      }
    } else {
      for (int k = 0; k < this.cardinality; ++k) {
        out.writeShort(Short.reverseBytes(content.get(k)));
      }
    }
  }

  @Override
  public void writeExternal(ObjectOutput out) throws IOException {
    out.write(this.cardinality & 0xFF);
    out.write((this.cardinality >>> 8) & 0xFF);
    if (BufferUtil.isBackedBySimpleArray(content)) {
      short[] a = content.array();
      for (int k = 0; k < this.cardinality; ++k) {
        out.writeShort(Short.reverseBytes(a[k]));
      }
    } else {
      for (int k = 0; k < this.cardinality; ++k) {
        out.writeShort(Short.reverseBytes(content.get(k)));
      }
    }
  }

  @Override
  public MappeableContainer xor(final MappeableArrayContainer value2) {
    final MappeableArrayContainer value1 = this;
    final int totalCardinality = value1.getCardinality() + value2.getCardinality();
    if (totalCardinality > DEFAULT_MAX_SIZE) {// it could be a bitmap!
      final MappeableBitmapContainer bc = new MappeableBitmapContainer();
      if (!BufferUtil.isBackedBySimpleArray(bc.bitmap)) {
        throw new RuntimeException("Should not happen. Internal bug.");
      }
      long[] bitArray = bc.bitmap.array();
      if (BufferUtil.isBackedBySimpleArray(value2.content)) {
        short[] sarray = value2.content.array();
        for (int k = 0; k < value2.cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] ^= (1L << v);
        }
      } else {
        for (int k = 0; k < value2.cardinality; ++k) {
          short v2 = value2.content.get(k);
          final int i = toIntUnsigned(v2) >>> 6;
          bitArray[i] ^= (1L << v2);
        }
      }
      if (BufferUtil.isBackedBySimpleArray(this.content)) {
        short[] sarray = this.content.array();
        for (int k = 0; k < this.cardinality; ++k) {
          short v = sarray[k];
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] ^= (1L << v);
        }
      } else {
        for (int k = 0; k < this.cardinality; ++k) {
          short v = this.content.get(k);
          final int i = toIntUnsigned(v) >>> 6;
          bitArray[i] ^= (1L << v);
        }
      }

      bc.cardinality = 0;
      int len = bc.bitmap.limit();
      for (int index = 0; index < len; ++index) {
        bc.cardinality += Long.bitCount(bitArray[index]);
      }
      if (bc.cardinality <= DEFAULT_MAX_SIZE) {
        return bc.toArrayContainer();
      }
      return bc;
    }
    final MappeableArrayContainer answer = new MappeableArrayContainer(totalCardinality);
    if (BufferUtil.isBackedBySimpleArray(value1.content)
        && BufferUtil.isBackedBySimpleArray(value2.content)) {
      answer.cardinality = org.roaringbitmap.Util.unsignedExclusiveUnion2by2(value1.content.array(),
          value1.getCardinality(), value2.content.array(), value2.getCardinality(),
          answer.content.array());
    } else {
      answer.cardinality = BufferUtil.unsignedExclusiveUnion2by2(value1.content,
          value1.getCardinality(), value2.content, value2.getCardinality(), answer.content.array());
    }
    return answer;
  }

  @Override
  public MappeableContainer xor(MappeableBitmapContainer x) {
    return x.xor(this);
  }

  @Override
  public MappeableContainer xor(final MappeableRunContainer value2) {
    return value2.xor(this);
  }


  protected MappeableContainer xor(ShortIterator it) {
    return or(it, true);
  }

  @Override
  public void forEach(short msb, IntConsumer ic) {
    int high = ((int) msb) << 16;
    if (BufferUtil.isBackedBySimpleArray(content)) {
      short[] c = content.array();
      for (int k = 0; k < cardinality; ++k) {
        ic.accept((c[k] & 0xFFFF) | high);
      }
    } else {
      for (int k = 0; k < cardinality; ++k) {
        ic.accept((content.get(k) & 0xFFFF) | high);
      }
    }
  }

  @Override
  public int andCardinality(MappeableArrayContainer value2) {
    if (BufferUtil.isBackedBySimpleArray(content)
        && BufferUtil.isBackedBySimpleArray(value2.content)) {
      return Util.unsignedLocalIntersect2by2Cardinality(content.array(), cardinality,
          value2.content.array(), value2.getCardinality());
    }
    return BufferUtil.unsignedLocalIntersect2by2Cardinality(content, cardinality, 
        value2.content, value2.getCardinality());
  }

  @Override
  public int andCardinality(MappeableBitmapContainer x) {
    return x.andCardinality(this);
  }

  @Override
  // see andNot for an approach that might be better.
  public int andCardinality(MappeableRunContainer x) {
    return x.andCardinality(this);
  }

  @Override
  protected boolean contains(MappeableRunContainer runContainer) {
    if (runContainer.getCardinality() > cardinality) {
      return false;
    }
    int startPos, stopPos = -1;
    for (int i = 0; i < runContainer.numberOfRuns(); ++i) {
      short start = runContainer.getValue(i);
      int stop = toIntUnsigned(start) + toIntUnsigned(runContainer.getLength(i));
      startPos = BufferUtil.advanceUntil(content, stopPos, cardinality, start);
      stopPos = BufferUtil.advanceUntil(content, stopPos, cardinality, (short)stop);
      if(startPos == cardinality) {
        return false;
      } else if(stopPos - startPos != stop - start
                || content.get(startPos) != start
                || content.get(stopPos) != stop) {
        return false;
      }
    }
    return true;
  }

  @Override
  protected boolean contains(MappeableArrayContainer arrayContainer) {
    if (cardinality < arrayContainer.cardinality) {
      return false;
    }
    int i1 = 0, i2 = 0;
    while(i1 < cardinality && i2 < arrayContainer.cardinality) {
      if(content.get(i1) == arrayContainer.content.get(i2)) {
        ++i1;
        ++i2;
      } else if(BufferUtil.compareUnsigned(content.get(i1), arrayContainer.content.get(i2)) < 0) {
        ++i1;
      } else {
        return false;
      }
    }
    return i2 == arrayContainer.cardinality;
  }

  @Override
  protected boolean contains(MappeableBitmapContainer bitmapContainer) {
    return false;
  }

  @Override
  public boolean intersects(int minimum, int supremum) {
    int pos = BufferUtil.unsignedBinarySearch(content, 0, cardinality, (short)minimum);
    int index = pos >= 0 ? pos : -pos - 1;
    return index < cardinality && BufferUtil.toIntUnsigned(content.get(index)) < supremum;
  }

  @Override
  public boolean contains(int minimum, int supremum) {
    int maximum = supremum - 1;
    int start = BufferUtil.advanceUntil(content, -1, cardinality, (short)minimum);
    int end = BufferUtil.advanceUntil(content, start - 1, cardinality, (short)maximum);
    return start < cardinality
            && end < cardinality
            && end - start == maximum - minimum
            && content.get(start) == (short)minimum
            && content.get(end) == (short)maximum;
  }

}


final class MappeableArrayContainerShortIterator implements PeekableShortIterator {
  int pos;
  MappeableArrayContainer parent;

  MappeableArrayContainerShortIterator() {}


  MappeableArrayContainerShortIterator(MappeableArrayContainer p) {
    wrap(p);
  }

  @Override
  public void advanceIfNeeded(short minval) {
    pos = BufferUtil.advanceUntil(parent.content, pos - 1, parent.cardinality, minval);
  }

  @Override
  public PeekableShortIterator clone() {
    try {
      return (PeekableShortIterator) super.clone();
    } catch (CloneNotSupportedException e) {
      return null;// will not happen
    }
  }

  @Override
  public boolean hasNext() {
    return pos < parent.cardinality;
  }

  @Override
  public short next() {
    return parent.content.get(pos++);
  }


  @Override
  public int nextAsInt() {
    return toIntUnsigned(parent.content.get(pos++));
  }

  @Override
  public short peekNext() {
    return parent.content.get(pos);
  }


  @Override
  public void remove() {
    parent.removeAtIndex(pos - 1);
    pos--;
  }

  void wrap(MappeableArrayContainer p) {
    parent = p;
    pos = 0;
  }



}


final class RawArrayContainerShortIterator implements PeekableShortIterator {
  int pos;
  MappeableArrayContainer parent;
  short[] content;


  RawArrayContainerShortIterator(MappeableArrayContainer p) {
    parent = p;
    if (!p.isArrayBacked()) {
      throw new RuntimeException("internal bug");
    }
    content = p.content.array();
    pos = 0;
  }


  @Override
  public void advanceIfNeeded(short minval) {
    pos = Util.advanceUntil(content, pos - 1, parent.cardinality, minval);
  }

  @Override
  public PeekableShortIterator clone() {
    try {
      return (PeekableShortIterator) super.clone();
    } catch (CloneNotSupportedException e) {
      return null;// will not happen
    }
  }

  @Override
  public boolean hasNext() {
    return pos < parent.cardinality;
  }

  @Override
  public short next() {
    return content[pos++];
  }

  @Override
  public int nextAsInt() {
    return toIntUnsigned(content[pos++]);
  }

  @Override
  public short peekNext() {
    return content[pos];
  }

  @Override
  public void remove() {
    parent.removeAtIndex(pos - 1);
    pos--;
  }

}


final class RawReverseArrayContainerShortIterator implements ShortIterator {
  int pos;
  MappeableArrayContainer parent;
  short[] content;


  RawReverseArrayContainerShortIterator(MappeableArrayContainer p) {
    parent = p;
    if (!p.isArrayBacked()) {
      throw new RuntimeException("internal bug");
    }
    content = p.content.array();
    pos = parent.cardinality - 1;
  }

  @Override
  public ShortIterator clone() {
    try {
      return (ShortIterator) super.clone();
    } catch (CloneNotSupportedException e) {
      return null;// will not happen
    }
  }

  @Override
  public boolean hasNext() {
    return pos >= 0;
  }


  @Override
  public short next() {
    return content[pos--];
  }

  @Override
  public int nextAsInt() {
    return toIntUnsigned(content[pos--]);
  }

  @Override
  public void remove() {
    parent.removeAtIndex(pos + 1);
    pos++;
  }

};


final class ReverseMappeableArrayContainerShortIterator implements ShortIterator {

  int pos;

  MappeableArrayContainer parent;

  ReverseMappeableArrayContainerShortIterator() {}


  ReverseMappeableArrayContainerShortIterator(MappeableArrayContainer p) {
    wrap(p);
  }

  @Override
  public ShortIterator clone() {
    try {
      return (ShortIterator) super.clone();
    } catch (CloneNotSupportedException e) {
      return null;// will not happen
    }
  }

  @Override
  public boolean hasNext() {
    return pos >= 0;
  }

  @Override
  public short next() {
    return parent.content.get(pos--);
  }


  @Override
  public int nextAsInt() {
    return toIntUnsigned(parent.content.get(pos--));
  }

  @Override
  public void remove() {
    parent.removeAtIndex(pos + 1);
    pos++;
  }

  void wrap(MappeableArrayContainer p) {
    parent = p;
    pos = parent.cardinality - 1;
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy