@Override
 public void readFields(DataInput in) throws IOException {
   RowKeySchema schema = new RowKeySchema();
   schema.readFields(in);
   int maxLength = getTerminatorCount(schema);
   int andLen = in.readInt();
   List<List<KeyRange>> slots = Lists.newArrayListWithExpectedSize(andLen);
   for (int i = 0; i < andLen; i++) {
     int orlen = in.readInt();
     List<KeyRange> orclause = Lists.newArrayListWithExpectedSize(orlen);
     slots.add(orclause);
     int maxSlotLength = 0;
     for (int j = 0; j < orlen; j++) {
       KeyRange range = new KeyRange();
       range.readFields(in);
       if (range.getLowerRange().length > maxSlotLength) {
         maxSlotLength = range.getLowerRange().length;
       }
       if (range.getUpperRange().length > maxSlotLength) {
         maxSlotLength = range.getUpperRange().length;
       }
       orclause.add(range);
     }
     maxLength += maxSlotLength;
   }
   this.init(slots, schema, maxLength);
 }
 private int getTerminatorCount(RowKeySchema schema) {
   int nTerminators = 0;
   for (int i = 0; i < schema.getFieldCount(); i++) {
     Field field = schema.getField(i);
     // We won't have a terminator on the last PK column
     // unless it is variable length and exclusive, but
     // having the extra byte irregardless won't hurt anything
     if (!field.getType().isFixedWidth()) {
       nTerminators++;
     }
   }
   return nTerminators;
 }
 @Override
 public void write(DataOutput out) throws IOException {
   schema.write(out);
   out.writeInt(slots.size());
   for (List<KeyRange> orclause : slots) {
     out.writeInt(orclause.size());
     for (KeyRange range : orclause) {
       range.write(out);
     }
   }
 }
 private int setStartKey(ImmutableBytesWritable ptr, int offset, int i) {
   int length = ptr.getOffset() - offset;
   startKey = copyKey(startKey, length + this.maxKeyLength, ptr.get(), offset, length);
   startKeyLength = length;
   // Add separator byte if we're at the end of the buffer, since trailing separator bytes are
   // stripped
   if (ptr.getOffset() + ptr.getLength() == offset + length
       && i - 1 > 0
       && !schema.getField(i - 1).getType().isFixedWidth()) {
     startKey[startKeyLength++] = QueryConstants.SEPARATOR_BYTE;
   }
   startKeyLength += setKey(Bound.LOWER, startKey, startKeyLength, i);
   return length;
 }
  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
      value = "QBA_QUESTIONABLE_BOOLEAN_ASSIGNMENT",
      justification = "Assignment designed to work this way.")
  private ReturnCode navigate(
      final byte[] currentKey, int offset, int length, Terminate terminate) {
    int nSlots = slots.size();
    // First check to see if we're in-range until we reach our end key
    if (endKeyLength > 0) {
      if (Bytes.compareTo(currentKey, offset, length, endKey, 0, endKeyLength) < 0) {
        return ReturnCode.INCLUDE;
      }

      // If key range of last slot is a single key, we can increment our position
      // since we know we'll be past the current row after including it.
      if (slots.get(nSlots - 1).get(position[nSlots - 1]).isSingleKey()) {
        if (nextPosition(nSlots - 1) < 0) {
          // Current row will be included, but we have no more
          isDone = true;
          return ReturnCode.NEXT_ROW;
        }
      } else {
        // Reset the positions to zero from the next slot after the earliest ranged slot, since the
        // next key could be bigger at this ranged slot, and smaller than the current position of
        // less significant slots.
        int earliestRangeIndex = nSlots - 1;
        for (int i = 0; i < nSlots; i++) {
          if (!slots.get(i).get(position[i]).isSingleKey()) {
            earliestRangeIndex = i;
            break;
          }
        }
        Arrays.fill(position, earliestRangeIndex + 1, position.length, 0);
      }
    }
    endKeyLength = 0;

    // We could have included the previous
    if (isDone) {
      return ReturnCode.NEXT_ROW;
    }

    int i = 0;
    boolean seek = false;
    int earliestRangeIndex = nSlots - 1;
    ptr.set(currentKey, offset, length);
    schema.first(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET);
    while (true) {
      // Increment to the next range while the upper bound of our current slot is less than our
      // current key
      while (position[i] < slots.get(i).size()
          && slots.get(i).get(position[i]).compareUpperToLowerBound(ptr) < 0) {
        position[i]++;
      }
      Arrays.fill(position, i + 1, position.length, 0);
      if (position[i] >= slots.get(i).size()) {
        // Our current key is bigger than the last range of the current slot.
        // If navigating after current key, backtrack and increment the key of the previous slot
        // values.
        // If navigating to current key, just return
        if (terminate == Terminate.AT) {
          return ReturnCode.SEEK_NEXT_USING_HINT;
        }
        if (i == 0) {
          isDone = true;
          return ReturnCode.NEXT_ROW;
        }
        // Increment key and backtrack until in range. We know at this point that we'll be
        // issuing a seek next hint.
        seek = true;
        Arrays.fill(position, i, position.length, 0);
        i--;
        // If we're positioned at a single key, no need to copy the current key and get the next key
        // .
        // Instead, just increment to the next key and continue.
        boolean incremented = false;
        while (i >= 0
            && slots.get(i).get(position[i]).isSingleKey()
            && (incremented = true)
            && (position[i] = (position[i] + 1) % slots.get(i).size()) == 0) {
          i--;
          incremented = false;
        }
        if (i < 0) {
          isDone = true;
          return ReturnCode.NEXT_ROW;
        }
        if (incremented) {
          // Continue the loop after setting the start key, because our start key maybe smaller than
          // the current key, so we'll end up incrementing the start key until it's bigger than the
          // current key.
          setStartKey();
          ptr.set(ptr.get(), offset, length);
          // Reinitialize iterator to be positioned at previous slot position
          schema.setAccessor(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET);
        } else {
          int currentLength = setStartKey(ptr, offset, i + 1);
          // From here on, we use startKey as our buffer with offset reset to 0
          // We've copied the part of the current key above that we need into startKey
          ptr.set(startKey, offset = 0, length = startKeyLength);
          // Reinitialize iterator to be positioned at previous slot position
          // TODO: a schema.previous would potentially be more efficient
          schema.setAccessor(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET);
          // Do nextKey after setting the accessor b/c otherwise the null byte may have
          // been incremented causing us not to find it
          ByteUtil.nextKey(startKey, currentLength);
        }
      } else if (slots.get(i).get(position[i]).compareLowerToUpperBound(ptr) > 0) {
        // Our current key is less than the lower range of the current position in the current slot.
        // Seek to the lower range, since it's bigger than the current key
        setStartKey(ptr, offset, i);
        return ReturnCode.SEEK_NEXT_USING_HINT;
      } else { // We're in range, check the next slot
        if (!slots.get(i).get(position[i]).isSingleKey() && i < earliestRangeIndex) {
          earliestRangeIndex = i;
        }
        i++;
        // If we're past the last slot or we know we're seeking to the next (in
        // which case the previously updated slot was verified to be within the
        // range, so we don't need to check the rest of the slots. If we were
        // to check the rest of the slots, we'd get into trouble because we may
        // have a null byte that was incremented which screws up our schema.next call)
        if (i >= nSlots || seek) {
          break;
        }
        // If we run out of slots in our key, it means we have a partial key. In this
        // case, we seek to the next full key after this one.
        // TODO: test for this
        if (schema.next(ptr, i, offset + length, ValueBitSet.EMPTY_VALUE_BITSET) == null) {
          setStartKey(ptr, offset, i);
          return ReturnCode.SEEK_NEXT_USING_HINT;
        }
      }
    }

    if (seek) {
      return ReturnCode.SEEK_NEXT_USING_HINT;
    }
    // Else, we're in range for all slots and can include this row plus all rows
    // up to the upper range of our last slot. We do this for ranges and single keys
    // since we potentially have multiple key values for the same row key.
    setEndKey(ptr, offset, slots.size() - 1);
    return ReturnCode.INCLUDE;
  }
 private boolean intersect(
     byte[] lowerInclusiveKey, byte[] upperExclusiveKey, List<List<KeyRange>> newSlots) {
   boolean lowerUnbound = (lowerInclusiveKey.length == 0);
   Arrays.fill(position, 0);
   isDone = false;
   int startPos = 0;
   int lastSlot = slots.size() - 1;
   if (!lowerUnbound) {
     // Find the position of the first slot of the lower range
     ptr.set(lowerInclusiveKey);
     schema.first(ptr, 0, ValueBitSet.EMPTY_VALUE_BITSET);
     startPos = ScanUtil.searchClosestKeyRangeWithUpperHigherThanPtr(slots.get(0), ptr, 0);
     // Lower range is past last upper range of first slot, so cannot possibly be in range
     if (startPos >= slots.get(0).size()) {
       return false;
     }
   }
   boolean upperUnbound = (upperExclusiveKey.length == 0);
   int endPos = slots.get(0).size() - 1;
   if (!upperUnbound) {
     // Find the position of the first slot of the upper range
     ptr.set(upperExclusiveKey);
     schema.first(ptr, 0, ValueBitSet.EMPTY_VALUE_BITSET);
     endPos = ScanUtil.searchClosestKeyRangeWithUpperHigherThanPtr(slots.get(0), ptr, startPos);
     // Upper range lower than first lower range of first slot, so cannot possibly be in range
     if (endPos == 0
         && Bytes.compareTo(upperExclusiveKey, slots.get(0).get(0).getLowerRange()) <= 0) {
       return false;
     }
     // Past last position, so we can include everything from the start position
     if (endPos >= slots.get(0).size()) {
       upperUnbound = true;
       endPos = slots.get(0).size() - 1;
     }
   }
   if (!lowerUnbound) {
     position[0] = startPos;
     navigate(lowerInclusiveKey, 0, lowerInclusiveKey.length, Terminate.AFTER);
     if (filterAllRemaining()) {
       return false;
     }
   }
   if (upperUnbound) {
     if (newSlots != null) {
       newSlots.add(slots.get(0).subList(startPos, endPos + 1));
       newSlots.addAll(slots.subList(1, slots.size()));
     }
     return true;
   }
   int[] lowerPosition = Arrays.copyOf(position, position.length);
   // Navigate to the upperExclusiveKey, but not past it
   ReturnCode endCode = navigate(upperExclusiveKey, 0, upperExclusiveKey.length, Terminate.AT);
   if (endCode == ReturnCode.INCLUDE) {
     setStartKey();
     // If the upperExclusiveKey is equal to the start key, we've gone one position too far, since
     // our upper key is exclusive. In that case, go to the previous key
     if (Bytes.compareTo(
                 startKey, 0, startKeyLength, upperExclusiveKey, 0, upperExclusiveKey.length)
             == 0
         && (previousPosition(lastSlot) < 0 || position[0] < lowerPosition[0])) {
       // If by backing up one position we have an empty range, then return
       return false;
     }
   } else if (endCode == ReturnCode.SEEK_NEXT_USING_HINT) {
     // The upperExclusive key is smaller than the slots stored in the position. Check if it's the
     // same position
     // as the slots for lowerInclusive. If so, there is no intersection.
     if (Arrays.equals(lowerPosition, position)) {
       return false;
     }
   }
   // Copy inclusive all positions
   for (int i = 0; i <= lastSlot; i++) {
     List<KeyRange> newRanges =
         slots.get(i).subList(lowerPosition[i], Math.min(position[i] + 1, slots.get(i).size()));
     if (newRanges.isEmpty()) {
       return false;
     }
     if (newSlots != null) {
       newSlots.add(newRanges);
     }
     if (position[i] > lowerPosition[i]) {
       if (newSlots != null) {
         newSlots.addAll(slots.subList(i + 1, slots.size()));
       }
       break;
     }
   }
   return true;
 }