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}