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    }