github.com/keysonZZZ/kmg@v0.0.0-20151121023212-05317bfd7d39/kmgRpc/kmgRpcJava/java/src/com/google/gson/stream/JsonWriter.java (about)

     1  /*
     2   * Copyright (C) 2010 Google Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *      http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package com.google.gson.stream;
    18  
    19  import java.io.Closeable;
    20  import java.io.Flushable;
    21  import java.io.IOException;
    22  import java.io.Writer;
    23  
    24  import static com.google.gson.stream.JsonScope.DANGLING_NAME;
    25  import static com.google.gson.stream.JsonScope.EMPTY_ARRAY;
    26  import static com.google.gson.stream.JsonScope.EMPTY_DOCUMENT;
    27  import static com.google.gson.stream.JsonScope.EMPTY_OBJECT;
    28  import static com.google.gson.stream.JsonScope.NONEMPTY_ARRAY;
    29  import static com.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT;
    30  import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
    31  
    32  /**
    33   * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
    34   * encoded value to a stream, one token at a time. The stream includes both
    35   * literal values (strings, numbers, booleans and nulls) as well as the begin
    36   * and end delimiters of objects and arrays.
    37   *
    38   * <h3>Encoding JSON</h3>
    39   * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
    40   * document must contain one top-level array or object. Call methods on the
    41   * writer as you walk the structure's contents, nesting arrays and objects as
    42   * necessary:
    43   * <ul>
    44   *   <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
    45   *       Write each of the array's elements with the appropriate {@link #value}
    46   *       methods or by nesting other arrays and objects. Finally close the array
    47   *       using {@link #endArray()}.
    48   *   <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
    49   *       Write each of the object's properties by alternating calls to
    50   *       {@link #name} with the property's value. Write property values with the
    51   *       appropriate {@link #value} method or by nesting other objects or arrays.
    52   *       Finally close the object using {@link #endObject()}.
    53   * </ul>
    54   *
    55   * <h3>Example</h3>
    56   * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
    57   * [
    58   *   {
    59   *     "id": 912345678901,
    60   *     "text": "How do I stream JSON in Java?",
    61   *     "geo": null,
    62   *     "user": {
    63   *       "name": "json_newb",
    64   *       "followers_count": 41
    65   *      }
    66   *   },
    67   *   {
    68   *     "id": 912345678902,
    69   *     "text": "@json_newb just use JsonWriter!",
    70   *     "geo": [50.454722, -104.606667],
    71   *     "user": {
    72   *       "name": "jesse",
    73   *       "followers_count": 2
    74   *     }
    75   *   }
    76   * ]}</pre>
    77   * This code encodes the above structure: <pre>   {@code
    78   *   public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
    79   *     JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
    80   *     writer.setIndentSpaces(4);
    81   *     writeMessagesArray(writer, messages);
    82   *     writer.close();
    83   *   }
    84   *
    85   *   public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
    86   *     writer.beginArray();
    87   *     for (Message message : messages) {
    88   *       writeMessage(writer, message);
    89   *     }
    90   *     writer.endArray();
    91   *   }
    92   *
    93   *   public void writeMessage(JsonWriter writer, Message message) throws IOException {
    94   *     writer.beginObject();
    95   *     writer.name("id").value(message.getId());
    96   *     writer.name("text").value(message.getText());
    97   *     if (message.getGeo() != null) {
    98   *       writer.name("geo");
    99   *       writeDoublesArray(writer, message.getGeo());
   100   *     } else {
   101   *       writer.name("geo").nullValue();
   102   *     }
   103   *     writer.name("user");
   104   *     writeUser(writer, message.getUser());
   105   *     writer.endObject();
   106   *   }
   107   *
   108   *   public void writeUser(JsonWriter writer, User user) throws IOException {
   109   *     writer.beginObject();
   110   *     writer.name("name").value(user.getName());
   111   *     writer.name("followers_count").value(user.getFollowersCount());
   112   *     writer.endObject();
   113   *   }
   114   *
   115   *   public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
   116   *     writer.beginArray();
   117   *     for (Double value : doubles) {
   118   *       writer.value(value);
   119   *     }
   120   *     writer.endArray();
   121   *   }}</pre>
   122   *
   123   * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
   124   * Instances of this class are not thread safe. Calls that would result in a
   125   * malformed JSON string will fail with an {@link IllegalStateException}.
   126   *
   127   * @author Jesse Wilson
   128   * @since 1.6
   129   */
   130  public class JsonWriter implements Closeable, Flushable {
   131  
   132    /*
   133     * From RFC 4627, "All Unicode characters may be placed within the
   134     * quotation marks except for the characters that must be escaped:
   135     * quotation mark, reverse solidus, and the control characters
   136     * (U+0000 through U+001F)."
   137     *
   138     * We also escape '\u2028' and '\u2029', which JavaScript interprets as
   139     * newline characters. This prevents eval() from failing with a syntax
   140     * error. http://code.google.com/p/google-gson/issues/detail?id=341
   141     */
   142    private static final String[] REPLACEMENT_CHARS;
   143    private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
   144    static {
   145      REPLACEMENT_CHARS = new String[128];
   146      for (int i = 0; i <= 0x1f; i++) {
   147        REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i);
   148      }
   149      REPLACEMENT_CHARS['"'] = "\\\"";
   150      REPLACEMENT_CHARS['\\'] = "\\\\";
   151      REPLACEMENT_CHARS['\t'] = "\\t";
   152      REPLACEMENT_CHARS['\b'] = "\\b";
   153      REPLACEMENT_CHARS['\n'] = "\\n";
   154      REPLACEMENT_CHARS['\r'] = "\\r";
   155      REPLACEMENT_CHARS['\f'] = "\\f";
   156      HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone();
   157      HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c";
   158      HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e";
   159      HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026";
   160      HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d";
   161      HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027";
   162    }
   163  
   164    /** The output data, containing at most one top-level array or object. */
   165    private final Writer out;
   166  
   167    private int[] stack = new int[32];
   168    private int stackSize = 0;
   169    {
   170      push(EMPTY_DOCUMENT);
   171    }
   172  
   173    /**
   174     * A string containing a full set of spaces for a single level of
   175     * indentation, or null for no pretty printing.
   176     */
   177    private String indent;
   178  
   179    /**
   180     * The name/value separator; either ":" or ": ".
   181     */
   182    private String separator = ":";
   183  
   184    private boolean lenient;
   185  
   186    private boolean htmlSafe;
   187  
   188    private String deferredName;
   189  
   190    private boolean serializeNulls = true;
   191  
   192    /**
   193     * Creates a new instance that writes a JSON-encoded stream to {@code out}.
   194     * For best performance, ensure {@link Writer} is buffered; wrapping in
   195     * {@link java.io.BufferedWriter BufferedWriter} if necessary.
   196     */
   197    public JsonWriter(Writer out) {
   198      if (out == null) {
   199        throw new NullPointerException("out == null");
   200      }
   201      this.out = out;
   202    }
   203  
   204    /**
   205     * Sets the indentation string to be repeated for each level of indentation
   206     * in the encoded document. If {@code indent.isEmpty()} the encoded document
   207     * will be compact. Otherwise the encoded document will be more
   208     * human-readable.
   209     *
   210     * @param indent a string containing only whitespace.
   211     */
   212    public final void setIndent(String indent) {
   213      if (indent.length() == 0) {
   214        this.indent = null;
   215        this.separator = ":";
   216      } else {
   217        this.indent = indent;
   218        this.separator = ": ";
   219      }
   220    }
   221  
   222    /**
   223     * Configure this writer to relax its syntax rules. By default, this writer
   224     * only emits well-formed JSON as specified by <a
   225     * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer
   226     * to lenient permits the following:
   227     * <ul>
   228     *   <li>Top-level values of any type. With strict writing, the top-level
   229     *       value must be an object or an array.
   230     *   <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
   231     *       Double#isInfinite() infinities}.
   232     * </ul>
   233     */
   234    public final void setLenient(boolean lenient) {
   235      this.lenient = lenient;
   236    }
   237  
   238    /**
   239     * Returns true if this writer has relaxed syntax rules.
   240     */
   241    public boolean isLenient() {
   242      return lenient;
   243    }
   244  
   245    /**
   246     * Configure this writer to emit JSON that's safe for direct inclusion in HTML
   247     * and XML documents. This escapes the HTML characters {@code <}, {@code >},
   248     * {@code &} and {@code =} before writing them to the stream. Without this
   249     * setting, your XML/HTML encoder should replace these characters with the
   250     * corresponding escape sequences.
   251     */
   252    public final void setHtmlSafe(boolean htmlSafe) {
   253      this.htmlSafe = htmlSafe;
   254    }
   255  
   256    /**
   257     * Returns true if this writer writes JSON that's safe for inclusion in HTML
   258     * and XML documents.
   259     */
   260    public final boolean isHtmlSafe() {
   261      return htmlSafe;
   262    }
   263  
   264    /**
   265     * Sets whether object members are serialized when their value is null.
   266     * This has no impact on array elements. The default is true.
   267     */
   268    public final void setSerializeNulls(boolean serializeNulls) {
   269      this.serializeNulls = serializeNulls;
   270    }
   271  
   272    /**
   273     * Returns true if object members are serialized when their value is null.
   274     * This has no impact on array elements. The default is true.
   275     */
   276    public final boolean getSerializeNulls() {
   277      return serializeNulls;
   278    }
   279  
   280    /**
   281     * Begins encoding a new array. Each call to this method must be paired with
   282     * a call to {@link #endArray}.
   283     *
   284     * @return this writer.
   285     */
   286    public JsonWriter beginArray() throws IOException {
   287      writeDeferredName();
   288      return open(EMPTY_ARRAY, "[");
   289    }
   290  
   291    /**
   292     * Ends encoding the current array.
   293     *
   294     * @return this writer.
   295     */
   296    public JsonWriter endArray() throws IOException {
   297      return close(EMPTY_ARRAY, NONEMPTY_ARRAY, "]");
   298    }
   299  
   300    /**
   301     * Begins encoding a new object. Each call to this method must be paired
   302     * with a call to {@link #endObject}.
   303     *
   304     * @return this writer.
   305     */
   306    public JsonWriter beginObject() throws IOException {
   307      writeDeferredName();
   308      return open(EMPTY_OBJECT, "{");
   309    }
   310  
   311    /**
   312     * Ends encoding the current object.
   313     *
   314     * @return this writer.
   315     */
   316    public JsonWriter endObject() throws IOException {
   317      return close(EMPTY_OBJECT, NONEMPTY_OBJECT, "}");
   318    }
   319  
   320    /**
   321     * Enters a new scope by appending any necessary whitespace and the given
   322     * bracket.
   323     */
   324    private JsonWriter open(int empty, String openBracket) throws IOException {
   325      beforeValue(true);
   326      push(empty);
   327      out.write(openBracket);
   328      return this;
   329    }
   330  
   331    /**
   332     * Closes the current scope by appending any necessary whitespace and the
   333     * given bracket.
   334     */
   335    private JsonWriter close(int empty, int nonempty, String closeBracket)
   336        throws IOException {
   337      int context = peek();
   338      if (context != nonempty && context != empty) {
   339        throw new IllegalStateException("Nesting problem.");
   340      }
   341      if (deferredName != null) {
   342        throw new IllegalStateException("Dangling name: " + deferredName);
   343      }
   344  
   345      stackSize--;
   346      if (context == nonempty) {
   347        newline();
   348      }
   349      out.write(closeBracket);
   350      return this;
   351    }
   352  
   353    private void push(int newTop) {
   354      if (stackSize == stack.length) {
   355        int[] newStack = new int[stackSize * 2];
   356        System.arraycopy(stack, 0, newStack, 0, stackSize);
   357        stack = newStack;
   358      }
   359      stack[stackSize++] = newTop;
   360    }
   361  
   362    /**
   363     * Returns the value on the top of the stack.
   364     */
   365    private int peek() {
   366      if (stackSize == 0) {
   367        throw new IllegalStateException("JsonWriter is closed.");
   368      }
   369      return stack[stackSize - 1];
   370    }
   371  
   372    /**
   373     * Replace the value on the top of the stack with the given value.
   374     */
   375    private void replaceTop(int topOfStack) {
   376      stack[stackSize - 1] = topOfStack;
   377    }
   378  
   379    /**
   380     * Encodes the property name.
   381     *
   382     * @param name the name of the forthcoming value. May not be null.
   383     * @return this writer.
   384     */
   385    public JsonWriter name(String name) throws IOException {
   386      if (name == null) {
   387        throw new NullPointerException("name == null");
   388      }
   389      if (deferredName != null) {
   390        throw new IllegalStateException();
   391      }
   392      if (stackSize == 0) {
   393        throw new IllegalStateException("JsonWriter is closed.");
   394      }
   395      deferredName = name;
   396      return this;
   397    }
   398  
   399    private void writeDeferredName() throws IOException {
   400      if (deferredName != null) {
   401        beforeName();
   402        string(deferredName);
   403        deferredName = null;
   404      }
   405    }
   406  
   407    /**
   408     * Encodes {@code value}.
   409     *
   410     * @param value the literal string value, or null to encode a null literal.
   411     * @return this writer.
   412     */
   413    public JsonWriter value(String value) throws IOException {
   414      if (value == null) {
   415        return nullValue();
   416      }
   417      writeDeferredName();
   418      beforeValue(false);
   419      string(value);
   420      return this;
   421    }
   422  
   423    /**
   424     * Encodes {@code null}.
   425     *
   426     * @return this writer.
   427     */
   428    public JsonWriter nullValue() throws IOException {
   429      if (deferredName != null) {
   430        if (serializeNulls) {
   431          writeDeferredName();
   432        } else {
   433          deferredName = null;
   434          return this; // skip the name and the value
   435        }
   436      }
   437      beforeValue(false);
   438      out.write("null");
   439      return this;
   440    }
   441  
   442    /**
   443     * Encodes {@code value}.
   444     *
   445     * @return this writer.
   446     */
   447    public JsonWriter value(boolean value) throws IOException {
   448      writeDeferredName();
   449      beforeValue(false);
   450      out.write(value ? "true" : "false");
   451      return this;
   452    }
   453  
   454    /**
   455     * Encodes {@code value}.
   456     *
   457     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
   458     *     {@link Double#isInfinite() infinities}.
   459     * @return this writer.
   460     */
   461    public JsonWriter value(double value) throws IOException {
   462      if (Double.isNaN(value) || Double.isInfinite(value)) {
   463        throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
   464      }
   465      writeDeferredName();
   466      beforeValue(false);
   467      out.append(Double.toString(value));
   468      return this;
   469    }
   470  
   471    /**
   472     * Encodes {@code value}.
   473     *
   474     * @return this writer.
   475     */
   476    public JsonWriter value(long value) throws IOException {
   477      writeDeferredName();
   478      beforeValue(false);
   479      out.write(Long.toString(value));
   480      return this;
   481    }
   482  
   483    /**
   484     * Encodes {@code value}.
   485     *
   486     * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
   487     *     {@link Double#isInfinite() infinities}.
   488     * @return this writer.
   489     */
   490    public JsonWriter value(Number value) throws IOException {
   491      if (value == null) {
   492        return nullValue();
   493      }
   494  
   495      writeDeferredName();
   496      String string = value.toString();
   497      if (!lenient
   498          && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
   499        throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
   500      }
   501      beforeValue(false);
   502      out.append(string);
   503      return this;
   504    }
   505  
   506    /**
   507     * Ensures all buffered data is written to the underlying {@link Writer}
   508     * and flushes that writer.
   509     */
   510    public void flush() throws IOException {
   511      if (stackSize == 0) {
   512        throw new IllegalStateException("JsonWriter is closed.");
   513      }
   514      out.flush();
   515    }
   516  
   517    /**
   518     * Flushes and closes this writer and the underlying {@link Writer}.
   519     *
   520     * @throws IOException if the JSON document is incomplete.
   521     */
   522    public void close() throws IOException {
   523      out.close();
   524  
   525      int size = stackSize;
   526      if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) {
   527        throw new IOException("Incomplete document");
   528      }
   529      stackSize = 0;
   530    }
   531  
   532    private void string(String value) throws IOException {
   533      String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS;
   534      out.write("\"");
   535      int last = 0;
   536      int length = value.length();
   537      for (int i = 0; i < length; i++) {
   538        char c = value.charAt(i);
   539        String replacement;
   540        if (c < 128) {
   541          replacement = replacements[c];
   542          if (replacement == null) {
   543            continue;
   544          }
   545        } else if (c == '\u2028') {
   546          replacement = "\\u2028";
   547        } else if (c == '\u2029') {
   548          replacement = "\\u2029";
   549        } else {
   550          continue;
   551        }
   552        if (last < i) {
   553          out.write(value, last, i - last);
   554        }
   555        out.write(replacement);
   556        last = i + 1;
   557      }
   558      if (last < length) {
   559        out.write(value, last, length - last);
   560      }
   561      out.write("\"");
   562    }
   563  
   564    private void newline() throws IOException {
   565      if (indent == null) {
   566        return;
   567      }
   568  
   569      out.write("\n");
   570      for (int i = 1, size = stackSize; i < size; i++) {
   571        out.write(indent);
   572      }
   573    }
   574  
   575    /**
   576     * Inserts any necessary separators and whitespace before a name. Also
   577     * adjusts the stack to expect the name's value.
   578     */
   579    private void beforeName() throws IOException {
   580      int context = peek();
   581      if (context == NONEMPTY_OBJECT) { // first in object
   582        out.write(',');
   583      } else if (context != EMPTY_OBJECT) { // not in an object!
   584        throw new IllegalStateException("Nesting problem.");
   585      }
   586      newline();
   587      replaceTop(DANGLING_NAME);
   588    }
   589  
   590    /**
   591     * Inserts any necessary separators and whitespace before a literal value,
   592     * inline array, or inline object. Also adjusts the stack to expect either a
   593     * closing bracket or another element.
   594     *
   595     * @param root true if the value is a new array or object, the two values
   596     *     permitted as top-level elements.
   597     */
   598    @SuppressWarnings("fallthrough")
   599    private void beforeValue(boolean root) throws IOException {
   600      switch (peek()) {
   601      case NONEMPTY_DOCUMENT:
   602        if (!lenient) {
   603          throw new IllegalStateException(
   604              "JSON must have only one top-level value.");
   605        }
   606        // fall-through
   607      case EMPTY_DOCUMENT: // first in document
   608        if (!lenient && !root) {
   609          throw new IllegalStateException(
   610              "JSON must start with an array or an object.");
   611        }
   612        replaceTop(NONEMPTY_DOCUMENT);
   613        break;
   614  
   615      case EMPTY_ARRAY: // first in array
   616        replaceTop(NONEMPTY_ARRAY);
   617        newline();
   618        break;
   619  
   620      case NONEMPTY_ARRAY: // another in array
   621        out.append(',');
   622        newline();
   623        break;
   624  
   625      case DANGLING_NAME: // value for name
   626        out.append(separator);
   627        replaceTop(NONEMPTY_OBJECT);
   628        break;
   629  
   630      default:
   631        throw new IllegalStateException("Nesting problem.");
   632      }
   633    }
   634  }