protected String encodeInternal(byte[] data) {
    if (data.length == 0)
      return "";

    // SHIFT is the number of bits per output character, so the length of
    // the
    // output is the length of the input multiplied by 8/SHIFT, rounded up.
    if (data.length >= (1 << 28))
      // The computation below will fail, so don't do it.
      throw new IllegalArgumentException();

    final int outputLength = (data.length * 8 + SHIFT - 1) / SHIFT;
    final StringBuilder result = new StringBuilder(outputLength);

    int buffer = data[0];
    int next = 1;
    int bitsLeft = 8;
    while (bitsLeft > 0 || next < data.length) {
      if (bitsLeft < SHIFT)
        if (next < data.length) {
          buffer <<= 8;
          buffer |= (data[next++] & 0xff);
          bitsLeft += 8;
        } else {
          final int pad = SHIFT - bitsLeft;
          buffer <<= pad;
          bitsLeft += pad;
        }
      final int index = MASK & (buffer >> (bitsLeft - SHIFT));
      bitsLeft -= SHIFT;
      result.append(DIGITS[index]);
    }
    return result.toString();
  }