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.ByteStringBuffer;
026    import com.unboundid.util.NotMutable;
027    import com.unboundid.util.StaticUtils;
028    import com.unboundid.util.ThreadSafety;
029    import 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)
126    public 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    }