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