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