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}