001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.json; 018 019import static org.apache.juneau.common.utils.StringUtils.*; 020import static org.apache.juneau.common.utils.Utils.*; 021 022import java.io.*; 023import java.lang.reflect.*; 024import java.nio.charset.*; 025import java.util.*; 026import java.util.function.*; 027 028import org.apache.juneau.*; 029import org.apache.juneau.collections.*; 030import org.apache.juneau.common.utils.*; 031import org.apache.juneau.httppart.*; 032import org.apache.juneau.internal.*; 033import org.apache.juneau.parser.*; 034import org.apache.juneau.swap.*; 035 036/** 037 * Session object that lives for the duration of a single use of {@link JsonParser}. 038 * 039 * <h5 class='section'>Notes:</h5><ul> 040 * <li class='warn'>This class is not thread safe and is typically discarded after one use. 041 * </ul> 042 * 043 * <h5 class='section'>See Also:</h5><ul> 044 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JsonBasics">JSON Basics</a> 045 046 * </ul> 047 */ 048@SuppressWarnings({ "unchecked", "rawtypes" }) 049public class JsonParserSession extends ReaderParserSession { 050 051 //------------------------------------------------------------------------------------------------------------------- 052 // Static 053 //------------------------------------------------------------------------------------------------------------------- 054 055 private static final AsciiSet decChars = AsciiSet.create().ranges("0-9").build(); 056 057 /** 058 * Creates a new builder for this object. 059 * 060 * @param ctx The context creating this session. 061 * @return A new builder. 062 */ 063 public static Builder create(JsonParser ctx) { 064 return new Builder(ctx); 065 } 066 067 //------------------------------------------------------------------------------------------------------------------- 068 // Builder 069 //------------------------------------------------------------------------------------------------------------------- 070 071 /** 072 * Builder class. 073 */ 074 public static class Builder extends ReaderParserSession.Builder { 075 076 JsonParser ctx; 077 078 /** 079 * Constructor 080 * 081 * @param ctx The context creating this session. 082 */ 083 protected Builder(JsonParser ctx) { 084 super(ctx); 085 this.ctx = ctx; 086 } 087 088 @Override 089 public JsonParserSession build() { 090 return new JsonParserSession(this); 091 } 092 @Override /* Overridden from Builder */ 093 public <T> Builder apply(Class<T> type, Consumer<T> apply) { 094 super.apply(type, apply); 095 return this; 096 } 097 098 @Override /* Overridden from Builder */ 099 public Builder debug(Boolean value) { 100 super.debug(value); 101 return this; 102 } 103 104 @Override /* Overridden from Builder */ 105 public Builder properties(Map<String,Object> value) { 106 super.properties(value); 107 return this; 108 } 109 110 @Override /* Overridden from Builder */ 111 public Builder property(String key, Object value) { 112 super.property(key, value); 113 return this; 114 } 115 116 @Override /* Overridden from Builder */ 117 public Builder unmodifiable() { 118 super.unmodifiable(); 119 return this; 120 } 121 122 @Override /* Overridden from Builder */ 123 public Builder locale(Locale value) { 124 super.locale(value); 125 return this; 126 } 127 128 @Override /* Overridden from Builder */ 129 public Builder localeDefault(Locale value) { 130 super.localeDefault(value); 131 return this; 132 } 133 134 @Override /* Overridden from Builder */ 135 public Builder mediaType(MediaType value) { 136 super.mediaType(value); 137 return this; 138 } 139 140 @Override /* Overridden from Builder */ 141 public Builder mediaTypeDefault(MediaType value) { 142 super.mediaTypeDefault(value); 143 return this; 144 } 145 146 @Override /* Overridden from Builder */ 147 public Builder timeZone(TimeZone value) { 148 super.timeZone(value); 149 return this; 150 } 151 152 @Override /* Overridden from Builder */ 153 public Builder timeZoneDefault(TimeZone value) { 154 super.timeZoneDefault(value); 155 return this; 156 } 157 158 @Override /* Overridden from Builder */ 159 public Builder javaMethod(Method value) { 160 super.javaMethod(value); 161 return this; 162 } 163 164 @Override /* Overridden from Builder */ 165 public Builder outer(Object value) { 166 super.outer(value); 167 return this; 168 } 169 170 @Override /* Overridden from Builder */ 171 public Builder schema(HttpPartSchema value) { 172 super.schema(value); 173 return this; 174 } 175 176 @Override /* Overridden from Builder */ 177 public Builder schemaDefault(HttpPartSchema value) { 178 super.schemaDefault(value); 179 return this; 180 } 181 182 @Override /* Overridden from Builder */ 183 public Builder fileCharset(Charset value) { 184 super.fileCharset(value); 185 return this; 186 } 187 188 @Override /* Overridden from Builder */ 189 public Builder streamCharset(Charset value) { 190 super.streamCharset(value); 191 return this; 192 } 193 } 194 195 //------------------------------------------------------------------------------------------------------------------- 196 // Instance 197 //------------------------------------------------------------------------------------------------------------------- 198 199 private final JsonParser ctx; 200 201 /** 202 * Constructor. 203 * 204 * @param builder The builder for this object. 205 */ 206 protected JsonParserSession(Builder builder) { 207 super(builder); 208 ctx = builder.ctx; 209 } 210 211 /** 212 * Returns <jk>true</jk> if the specified character is whitespace. 213 * 214 * <p> 215 * The definition of whitespace is different for strict vs lax mode. 216 * Strict mode only interprets 0x20 (space), 0x09 (tab), 0x0A (line feed) and 0x0D (carriage return) as whitespace. 217 * Lax mode uses {@link Character#isWhitespace(int)} to make the determination. 218 * 219 * @param cp The codepoint. 220 * @return <jk>true</jk> if the specified character is whitespace. 221 */ 222 protected boolean isWhitespace(int cp) { 223 if (isStrict()) 224 return cp <= 0x20 && (cp == 0x09 || cp == 0x0A || cp == 0x0D || cp == 0x20); 225 return Character.isWhitespace(cp); 226 } 227 228 /** 229 * Returns <jk>true</jk> if the specified character is whitespace or '/'. 230 * 231 * @param cp The codepoint. 232 * @return <jk>true</jk> if the specified character is whitespace or '/'. 233 */ 234 protected boolean isCommentOrWhitespace(int cp) { 235 if (cp == '/') 236 return true; 237 if (isStrict()) 238 return cp <= 0x20 && (cp == 0x09 || cp == 0x0A || cp == 0x0D || cp == 0x20); 239 return Character.isWhitespace(cp); 240 } 241 242 @Override /* ParserSession */ 243 protected <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException { 244 try (ParserReader r = pipe.getParserReader()) { 245 if (r == null) 246 return null; 247 T o = parseAnything(type, r, getOuter(), null); 248 validateEnd(r); 249 return o; 250 } 251 } 252 253 @Override /* ReaderParserSession */ 254 protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws IOException, ParseException, ExecutableException { 255 try (ParserReader r = pipe.getParserReader()) { 256 m = parseIntoMap2(r, m, (ClassMeta<K>)getClassMeta(keyType), (ClassMeta<V>)getClassMeta(valueType), null); 257 validateEnd(r); 258 return m; 259 } 260 } 261 262 @Override /* ReaderParserSession */ 263 protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws IOException, ParseException, ExecutableException { 264 try (ParserReader r = pipe.getParserReader()) { 265 c = parseIntoCollection2(r, c, getClassMeta(elementType), null); 266 validateEnd(r); 267 return c; 268 } 269 } 270 271 private <T> T parseAnything(ClassMeta<?> eType, ParserReader r, Object outer, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException { 272 273 if (eType == null) 274 eType = object(); 275 ObjectSwap<T,Object> swap = (ObjectSwap<T,Object>)eType.getSwap(this); 276 BuilderSwap<T,Object> builder = (BuilderSwap<T,Object>)eType.getBuilderSwap(this); 277 ClassMeta<?> sType = null; 278 if (builder != null) 279 sType = builder.getBuilderClassMeta(this); 280 else if (swap != null) 281 sType = swap.getSwapClassMeta(this); 282 else 283 sType = eType; 284 285 if (sType.isOptional()) 286 return (T)Utils.opt(parseAnything(eType.getElementType(), r, outer, pMeta)); 287 288 setCurrentClass(sType); 289 String wrapperAttr = getJsonClassMeta(sType).getWrapperAttr(); 290 291 Object o = null; 292 293 skipCommentsAndSpace(r); 294 if (wrapperAttr != null) 295 skipWrapperAttrStart(r, wrapperAttr); 296 int c = r.peek(); 297 if (c == -1) { 298 if (isStrict()) 299 throw new ParseException(this, "Empty input."); 300 // Let o be null. 301 } else if ((c == ',' || c == '}' || c == ']')) { 302 if (isStrict()) 303 throw new ParseException(this, "Missing value detected."); 304 // Handle bug in Cognos 10.2.1 that can product non-existent values. 305 // Let o be null; 306 } else if (c == 'n') { 307 parseKeyword("null", r); 308 } else if (sType.isObject()) { 309 if (c == '{') { 310 JsonMap m2 = new JsonMap(this); 311 parseIntoMap2(r, m2, string(), object(), pMeta); 312 o = cast(m2, pMeta, eType); 313 } else if (c == '[') { 314 o = parseIntoCollection2(r, new JsonList(this), object(), pMeta); 315 } else if (c == '\'' || c == '"') { 316 o = parseString(r); 317 if (sType.isChar()) 318 o = parseCharacter(o); 319 } else if (c >= '0' && c <= '9' || c == '-' || c == '.') { 320 o = parseNumber(r, null); 321 } else if (c == 't') { 322 parseKeyword("true", r); 323 o = Boolean.TRUE; 324 } else { 325 parseKeyword("false", r); 326 o = Boolean.FALSE; 327 } 328 } else if (sType.isBoolean()) { 329 o = parseBoolean(r); 330 } else if (sType.isCharSequence()) { 331 o = parseString(r); 332 } else if (sType.isChar()) { 333 o = parseCharacter(parseString(r)); 334 } else if (sType.isNumber()) { 335 o = parseNumber(r, (Class<? extends Number>)sType.getInnerClass()); 336 } else if (sType.isMap()) { 337 Map m = (sType.canCreateNewInstance(outer) ? (Map)sType.newInstance(outer) : newGenericMap(sType)); 338 o = parseIntoMap2(r, m, sType.getKeyType(), sType.getValueType(), pMeta); 339 } else if (sType.isCollection()) { 340 if (c == '{') { 341 JsonMap m = new JsonMap(this); 342 parseIntoMap2(r, m, string(), object(), pMeta); 343 o = cast(m, pMeta, eType); 344 } else { 345 Collection l = (sType.canCreateNewInstance(outer) ? (Collection)sType.newInstance() : new JsonList(this)); 346 o = parseIntoCollection2(r, l, sType, pMeta); 347 } 348 } else if (builder != null) { 349 BeanMap m = toBeanMap(builder.create(this, eType)); 350 o = builder.build(this, parseIntoBeanMap2(r, m).getBean(), eType); 351 } else if (sType.canCreateNewBean(outer)) { 352 BeanMap m = newBeanMap(outer, sType.getInnerClass()); 353 o = parseIntoBeanMap2(r, m).getBean(); 354 } else if (sType.canCreateNewInstanceFromString(outer) && (c == '\'' || c == '"')) { 355 o = sType.newInstanceFromString(outer, parseString(r)); 356 } else if (sType.isArray() || sType.isArgs()) { 357 if (c == '{') { 358 JsonMap m = new JsonMap(this); 359 parseIntoMap2(r, m, string(), object(), pMeta); 360 o = cast(m, pMeta, eType); 361 } else { 362 ArrayList l = (ArrayList)parseIntoCollection2(r, list(), sType, pMeta); 363 o = toArray(sType, l); 364 } 365 } else if (c == '{') { 366 Map m = new JsonMap(this); 367 parseIntoMap2(r, m, sType.getKeyType(), sType.getValueType(), pMeta); 368 if (m.containsKey(getBeanTypePropertyName(eType))) 369 o = cast((JsonMap)m, pMeta, eType); 370 else if (sType.getProxyInvocationHandler() != null) 371 o = newBeanMap(outer, sType.getInnerClass()).load(m).getBean(); 372 else 373 throw new ParseException(this, "Class ''{0}'' could not be instantiated. Reason: ''{1}''", 374 sType.getInnerClass().getName(), sType.getNotABeanReason()); 375 } else if (sType.canCreateNewInstanceFromString(outer) && ! isStrict()) { 376 o = sType.newInstanceFromString(outer, parseString(r)); 377 } else { 378 throw new ParseException(this, "Unrecognized syntax for class type ''{0}'', starting character ''{1}''", 379 sType, (char)c); 380 } 381 382 if (wrapperAttr != null) 383 skipWrapperAttrEnd(r); 384 385 if (swap != null && o != null) 386 o = unswap(swap, o, eType); 387 388 if (outer != null) 389 setParent(eType, o, outer); 390 391 return (T)o; 392 } 393 394 private Number parseNumber(ParserReader r, Class<? extends Number> type) throws IOException, ParseException { 395 int c = r.peek(); 396 if (c == '\'' || c == '"') 397 return parseNumber(r, parseString(r), type); 398 return parseNumber(r, r.parseNumberString(), type); 399 } 400 401 private Number parseNumber(ParserReader r, String s, Class<? extends Number> type) throws ParseException { 402 403 // JSON has slightly different number rules from Java. 404 // Strict mode enforces these different rules, lax does not. 405 if (isStrict()) { 406 407 // Lax allows blank strings to represent 0. 408 // Strict does not allow blank strings. 409 if (s.isEmpty()) 410 throw new ParseException(this, "Invalid JSON number: ''{0}''", s); 411 412 // Need to weed out octal and hexadecimal formats: 0123,-0123,0x123,-0x123. 413 // Don't weed out 0 or -0. 414 boolean isNegative = false; 415 char c = s.charAt(0); 416 if (c == '-') { 417 isNegative = true; 418 c = (s.length() == 1 ? 'x' : s.charAt(1)); 419 } 420 421 // JSON doesn't allow '.123' and '-.123'. 422 if (c == '.') 423 throw new ParseException(this, "Invalid JSON number: ''{0}''", s); 424 425 // '01' is not a valid number, but '0.1', '0e1', '0e+1' are valid. 426 if (c == '0' && s.length() > (isNegative ? 2 : 1)) { 427 char c2 = s.charAt((isNegative ? 2 : 1)); 428 if (c2 != '.' && c2 != 'e' && c2 != 'E') 429 throw new ParseException(this, "Invalid JSON number: ''{0}''", s); 430 } 431 432 // JSON doesn't allow '1.' or '0.e1'. 433 int i = s.indexOf('.'); 434 if (i != -1 && (s.length() == (i+1) || ! decChars.contains(s.charAt(i+1)))) 435 throw new ParseException(this, "Invalid JSON number: ''{0}''", s); 436 437 } 438 return StringUtils.parseNumber(s, type); 439 } 440 441 private Boolean parseBoolean(ParserReader r) throws IOException, ParseException { 442 int c = r.peek(); 443 if (c == '\'' || c == '"') 444 return Boolean.valueOf(parseString(r)); 445 if (c == 't') { 446 parseKeyword("true", r); 447 return Boolean.TRUE; 448 } else if (c == 'f') { 449 parseKeyword("false", r); 450 return Boolean.FALSE; 451 } else { 452 throw new ParseException(this, "Unrecognized syntax. Expected boolean value, actual=''{0}''", r.read(100)); 453 } 454 } 455 456 private <K,V> Map<K,V> parseIntoMap2(ParserReader r, Map<K,V> m, ClassMeta<K> keyType, 457 ClassMeta<V> valueType, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException { 458 459 if (keyType == null) 460 keyType = (ClassMeta<K>)string(); 461 462 int S0=0; // Looking for outer { 463 int S1=1; // Looking for attrName start. 464 int S3=3; // Found attrName end, looking for :. 465 int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. 466 int S5=5; // Looking for , or } 467 int S6=6; // Found , looking for attr start. 468 469 skipCommentsAndSpace(r); 470 int state = S0; 471 String currAttr = null; 472 int c = 0; 473 while (c != -1) { 474 c = r.read(); 475 if (state == S0) { 476 if (c == '{') 477 state = S1; 478 else 479 break; 480 } else if (state == S1) { 481 if (c == '}') { 482 return m; 483 } else if (isCommentOrWhitespace(c)) { 484 skipCommentsAndSpace(r.unread()); 485 } else { 486 currAttr = parseFieldName(r.unread()); 487 state = S3; 488 } 489 } else if (state == S3) { 490 if (c == ':') 491 state = S4; 492 } else if (state == S4) { 493 if (isCommentOrWhitespace(c)) { 494 skipCommentsAndSpace(r.unread()); 495 } else { 496 K key = convertAttrToType(m, currAttr, keyType); 497 V value = parseAnything(valueType, r.unread(), m, pMeta); 498 setName(valueType, value, key); 499 m.put(key, value); 500 state = S5; 501 } 502 } else if (state == S5) { 503 if (c == ',') { 504 state = S6; 505 } else if (isCommentOrWhitespace(c)) { 506 skipCommentsAndSpace(r.unread()); 507 } else if (c == '}') { 508 return m; 509 } else { 510 break; 511 } 512 } else if (state == S6) { 513 if (c == '}') { 514 break; 515 } else if (isCommentOrWhitespace(c)) { 516 skipCommentsAndSpace(r.unread()); 517 } else { 518 currAttr = parseFieldName(r.unread()); 519 state = S3; 520 } 521 } 522 } 523 if (state == S0) 524 throw new ParseException(this, "Expected '{' at beginning of JSON object."); 525 if (state == S1) 526 throw new ParseException(this, "Could not find attribute name on JSON object."); 527 if (state == S3) 528 throw new ParseException(this, "Could not find ':' following attribute name on JSON object."); 529 if (state == S4) 530 throw new ParseException(this, "Expected one of the following characters: {,[,',\",LITERAL."); 531 if (state == S5) 532 throw new ParseException(this, "Could not find '}' marking end of JSON object."); 533 if (state == S6) 534 throw new ParseException(this, "Unexpected '}' found in JSON object."); 535 536 return null; // Unreachable. 537 } 538 539 /* 540 * Parse a JSON attribute from the character array at the specified position, then 541 * set the position marker to the last character in the field name. 542 */ 543 private String parseFieldName(ParserReader r) throws IOException, ParseException { 544 int c = r.peek(); 545 if (c == '\'' || c == '"') 546 return parseString(r); 547 if (isStrict()) 548 throw new ParseException(this, "Unquoted attribute detected."); 549 if (! VALID_BARE_CHARS.contains(c)) 550 throw new ParseException(this, "Could not find the start of the field name."); 551 r.mark(); 552 // Look for whitespace. 553 while (c != -1) { 554 c = r.read(); 555 if (! VALID_BARE_CHARS.contains(c)) { 556 r.unread(); 557 String s = r.getMarked().intern(); 558 return s.equals("null") ? null : s; 559 } 560 } 561 throw new ParseException(this, "Could not find the end of the field name."); 562 } 563 564 private static final AsciiSet VALID_BARE_CHARS = AsciiSet.create().range('A','Z').range('a','z').range('0','9').chars("$_-.").build(); 565 566 private <E> Collection<E> parseIntoCollection2(ParserReader r, Collection<E> l, 567 ClassMeta<?> type, BeanPropertyMeta pMeta) throws IOException, ParseException, ExecutableException { 568 569 final int 570 S0=0, // Looking for outermost [ 571 S1=1, // Looking for starting [ or { or " or ' or LITERAL or ] 572 S2=2, // Looking for , or ] 573 S3=3; // Looking for starting [ or { or " or ' or LITERAL 574 575 int argIndex = 0; 576 577 int state = S0; 578 int c = 0; 579 while (c != -1) { 580 c = r.read(); 581 if (state == S0) { 582 if (c == '[') 583 state = S1; 584 else if (isCommentOrWhitespace(c)) 585 skipCommentsAndSpace(r.unread()); 586 else 587 break; // Invalid character found. 588 } else if (state == S1) { 589 if (c == ']') { 590 return l; 591 } else if (isCommentOrWhitespace(c)) { 592 skipCommentsAndSpace(r.unread()); 593 } else if (c != -1) { 594 l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta)); 595 state = S2; 596 } 597 } else if (state == S2) { 598 if (c == ',') { 599 state = S3; 600 } else if (isCommentOrWhitespace(c)) { 601 skipCommentsAndSpace(r.unread()); 602 } else if (c == ']') { 603 return l; 604 } else { 605 break; // Invalid character found. 606 } 607 } else if (state == S3) { 608 if (isCommentOrWhitespace(c)) { 609 skipCommentsAndSpace(r.unread()); 610 } else if (c == ']') { 611 break; 612 } else if (c != -1) { 613 l.add((E)parseAnything(type.isArgs() ? type.getArg(argIndex++) : type.getElementType(), r.unread(), l, pMeta)); 614 state = S2; 615 } 616 } 617 } 618 if (state == S0) 619 throw new ParseException(this, "Expected '[' at beginning of JSON array."); 620 if (state == S1) 621 throw new ParseException(this, "Expected one of the following characters: {,[,',\",LITERAL."); 622 if (state == S2) 623 throw new ParseException(this, "Expected ',' or ']'."); 624 if (state == S3) 625 throw new ParseException(this, "Unexpected trailing comma in array."); 626 627 return null; // Unreachable. 628 } 629 630 private <T> BeanMap<T> parseIntoBeanMap2(ParserReader r, BeanMap<T> m) throws IOException, ParseException, ExecutableException { 631 632 int S0=0; // Looking for outer { 633 int S1=1; // Looking for attrName start. 634 int S3=3; // Found attrName end, looking for :. 635 int S4=4; // Found :, looking for valStart: { [ " ' LITERAL. 636 int S5=5; // Looking for , or } 637 638 int state = S0; 639 String currAttr = ""; 640 int c = 0; 641 mark(); 642 try { 643 while (c != -1) { 644 c = r.read(); 645 if (state == S0) { 646 if (c == '{') { 647 state = S1; 648 } else if (isCommentOrWhitespace(c)) { 649 skipCommentsAndSpace(r.unread()); 650 } else { 651 break; 652 } 653 } else if (state == S1) { 654 if (c == '}') { 655 return m; 656 } else if (isCommentOrWhitespace(c)) { 657 skipCommentsAndSpace(r.unread()); 658 } else { 659 r.unread(); 660 mark(); 661 currAttr = parseFieldName(r); 662 state = S3; 663 } 664 } else if (state == S3) { 665 if (c == ':') 666 state = S4; 667 } else if (state == S4) { 668 if (isCommentOrWhitespace(c)) { 669 skipCommentsAndSpace(r.unread()); 670 } else { 671 if (! currAttr.equals(getBeanTypePropertyName(m.getClassMeta()))) { 672 BeanPropertyMeta pMeta = m.getPropertyMeta(currAttr); 673 setCurrentProperty(pMeta); 674 if (pMeta == null) { 675 onUnknownProperty(currAttr, m, parseAnything(object(), r.unread(), m.getBean(false), null)); 676 unmark(); 677 } else { 678 unmark(); 679 ClassMeta<?> cm = pMeta.getClassMeta(); 680 Object value = parseAnything(cm, r.unread(), m.getBean(false), pMeta); 681 setName(cm, value, currAttr); 682 try { 683 pMeta.set(m, currAttr, value); 684 } catch (BeanRuntimeException e) { 685 onBeanSetterException(pMeta, e); 686 throw e; 687 } 688 } 689 setCurrentProperty(null); 690 } 691 state = S5; 692 } 693 } else if (state == S5) { 694 if (c == ',') 695 state = S1; 696 else if (isCommentOrWhitespace(c)) 697 skipCommentsAndSpace(r.unread()); 698 else if (c == '}') { 699 return m; 700 } 701 } 702 } 703 if (state == S0) 704 throw new ParseException(this, "Expected '{' at beginning of JSON object."); 705 if (state == S1) 706 throw new ParseException(this, "Could not find attribute name on JSON object."); 707 if (state == S3) 708 throw new ParseException(this, "Could not find ':' following attribute name on JSON object."); 709 if (state == S4) 710 throw new ParseException(this, "Expected one of the following characters: {,[,',\",LITERAL."); 711 if (state == S5) 712 throw new ParseException(this, "Could not find '}' marking end of JSON object."); 713 } finally { 714 unmark(); 715 } 716 717 return null; // Unreachable. 718 } 719 720 /* 721 * Starting from the specified position in the character array, returns the 722 * position of the character " or '. 723 * If the string consists of a concatenation of strings (e.g. 'AAA' + "BBB"), this method 724 * will automatically concatenate the strings and return the result. 725 */ 726 private String parseString(ParserReader r) throws IOException, ParseException { 727 r.mark(); 728 int qc = r.read(); // The quote character being used (" or ') 729 if (qc != '"' && isStrict()) { 730 String msg = ( 731 qc == '\'' 732 ? "Invalid quote character \"{0}\" being used." 733 : "Did not find quote character marking beginning of string. Character=\"{0}\"" 734 ); 735 throw new ParseException(this, msg, (char)qc); 736 } 737 final boolean isQuoted = (qc == '\'' || qc == '"'); 738 String s = null; 739 boolean isInEscape = false; 740 int c = 0; 741 while (c != -1) { 742 c = r.read(); 743 // Strict syntax requires that all control characters be escaped. 744 if (isStrict() && c <= 0x1F) 745 throw new ParseException(this, "Unescaped control character encountered: ''0x{0}''", String.format("%04X", c)); 746 if (isInEscape) { 747 switch (c) { 748 case 'n': r.replace('\n'); break; 749 case 'r': r.replace('\r'); break; 750 case 't': r.replace('\t'); break; 751 case 'f': r.replace('\f'); break; 752 case 'b': r.replace('\b'); break; 753 case '\\': r.replace('\\'); break; 754 case '/': r.replace('/'); break; 755 case '\'': r.replace('\''); break; 756 case '"': r.replace('"'); break; 757 case 'u': { 758 String n = r.read(4); 759 try { 760 r.replace(Integer.parseInt(n, 16), 6); 761 } catch (NumberFormatException e) { 762 throw new ParseException(this, "Invalid Unicode escape sequence in string."); 763 } 764 break; 765 } 766 default: 767 throw new ParseException(this, "Invalid escape sequence in string."); 768 } 769 isInEscape = false; 770 } else { 771 if (c == '\\') { 772 isInEscape = true; 773 r.delete(); 774 } else if (isQuoted) { 775 if (c == qc) { 776 s = r.getMarked(1, -1); 777 break; 778 } 779 } else { 780 if (c == ',' || c == '}' || c == ']' || isWhitespace(c)) { 781 s = r.getMarked(0, -1); 782 r.unread(); 783 break; 784 } else if (c == -1) { 785 s = r.getMarked(0, 0); 786 break; 787 } 788 } 789 } 790 } 791 if (s == null) 792 throw new ParseException(this, "Could not find expected end character ''{0}''.", (char)qc); 793 794 // Look for concatenated string (i.e. whitespace followed by +). 795 skipCommentsAndSpace(r); 796 if (r.peek() == '+') { 797 if (isStrict()) 798 throw new ParseException(this, "String concatenation detected."); 799 r.read(); // Skip past '+', NOSONAR - Intentional. 800 skipCommentsAndSpace(r); 801 s += parseString(r); 802 } 803 return trim(s); // End of input reached. 804 } 805 806 /* 807 * Looks for the keywords true, false, or null. 808 * Throws an exception if any of these keywords are not found at the specified position. 809 */ 810 private void parseKeyword(String keyword, ParserReader r) throws IOException, ParseException { 811 try { 812 String s = r.read(keyword.length()); 813 if (s.equals(keyword)) 814 return; 815 throw new ParseException(this, "Unrecognized syntax. Expected=''{0}'', Actual=''{1}''", keyword, s); 816 } catch (IndexOutOfBoundsException e) { 817 throw new ParseException(this, "Unrecognized syntax. Expected=''{0}'', found end-of-file.", keyword); 818 } 819 } 820 821 /* 822 * Doesn't actually parse anything, but moves the position beyond any whitespace or comments. 823 * If positionOnNext is 'true', then the cursor will be set to the point immediately after 824 * the comments and whitespace. Otherwise, the cursor will be set to the last position of 825 * the comments and whitespace. 826 */ 827 private void skipCommentsAndSpace(ParserReader r) throws IOException, ParseException { 828 int c = 0; 829 while ((c = r.read()) != -1) { 830 if (! isWhitespace(c)) { 831 if (c == '/') { 832 if (isStrict()) 833 throw new ParseException(this, "Javascript comment detected."); 834 skipComments(r); 835 } else { 836 r.unread(); 837 return; 838 } 839 } 840 } 841 } 842 843 /* 844 * Doesn't actually parse anything, but moves the position beyond the construct "{wrapperAttr:" when 845 * the @Json(wrapperAttr) annotation is used on a class. 846 */ 847 private void skipWrapperAttrStart(ParserReader r, String wrapperAttr) throws IOException, ParseException { 848 849 final int 850 S0=0, // Looking for outer '{' 851 S1=1, // Looking for attrName start. 852 S3=3, // Found attrName end, looking for :. 853 S4=4; // Found :, looking for valStart: { [ " ' LITERAL. 854 855 int state = S0; 856 String currAttr = null; 857 int c = 0; 858 while (c != -1) { 859 c = r.read(); 860 if (state == S0) { 861 if (c == '{') 862 state = S1; 863 } else if (state == S1) { 864 if (isCommentOrWhitespace(c)) { 865 skipCommentsAndSpace(r.unread()); 866 } else { 867 currAttr = parseFieldName(r.unread()); 868 if (! currAttr.equals(wrapperAttr)) 869 throw new ParseException(this, 870 "Expected to find wrapper attribute ''{0}'' but found attribute ''{1}''", wrapperAttr, currAttr); 871 state = S3; 872 } 873 } else if (state == S3) { 874 if (c == ':') 875 state = S4; 876 } else if (state == S4) { 877 if (isCommentOrWhitespace(c)) { 878 skipCommentsAndSpace(r.unread()); 879 } else { 880 r.unread(); 881 return; 882 } 883 } 884 } 885 if (state == S0) 886 throw new ParseException(this, "Expected '{' at beginning of JSON object."); 887 if (state == S1) 888 throw new ParseException(this, "Could not find attribute name on JSON object."); 889 if (state == S3) 890 throw new ParseException(this, "Could not find ':' following attribute name on JSON object."); 891 if (state == S4) 892 throw new ParseException(this, "Expected one of the following characters: {,[,',\",LITERAL."); 893 } 894 895 /* 896 * Doesn't actually parse anything, but moves the position beyond the construct "}" when 897 * the @Json(wrapperAttr) annotation is used on a class. 898 */ 899 private void skipWrapperAttrEnd(ParserReader r) throws ParseException, IOException { 900 int c = 0; 901 while ((c = r.read()) != -1) { 902 if (! isWhitespace(c)) { 903 if (c == '/') { 904 if (isStrict()) 905 throw new ParseException(this, "Javascript comment detected."); 906 skipComments(r); 907 } else if (c == '}') { 908 return; 909 } else { 910 throw new ParseException(this, "Could not find '}' at the end of JSON wrapper object."); 911 } 912 } 913 } 914 } 915 916 /* 917 * Doesn't actually parse anything, but when positioned at the beginning of comment, 918 * it will move the pointer to the last character in the comment. 919 */ 920 private void skipComments(ParserReader r) throws ParseException, IOException { 921 int c = r.read(); 922 // "/* */" style comments 923 if (c == '*') { 924 while (c != -1) 925 if ((c = r.read()) == '*') 926 if ((c = r.read()) == '/') 927 return; 928 // "//" style comments 929 } else if (c == '/') { 930 while (c != -1) { 931 c = r.read(); 932 if (c == -1 || c == '\n') 933 return; 934 } 935 } 936 throw new ParseException(this, "Open ended comment."); 937 } 938 939 /* 940 * Call this method after you've finished a parsing a string to make sure that if there's any 941 * remainder in the input, that it consists only of whitespace and comments. 942 */ 943 private void validateEnd(ParserReader r) throws IOException, ParseException { 944 if (! isValidateEnd()) 945 return; 946 skipCommentsAndSpace(r); 947 int c = r.read(); 948 if (c != -1 && c != ';') // var x = {...}; expressions can end with a semicolon. 949 throw new ParseException(this, "Remainder after parse: ''{0}''.", (char)c); 950 } 951 952 //----------------------------------------------------------------------------------------------------------------- 953 // Properties 954 //----------------------------------------------------------------------------------------------------------------- 955 956 /** 957 * Validate end. 958 * 959 * @see JsonParser.Builder#validateEnd() 960 * @return 961 * <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in 962 * the stream consists of only comments or whitespace. 963 */ 964 protected boolean isValidateEnd() { 965 return ctx.isValidateEnd(); 966 } 967 968 //----------------------------------------------------------------------------------------------------------------- 969 // Extended metadata 970 //----------------------------------------------------------------------------------------------------------------- 971 972 /** 973 * Returns the language-specific metadata on the specified class. 974 * 975 * @param cm The class to return the metadata on. 976 * @return The metadata. 977 */ 978 protected JsonClassMeta getJsonClassMeta(ClassMeta<?> cm) { 979 return ctx.getJsonClassMeta(cm); 980 } 981}