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}