content: /*
   * Copyright (C) 2016-2023 ActionTech.
   * based on code by MyCATCopyrightHolder Copyright (c) 2013, OpenCloudDB/MyCAT.
   * License: http://www.gnu.org/licenses/gpl.html GPL version 2 or higher.
   */
  
  package com.actiontech.dble.plan.common.item.function.jsonfunc;
  
  import java.io.Closeable;
  import java.io.Flushable;
  import java.io.IOException;
  import java.io.Writer;
  import java.util.Arrays;
  
  import static com.actiontech.dble.plan.common.item.function.jsonfunc.JsonScope.*;
  
  /**
   * migrate from com.google.gson.stream.JsonWriter class in package gson 2.8.9
   * for issue inner-1940
   * <p>
   * migrate reason: "keep the same format as mysql"
   * improve: add space for ":"&","
   */
  public class CustomJsonWriter implements Closeable, Flushable {
  
      /*
       * From RFC 7159, "All Unicode characters may be placed within the
       * quotation marks except for the characters that must be escaped:
       * quotation mark, reverse solidus, and the control characters
       * (U+0000 through U+001F)."
       *
       * We also escape '\u2028' and '\u2029', which JavaScript interprets as
       * newline characters. This prevents eval() from failing with a syntax
       * error. http://code.google.com/p/google-gson/issues/detail?id=341
       */
      private static final String[] REPLACEMENT_CHARS;
      private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
  
      static {
          REPLACEMENT_CHARS = new String[128];
          for (int i = 0; i <= 0x1f; i++) {
              REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i);
          }
          REPLACEMENT_CHARS['"'] = "\\\"";
          REPLACEMENT_CHARS['\\'] = "\\\\";
          REPLACEMENT_CHARS['\t'] = "\\t";
          REPLACEMENT_CHARS['\b'] = "\\b";
          REPLACEMENT_CHARS['\n'] = "\\n";
          REPLACEMENT_CHARS['\r'] = "\\r";
          REPLACEMENT_CHARS['\f'] = "\\f";
          HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone();
          HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c";
          HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e";
          HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026";
          HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d";
          HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027";
      }
  
      /**
       * The output data, containing at most one top-level array or object.
       */
      private final Writer out;
  
      private int[] stack = new int[32];
      private int stackSize = 0;
  
      {
          push(EMPTY_DOCUMENT);
      }
  
      /**
       * A string containing a full set of spaces for a single level of
       * indentation, or null for no pretty printing.
       */
      private String indent;
  
      /**
       * The name/value separator; either ":" or ": ".
       */
      private String separator = ": ";
  
      private boolean lenient;
  
      private boolean htmlSafe;
  
      private String deferredName;
  
      private boolean serializeNulls = true;
  
      /**
       * Creates a new instance that writes a JSON-encoded stream to {@code out}.
       * For best performance, ensure {@link Writer} is buffered; wrapping in
       * {@link java.io.BufferedWriter BufferedWriter} if necessary.
       */
      public CustomJsonWriter(Writer out) {
          if (out == null) {
              throw new NullPointerException("out == null");
          }
          this.out = out;
      }
  
      /**
       * Sets the indentation string to be repeated for each level of indentation
       * in the encoded document. If {@code indent.isEmpty()} the encoded document
       * will be compact. Otherwise the encoded document will be more
       * human-readable.
       *
       * @param indent a string containing only whitespace.
       */
      public final void setIndent(String indent) {
          if (indent.length() == 0) {
              this.indent = null;
              this.separator = ":";
          } else {
              this.indent = indent;
              this.separator = ": ";
          }
      }
  
      /**
       * Configure this writer to relax its syntax rules. By default, this writer
       * only emits well-formed JSON as specified by <a
       * href="http://www.ietf.org/rfc/rfc7159.txt">RFC 7159</a>. Setting the writer
       * to lenient permits the following:
       * <ul>
       *   <li>Top-level values of any type. With strict writing, the top-level
       *       value must be an object or an array.
       *   <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
       *       Double#isInfinite() infinities}.
       * </ul>
       */
      public final void setLenient(boolean lenient) {
          this.lenient = lenient;
      }
  
      /**
       * Returns true if this writer has relaxed syntax rules.
       */
      public boolean isLenient() {
          return lenient;
      }
  
      /**
       * Configure this writer to emit JSON that's safe for direct inclusion in HTML
       * and XML documents. This escapes the HTML characters {@code <}, {@code >},
       * {@code &} and {@code =} before writing them to the stream. Without this
       * setting, your XML/HTML encoder should replace these characters with the
       * corresponding escape sequences.
       */
      public final void setHtmlSafe(boolean htmlSafe) {
          this.htmlSafe = htmlSafe;
      }
  
      /**
       * Returns true if this writer writes JSON that's safe for inclusion in HTML
       * and XML documents.
       */
      public final boolean isHtmlSafe() {
          return htmlSafe;
      }
  
      /**
       * Sets whether object members are serialized when their value is null.
       * This has no impact on array elements. The default is true.
       */
      public final void setSerializeNulls(boolean serializeNulls) {
          this.serializeNulls = serializeNulls;
      }
  
      /**
       * Returns true if object members are serialized when their value is null.
       * This has no impact on array elements. The default is true.
       */
      public final boolean getSerializeNulls() {
          return serializeNulls;
      }
  
      /**
       * Begins encoding a new array. Each call to this method must be paired with
       * a call to {@link #endArray}.
       *
       * @return this writer.
       */
      public CustomJsonWriter beginArray() throws IOException {
          writeDeferredName();
          return open(EMPTY_ARRAY, '[');
      }
  
      /**
       * Ends encoding the current array.
       *
       * @return this writer.
       */
      public CustomJsonWriter endArray() throws IOException {
          return close(EMPTY_ARRAY, NONEMPTY_ARRAY, ']');
      }
  
      /**
       * Begins encoding a new object. Each call to this method must be paired
       * with a call to {@link #endObject}.
       *
       * @return this writer.
       */
      public CustomJsonWriter beginObject() throws IOException {
          writeDeferredName();
          return open(EMPTY_OBJECT, '{');
      }
  
      /**
       * Ends encoding the current object.
       *
       * @return this writer.
       */
      public CustomJsonWriter endObject() throws IOException {
          return close(EMPTY_OBJECT, NONEMPTY_OBJECT, '}');
      }
  
      /**
       * Enters a new scope by appending any necessary whitespace and the given
       * bracket.
       */
      private CustomJsonWriter open(int empty, char openBracket) throws IOException {
          beforeValue();
          push(empty);
          out.write(openBracket);
          return this;
      }
  
      /**
       * Flushes and closes this writer and the underlying {@link Writer}.
       *
       * @throws IOException if the JSON document is incomplete.
       */
      public void close() throws IOException {
          out.close();
  
          int size = stackSize;
          if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) {
              throw new IOException("Incomplete document");
          }
          stackSize = 0;
      }
  
      /**
       * Closes the current scope by appending any necessary whitespace and the
       * given bracket.
       */
      private CustomJsonWriter close(int empty, int nonempty, char closeBracket)
              throws IOException {
          int context = peek();
          if (context != nonempty && context != empty) {
              throw new IllegalStateException("Nesting problem.");
          }
          if (deferredName != null) {
              throw new IllegalStateException("Dangling name: " + deferredName);
          }
  
          stackSize--;
          if (context == nonempty) {
              newline();
          }
          out.write(closeBracket);
          return this;
      }
  
      private void push(int newTop) {
          if (stackSize == stack.length) {
              stack = Arrays.copyOf(stack, stackSize * 2);
          }
          stack[stackSize++] = newTop;
      }
  
      /**
       * Returns the value on the top of the stack.
       */
      private int peek() {
          if (stackSize == 0) {
              throw new IllegalStateException("JsonWriter is closed.");
          }
          return stack[stackSize - 1];
      }
  
      /**
       * Replace the value on the top of the stack with the given value.
       */
      private void replaceTop(int topOfStack) {
          stack[stackSize - 1] = topOfStack;
      }
  
      /**
       * Encodes the property name.
       *
       * @param name the name of the forthcoming value. May not be null.
       * @return this writer.
       */
      public CustomJsonWriter name(String name) throws IOException {
          if (name == null) {
              throw new NullPointerException("name == null");
          }
          if (deferredName != null) {
              throw new IllegalStateException();
          }
          if (stackSize == 0) {
              throw new IllegalStateException("JsonWriter is closed.");
          }
          deferredName = name;
          return this;
      }
  
      private void writeDeferredName() throws IOException {
          if (deferredName != null) {
              beforeName();
              string(deferredName);
              deferredName = null;
          }
      }
  
  
      /**
       * Writes {@code value} directly to the writer without quoting or
       * escaping.
       *
       * @param value the literal string value, or null to encode a null literal.
       * @return this writer.
       */
      public CustomJsonWriter jsonValue(String value) throws IOException {
          if (value == null) {
              return nullValue();
          }
          writeDeferredName();
          beforeValue();
          out.append(value);
          return this;
      }
  
      /**
       * Encodes {@code null}.
       *
       * @return this writer.
       */
      public CustomJsonWriter nullValue() throws IOException {
          if (deferredName != null) {
              if (serializeNulls) {
                  writeDeferredName();
              } else {
                  deferredName = null;
                  return this; // skip the name and the value
              }
          }
          beforeValue();
          out.write("null");
          return this;
      }
  
  
      /**
       * Encodes {@code value}.
       *
       * @param value the literal string value, or null to encode a null literal.
       * @return this writer.
       */
      public CustomJsonWriter value(String value) throws IOException {
          if (value == null) {
              return nullValue();
          }
          writeDeferredName();
          beforeValue();
          string(value);
          return this;
      }
  
      /**
       * Encodes {@code value}.
       *
       * @return this writer.
       */
      public CustomJsonWriter value(boolean value) throws IOException {
          writeDeferredName();
          beforeValue();
          out.write(value ? "true" : "false");
          return this;
      }
  
      /**
       * Encodes {@code value}.
       *
       * @return this writer.
       */
      public CustomJsonWriter value(Boolean value) throws IOException {
          if (value == null) {
              return nullValue();
          }
          writeDeferredName();
          beforeValue();
          out.write(value ? "true" : "false");
          return this;
      }
  
      /**
       * Encodes {@code value}.
       *
       * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
       *              {@link Double#isInfinite() infinities}.
       * @return this writer.
       */
      public CustomJsonWriter value(double value) throws IOException {
          writeDeferredName();
          if (!lenient && (Double.isNaN(value) || Double.isInfinite(value))) {
              throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
          }
          beforeValue();
          out.append(Double.toString(value));
          return this;
      }
  
      /**
       * Encodes {@code value}.
       *
       * @return this writer.
       */
      public CustomJsonWriter value(long value) throws IOException {
          writeDeferredName();
          beforeValue();
          out.write(Long.toString(value));
          return this;
      }
  
      /**
       * Encodes {@code value}.
       *
       * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
       *              {@link Double#isInfinite() infinities}.
       * @return this writer.
       */
      public CustomJsonWriter value(Number value) throws IOException {
          if (value == null) {
              return nullValue();
          }
  
          writeDeferredName();
          String string = value.toString();
          if (!lenient && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
              throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
          }
          beforeValue();
          out.append(string);
          return this;
      }
  
      /**
       * Ensures all buffered data is written to the underlying {@link Writer}
       * and flushes that writer.
       */
      public void flush() throws IOException {
          if (stackSize == 0) {
              throw new IllegalStateException("JsonWriter is closed.");
          }
          out.flush();
      }
  
  
      private void string(String value) throws IOException {
          String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS;
          out.write('\"');
          int last = 0;
          int length = value.length();
          // CHECKSTYLE:OFF
          for (int i = 0; i < length; i++) {
              char c = value.charAt(i);
              String replacement;
              if (c < 128) {
                  replacement = replacements[c];
                  if (replacement == null) {
                      continue;
                  }
  
              } else if (c == '\u2028') {
                  replacement = "\\u2028";
              } else if (c == '\u2029') {
                  replacement = "\\u2029";
              } else {
                  continue;
              }
              if (last < i) {
                  out.write(value, last, i - last);
              }
              out.write(replacement);
              last = i + 1;
          }
          // CHECKSTYLE:ON
          if (last < length) {
              out.write(value, last, length - last);
          }
          out.write('\"');
      }
  
  
      private void newline() throws IOException {
          if (indent == null) {
              return;
          }
  
          out.write('\n');
          for (int i = 1, size = stackSize; i < size; i++) {
              out.write(indent);
          }
      }
  
      /**
       * Inserts any necessary separators and whitespace before a name. Also
       * adjusts the stack to expect the name's value.
       */
      private void beforeName() throws IOException {
          int context = peek();
          if (context == NONEMPTY_OBJECT) { // first in object
              out.write(", ");
          } else if (context != EMPTY_OBJECT) { // not in an object!
              throw new IllegalStateException("Nesting problem.");
          }
          newline();
          replaceTop(DANGLING_NAME);
      }
  
      /**
       * Inserts any necessary separators and whitespace before a literal value,
       * inline array, or inline object. Also adjusts the stack to expect either a
       * closing bracket or another element.
       */
      @SuppressWarnings("fallthrough")
      private void beforeValue() throws IOException {
          switch (peek()) {
              case NONEMPTY_DOCUMENT:
                  if (!lenient) {
                      throw new IllegalStateException(
                              "JSON must have only one top-level value.");
                  }
                  // fall-through
              case EMPTY_DOCUMENT: // first in document
                  replaceTop(NONEMPTY_DOCUMENT);
                  break;
  
              case EMPTY_ARRAY: // first in array
                  replaceTop(NONEMPTY_ARRAY);
                  newline();
                  break;
  
              case NONEMPTY_ARRAY: // another in array
                  out.append(", ");
                  newline();
                  break;
  
              case DANGLING_NAME: // value for name
                  out.append(separator);
                  replaceTop(NONEMPTY_OBJECT);
                  break;
  
              default:
                  throw new IllegalStateException("Nesting problem.");
          }
      }
  }
  