001 /*
002 * Copyright 2015-2016 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2015-2016 UnboundID Corp.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021 package com.unboundid.util.json;
022
023
024
025 import com.unboundid.util.ByteStringBuffer;
026 import com.unboundid.util.NotMutable;
027 import com.unboundid.util.StaticUtils;
028 import com.unboundid.util.ThreadSafety;
029 import com.unboundid.util.ThreadSafetyLevel;
030
031
032
033 /**
034 * This class provides an implementation of a JSON value that represents a
035 * string of Unicode characters. The string representation of a JSON string
036 * must start and end with the double quotation mark character, and a Unicode
037 * (preferably UTF-8) representation of the string between the quotes. The
038 * following special characters must be escaped:
039 * <UL>
040 * <LI>
041 * The double quotation mark (Unicode character U+0022) must be escaped as
042 * either {@code \"} or {@code \}{@code u0022}.
043 * </LI>
044 * <LI>
045 * The backslash (Unicode character U+005C) must be escaped as either
046 * {@code \\} or {@code \}{@code u005C}.
047 * </LI>
048 * <LI>
049 * All ASCII control characters (Unicode characters U+0000 through U+001F)
050 * must be escaped. They can all be escaped by prefixing the
051 * four-hexadecimal-digit Unicode character code with {@code \}{@code u},
052 * like {@code \}{@code u0000} to represent the ASCII null character U+0000.
053 * For certain characters, a more user-friendly escape sequence is also
054 * defined:
055 * <UL>
056 * <LI>
057 * The horizontal tab character can be escaped as either {@code \t} or
058 * {@code \}{@code u0009}.
059 * </LI>
060 * <LI>
061 * The newline character can be escaped as either {@code \n} or
062 * {@code \}{@code u000A}.
063 * </LI>
064 * <LI>
065 * The formfeed character can be escaped as either {@code \f} or
066 * {@code \}{@code u000C}.
067 * </LI>
068 * <LI>
069 * The carriage return character can be escaped as either {@code \r} or
070 * {@code \}{@code u000D}.
071 * </LI>
072 * </UL>
073 * </LI>
074 * </UL>
075 * In addition, any other character may optionally be escaped by placing the
076 * {@code \}{@code u} prefix in front of each four-hexadecimal digit sequence in
077 * the UTF-16 representation of that character. For example, the "LATIN SMALL
078 * LETTER N WITH TILDE" character U+00F1 may be escaped as
079 * {@code \}{@code u00F1}, while the "MUSICAL SYMBOL G CLEF" character U+1D11E
080 * may be escaped as {@code \}{@code uD834}{@code \}{@code uDD1E}. And while
081 * the forward slash character is not required to be escaped in JSON strings, it
082 * can be escaped using {@code \/} as a more human-readable alternative to
083 * {@code \}{@code u002F}.
084 * <BR><BR>
085 * The string provided to the {@link #JSONString(String)} constructor should not
086 * have any escaping performed, and the string returned by the
087 * {@link #stringValue()} method will not have any escaping performed. These
088 * methods work with the Java string that is represented by the JSON string.
089 * <BR><BR>
090 * If this JSON string was parsed from the string representation of a JSON
091 * object, then the value returned by the {@link #toString()} method (or
092 * appended to the buffer provided to the {@link #toString(StringBuilder)}
093 * method) will be the string representation used in the JSON object that was
094 * parsed. Otherwise, this class will generate an appropriate string
095 * representation, which will be surrounded by quotation marks and will have the
096 * minimal required encoding applied.
097 * <BR><BR>
098 * The string returned by the {@link #toNormalizedString()} method (or appended
099 * to the buffer provided to the {@link #toNormalizedString(StringBuilder)}
100 * method) will be generated by converting it to lowercase, surrounding it with
101 * quotation marks, and using the {@code \}{@code u}-style escaping for all
102 * characters other than the following (as contained in the LDAP printable
103 * character set defined in <A HREF="http://www.ietf.org/rfc/rfc4517.txt">RFC
104 * 4517</A> section 3.2, and indicated by the
105 * {@link StaticUtils#isPrintable(char)} method):
106 * <UL>
107 * <LI>All uppercase ASCII alphabetic letters (U+0041 through U+005A).</LI>
108 * <LI>All lowercase ASCII alphabetic letters (U+0061 through U+007A).</LI>
109 * <LI>All ASCII numeric digits (U+0030 through U+0039).</LI>
110 * <LI>The ASCII space character U+0020.</LI>
111 * <LI>The ASCII single quote (aka apostrophe) character U+0027.</LI>
112 * <LI>The ASCII left parenthesis character U+0028.</LI>
113 * <LI>The ASCII right parenthesis character U+0029.</LI>
114 * <LI>The ASCII plus sign character U+002B.</LI>
115 * <LI>The ASCII comma character U+002C.</LI>
116 * <LI>The ASCII minus sign (aka hyphen) character U+002D.</LI>
117 * <LI>The ASCII period character U+002E.</LI>
118 * <LI>The ASCII forward slash character U+002F.</LI>
119 * <LI>The ASCII colon character U+003A.</LI>
120 * <LI>The ASCII equals sign character U+003D.</LI>
121 * <LI>The ASCII question mark character U+003F.</LI>
122 * </UL>
123 */
124 @NotMutable()
125 @ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
126 public final class JSONString
127 extends JSONValue
128 {
129 /**
130 * The serial version UID for this serializable class.
131 */
132 private static final long serialVersionUID = -4677194657299153890L;
133
134
135
136 // The JSON-formatted string representation for this JSON string. It will be
137 // surrounded by quotation marks and any necessary escaping will have been
138 // performed.
139 private String jsonStringRepresentation;
140
141 // The string value for this object.
142 private final String value;
143
144
145
146 /**
147 * Creates a new JSON string.
148 *
149 * @param value The string to represent in this JSON value. It must not be
150 * {@code null}.
151 */
152 public JSONString(final String value)
153 {
154 this.value = value;
155 jsonStringRepresentation = null;
156 }
157
158
159
160 /**
161 * Creates a new JSON string. This method should be used for strings parsed
162 * from the string representation of a JSON object.
163 *
164 * @param javaString The Java string to represent.
165 * @param jsonString The JSON string representation to use for the Java
166 * string.
167 */
168 JSONString(final String javaString, final String jsonString)
169 {
170 value = javaString;
171 jsonStringRepresentation = jsonString;
172 }
173
174
175
176 /**
177 * Retrieves the string value for this object. This will be the interpreted
178 * value, without the surrounding quotation marks or escaping.
179 *
180 * @return The string value for this object.
181 */
182 public String stringValue()
183 {
184 return value;
185 }
186
187
188
189 /**
190 * {@inheritDoc}
191 */
192 @Override()
193 public int hashCode()
194 {
195 return stringValue().hashCode();
196 }
197
198
199
200 /**
201 * {@inheritDoc}
202 */
203 @Override()
204 public boolean equals(final Object o)
205 {
206 if (o == this)
207 {
208 return true;
209 }
210
211 if (o instanceof JSONString)
212 {
213 final JSONString s = (JSONString) o;
214 return value.equals(s.value);
215 }
216
217 return false;
218 }
219
220
221
222 /**
223 * Indicates whether the value of this JSON string matches that of the
224 * provided string, optionally ignoring differences in capitalization.
225 *
226 * @param s The JSON string to compare against this JSON string.
227 * It must not be {@code null}.
228 * @param ignoreCase Indicates whether to ignore differences in
229 * capitalization.
230 *
231 * @return {@code true} if the value of this JSON string matches the value of
232 * the provided string (optionally ignoring differences in
233 * capitalization), or {@code false} if not.
234 */
235 public boolean equals(final JSONString s, final boolean ignoreCase)
236 {
237 if (ignoreCase)
238 {
239 return value.equalsIgnoreCase(s.value);
240 }
241 else
242 {
243 return value.equals(s.value);
244 }
245 }
246
247
248
249 /**
250 * {@inheritDoc}
251 */
252 @Override()
253 public boolean equals(final JSONValue v, final boolean ignoreFieldNameCase,
254 final boolean ignoreValueCase,
255 final boolean ignoreArrayOrder)
256 {
257 return ((v instanceof JSONString) &&
258 equals((JSONString) v, ignoreValueCase));
259 }
260
261
262
263 /**
264 * {@inheritDoc}
265 */
266 @Override()
267 public String toString()
268 {
269 if (jsonStringRepresentation == null)
270 {
271 final StringBuilder buffer = new StringBuilder();
272 toString(buffer);
273 jsonStringRepresentation = buffer.toString();
274 }
275
276 return jsonStringRepresentation;
277 }
278
279
280
281 /**
282 * {@inheritDoc}
283 */
284 @Override()
285 public void toString(final StringBuilder buffer)
286 {
287 if (jsonStringRepresentation != null)
288 {
289 buffer.append(jsonStringRepresentation);
290 }
291 else
292 {
293 final boolean emptyBufferProvided = (buffer.length() == 0);
294 encodeString(value, buffer);
295
296 if (emptyBufferProvided)
297 {
298 jsonStringRepresentation = buffer.toString();
299 }
300 }
301 }
302
303
304
305 /**
306 * {@inheritDoc}
307 */
308 @Override()
309 public String toSingleLineString()
310 {
311 return toString();
312 }
313
314
315
316 /**
317 * {@inheritDoc}
318 */
319 @Override()
320 public void toSingleLineString(final StringBuilder buffer)
321 {
322 toString(buffer);
323 }
324
325
326
327 /**
328 * Appends a minimally-escaped JSON representation of the provided string to
329 * the given buffer. When escaping is required, the most user-friendly form
330 * of escaping will be used.
331 *
332 * @param s The string to be encoded.
333 * @param buffer The buffer to which the encoded representation should be
334 * appended.
335 */
336 static void encodeString(final String s, final StringBuilder buffer)
337 {
338 buffer.append('"');
339
340 for (final char c : s.toCharArray())
341 {
342 switch (c)
343 {
344 case '"':
345 buffer.append("\\\"");
346 break;
347 case '\\':
348 buffer.append("\\\\");
349 break;
350 case '\b': // backspace
351 buffer.append("\\b");
352 break;
353 case '\f': // formfeed
354 buffer.append("\\f");
355 break;
356 case '\n': // newline
357 buffer.append("\\n");
358 break;
359 case '\r': // carriage return
360 buffer.append("\\r");
361 break;
362 case '\t': // horizontal tab
363 buffer.append("\\t");
364 break;
365 default:
366 if (c <= '\u001F')
367 {
368 buffer.append("\\u");
369 buffer.append(String.format("%04X", (int) c));
370 }
371 else
372 {
373 buffer.append(c);
374 }
375 break;
376 }
377 }
378
379 buffer.append('"');
380 }
381
382
383
384 /**
385 * Appends a minimally-escaped JSON representation of the provided string to
386 * the given buffer. When escaping is required, the most user-friendly form
387 * of escaping will be used.
388 *
389 * @param s The string to be encoded.
390 * @param buffer The buffer to which the encoded representation should be
391 * appended.
392 */
393 static void encodeString(final String s, final ByteStringBuffer buffer)
394 {
395 buffer.append('"');
396
397 for (final char c : s.toCharArray())
398 {
399 switch (c)
400 {
401 case '"':
402 buffer.append("\\\"");
403 break;
404 case '\\':
405 buffer.append("\\\\");
406 break;
407 case '\b': // backspace
408 buffer.append("\\b");
409 break;
410 case '\f': // formfeed
411 buffer.append("\\f");
412 break;
413 case '\n': // newline
414 buffer.append("\\n");
415 break;
416 case '\r': // carriage return
417 buffer.append("\\r");
418 break;
419 case '\t': // horizontal tab
420 buffer.append("\\t");
421 break;
422 default:
423 if (c <= '\u001F')
424 {
425 buffer.append("\\u");
426 buffer.append(String.format("%04X", (int) c));
427 }
428 else
429 {
430 buffer.append(c);
431 }
432 break;
433 }
434 }
435
436 buffer.append('"');
437 }
438
439
440
441 /**
442 * {@inheritDoc}
443 */
444 @Override()
445 public String toNormalizedString()
446 {
447 final StringBuilder buffer = new StringBuilder();
448 toNormalizedString(buffer);
449 return buffer.toString();
450 }
451
452
453
454 /**
455 * {@inheritDoc}
456 */
457 @Override()
458 public void toNormalizedString(final StringBuilder buffer)
459 {
460 buffer.append('"');
461
462 for (final char c : value.toLowerCase().toCharArray())
463 {
464 if (StaticUtils.isPrintable(c))
465 {
466 buffer.append(c);
467 }
468 else
469 {
470 buffer.append("\\u");
471 buffer.append(String.format("%04X", (int) c));
472 }
473 }
474
475 buffer.append('"');
476 }
477
478
479
480 /**
481 * {@inheritDoc}
482 */
483 @Override()
484 public void appendToJSONBuffer(final JSONBuffer buffer)
485 {
486 buffer.appendString(value);
487 }
488
489
490
491 /**
492 * {@inheritDoc}
493 */
494 @Override()
495 public void appendToJSONBuffer(final String fieldName,
496 final JSONBuffer buffer)
497 {
498 buffer.appendString(fieldName, value);
499 }
500 }