///|
fn utf16_text(units : Array[Int]) -> String raise ParseError {
  let builder = StringBuilder()
  let mut i = 0
  while i < units.length() {
    let mut value = units[i]
    i += 1
    if value >= 0xd800 && value <= 0xdbff {
      if i >= units.length() || units[i] < 0xdc00 || units[i] > 0xdfff {
        raise Invalid(i - 1, "unpaired high surrogate in Java String")
      }
      value = 0x10000 + ((value - 0xd800) << 10) + units[i] - 0xdc00
      i += 1
    } else if value >= 0xdc00 && value <= 0xdfff {
      raise Invalid(i - 1, "unpaired low surrogate in Java String")
    }
    match value.to_char() {
      Some(char) => builder.write_char(char)
      None => raise Invalid(i - 1, "invalid character in Java String")
    }
  }
  builder.to_string()
}

///|
/// Reads JDK char[] Strings and compact byte[] Strings with explicit byte order.
/// HPROF does not encode the native byte order of compact UTF-16 String storage.
pub fn Summary::java_string(
  self : Summary,
  id : UInt64,
  utf16_little_endian~ : Bool,
  max_chars? : Int = 4096,
) -> String raise ParseError {
  if max_chars < 0 || max_chars > 100_000 {
    raise Invalid(0, "invalid String character limit")
  }
  let object = self.require_object(id)
  if object.kind != Instance ||
    self.class_name(object.class_id) != Some("java/lang/String") {
    raise Invalid(0, "object is not a java/lang/String")
  }
  let array_id = match self.field(id, "value") {
    Some(Reference(value)) if value != 0UL => value
    _ => raise Invalid(0, "missing Java String backing array")
  }
  let array = self.require_object(array_id)
  if array.kind != PrimitiveArray {
    raise Invalid(0, "invalid String backing array kind")
  }
  if array.primitive_kind == 5 {
    let offset = match self.field(id, "offset") {
      Some(IntValue(n)) => n
      None => 0
      _ => raise Invalid(0, "invalid legacy String offset")
    }
    let count = match self.field(id, "count") {
      Some(IntValue(n)) => n
      None => array.length - offset
      _ => raise Invalid(0, "invalid legacy String count")
    }
    if count < 0 ||
      count > max_chars ||
      offset < 0 ||
      offset > array.length ||
      count > array.length - offset {
      raise Invalid(0, "legacy String slice exceeds backing array or limit")
    }
    let units = []
    for value in self.array_values(array_id, offset~, limit=count) {
      match value {
        Character(unit) => units.push(unit)
        _ => raise Invalid(0, "invalid character backing array")
      }
    }
    return utf16_text(units)
  }
  if array.primitive_kind != 8 {
    raise Invalid(0, "unsupported String backing array element type")
  }
  let coder = match self.field(id, "coder") {
    Some(ByteValue(n)) => n
    _ => raise Invalid(0, "missing compact String coder")
  }
  let units = []
  let start = array.payload_offset
  match coder {
    0 => {
      if array.length > max_chars {
        raise Invalid(start, "String exceeds character limit")
      }
      for i in 0.. {
      if array.length % 2 != 0 || array.length / 2 > max_chars {
        raise Invalid(start, "invalid compact UTF-16 length or character limit")
      }
      for i in 0..<(array.length / 2) {
        let a = self.data[start + i * 2].to_int()
        let b = self.data[start + i * 2 + 1].to_int()
        units.push(
          if utf16_little_endian {
            a | (b << 8)
          } else {
            (a << 8) | b
          },
        )
      }
    }
    _ => raise Invalid(start, "unknown compact String coder")
  }
  utf16_text(units)
}