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