content: package com.google.flatbuffers;
  
  import java.nio.ByteBuffer;
  import static java.lang.Character.MAX_SURROGATE;
  import static java.lang.Character.MIN_SUPPLEMENTARY_CODE_POINT;
  import static java.lang.Character.MIN_SURROGATE;
  import static java.lang.Character.isSurrogatePair;
  import static java.lang.Character.toCodePoint;
  
  /**
   * A set of low-level, high-performance static utility methods related
   * to the UTF-8 character encoding.  This class has no dependencies
   * outside of the core JDK libraries.
   *
   * <p>There are several variants of UTF-8.  The one implemented by
   * this class is the restricted definition of UTF-8 introduced in
   * Unicode 3.1, which mandates the rejection of "overlong" byte
   * sequences as well as rejection of 3-byte surrogate codepoint byte
   * sequences.  Note that the UTF-8 decoder included in Oracle's JDK
   * has been modified to also reject "overlong" byte sequences, but (as
   * of 2011) still accepts 3-byte surrogate codepoint byte sequences.
   *
   * <p>The byte sequences considered valid by this class are exactly
   * those that can be roundtrip converted to Strings and back to bytes
   * using the UTF-8 charset, without loss: <pre> {@code
   * Arrays.equals(bytes, new String(bytes, Internal.UTF_8).getBytes(Internal.UTF_8))
   * }</pre>
   *
   * <p>See the Unicode Standard,</br>
   * Table 3-6. <em>UTF-8 Bit Distribution</em>,</br>
   * Table 3-7. <em>Well Formed UTF-8 Byte Sequences</em>.
   */
  final public class Utf8Safe extends Utf8 {
  
    /**
     * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string,
     * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in
     * both time and space.
     *
     * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
     *     surrogates)
     */
    private static int computeEncodedLength(CharSequence sequence) {
      // Warning to maintainers: this implementation is highly optimized.
      int utf16Length = sequence.length();
      int utf8Length = utf16Length;
      int i = 0;
  
      // This loop optimizes for pure ASCII.
      while (i < utf16Length && sequence.charAt(i) < 0x80) {
        i++;
      }
  
      // This loop optimizes for chars less than 0x800.
      for (; i < utf16Length; i++) {
        char c = sequence.charAt(i);
        if (c < 0x800) {
          utf8Length += ((0x7f - c) >>> 31);  // branch free!
        } else {
          utf8Length += encodedLengthGeneral(sequence, i);
          break;
        }
      }
  
      if (utf8Length < utf16Length) {
        // Necessary and sufficient condition for overflow because of maximum 3x expansion
        throw new IllegalArgumentException("UTF-8 length does not fit in int: "
                                               + (utf8Length + (1L << 32)));
      }
      return utf8Length;
    }
  
    private static int encodedLengthGeneral(CharSequence sequence, int start) {
      int utf16Length = sequence.length();
      int utf8Length = 0;
      for (int i = start; i < utf16Length; i++) {
        char c = sequence.charAt(i);
        if (c < 0x800) {
          utf8Length += (0x7f - c) >>> 31; // branch free!
        } else {
          utf8Length += 2;
          // jdk7+: if (Character.isSurrogate(c)) {
          if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) {
            // Check that we have a well-formed surrogate pair.
            int cp = Character.codePointAt(sequence, i);
            if (cp < MIN_SUPPLEMENTARY_CODE_POINT) {
              throw new Utf8Safe.UnpairedSurrogateException(i, utf16Length);
            }
            i++;
          }
        }
      }
      return utf8Length;
    }
  
    public static String decodeUtf8Array(byte[] bytes, int index, int size) {
      // Bitwise OR combines the sign bits so any negative value fails the check.
      if ((index | size | bytes.length - index - size) < 0) {
        throw new ArrayIndexOutOfBoundsException(
            String.format("buffer length=%d, index=%d, size=%d", bytes.length, index, size));
      }
  
      int offset = index;
      final int limit = offset + size;
  
      // The longest possible resulting String is the same as the number of input bytes, when it is
      // all ASCII. For other cases, this over-allocates and we will truncate in the end.
      char[] resultArr = new char[size];
      int resultPos = 0;
  
      // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this).
      // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII).
      while (offset < limit) {
        byte b = bytes[offset];
        if (!DecodeUtil.isOneByte(b)) {
          break;
        }
        offset++;
        DecodeUtil.handleOneByte(b, resultArr, resultPos++);
      }
  
      while (offset < limit) {
        byte byte1 = bytes[offset++];
        if (DecodeUtil.isOneByte(byte1)) {
          DecodeUtil.handleOneByte(byte1, resultArr, resultPos++);
          // It's common for there to be multiple ASCII characters in a run mixed in, so add an
          // extra optimized loop to take care of these runs.
          while (offset < limit) {
            byte b = bytes[offset];
            if (!DecodeUtil.isOneByte(b)) {
              break;
            }
            offset++;
            DecodeUtil.handleOneByte(b, resultArr, resultPos++);
          }
        } else if (DecodeUtil.isTwoBytes(byte1)) {
          if (offset >= limit) {
            throw new IllegalArgumentException("Invalid UTF-8");
          }
          DecodeUtil.handleTwoBytes(byte1, /* byte2 */ bytes[offset++], resultArr, resultPos++);
        } else if (DecodeUtil.isThreeBytes(byte1)) {
          if (offset >= limit - 1) {
            throw new IllegalArgumentException("Invalid UTF-8");
          }
          DecodeUtil.handleThreeBytes(
              byte1,
              /* byte2 */ bytes[offset++],
              /* byte3 */ bytes[offset++],
              resultArr,
              resultPos++);
        } else {
          if (offset >= limit - 2) {
            throw new IllegalArgumentException("Invalid UTF-8");
          }
          DecodeUtil.handleFourBytes(
              byte1,
              /* byte2 */ bytes[offset++],
              /* byte3 */ bytes[offset++],
              /* byte4 */ bytes[offset++],
              resultArr,
              resultPos++);
          // 4-byte case requires two chars.
          resultPos++;
        }
      }
  
      return new String(resultArr, 0, resultPos);
    }
  
    public static String decodeUtf8Buffer(ByteBuffer buffer, int offset,
                                           int length) {
      // Bitwise OR combines the sign bits so any negative value fails the check.
      if ((offset | length | buffer.limit() - offset - length) < 0) {
        throw new ArrayIndexOutOfBoundsException(
            String.format("buffer limit=%d, index=%d, limit=%d", buffer.limit(),
                offset, length));
      }
  
      final int limit = offset + length;
  
      // The longest possible resulting String is the same as the number of input bytes, when it is
      // all ASCII. For other cases, this over-allocates and we will truncate in the end.
      char[] resultArr = new char[length];
      int resultPos = 0;
  
      // Optimize for 100% ASCII (Hotspot loves small simple top-level loops like this).
      // This simple loop stops when we encounter a byte >= 0x80 (i.e. non-ASCII).
      while (offset < limit) {
        byte b = buffer.get(offset);
        if (!DecodeUtil.isOneByte(b)) {
          break;
        }
        offset++;
        DecodeUtil.handleOneByte(b, resultArr, resultPos++);
      }
  
      while (offset < limit) {
        byte byte1 = buffer.get(offset++);
        if (DecodeUtil.isOneByte(byte1)) {
          DecodeUtil.handleOneByte(byte1, resultArr, resultPos++);
          // It's common for there to be multiple ASCII characters in a run mixed in, so add an
          // extra optimized loop to take care of these runs.
          while (offset < limit) {
            byte b = buffer.get(offset);
            if (!DecodeUtil.isOneByte(b)) {
              break;
            }
            offset++;
            DecodeUtil.handleOneByte(b, resultArr, resultPos++);
          }
        } else if (DecodeUtil.isTwoBytes(byte1)) {
          if (offset >= limit) {
            throw new IllegalArgumentException("Invalid UTF-8");
          }
          DecodeUtil.handleTwoBytes(
              byte1, /* byte2 */ buffer.get(offset++), resultArr, resultPos++);
        } else if (DecodeUtil.isThreeBytes(byte1)) {
          if (offset >= limit - 1) {
            throw new IllegalArgumentException("Invalid UTF-8");
          }
          DecodeUtil.handleThreeBytes(
              byte1,
              /* byte2 */ buffer.get(offset++),
              /* byte3 */ buffer.get(offset++),
              resultArr,
              resultPos++);
        } else {
          if (offset >= limit - 2) {
            throw new IllegalArgumentException("Invalid UTF-8");
          }
          DecodeUtil.handleFourBytes(
              byte1,
              /* byte2 */ buffer.get(offset++),
              /* byte3 */ buffer.get(offset++),
              /* byte4 */ buffer.get(offset++),
              resultArr,
              resultPos++);
          // 4-byte case requires two chars.
          resultPos++;
        }
      }
  
      return new String(resultArr, 0, resultPos);
    }
  
    @Override
    public int encodedLength(CharSequence in) {
      return computeEncodedLength(in);
    }
  
    /**
     * Decodes the given UTF-8 portion of the {@link ByteBuffer} into a {@link String}.
     *
     * @throws IllegalArgumentException if the input is not valid UTF-8.
     */
    @Override
    public String decodeUtf8(ByteBuffer buffer, int offset, int length)
        throws IllegalArgumentException {
      if (buffer.hasArray()) {
        return decodeUtf8Array(buffer.array(), buffer.arrayOffset() + offset, length);
      } else {
        return decodeUtf8Buffer(buffer, offset, length);
      }
    }
  
  
    private static void encodeUtf8Buffer(CharSequence in, ByteBuffer out) {
      final int inLength = in.length();
      int outIx = out.position();
      int inIx = 0;
  
      // Since ByteBuffer.putXXX() already checks boundaries for us, no need to explicitly check
      // access. Assume the buffer is big enough and let it handle the out of bounds exception
      // if it occurs.
      try {
        // Designed to take advantage of
        // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
        for (char c; inIx < inLength && (c = in.charAt(inIx)) < 0x80; ++inIx) {
          out.put(outIx + inIx, (byte) c);
        }
        if (inIx == inLength) {
          // Successfully encoded the entire string.
          out.position(outIx + inIx);
          return;
        }
  
        outIx += inIx;
        for (char c; inIx < inLength; ++inIx, ++outIx) {
          c = in.charAt(inIx);
          if (c < 0x80) {
            // One byte (0xxx xxxx)
            out.put(outIx, (byte) c);
          } else if (c < 0x800) {
            // Two bytes (110x xxxx 10xx xxxx)
  
            // Benchmarks show put performs better than putShort here (for HotSpot).
            out.put(outIx++, (byte) (0xC0 | (c >>> 6)));
            out.put(outIx, (byte) (0x80 | (0x3F & c)));
          } else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
            // Three bytes (1110 xxxx 10xx xxxx 10xx xxxx)
            // Maximum single-char code point is 0xFFFF, 16 bits.
  
            // Benchmarks show put performs better than putShort here (for HotSpot).
            out.put(outIx++, (byte) (0xE0 | (c >>> 12)));
            out.put(outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
            out.put(outIx, (byte) (0x80 | (0x3F & c)));
          } else {
            // Four bytes (1111 xxxx 10xx xxxx 10xx xxxx 10xx xxxx)
  
            // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8
            // bytes
            final char low;
            if (inIx + 1 == inLength || !isSurrogatePair(c, (low = in.charAt(++inIx)))) {
              throw new UnpairedSurrogateException(inIx, inLength);
            }
            // TODO(nathanmittler): Consider using putInt() to improve performance.
            int codePoint = toCodePoint(c, low);
            out.put(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
            out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
            out.put(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
            out.put(outIx, (byte) (0x80 | (0x3F & codePoint)));
          }
        }
  
        // Successfully encoded the entire string.
        out.position(outIx);
      } catch (IndexOutOfBoundsException e) {
        // TODO(nathanmittler): Consider making the API throw IndexOutOfBoundsException instead.
  
        // If we failed in the outer ASCII loop, outIx will not have been updated. In this case,
        // use inIx to determine the bad write index.
        int badWriteIndex = out.position() + Math.max(inIx, outIx - out.position() + 1);
        throw new ArrayIndexOutOfBoundsException(
            "Failed writing " + in.charAt(inIx) + " at index " + badWriteIndex);
      }
    }
  
    private static int encodeUtf8Array(CharSequence in, byte[] out,
                                       int offset, int length) {
      int utf16Length = in.length();
      int j = offset;
      int i = 0;
      int limit = offset + length;
      // Designed to take advantage of
      // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
      for (char c; i < utf16Length && i + j < limit && (c = in.charAt(i)) < 0x80; i++) {
        out[j + i] = (byte) c;
      }
      if (i == utf16Length) {
        return j + utf16Length;
      }
      j += i;
      for (char c; i < utf16Length; i++) {
        c = in.charAt(i);
        if (c < 0x80 && j < limit) {
          out[j++] = (byte) c;
        } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes
          out[j++] = (byte) ((0xF << 6) | (c >>> 6));
          out[j++] = (byte) (0x80 | (0x3F & c));
        } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) {
          // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
          out[j++] = (byte) ((0xF << 5) | (c >>> 12));
          out[j++] = (byte) (0x80 | (0x3F & (c >>> 6)));
          out[j++] = (byte) (0x80 | (0x3F & c));
        } else if (j <= limit - 4) {
          // Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
          // four UTF-8 bytes
          final char low;
          if (i + 1 == in.length()
                  || !Character.isSurrogatePair(c, (low = in.charAt(++i)))) {
            throw new UnpairedSurrogateException((i - 1), utf16Length);
          }
          int codePoint = Character.toCodePoint(c, low);
          out[j++] = (byte) ((0xF << 4) | (codePoint >>> 18));
          out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12)));
          out[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6)));
          out[j++] = (byte) (0x80 | (0x3F & codePoint));
        } else {
          // If we are surrogates and we're not a surrogate pair, always throw an
          // UnpairedSurrogateException instead of an ArrayOutOfBoundsException.
          if ((Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE)
                  && (i + 1 == in.length()
                          || !Character.isSurrogatePair(c, in.charAt(i + 1)))) {
            throw new UnpairedSurrogateException(i, utf16Length);
          }
          throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j);
        }
      }
      return j;
    }
  
    /**
     * Encodes the given characters to the target {@link ByteBuffer} using UTF-8 encoding.
     *
     * <p>Selects an optimal algorithm based on the type of {@link ByteBuffer} (i.e. heap or direct)
     * and the capabilities of the platform.
     *
     * @param in the source string to be encoded
     * @param out the target buffer to receive the encoded string.
     */
    @Override
    public void encodeUtf8(CharSequence in, ByteBuffer out) {
      if (out.hasArray()) {
        int start = out.arrayOffset();
        int end = encodeUtf8Array(in, out.array(), start + out.position(),
            out.remaining());
        out.position(end - start);
      } else {
        encodeUtf8Buffer(in, out);
      }
    }
  
    // These UTF-8 handling methods are copied from Guava's Utf8Unsafe class with
    // a modification to throw a local exception. This exception can be caught
    // to fallback to more lenient behavior.
    static class UnpairedSurrogateException extends IllegalArgumentException {
      UnpairedSurrogateException(int index, int length) {
        super("Unpaired surrogate at index " + index + " of " + length);
      }
    }
  }
  