001 /*
002 * Copyright 2015 UnboundID Corp.
003 * All Rights Reserved.
004 */
005 /*
006 * Copyright (C) 2015 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 * Appends a minimally-escaped JSON representation of the provided string to
306 * the given buffer. When escaping is required, the most user-friendly form
307 * of escaping will be used.
308 *
309 * @param s The string to be encoded.
310 * @param buffer The buffer to which the encoded representation should be
311 * appended.
312 */
313 static void encodeString(final String s, final StringBuilder buffer)
314 {
315 buffer.append('"');
316
317 for (final char c : s.toCharArray())
318 {
319 switch (c)
320 {
321 case '"':
322 buffer.append("\\\"");
323 break;
324 case '\\':
325 buffer.append("\\\\");
326 break;
327 case '\b': // backspace
328 buffer.append("\\b");
329 break;
330 case '\f': // formfeed
331 buffer.append("\\f");
332 break;
333 case '\n': // newline
334 buffer.append("\\n");
335 break;
336 case '\r': // carriage return
337 buffer.append("\\r");
338 break;
339 case '\t': // horizontal tab
340 buffer.append("\\t");
341 break;
342 default:
343 if (c <= '\u001F')
344 {
345 buffer.append("\\u");
346 buffer.append(String.format("%04X", (int) c));
347 }
348 else
349 {
350 buffer.append(c);
351 }
352 break;
353 }
354 }
355
356 buffer.append('"');
357 }
358
359
360
361 /**
362 * {@inheritDoc}
363 */
364 @Override()
365 public String toNormalizedString()
366 {
367 final StringBuilder buffer = new StringBuilder();
368 toNormalizedString(buffer);
369 return buffer.toString();
370 }
371
372
373
374 /**
375 * {@inheritDoc}
376 */
377 @Override()
378 public void toNormalizedString(final StringBuilder buffer)
379 {
380 buffer.append('"');
381
382 for (final char c : value.toLowerCase().toCharArray())
383 {
384 if (StaticUtils.isPrintable(c))
385 {
386 buffer.append(c);
387 }
388 else
389 {
390 buffer.append("\\u");
391 buffer.append(String.format("%04X", (int) c));
392 }
393 }
394
395 buffer.append('"');
396 }
397 }