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