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 */
021package com.unboundid.util.json;
022
023
024
025import com.unboundid.util.ByteStringBuffer;
026import com.unboundid.util.NotMutable;
027import com.unboundid.util.StaticUtils;
028import com.unboundid.util.ThreadSafety;
029import 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)
126public 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}