001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.json;
014
015import java.io.*;
016
017import org.apache.juneau.*;
018import org.apache.juneau.common.internal.*;
019import org.apache.juneau.internal.*;
020import org.apache.juneau.serializer.*;
021
022/**
023 * Specialized writer for serializing JSON.
024 *
025 * <h5 class='section'>Notes:</h5><ul>
026 *    <li class='note'>
027 *       This class is not intended for external use.
028 * </ul>
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.JsonDetails">JSON Details</a>
032
033 * </ul>
034 */
035public final class JsonWriter extends SerializerWriter {
036
037   private final boolean simpleAttrs, escapeSolidus;
038
039   // Characters that trigger special handling of serializing attribute values.
040   private static final AsciiSet
041      encodedChars = AsciiSet.create("\n\t\b\f\r'\"\\"),
042      encodedChars2 = AsciiSet.create("\n\t\b\f\r'\"\\/");
043
044   private static final KeywordSet reservedWords = new KeywordSet(
045      "arguments","break","case","catch","class","const","continue","debugger","default","delete",
046      "do","else","enum","eval","export","extends","false","finally","for","function","if",
047      "implements","import","in","instanceof","interface","let","new","null","package",
048      "private","protected","public","return","static","super","switch","this","throw",
049      "true","try","typeof","var","void","while","with","undefined","yield"
050   );
051
052
053   // Characters that represent attribute name characters that don't trigger quoting.
054   // These are actually more strict than the actual Javascript specification, but
055   // can be narrowed in the future if necessary.
056   // For example, we quote attributes that start with $ even though we don't need to.
057   private static final AsciiSet validAttrChars = AsciiSet.create().ranges("a-z","A-Z","0-9").chars("_").build();
058   private static final AsciiSet validFirstAttrChars = AsciiSet.create().ranges("a-z","A-Z").chars("_").build();
059
060   private final AsciiSet ec;
061
062   /**
063    * Constructor.
064    *
065    * @param out The writer being wrapped.
066    * @param useWhitespace If <jk>true</jk>, tabs and spaces will be used in output.
067    * @param maxIndent The maximum indentation level.
068    * @param escapeSolidus If <jk>true</jk>, forward slashes should be escaped in the output.
069    * @param quoteChar The quote character to use (i.e. <js>'\''</js> or <js>'"'</js>)
070    * @param simpleAttrs If <jk>true</jk>, JSON attributes will only be quoted when necessary.
071    * @param trimStrings If <jk>true</jk>, strings will be trimmed before being serialized.
072    * @param uriResolver The URI resolver for resolving URIs to absolute or root-relative form.
073    */
074   protected JsonWriter(Writer out, boolean useWhitespace, int maxIndent, boolean escapeSolidus, char quoteChar,
075         boolean simpleAttrs, boolean trimStrings, UriResolver uriResolver) {
076      super(out, useWhitespace, maxIndent, trimStrings, quoteChar, uriResolver);
077      this.simpleAttrs = simpleAttrs;
078      this.escapeSolidus = escapeSolidus;
079      this.ec = escapeSolidus ? encodedChars2 : encodedChars;
080   }
081
082   /**
083    * Serializes the specified object as a JSON string value.
084    *
085    * @param s The object being serialized.
086    * @return This object.
087    */
088   public JsonWriter stringValue(String s) {
089      if (s == null)
090         return this;
091      boolean doConvert = false;
092      for (int i = 0; i < s.length() && ! doConvert; i++) {
093         char c = s.charAt(i);
094         doConvert |= ec.contains(c);
095      }
096      q();
097      if (! doConvert) {
098         w(s);
099      } else {
100         for (int i = 0; i < s.length(); i++) {
101            char c = s.charAt(i);
102            if (ec.contains(c)) {
103               if (c == '\n')
104                  w('\\').w('n');
105               else if (c == '\t')
106                  w('\\').w('t');
107               else if (c == '\b')
108                  w('\\').w('b');
109               else if (c == '\f')
110                  w('\\').w('f');
111               else if (c == quoteChar)
112                  w('\\').w(quoteChar);
113               else if (c == '\\')
114                  w('\\').w('\\');
115               else if (c == '/' && escapeSolidus)
116                  w('\\').w('/');
117               else if (c != '\r')
118                  w(c);
119            } else {
120               w(c);
121            }
122         }
123      }
124      q();
125      return this;
126   }
127
128   /**
129    * Serializes the specified object as a JSON attribute name.
130    *
131    * @param s The object being serialized.
132    * @return This object.
133    */
134   public JsonWriter attr(String s) {
135
136      if (trimStrings)
137         s = StringUtils.trim(s);
138
139      /*
140       * Converts a Java string to an acceptable JSON attribute name. If
141       * simpleAttrs is true, then quotes will only be used if the attribute
142       * name consists of only alphanumeric characters.
143       */
144      boolean doConvert = ! simpleAttrs;     // Always convert when not in lax mode.
145
146      // If the attribute is null, it must always be printed as null without quotes.
147      // Technically, this isn't part of the JSON spec, but it does allow for null key values.
148      if (s == null) {
149         s = "null";
150         doConvert = false;
151
152      } else {
153
154         // Look for characters that would require the attribute to be quoted.
155         // All possible numbers should be caught here.
156         if (! doConvert) {
157            for (int i = 0; i < s.length() && ! doConvert; i++) {
158               char c = s.charAt(i);
159               doConvert |= ! (i == 0 ? validFirstAttrChars.contains(c) : validAttrChars.contains(c));
160            }
161         }
162
163         // Reserved words and blanks must be quoted.
164         if (! doConvert) {
165            if (s.isEmpty() || reservedWords.contains(s))
166               doConvert = true;
167         }
168      }
169
170      // If no conversion necessary, just print the attribute as-is.
171      if (doConvert)
172         stringValue(s);
173      else
174         w(s);
175
176      return this;
177   }
178
179   /**
180    * Appends a URI to the output.
181    *
182    * @param uri The URI to append to the output.
183    * @return This object.
184    */
185   public SerializerWriter uriValue(Object uri) {
186      return stringValue(uriResolver.resolve(uri));
187   }
188
189   /**
190    * Performs an indentation only if we're currently past max indentation.
191    *
192    * @param depth The current indentation depth.
193    * @return This object.
194    */
195   public JsonWriter smi(int depth) {
196      if (depth > maxIndent)
197         super.s();
198      return this;
199   }
200
201   /**
202    * Adds a space only if the current indentation level is below maxIndent.
203    *
204    * @param indent The number of spaces to indent.
205    * @return This object.
206    */
207   public JsonWriter s(int indent) {
208      if (indent <= maxIndent)
209         super.s();
210      return this;
211   }
212
213   //-----------------------------------------------------------------------------------------------------------------
214   // Overridden methods
215   //-----------------------------------------------------------------------------------------------------------------
216
217   // <FluentSetters>
218
219   @Override /* SerializerWriter */
220   public JsonWriter cr(int depth) {
221      super.cr(depth);
222      return this;
223   }
224
225   @Override /* SerializerWriter */
226   public JsonWriter cre(int depth) {
227      super.cre(depth);
228      return this;
229   }
230
231   @Override /* SerializerWriter */
232   public JsonWriter appendln(int indent, String text) {
233      super.appendln(indent, text);
234      return this;
235   }
236
237   @Override /* SerializerWriter */
238   public JsonWriter appendln(String text) {
239      super.appendln(text);
240      return this;
241   }
242
243   @Override /* SerializerWriter */
244   public JsonWriter append(int indent, String text) {
245      super.append(indent, text);
246      return this;
247   }
248
249   @Override /* SerializerWriter */
250   public JsonWriter append(int indent, char c) {
251      super.append(indent, c);
252      return this;
253   }
254
255   @Override /* SerializerWriter */
256   public JsonWriter s() {
257      super.s();
258      return this;
259   }
260
261   @Override /* SerializerWriter */
262   public JsonWriter w(char value) {
263      super.w(value);
264      return this;
265   }
266
267   @Override /* SerializerWriter */
268   public JsonWriter w(String value) {
269      super.w(value);
270      return this;
271   }
272
273   @Override /* SerializerWriter */
274   public JsonWriter q() {
275      super.q();
276      return this;
277   }
278
279   @Override /* SerializerWriter */
280   public JsonWriter i(int indent) {
281      super.i(indent);
282      return this;
283   }
284
285   @Override /* SerializerWriter */
286   public JsonWriter nl(int indent) {
287      super.nl(indent);
288      return this;
289   }
290
291   @Override /* SerializerWriter */
292   public JsonWriter append(Object text) {
293      super.append(text);
294      return this;
295   }
296
297   @Override /* SerializerWriter */
298   public JsonWriter append(String text) {
299      super.append(text);
300      return this;
301   }
302
303   @Override /* SerializerWriter */
304   public JsonWriter appendIf(boolean b, String text) {
305      super.appendIf(b, text);
306      return this;
307   }
308
309   @Override /* SerializerWriter */
310   public JsonWriter appendIf(boolean b, char c) {
311      super.appendIf(b, c);
312      return this;
313   }
314
315   @Override /* SerializerWriter */
316   public JsonWriter append(char c) {
317      super.append(c);
318      return this;
319   }
320
321   // </FluentSetters>
322}