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