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.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 * {@inheritDoc} 306 */ 307 @Override() 308 public String toSingleLineString() 309 { 310 return toString(); 311 } 312 313 314 315 /** 316 * {@inheritDoc} 317 */ 318 @Override() 319 public void toSingleLineString(final StringBuilder buffer) 320 { 321 toString(buffer); 322 } 323 324 325 326 /** 327 * Appends a minimally-escaped JSON representation of the provided string to 328 * the given buffer. When escaping is required, the most user-friendly form 329 * of escaping will be used. 330 * 331 * @param s The string to be encoded. 332 * @param buffer The buffer to which the encoded representation should be 333 * appended. 334 */ 335 static void encodeString(final String s, final StringBuilder buffer) 336 { 337 buffer.append('"'); 338 339 for (final char c : s.toCharArray()) 340 { 341 switch (c) 342 { 343 case '"': 344 buffer.append("\\\""); 345 break; 346 case '\\': 347 buffer.append("\\\\"); 348 break; 349 case '\b': // backspace 350 buffer.append("\\b"); 351 break; 352 case '\f': // formfeed 353 buffer.append("\\f"); 354 break; 355 case '\n': // newline 356 buffer.append("\\n"); 357 break; 358 case '\r': // carriage return 359 buffer.append("\\r"); 360 break; 361 case '\t': // horizontal tab 362 buffer.append("\\t"); 363 break; 364 default: 365 if (c <= '\u001F') 366 { 367 buffer.append("\\u"); 368 buffer.append(String.format("%04X", (int) c)); 369 } 370 else 371 { 372 buffer.append(c); 373 } 374 break; 375 } 376 } 377 378 buffer.append('"'); 379 } 380 381 382 383 /** 384 * {@inheritDoc} 385 */ 386 @Override() 387 public String toNormalizedString() 388 { 389 final StringBuilder buffer = new StringBuilder(); 390 toNormalizedString(buffer); 391 return buffer.toString(); 392 } 393 394 395 396 /** 397 * {@inheritDoc} 398 */ 399 @Override() 400 public void toNormalizedString(final StringBuilder buffer) 401 { 402 buffer.append('"'); 403 404 for (final char c : value.toLowerCase().toCharArray()) 405 { 406 if (StaticUtils.isPrintable(c)) 407 { 408 buffer.append(c); 409 } 410 else 411 { 412 buffer.append("\\u"); 413 buffer.append(String.format("%04X", (int) c)); 414 } 415 } 416 417 buffer.append('"'); 418 } 419}