content: package edu.berkeley.cs.succinct.buffers;
  
  import edu.berkeley.cs.succinct.StorageMode;
  import edu.berkeley.cs.succinct.SuccinctTable;
  import edu.berkeley.cs.succinct.util.container.Range;
  
  import java.io.DataInputStream;
  import java.nio.ByteBuffer;
  import java.util.*;
  
  public class SuccinctTableBuffer extends SuccinctIndexedFileBuffer implements SuccinctTable {
  
    /**
     * Constructor to initialize SuccinctIndexedBuffer from input byte array and offsets corresponding to records.
     *
     * @param input   The input byte array.
     * @param offsets Offsets corresponding to records.
     */
    public SuccinctTableBuffer(byte[] input, int[] offsets) {
      super(input, offsets);
    }
  
    /**
     * Constructor to load the data from persisted Succinct data-structures.
     *
     * @param path        Path to load data from.
     * @param storageMode Mode in which data is stored (In-memory or Memory-mapped)
     */
    public SuccinctTableBuffer(String path, StorageMode storageMode) {
      super(path, storageMode);
    }
  
    /**
     * Constructor to load the data from a DataInputStream.
     *
     * @param is Input stream to load the data from
     */
    public SuccinctTableBuffer(DataInputStream is) {
      super(is);
    }
  
    /**
     * Constructor to load the data from a ByteBuffer.
     *
     * @param buf Input buffer to load the data from
     */
    public SuccinctTableBuffer(ByteBuffer buf) {
      super(buf);
    }
  
    /**
     * Perform multiple searches with different query types and return the intersection of the results.
     *
     * @param queryTypes The QueryType corresponding to each query
     * @param queries    The actual query parameters associated with each query
     * @return The records matching the multi-search queries.
     */
    @Override public Integer[] recordMultiSearchIds(SuccinctTable.QueryType[] queryTypes,
      byte[][][] queries) {
      assert (queryTypes.length == queries.length);
      Set<Integer> recordIds = new HashSet<>();
  
      if (queries.length == 0) {
        throw new IllegalArgumentException("recordMultiSearchIds called with empty queries");
      }
  
      // Get all ranges
      ArrayList<Range> ranges = new ArrayList<>();
      for (int qid = 0; qid < queries.length; qid++) {
        Range range;
  
        switch (queryTypes[qid]) {
          case Search: {
            range = bwdSearch(queries[qid][0]);
            break;
          }
          case RangeSearch: {
            byte[] queryBegin = queries[qid][0];
            byte[] queryEnd = queries[qid][1];
            range = rangeSearch(queryBegin, queryEnd);
            break;
          }
          default: {
            throw new UnsupportedOperationException("Unsupported QueryType");
          }
        }
  
        if (range.second - range.first + 1 > 0) {
          ranges.add(range);
        } else {
          return new Integer[0];
        }
      }
  
      Collections.sort(ranges, new RangeSizeComparator());
  
      // Populate the set of offsets corresponding to the first range
      Range firstRange = ranges.get(0);
      Map<Integer, Integer> counts = new HashMap<>();
      {
        long sp = firstRange.first, ep = firstRange.second;
        for (long i = 0; i < ep - sp + 1; i++) {
          long saVal = lookupSA(sp + i);
          int recordId = offsetToRecordId((int) saVal);
          recordIds.add(recordId);
          counts.put(recordId, 1);
        }
      }
  
      ranges.remove(firstRange);
      for (Range range : ranges) {
        long sp = range.first, ep = range.second;
        for (long i = 0; i < ep - sp + 1; i++) {
          long saVal = lookupSA(sp + i);
          int recordId = offsetToRecordId((int) saVal);
          if (recordIds.contains(recordId)) {
            counts.put(recordId, counts.get(recordId) + 1);
          }
        }
      }
  
      return recordIds.toArray(new Integer[recordIds.size()]);
    }
  }
  