gt:             StringBuilder sb = WireInternal.acquireStringBuilder();
context: /*
   *     Copyright (C) 2015  higherfrequencytrading.com
   *
   *     This program is free software: you can redistribute it and/or modify
   *     it under the terms of the GNU Lesser General Public License as published by
   *     the Free Software Foundation, either version 3 of the License.
   *
   *     This program is distributed in the hope that it will be useful,
   *     but WITHOUT ANY WARRANTY; without even the implied warranty of
   *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *     GNU Lesser General Public License for more details.
   *
   *     You should have received a copy of the GNU Lesser General Public License
   *     along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package net.openhft.chronicle.wire;
  
  import net.openhft.chronicle.bytes.*;
  import net.openhft.chronicle.core.Maths;
  import net.openhft.chronicle.core.pool.ClassAliasPool;
  import net.openhft.chronicle.core.util.ObjectUtils;
  import net.openhft.chronicle.core.util.StringUtils;
  import net.openhft.chronicle.core.values.IntValue;
  import net.openhft.chronicle.core.values.LongArrayValues;
  import net.openhft.chronicle.core.values.LongValue;
  import org.jetbrains.annotations.NotNull;
  import org.jetbrains.annotations.Nullable;
  import org.xerial.snappy.Snappy;
  
  import java.io.IOException;
  import java.time.LocalDate;
  import java.time.LocalTime;
  import java.time.ZonedDateTime;
  import java.util.Base64;
  import java.util.Map;
  import java.util.UUID;
  import java.util.function.BiConsumer;
  import java.util.function.Consumer;
  import java.util.function.Function;
  
  import static net.openhft.chronicle.bytes.Bytes.empty;
  import static net.openhft.chronicle.bytes.NativeBytes.nativeBytes;
  import static net.openhft.chronicle.core.util.ReadResolvable.readResolve;
  
  /**
   * JSON wire format
   * <p>
   * At the moment, this is a cut down version of the YAML wire format.
   */
  public class JSONWire extends TextWire {
      static final BytesStore COMMA = BytesStore.from(",");
  
      public JSONWire(Bytes bytes, boolean use8bit) {
          super(bytes, use8bit);
      }
  
      public JSONWire(Bytes bytes) {
          this(bytes, false);
      }
  
      @NotNull
      public static JSONWire from(@NotNull String text) {
          return new JSONWire(Bytes.from(text));
      }
  
      public static String asText(@NotNull Wire wire) {
          long pos = wire.bytes().readPosition();
          JSONWire tw = new JSONWire(nativeBytes());
          wire.copyTo(tw);
          wire.bytes().readPosition(pos);
  
          return tw.toString();
      }
  
      public static <ACS extends Appendable & CharSequence> void unescape(@NotNull ACS sb) {
          int end = 0;
          int length = sb.length();
          for (int i = 0; i < length; i++) {
              char ch = sb.charAt(i);
              if (ch == '\\' && i < length - 1) {
                  char ch3 = sb.charAt(++i);
                  switch (ch3) {
                      case 'n':
                          ch = '\n';
                          break;
                      case 't':
                          ch = '\t';
                          break;
                      default:
                          ch = ch3;
                  }
              }
              AppendableUtil.setCharAt(sb, end++, ch);
          }
          if (length != sb.length())
              throw new IllegalStateException("Length changed from " + length + " to " + sb.length() + " for " + sb);
          AppendableUtil.setLength(sb, end);
      }
  
      @NotNull
      @Override
      protected TextValueOut createValueOut() {
          return new JSONValueOut();
      }
  
      @NotNull
      @Override
      protected TextValueIn createValueIn() {
          return new JSONValueIn();
      }
  
      @Override
      public void copyTo(@NotNull WireOut wire) {
          throw new UnsupportedOperationException();
      }
  
      @NotNull
      private StopCharsTester getEscapingEndOfText() {
          StopCharsTester escaping = ESCAPED_END_OF_TEXT.get();
          // reset it.
          escaping.isStopChar(' ', ' ');
          return escaping;
      }
  
      private StopCharTester getEscapingQuotes() {
          StopCharTester sct = ESCAPED_QUOTES.get();
          // reset it.
          sct.isStopChar(' ');
          return sct;
      }
  
      void consumeWhiteSpace() {
          for (; ; ) {
              int codePoint = peekCode();
              if (codePoint == '#') {
                  //noinspection StatementWithEmptyBody
                  while (readCode() >= ' ') ;
              } else if (Character.isWhitespace(codePoint) || codePoint == ',') {
                  bytes.readSkip(1);
              } else {
                  break;
              }
          }
      }
  
      /**
       * returns true if the next string is {@code str}
       *
       * @param source string
       * @return true if the strings are the same
       */
      private boolean peekStringIgnoreCase(@NotNull final String source) {
          if (source.isEmpty())
              return true;
  
          if (bytes.readRemaining() < 1)
              return false;
  
          long pos = bytes.readPosition();
  
          try {
              for (int i = 0; i < source.length(); i++) {
                  if (Character.toLowerCase(source.charAt(i)) != Character.toLowerCase(bytes.readByte()))
                      return false;
              }
          } finally {
              bytes.readPosition(pos);
          }
  
          return true;
      }
  
      private int readCode() {
          if (bytes.readRemaining() < 1)
              return -1;
          return bytes.readUnsignedByte();
      }
  
      @NotNull
      @Override
      public ValueIn read(@NotNull WireKey key) {
          long position = bytes.readPosition();
          StringBuilder sb = readField(WireInternal.acquireStringBuilder());
          if (sb.length() == 0 || StringUtils.isEqual(sb, key.name()))
              return valueIn;
          bytes.readPosition(position);
          throw new UnsupportedOperationException("Unordered fields not supported yet. key=" + key
                  .name() + ", was=" + sb + ", data='" + sb + "'");
      }
  
      @NotNull
      @Override
      public ValueIn read(@NotNull StringBuilder name) {
          consumeWhiteSpace();
          readField(name);
          return valueIn;
      }
  
      @Override
      public boolean hasMore() {
          consumeWhiteSpace();
  
          return bytes.readRemaining() > 0;
      }
  
      @NotNull
      @Override
      public ValueOut write(@NotNull WireKey key) {
          return valueOut.write(key);
      }
  
      @NotNull
      @Override
      public Wire writeComment(@NotNull CharSequence s) {
          valueOut.writeComment(s);
          return this;
      }
  
      @NotNull
      @Override
      public WireOut addPadding(int paddingToAdd) {
          for (int i = 0; i < paddingToAdd; i++)
              bytes.appendUtf8((bytes.writePosition() & 63) == 0 ? '\n' : ' ');
          return this;
      }
  
      void escaped(@NotNull CharSequence s) {
          if (needsQuotes(s) == Quotes.NONE) {
              bytes.appendUtf8(s);
              return;
          }
          doEscape(s);
      }
  
      private void doEscape(@NotNull CharSequence s) {
          for (int i = 0; i < s.length(); i++) {
              char ch = s.charAt(i);
              switch (ch) {
                  case '"':
                  case '\\':
                      bytes.appendUtf8('\\').appendUtf8(ch);
                      break;
  
                  case '\n':
                      bytes.appendUtf8("\\n");
                      break;
                  default:
                      bytes.appendUtf8(ch);
                      break;
              }
          }
      }
  
  
      public void parseWord(StringBuilder sb) {
          parseUntil(sb, StopCharTesters.SPACE_STOP);
      }
  
      public void parseUntil(StringBuilder sb, StopCharTester testers) {
          if (use8bit)
              bytes.parse8bit(sb, testers);
          else
              bytes.parseUTF(sb, testers);
      }
  
      public void parseUntil(StringBuilder sb, StopCharsTester testers) {
          sb.setLength(0);
          if (use8bit) {
              AppendableUtil.read8bitAndAppend(bytes, sb, testers);
          } else {
              try {
                  AppendableUtil.readUTFAndAppend(bytes, sb, testers);
              } catch (IOException e) {
                  throw new IORuntimeException(e);
              }
          }
      }
  
      public void append(CharSequence cs) {
          if (use8bit)
              bytes.append8bit(cs);
          else
              bytes.appendUtf8(cs);
      }
  
      public void append(CharSequence cs, int offset) {
          if (use8bit)
              bytes.append8bit(cs, offset, offset + cs.length());
          else
              bytes.appendUtf8(cs, offset, offset + cs.length());
      }
  
      class JSONValueOut extends TextValueOut {
          @NotNull
          BytesStore sep = Bytes.empty();
          boolean leaf = false;
  
          void prependSeparator() {
              append(sep);
              if (sep.endsWith('\n'))
                  indent();
              sep = Bytes.empty();
          }
  
          @NotNull
          @Override
          public ValueOut leaf() {
              leaf = true;
              return this;
          }
  
          @NotNull
          @Override
          public WireOut wireOut() {
              return JSONWire.this;
          }
  
          private void indent() {
          }
  
          public void elementSeparator() {
              sep = COMMA;
          }
  
          @NotNull
          @Override
          public WireOut bool(@Nullable Boolean flag) {
              prependSeparator();
              append(flag == null ? "!" + NULL : flag ? "true" : "false");
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut text(@Nullable CharSequence s) {
              prependSeparator();
              if (s == null) {
                  append("!" + NULL);
              } else {
                  bytes.appendUtf8('"');
                  escaped(s);
                  bytes.appendUtf8('"');
              }
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int8(byte i8) {
              prependSeparator();
              bytes.appendUtf8(i8);
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut bytes(@Nullable BytesStore fromBytes) {
  
              if (isText(fromBytes))
                  return text(fromBytes);
  
              int length = Maths.toInt32(fromBytes.readRemaining());
              byte[] byteArray = new byte[length];
              fromBytes.copyTo(byteArray);
              return bytes(byteArray);
          }
  
          @NotNull
          @Override
          public WireOut rawBytes(@NotNull byte[] value) {
              prependSeparator();
              bytes.write(value);
              elementSeparator();
              return JSONWire.this;
          }
  
          private boolean isText(@Nullable BytesStore fromBytes) {
  
              if (fromBytes == null)
                  return true;
              for (long i = fromBytes.readPosition(); i < fromBytes.readLimit(); i++) {
                  int ch = fromBytes.readUnsignedByte(i);
                  if ((ch < ' ' && ch != '\t') || ch >= 127)
                      return false;
              }
              return true;
          }
  
          @NotNull
          @Override
          public ValueOut writeLength(long remaining) {
              throw new UnsupportedOperationException();
          }
  
          @NotNull
          @Override
          public WireOut bytes(byte[] byteArray) {
              prependSeparator();
              append("!!binary ");
              append(Base64.getEncoder().encodeToString(byteArray));
              append(END_FIELD);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut uint8checked(int u8) {
              prependSeparator();
              bytes.appendUtf8(u8);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int16(short i16) {
              prependSeparator();
              bytes.appendUtf8(i16);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut uint16checked(int u16) {
              prependSeparator();
              bytes.appendUtf8(u16);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut utf8(int codepoint) {
              prependSeparator();
              StringBuilder sb = WireInternal.acquireStringBuilder();
              sb.appendCodePoint(codepoint);
              text(sb);
              sep = empty();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int32(int i32) {
              prependSeparator();
              bytes.appendUtf8(i32);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut uint32checked(long u32) {
              prependSeparator();
              bytes.append(u32);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int64(long i64) {
              prependSeparator();
              bytes.append(i64);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int64array(long capacity) {
              TextLongArrayReference.write(bytes, capacity);
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int64array(long capacity, @NotNull LongArrayValues values) {
              long pos = bytes.writePosition();
              TextLongArrayReference.write(bytes, capacity);
              ((Byteable) values).bytesStore(bytes, pos, bytes.writePosition() - pos);
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut float32(float f) {
              prependSeparator();
              bytes.append(f);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut float64(double d) {
              prependSeparator();
              bytes.append(d);
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut time(@NotNull LocalTime localTime) {
              prependSeparator();
              append(localTime.toString());
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut zonedDateTime(@NotNull ZonedDateTime zonedDateTime) {
              prependSeparator();
              append(zonedDateTime.toString());
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut date(@NotNull LocalDate localDate) {
              prependSeparator();
              append(localDate.toString());
              elementSeparator();
  
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public ValueOut typePrefix(@NotNull CharSequence typeName) {
              prependSeparator();
              bytes.appendUtf8('!');
              append(typeName);
              sep = SPACE;
              return this;
          }
  
          @NotNull
          @Override
          public WireOut typeLiteral(@NotNull BiConsumer<Class, Bytes> typeTranslator, Class type) {
              prependSeparator();
              append(TYPE);
              typeTranslator.accept(type, bytes);
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut typeLiteral(@NotNull CharSequence type) {
              prependSeparator();
              append(TYPE);
              text(type);
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut uuid(@NotNull UUID uuid) {
              prependSeparator();
              append(sep);
              append(uuid.toString());
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int32forBinding(int value) {
              prependSeparator();
              TextIntReference.write(bytes, value);
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int32forBinding(int value, IntValue intValue) {
              throw new UnsupportedOperationException("todo");
          }
  
          @NotNull
          @Override
          public WireOut int64forBinding(long value) {
              prependSeparator();
              TextLongReference.write(bytes, value);
              elementSeparator();
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut int64forBinding(long value, LongValue longValue) {
              throw new UnsupportedOperationException("todo");
          }
  
          @NotNull
          @Override
          public WireOut sequence(@NotNull Consumer<ValueOut> writer) {
              pushState();
              bytes.appendUtf8('[');
              sep = NEW_LINE;
              long pos = bytes.readPosition();
              writer.accept(this);
              if (bytes.writePosition() > pos + 1)
                  bytes.appendUtf8('\n');
  
              popState();
              indent();
              bytes.appendUtf8(']');
              sep = END_FIELD;
              return JSONWire.this;
          }
  
          @Override
          public WireOut array(@NotNull Consumer<ValueOut> writer, Class arrayType) {
              if (arrayType == String[].class) append("!String[] ");
              else {
                  bytes.appendUtf8('!');
                  append(arrayType.getName());
                  bytes.appendUtf8(' ');
              }
              return sequence(writer);
          }
  
          private void popState() {
              leaf = false;
          }
  
          private void pushState() {
  
          }
  
          @NotNull
          @Override
          public WireOut marshallable(@NotNull WriteMarshallable object) {
              if (!leaf)
                  pushState();
  
              prependSeparator();
              bytes.appendUtf8('{');
              sep = leaf ? SPACE : END_FIELD;
  
              object.writeMarshallable(JSONWire.this);
  
              if (!leaf)
                  popState();
              else
                  leaf = false;
              if (sep.startsWith(','))
                  append(sep, 1);
              else
                  prependSeparator();
              bytes.appendUtf8('}');
  
              sep = COMMA;
              return JSONWire.this;
          }
  
          @NotNull
          @Override
          public WireOut map(@NotNull final Map map) {
              typePrefix(SEQ_MAP);
              bytes.appendUtf8(' ');
              bytes.appendUtf8('[');
              pushState();
              sep = END_FIELD;
              map.forEach((k, v) -> {
                  prependSeparator();
                  append("{ key: ");
                  leaf();
                  object2(k);
                  sep = COMMA;
                  prependSeparator();
                  append("  value: ");
                  leaf();
                  object2(v);
                  bytes.appendUtf8(' ');
                  bytes.appendUtf8('}');
                  sep = COMMA;
              });
              popState();
              sep = END_FIELD;
              prependSeparator();
              bytes.appendUtf8(']');
              sep = END_FIELD;
              return JSONWire.this;
          }
  
          private void object2(Object v) {
              if (v instanceof CharSequence)
                  text((CharSequence) v);
              else if (v instanceof WriteMarshallable)
                  typedMarshallable((WriteMarshallable) v);
              else if (v == null)
                  append("!" + NULL);
              else
                  text(String.valueOf(v));
          }
  
          @NotNull
          @Override
          public WireOut typedMap(@NotNull Map<? extends WriteMarshallable, ? extends Marshallable> map) {
              typePrefix(SEQ_MAP);
              map.forEach((k, v) -> sequence(w -> w.marshallable(m -> m
                      .write(() -> "key").typedMarshallable(k)
                      .write(() -> "value").typedMarshallable(v))));
              return JSONWire.this;
          }
  
          @NotNull
          public ValueOut write() {
              append(sep);
              bytes.appendUtf8('"');
              bytes.appendUtf8('"');
              bytes.appendUtf8(':');
              sep = empty();
              return this;
          }
  
          @NotNull
          public ValueOut write(@NotNull WireKey key) {
              CharSequence name = key.name();
              if (name == null) name = Integer.toString(key.code());
              prependSeparator();
              bytes.appendUtf8('"');
              escaped(name);
              bytes.appendUtf8('"');
              bytes.appendUtf8(':');
              return this;
          }
  
          public void writeComment(@NotNull CharSequence s) {
          }
      }
  
      class JSONValueIn extends TextValueIn {
          @Override
          public String text() {
              return StringUtils.toString(textTo(WireInternal.acquireStringBuilder()));
          }
  
          @Nullable
          @Override
          public StringBuilder textTo(@NotNull StringBuilder sb) {
              return textTo0(sb);
          }
  
          @Nullable
          @Override
          public Bytes textTo(@NotNull Bytes bytes) {
              return textTo0(bytes);
          }
  
          @Nullable
          <ACS extends Appendable & CharSequence> ACS textTo0(@NotNull ACS a) {
              consumeWhiteSpace();
              int ch = peekCode();
  
              if (ch == '{') {
                  final long len = readLength();
                  try {
                      a.append(Bytes.toString(bytes, bytes.readPosition(), len));
                  } catch (IOException e) {
                      throw new AssertionError(e);
                  }
                  bytes.readSkip(len);
  
                  // read the next comma
                  bytes.skipTo(StopCharTesters.COMMA_STOP);
  
                  return a;
  
              } else if (ch == '"') {
                  bytes.readSkip(1);
                  if (use8bit)
                      bytes.parse8bit(a, getEscapingQuotes());
                  else
                      bytes.parseUTF(a, getEscapingQuotes());
                  unescape(a);
                  int code = peekCode();
                  if (code == '"')
                      readCode();
  
              } else if (ch == '!') {
                  bytes.readSkip(1);
                  ch = peekCode();
                  if (ch == '!') {
                      bytes.readSkip(1);
                      StringBuilder sb = WireInternal.acquireStringBuilder();
                      parseWord(sb);
                      if (StringUtils.isEqual(sb, "null")) {
                          textTo(sb);
                          return null;
                      } else if (StringUtils.isEqual(sb, "snappy")) {
                          textTo(sb);
                          try {
                              //todo needs to be made efficient
                              byte[] decodedBytes = Base64.getDecoder().decode(sb.toString().getBytes());
                              String csq = Snappy.uncompressString(decodedBytes);
                              return (ACS) WireInternal.acquireStringBuilder().append(csq);
                          } catch (IOException e) {
                              throw new AssertionError(e);
                          }
                      }
                  } else {
                      StringBuilder sb = WireInternal.acquireStringBuilder();
                      textTo(sb);
                      // ignore the type.
                  }
  
              } else {
                  if (bytes.readRemaining() > 0) {
                      if (a instanceof Bytes || use8bit)
                          bytes.parse8bit(a, getEscapingEndOfText());
                      else
                          bytes.parseUTF(a, getEscapingEndOfText());
  
                  } else {
                      AppendableUtil.setLength(a, 0);
                  }
                  // trim trailing spaces.
                  while (a.length() > 0)
                      if (Character.isWhitespace(a.charAt(a.length() - 1)))
                          AppendableUtil.setLength(a, a.length() - 1);
                      else
                          break;
              }
  
              int prev = rewindAndRead();
              if (prev == ':')
                  bytes.readSkip(-1);
              return a;
          }
  
          private int rewindAndRead() {
              return bytes.readPosition() > 0 ? bytes.readUnsignedByte(bytes.readPosition() - 1) : -1;
          }
  
          @NotNull
          @Override
          public WireIn bytes(@NotNull Bytes toBytes) {
              return bytes(wi -> toBytes.write(wi.bytes()));
          }
  
          @NotNull
          public WireIn bytes(@NotNull ReadMarshallable bytesConsumer) {
              consumeWhiteSpace();
  
              // TODO needs to be made much more efficient.
  