001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.parser; 014 015import static org.apache.juneau.internal.StringUtils.*; 016import static org.apache.juneau.parser.Parser.*; 017 018import java.io.*; 019import java.lang.reflect.*; 020import java.util.*; 021 022import org.apache.juneau.*; 023import org.apache.juneau.annotation.*; 024import org.apache.juneau.transform.*; 025import org.apache.juneau.utils.*; 026 027/** 028 * Session object that lives for the duration of a single use of {@link Parser}. 029 * 030 * <p> 031 * This class is NOT thread safe. 032 * It is typically discarded after one-time use although it can be reused against multiple inputs. 033 */ 034public abstract class ParserSession extends BeanSession { 035 036 private final Parser ctx; 037 private final Method javaMethod; 038 private final Object outer; 039 040 // Writable properties. 041 private BeanPropertyMeta currentProperty; 042 private ClassMeta<?> currentClass; 043 private final ParserListener listener; 044 045 private Position mark = new Position(-1); 046 047 private ParserPipe pipe; 048 049 /** 050 * Create a new session using properties specified in the context. 051 * 052 * @param ctx 053 * The context creating this session object. 054 * The context contains all the configuration settings for this object. 055 * @param args 056 * Runtime session arguments. 057 */ 058 protected ParserSession(Parser ctx, ParserSessionArgs args) { 059 super(ctx, args == null ? ParserSessionArgs.DEFAULT : args); 060 args = args == null ? ParserSessionArgs.DEFAULT : args; 061 this.ctx = ctx; 062 javaMethod = args.javaMethod; 063 outer = args.outer; 064 listener = getInstanceProperty(PARSER_listener, ParserListener.class, ctx.getListener()); 065 } 066 067 /** 068 * Default constructor. 069 * 070 * @param args 071 * Runtime session arguments. 072 */ 073 protected ParserSession(ParserSessionArgs args) { 074 this(Parser.DEFAULT, args); 075 } 076 077 //----------------------------------------------------------------------------------------------------------------- 078 // Abstract methods 079 //----------------------------------------------------------------------------------------------------------------- 080 081 /** 082 * Workhorse method. 083 * 084 * <p> 085 * Subclasses are expected to implement this method. 086 * 087 * @param pipe Where to get the input from. 088 * @param type 089 * The class type of the object to create. 090 * If <jk>null</jk> or <code>Object.<jk>class</jk></code>, object type is based on what's being parsed. 091 * For example, when parsing JSON text, it may return a <c>String</c>, <c>Number</c>, 092 * <c>ObjectMap</c>, etc... 093 * @param <T> The class type of the object to create. 094 * @return The parsed object. 095 * @throws IOException Thrown by underlying stream. 096 * @throws ParseException Malformed input encountered. 097 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 098 */ 099 protected abstract <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws IOException, ParseException, ExecutableException; 100 101 /** 102 * Returns <jk>true</jk> if this parser subclasses from {@link ReaderParser}. 103 * 104 * @return <jk>true</jk> if this parser subclasses from {@link ReaderParser}. 105 */ 106 public abstract boolean isReaderParser(); 107 108 109 //----------------------------------------------------------------------------------------------------------------- 110 // Other methods 111 //----------------------------------------------------------------------------------------------------------------- 112 113 /** 114 * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into 115 * a stream or reader. 116 * 117 * @param input 118 * The input. 119 * <br>For character-based parsers, this can be any of the following types: 120 * <ul> 121 * <li><jk>null</jk> 122 * <li>{@link Reader} 123 * <li>{@link CharSequence} 124 * <li>{@link InputStream} containing UTF-8 encoded text (or whatever the encoding specified by 125 * {@link ReaderParser#RPARSER_streamCharset}). 126 * <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or whatever the encoding specified by 127 * {@link ReaderParser#RPARSER_streamCharset}). 128 * <li>{@link File} containing system encoded text (or whatever the encoding specified by 129 * {@link ReaderParser#RPARSER_fileCharset}). 130 * </ul> 131 * <br>For byte-based parsers, this can be any of the following types: 132 * <ul> 133 * <li><jk>null</jk> 134 * <li>{@link InputStream} 135 * <li><code><jk>byte</jk>[]</code> 136 * <li>{@link File} 137 * <li>{@link CharSequence} containing encoded bytes according to the {@link InputStreamParser#ISPARSER_binaryFormat} setting. 138 * </ul> 139 * @return 140 * A new {@link ParserPipe} wrapper around the specified input object. 141 */ 142 protected ParserPipe createPipe(Object input) { 143 return null; 144 } 145 146 /** 147 * Returns information used to determine at what location in the parse a failure occurred. 148 * 149 * @return A map, typically containing something like <c>{line:123,column:456,currentProperty:"foobar"}</c> 150 */ 151 public final ObjectMap getLastLocation() { 152 ObjectMap m = new ObjectMap(); 153 if (currentClass != null) 154 m.put("currentClass", currentClass.toString(true)); 155 if (currentProperty != null) 156 m.put("currentProperty", currentProperty); 157 return m; 158 } 159 160 /** 161 * Returns the Java method that invoked this parser. 162 * 163 * <p> 164 * When using the REST API, this is the Java method invoked by the REST call. 165 * Can be used to access annotations defined on the method or class. 166 * 167 * @return The Java method that invoked this parser. 168 */ 169 protected final Method getJavaMethod() { 170 return javaMethod; 171 } 172 173 /** 174 * Returns the outer object used for instantiating top-level non-static member classes. 175 * 176 * <p> 177 * When using the REST API, this is the servlet object. 178 * 179 * @return The outer object. 180 */ 181 protected final Object getOuter() { 182 return outer; 183 } 184 185 /** 186 * Sets the current bean property being parsed for proper error messages. 187 * 188 * @param currentProperty The current property being parsed. 189 */ 190 protected final void setCurrentProperty(BeanPropertyMeta currentProperty) { 191 this.currentProperty = currentProperty; 192 } 193 194 /** 195 * Sets the current class being parsed for proper error messages. 196 * 197 * @param currentClass The current class being parsed. 198 */ 199 protected final void setCurrentClass(ClassMeta<?> currentClass) { 200 this.currentClass = currentClass; 201 } 202 203 /** 204 * Trims the specified object if it's a <c>String</c> and {@link #isTrimStrings()} returns <jk>true</jk>. 205 * 206 * @param o The object to trim. 207 * @return The trimmed string if it's a string. 208 */ 209 @SuppressWarnings("unchecked") 210 protected final <K> K trim(K o) { 211 if (isTrimStrings() && o instanceof String) 212 return (K)o.toString().trim(); 213 return o; 214 215 } 216 217 /** 218 * Trims the specified string if {@link ParserSession#isTrimStrings()} returns <jk>true</jk>. 219 * 220 * @param s The input string to trim. 221 * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. 222 */ 223 protected final String trim(String s) { 224 if (isTrimStrings() && s != null) 225 return s.trim(); 226 return s; 227 } 228 229 /** 230 * Converts the specified <c>ObjectMap</c> into a bean identified by the <js>"_type"</js> property in the map. 231 * 232 * @param m The map to convert to a bean. 233 * @param pMeta The current bean property being parsed. 234 * @param eType The current expected type being parsed. 235 * @return 236 * The converted bean, or the same map if the <js>"_type"</js> entry wasn't found or didn't resolve to a bean. 237 */ 238 protected final Object cast(ObjectMap m, BeanPropertyMeta pMeta, ClassMeta<?> eType) { 239 240 String btpn = getBeanTypePropertyName(eType); 241 242 Object o = m.get(btpn); 243 if (o == null) 244 return m; 245 String typeName = o.toString(); 246 247 ClassMeta<?> cm = getClassMeta(typeName, pMeta, eType); 248 249 if (cm != null) { 250 BeanMap<?> bm = m.getBeanSession().newBeanMap(cm.getInnerClass()); 251 252 // Iterate through all the entries in the map and set the individual field values. 253 for (Map.Entry<String,Object> e : m.entrySet()) { 254 String k = e.getKey(); 255 Object v = e.getValue(); 256 if (! k.equals(btpn)) { 257 // Attempt to recursively cast child maps. 258 if (v instanceof ObjectMap) 259 v = cast((ObjectMap)v, pMeta, eType); 260 bm.put(k, v); 261 } 262 } 263 return bm.getBean(); 264 } 265 266 return m; 267 } 268 269 /** 270 * Give the specified dictionary name, resolve it to a class. 271 * 272 * @param typeName The dictionary name to resolve. 273 * @param pMeta The bean property we're currently parsing. 274 * @param eType The expected type we're currently parsing. 275 * @return The resolved class, or <jk>null</jk> if the type name could not be resolved. 276 */ 277 protected final ClassMeta<?> getClassMeta(String typeName, BeanPropertyMeta pMeta, ClassMeta<?> eType) { 278 BeanRegistry br = null; 279 280 // Resolve via @Beanp(dictionary={}) 281 if (pMeta != null) { 282 br = pMeta.getBeanRegistry(); 283 if (br != null && br.hasName(typeName)) 284 return br.getClassMeta(typeName); 285 } 286 287 // Resolve via @Bean(dictionary={}) on the expected type where the 288 // expected type is an interface with subclasses. 289 if (eType != null) { 290 br = eType.getBeanRegistry(); 291 if (br != null && br.hasName(typeName)) 292 return br.getClassMeta(typeName); 293 } 294 295 // Last resort, resolve using the session registry. 296 return getBeanRegistry().getClassMeta(typeName); 297 } 298 299 /** 300 * Method that gets called when an unknown bean property name is encountered. 301 * 302 * @param propertyName The unknown bean property name. 303 * @param beanMap The bean that doesn't have the expected property. 304 * @throws ParseException 305 * Automatically thrown if {@link BeanContext#BEAN_ignoreUnknownBeanProperties} setting on this parser is 306 * <jk>false</jk> 307 * @param <T> The class type of the bean map that doesn't have the expected property. 308 */ 309 protected final <T> void onUnknownProperty(String propertyName, BeanMap<T> beanMap) throws ParseException { 310 if (propertyName.equals(getBeanTypePropertyName(beanMap.getClassMeta()))) 311 return; 312 if (! isIgnoreUnknownBeanProperties()) 313 throw new ParseException(this, 314 "Unknown property ''{0}'' encountered while trying to parse into class ''{1}''", propertyName, 315 beanMap.getClassMeta()); 316 if (listener != null) 317 listener.onUnknownBeanProperty(this, propertyName, beanMap.getClassMeta().getInnerClass(), beanMap.getBean()); 318 } 319 320 /** 321 * Parses input into the specified object type. 322 * 323 * <p> 324 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 325 * 326 * <h5 class='section'>Examples:</h5> 327 * <p class='bcode w800'> 328 * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; 329 * 330 * <jc>// Parse into a linked-list of strings.</jc> 331 * List l = p.parse(json, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 332 * 333 * <jc>// Parse into a linked-list of beans.</jc> 334 * List l = p.parse(json, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 335 * 336 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 337 * List l = p.parse(json, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 338 * 339 * <jc>// Parse into a map of string keys/values.</jc> 340 * Map m = p.parse(json, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 341 * 342 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 343 * Map m = p.parse(json, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 344 * </p> 345 * 346 * <p> 347 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 348 * 349 * <p> 350 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 351 * 352 * <p> 353 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 354 * 355 * <ul class='notes'> 356 * <li> 357 * Use the {@link #parse(Object, Class)} method instead if you don't need a parameterized map/collection. 358 * </ul> 359 * 360 * @param <T> The class type of the object to create. 361 * @param input 362 * The input. 363 * <br>Character-based parsers can handle the following input class types: 364 * <ul> 365 * <li><jk>null</jk> 366 * <li>{@link Reader} 367 * <li>{@link CharSequence} 368 * <li>{@link InputStream} containing UTF-8 encoded text (or charset defined by 369 * {@link ReaderParser#RPARSER_streamCharset} property value). 370 * <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or charset defined by 371 * {@link ReaderParser#RPARSER_streamCharset} property value). 372 * <li>{@link File} containing system encoded text (or charset defined by 373 * {@link ReaderParser#RPARSER_fileCharset} property value). 374 * </ul> 375 * <br>Stream-based parsers can handle the following input class types: 376 * <ul> 377 * <li><jk>null</jk> 378 * <li>{@link InputStream} 379 * <li><code><jk>byte</jk>[]</code> 380 * <li>{@link File} 381 * </ul> 382 * @param type 383 * The object type to create. 384 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 385 * @param args 386 * The type arguments of the class if it's a collection or map. 387 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 388 * <br>Ignored if the main type is not a map or collection. 389 * @return The parsed object. 390 * @throws ParseException Malformed input encountered. 391 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. 392 * @throws IOException Thrown by the underlying stream. 393 */ 394 @SuppressWarnings("unchecked") 395 public final <T> T parse(Object input, Type type, Type...args) throws ParseException, IOException { 396 try (ParserPipe pipe = createPipe(input)) { 397 return (T)parseInner(pipe, getClassMeta(type, args)); 398 } 399 } 400 401 /** 402 * Same as {@link #parse(Object,Type,Type...)} but parses from a string and doesn't throw an {@link IOException}. 403 * 404 * @param <T> The class type of the object to create. 405 * @param input 406 * The input. 407 * <br>Character-based parsers can handle the following input class types: 408 * <ul> 409 * <li><jk>null</jk> 410 * <li>{@link Reader} 411 * <li>{@link CharSequence} 412 * <li>{@link InputStream} containing UTF-8 encoded text (or charset defined by 413 * {@link ReaderParser#RPARSER_streamCharset} property value). 414 * <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or charset defined by 415 * {@link ReaderParser#RPARSER_streamCharset} property value). 416 * <li>{@link File} containing system encoded text (or charset defined by 417 * {@link ReaderParser#RPARSER_fileCharset} property value). 418 * </ul> 419 * <br>Stream-based parsers can handle the following input class types: 420 * <ul> 421 * <li><jk>null</jk> 422 * <li>{@link InputStream} 423 * <li><code><jk>byte</jk>[]</code> 424 * <li>{@link File} 425 * </ul> 426 * @param type 427 * The object type to create. 428 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 429 * @param args 430 * The type arguments of the class if it's a collection or map. 431 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 432 * <br>Ignored if the main type is not a map or collection. 433 * @return The parsed object. 434 * @throws ParseException Malformed input encountered. 435 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. 436 */ 437 @SuppressWarnings("unchecked") 438 public final <T> T parse(String input, Type type, Type...args) throws ParseException { 439 try (ParserPipe pipe = createPipe(input)) { 440 return (T)parseInner(pipe, getClassMeta(type, args)); 441 } catch (IOException e) { 442 throw new ParseException(e); // Shouldn't happen. 443 } 444 } 445 446 /** 447 * Same as {@link #parse(Object, Type, Type...)} except optimized for a non-parameterized class. 448 * 449 * <p> 450 * This is the preferred parse method for simple types since you don't need to cast the results. 451 * 452 * <h5 class='section'>Examples:</h5> 453 * <p class='bcode w800'> 454 * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; 455 * 456 * <jc>// Parse into a string.</jc> 457 * String s = p.parse(json, String.<jk>class</jk>); 458 * 459 * <jc>// Parse into a bean.</jc> 460 * MyBean b = p.parse(json, MyBean.<jk>class</jk>); 461 * 462 * <jc>// Parse into a bean array.</jc> 463 * MyBean[] ba = p.parse(json, MyBean[].<jk>class</jk>); 464 * 465 * <jc>// Parse into a linked-list of objects.</jc> 466 * List l = p.parse(json, LinkedList.<jk>class</jk>); 467 * 468 * <jc>// Parse into a map of object keys/values.</jc> 469 * Map m = p.parse(json, TreeMap.<jk>class</jk>); 470 * </p> 471 * 472 * @param <T> The class type of the object being created. 473 * @param input 474 * The input. 475 * See {@link #parse(Object, Type, Type...)} for details. 476 * @param type The object type to create. 477 * @return The parsed object. 478 * @throws ParseException Malformed input encountered. 479 * @throws IOException Thrown by the underlying stream. 480 */ 481 public final <T> T parse(Object input, Class<T> type) throws ParseException, IOException { 482 try (ParserPipe pipe = createPipe(input)) { 483 return parseInner(pipe, getClassMeta(type)); 484 } 485 } 486 487 /** 488 * Same as {@link #parse(Object, Class)} but parses from a string and doesn't throw an {@link IOException}. 489 * 490 * <p> 491 * This is the preferred parse method for simple types since you don't need to cast the results. 492 * 493 * <h5 class='section'>Examples:</h5> 494 * <p class='bcode w800'> 495 * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; 496 * 497 * <jc>// Parse into a string.</jc> 498 * String s = p.parse(json, String.<jk>class</jk>); 499 * 500 * <jc>// Parse into a bean.</jc> 501 * MyBean b = p.parse(json, MyBean.<jk>class</jk>); 502 * 503 * <jc>// Parse into a bean array.</jc> 504 * MyBean[] ba = p.parse(json, MyBean[].<jk>class</jk>); 505 * 506 * <jc>// Parse into a linked-list of objects.</jc> 507 * List l = p.parse(json, LinkedList.<jk>class</jk>); 508 * 509 * <jc>// Parse into a map of object keys/values.</jc> 510 * Map m = p.parse(json, TreeMap.<jk>class</jk>); 511 * </p> 512 * 513 * @param <T> The class type of the object being created. 514 * @param input 515 * The input. 516 * See {@link #parse(Object, Type, Type...)} for details. 517 * @param type The object type to create. 518 * @return The parsed object. 519 * @throws ParseException Malformed input encountered. 520 */ 521 public final <T> T parse(String input, Class<T> type) throws ParseException { 522 try (ParserPipe pipe = createPipe(input)) { 523 return parseInner(pipe, getClassMeta(type)); 524 } catch (IOException e) { 525 throw new ParseException(e); // Shouldn't happen. 526 } 527 } 528 529 /** 530 * Same as {@link #parse(Object, Type, Type...)} except the type has already been converted into a {@link ClassMeta} 531 * object. 532 * 533 * <p> 534 * This is mostly an internal method used by the framework. 535 * 536 * @param <T> The class type of the object being created. 537 * @param input 538 * The input. 539 * See {@link #parse(Object, Type, Type...)} for details. 540 * @param type The object type to create. 541 * @return The parsed object. 542 * @throws ParseException Malformed input encountered. 543 * @throws IOException Thrown by the underlying stream. 544 */ 545 public final <T> T parse(Object input, ClassMeta<T> type) throws ParseException, IOException { 546 try (ParserPipe pipe = createPipe(input)) { 547 return parseInner(pipe, type); 548 } 549 } 550 551 /** 552 * Same as {@link #parse(Object, ClassMeta)} except parses from a string and doesn't throw an {@link IOException}. 553 * 554 * <p> 555 * This is mostly an internal method used by the framework. 556 * 557 * @param <T> The class type of the object being created. 558 * @param input 559 * The input. 560 * See {@link #parse(Object, Type, Type...)} for details. 561 * @param type The object type to create. 562 * @return The parsed object. 563 * @throws ParseException Malformed input encountered. 564 */ 565 public final <T> T parse(String input, ClassMeta<T> type) throws ParseException { 566 try (ParserPipe pipe = createPipe(input)) { 567 return parseInner(pipe, type); 568 } catch (IOException e) { 569 throw new ParseException(e); // Shouldn't happen. 570 } 571 } 572 573 /** 574 * Entry point for all parsing calls. 575 * 576 * <p> 577 * Calls the {@link #doParse(ParserPipe, ClassMeta)} implementation class and catches/re-wraps any exceptions 578 * thrown. 579 * 580 * @param pipe The parser input. 581 * @param type The class type of the object to create. 582 * @param <T> The class type of the object to create. 583 * @return The parsed object. 584 * @throws ParseException Malformed input encountered. 585 * @throws IOException Thrown by the underlying stream. 586 */ 587 private <T> T parseInner(ParserPipe pipe, ClassMeta<T> type) throws ParseException, IOException { 588 if (type.isVoid()) 589 return null; 590 try { 591 return doParse(pipe, type); 592 } catch (ParseException | IOException e) { 593 throw e; 594 } catch (StackOverflowError e) { 595 throw new ParseException(this, "Depth too deep. Stack overflow occurred."); 596 } catch (Exception e) { 597 throw new ParseException(this, e, "Exception occurred. exception={0}, message={1}.", 598 e.getClass().getSimpleName(), e.getLocalizedMessage()); 599 } finally { 600 checkForWarnings(); 601 } 602 } 603 604 /** 605 * Parses the contents of the specified reader and loads the results into the specified map. 606 * 607 * <p> 608 * Reader must contain something that serializes to a map (such as text containing a JSON object). 609 * 610 * <p> 611 * Used in the following locations: 612 * <ul class='spaced-list'> 613 * <li> 614 * The various character-based constructors in {@link ObjectMap} (e.g. 615 * {@link ObjectMap#ObjectMap(CharSequence,Parser)}). 616 * </ul> 617 * 618 * @param <K> The key class type. 619 * @param <V> The value class type. 620 * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. 621 * @param m The map being loaded. 622 * @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>. 623 * @param valueType The class type of the values, or <jk>null</jk> to default to whatever is being parsed. 624 * @return The same map that was passed in to allow this method to be chained. 625 * @throws ParseException Malformed input encountered. 626 * @throws UnsupportedOperationException If not implemented. 627 */ 628 public final <K,V> Map<K,V> parseIntoMap(Object input, Map<K,V> m, Type keyType, Type valueType) throws ParseException { 629 try (ParserPipe pipe = createPipe(input)) { 630 return doParseIntoMap(pipe, m, keyType, valueType); 631 } catch (ParseException e) { 632 throw e; 633 } catch (Exception e) { 634 throw new ParseException(this, e); 635 } finally { 636 checkForWarnings(); 637 } 638 } 639 640 /** 641 * Implementation method. 642 * 643 * <p> 644 * Default implementation throws an {@link UnsupportedOperationException}. 645 * 646 * @param pipe The parser input. 647 * @param m The map being loaded. 648 * @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>. 649 * @param valueType The class type of the values, or <jk>null</jk> to default to whatever is being parsed. 650 * @return The same map that was passed in to allow this method to be chained. 651 * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. 652 */ 653 protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception { 654 throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); 655 } 656 657 /** 658 * Parses the contents of the specified reader and loads the results into the specified collection. 659 * 660 * <p> 661 * Used in the following locations: 662 * <ul class='spaced-list'> 663 * <li> 664 * The various character-based constructors in {@link ObjectList} (e.g. 665 * {@link ObjectList#ObjectList(CharSequence,Parser)}. 666 * </ul> 667 * 668 * @param <E> The element class type. 669 * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. 670 * @param c The collection being loaded. 671 * @param elementType The class type of the elements, or <jk>null</jk> to default to whatever is being parsed. 672 * @return The same collection that was passed in to allow this method to be chained. 673 * @throws ParseException Malformed input encountered. 674 * @throws UnsupportedOperationException If not implemented. 675 */ 676 public final <E> Collection<E> parseIntoCollection(Object input, Collection<E> c, Type elementType) throws ParseException { 677 try (ParserPipe pipe = createPipe(input)) { 678 return doParseIntoCollection(pipe, c, elementType); 679 } catch (ParseException e) { 680 throw e; 681 } catch (StackOverflowError e) { 682 throw new ParseException(this, "Depth too deep. Stack overflow occurred."); 683 } catch (IOException e) { 684 throw new ParseException(this, e, "I/O exception occurred. exception={0}, message={1}.", 685 e.getClass().getSimpleName(), e.getLocalizedMessage()); 686 } catch (Exception e) { 687 throw new ParseException(this, e, "Exception occurred. exception={0}, message={1}.", 688 e.getClass().getSimpleName(), e.getLocalizedMessage()); 689 } finally { 690 checkForWarnings(); 691 } 692 } 693 694 /** 695 * Implementation method. 696 * 697 * <p> 698 * Default implementation throws an {@link UnsupportedOperationException}. 699 * 700 * @param pipe The parser input. 701 * @param c The collection being loaded. 702 * @param elementType The class type of the elements, or <jk>null</jk> to default to whatever is being parsed. 703 * @return The same collection that was passed in to allow this method to be chained. 704 * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. 705 */ 706 protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception { 707 throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); 708 } 709 710 /** 711 * Parses the specified array input with each entry in the object defined by the {@code argTypes} 712 * argument. 713 * 714 * <p> 715 * Used for converting arrays (e.g. <js>"[arg1,arg2,...]"</js>) into an {@code Object[]} that can be passed 716 * to the {@code Method.invoke(target, args)} method. 717 * 718 * <p> 719 * Used in the following locations: 720 * <ul class='spaced-list'> 721 * <li> 722 * Used to parse argument strings in the {@link PojoIntrospector#invokeMethod(Method, Reader)} method. 723 * </ul> 724 * 725 * @param input The input. Subclasses can support different input types. 726 * @param argTypes Specifies the type of objects to create for each entry in the array. 727 * @return An array of parsed objects. 728 * @throws ParseException Malformed input encountered. 729 */ 730 public final Object[] parseArgs(Object input, Type[] argTypes) throws ParseException { 731 try (ParserPipe pipe = createPipe(input)) { 732 return doParse(pipe, getArgsClassMeta(argTypes)); 733 } catch (ParseException e) { 734 throw e; 735 } catch (StackOverflowError e) { 736 throw new ParseException(this, "Depth too deep. Stack overflow occurred."); 737 } catch (IOException e) { 738 throw new ParseException(this, e, "I/O exception occurred. exception={0}, message={1}.", 739 e.getClass().getSimpleName(), e.getLocalizedMessage()); 740 } catch (Exception e) { 741 throw new ParseException(this, e, "Exception occurred. exception={0}, message={1}.", 742 e.getClass().getSimpleName(), e.getLocalizedMessage()); 743 } finally { 744 checkForWarnings(); 745 } 746 } 747 748 /** 749 * Converts the specified string to the specified type. 750 * 751 * @param outer 752 * The outer object if we're converting to an inner object that needs to be created within the context 753 * of an outer object. 754 * @param s The string to convert. 755 * @param type The class type to convert the string to. 756 * @return The string converted as an object of the specified type. 757 * @param <T> The class type to convert the string to. 758 * @throws IOException Thrown by underlying stream. 759 * @throws ParseException Malformed input encountered. 760 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 761 */ 762 @SuppressWarnings({ "unchecked", "rawtypes" }) 763 protected final <T> T convertAttrToType(Object outer, String s, ClassMeta<T> type) throws IOException, ParseException, ExecutableException { 764 if (s == null) 765 return null; 766 767 if (type == null) 768 type = (ClassMeta<T>)object(); 769 PojoSwap swap = type.getPojoSwap(this); 770 ClassMeta<?> sType = swap == null ? type : swap.getSwapClassMeta(this); 771 772 Object o = s; 773 if (sType.isChar()) 774 o = parseCharacter(s); 775 else if (sType.isNumber()) 776 o = parseNumber(s, (Class<? extends Number>)sType.getInnerClass()); 777 else if (sType.isBoolean()) 778 o = Boolean.parseBoolean(s); 779 else if (! (sType.isCharSequence() || sType.isObject())) { 780 if (sType.canCreateNewInstanceFromString(outer)) 781 o = sType.newInstanceFromString(outer, s); 782 else 783 throw new ParseException(this, "Invalid conversion from string to class ''{0}''", type); 784 } 785 786 if (swap != null) 787 o = unswap(swap, o, type); 788 789 return (T)o; 790 } 791 792 /** 793 * Convenience method for calling the {@link ParentProperty @ParentProperty} method on the specified object if it 794 * exists. 795 * 796 * @param cm The class type of the object. 797 * @param o The object. 798 * @param parent The parent to set. 799 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 800 */ 801 protected static final void setParent(ClassMeta<?> cm, Object o, Object parent) throws ExecutableException { 802 Setter m = cm.getParentProperty(); 803 if (m != null) 804 m.set(o, parent); 805 } 806 807 /** 808 * Convenience method for calling the {@link NameProperty @NameProperty} method on the specified object if it exists. 809 * 810 * @param cm The class type of the object. 811 * @param o The object. 812 * @param name The name to set. 813 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 814 */ 815 protected static final void setName(ClassMeta<?> cm, Object o, Object name) throws ExecutableException { 816 if (cm != null) { 817 Setter m = cm.getNameProperty(); 818 if (m != null) 819 m.set(o, name); 820 } 821 } 822 823 /** 824 * Returns the listener associated with this session. 825 * 826 * @param c The listener class to cast to. 827 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 828 */ 829 @SuppressWarnings("unchecked") 830 public <T extends ParserListener> T getListener(Class<T> c) { 831 return (T)listener; 832 } 833 834 /** 835 * The {@link #createPipe(Object)} method should call this method to set the pipe for debugging purposes. 836 * 837 * @param pipe The pipe created for this session. 838 * @return The same pipe. 839 */ 840 protected ParserPipe setPipe(ParserPipe pipe) { 841 this.pipe = pipe; 842 return pipe; 843 } 844 845 /** 846 * Returns the current position into the reader or input stream. 847 * 848 * @return 849 * The current position into the reader or input stream. 850 * <br>Never <jk>null</jk>. 851 */ 852 public Position getPosition() { 853 if (mark.line != -1 || mark.column != -1 || mark.position != -1) 854 return mark; 855 if (pipe == null) 856 return Position.UNKNOWN; 857 return pipe.getPosition(); 858 } 859 860 /** 861 * Marks the current position. 862 */ 863 protected void mark() { 864 if (pipe != null) { 865 Position p = pipe.getPosition(); 866 mark.line = p.line; 867 mark.column = p.column; 868 mark.position = p.position; 869 } 870 } 871 872 /** 873 * Unmarks the current position. 874 */ 875 protected void unmark() { 876 mark.line = -1; 877 mark.column = -1; 878 mark.position = -1; 879 } 880 881 /** 882 * Returns the input as a string. 883 * 884 * <p> 885 * This always returns a value for input of type {@link CharSequence}. 886 * <br>For other input types, use {@link BeanContext#BEAN_debug} setting to enable caching to a string 887 * before parsing so that this method returns the input. 888 * 889 * @return The input as a string, or <jk>null</jk> if no pipe has been created or we're reading from an uncached reader or input stream source. 890 */ 891 public String getInputAsString() { 892 return pipe == null ? null : pipe.getInputAsString(); 893 } 894 895 /** 896 * Invokes the specified swap on the specified object. 897 * 898 * @param swap The swap to invoke. 899 * @param o The input object. 900 * @param eType The expected type. 901 * @return The swapped object. 902 * @throws ParseException If swap method threw an exception. 903 */ 904 @SuppressWarnings({ "rawtypes", "unchecked" }) 905 protected Object unswap(PojoSwap swap, Object o, ClassMeta<?> eType) throws ParseException { 906 try { 907 return swap.unswap(this, o, eType); 908 } catch (Exception e) { 909 throw new ParseException(e); 910 } 911 } 912 913 //----------------------------------------------------------------------------------------------------------------- 914 // Properties 915 //----------------------------------------------------------------------------------------------------------------- 916 917 /** 918 * Configuration property: Auto-close streams. 919 * 920 * @see Parser#PARSER_autoCloseStreams 921 * @return 922 * <jk>true</jk> if <l>InputStreams</l> and <l>Readers</l> passed into parsers will be closed 923 * after parsing is complete. 924 */ 925 protected final boolean isAutoCloseStreams() { 926 return ctx.isAutoCloseStreams(); 927 } 928 929 /** 930 * Configuration property: Debug output lines. 931 * 932 * @see Parser#PARSER_debugOutputLines 933 * @return 934 * The number of lines of input before and after the error location to be printed as part of the exception message. 935 */ 936 protected final int getDebugOutputLines() { 937 return ctx.getDebugOutputLines(); 938 } 939 940 /** 941 * Returns the listener associated with this session. 942 * 943 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 944 */ 945 public ParserListener getListener() { 946 return listener; 947 } 948 949 /** 950 * Configuration property: Strict mode. 951 * 952 * @see Parser#PARSER_strict 953 * @return 954 * <jk>true</jk> if strict mode for the parser is enabled. 955 */ 956 protected final boolean isStrict() { 957 return ctx.isStrict(); 958 } 959 960 /** 961 * Configuration property: Trim parsed strings. 962 * 963 * @see Parser#PARSER_trimStrings 964 * @return 965 * <jk>true</jk> if string values will be trimmed of whitespace using {@link String#trim()} before being added to 966 * the POJO. 967 */ 968 protected final boolean isTrimStrings() { 969 return ctx.isTrimStrings(); 970 } 971 972 /** 973 * Configuration property: Unbuffered. 974 * 975 * @see Parser#PARSER_unbuffered 976 * @return 977 * <jk>true</jk> if parsers don't use internal buffering during parsing. 978 */ 979 protected final boolean isUnbuffered() { 980 return ctx.isUnbuffered(); 981 } 982 983 //----------------------------------------------------------------------------------------------------------------- 984 // Other methods 985 //----------------------------------------------------------------------------------------------------------------- 986 987 /** 988 * Configuration property: Parser listener. 989 * 990 * @see Parser#PARSER_listener 991 * @return 992 * Class used to listen for errors and warnings that occur during parsing. 993 */ 994 protected final Class<? extends ParserListener> getListenerClass() { 995 return ctx.getListener(); 996 } 997 998 @Override /* Session */ 999 public ObjectMap toMap() { 1000 return super.toMap() 1001 .append("ParserSession", new DefaultFilteringObjectMap() 1002 .append("javaMethod", javaMethod) 1003 .append("listener", listener) 1004 .append("outer", outer) 1005 ); 1006 } 1007}