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 boolean trimStrings, strict, autoCloseStreams, unbuffered; 037 private final String inputStreamCharset, fileCharset; 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 /** 047 * Create a new session using properties specified in the context. 048 * 049 * @param ctx 050 * The context creating this session object. 051 * The context contains all the configuration settings for this object. 052 * @param args 053 * Runtime session arguments. 054 */ 055 protected ParserSession(Parser ctx, ParserSessionArgs args) { 056 super(ctx, args); 057 trimStrings = getProperty(PARSER_trimStrings, boolean.class, ctx.trimStrings); 058 strict = getProperty(PARSER_strict, boolean.class, ctx.strict); 059 autoCloseStreams = getProperty(PARSER_autoCloseStreams, boolean.class, ctx.autoCloseStreams); 060 unbuffered = getProperty(PARSER_unbuffered, boolean.class, ctx.unbuffered); 061 inputStreamCharset = getProperty(PARSER_inputStreamCharset, String.class, ctx.inputStreamCharset); 062 fileCharset = getProperty(PARSER_fileCharset, String.class, ctx.fileCharset); 063 javaMethod = args.javaMethod; 064 outer = args.outer; 065 listener = getInstanceProperty(PARSER_listener, ParserListener.class, ctx.listener); 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 @Override /* Session */ 079 public ObjectMap asMap() { 080 return super.asMap() 081 .append("ParserSession", new ObjectMap() 082 .append("fileCharset", fileCharset) 083 .append("inputStreamCharset", inputStreamCharset) 084 .append("javaMethod", javaMethod) 085 .append("listener", listener) 086 .append("outer", outer) 087 .append("strict", strict) 088 .append("trimStrings", trimStrings) 089 ); 090 } 091 092 //-------------------------------------------------------------------------------- 093 // Abstract methods 094 //-------------------------------------------------------------------------------- 095 096 /** 097 * Workhorse method. Subclasses are expected to implement this method. 098 * 099 * @param pipe Where to get the input from. 100 * @param type 101 * The class type of the object to create. 102 * If <jk>null</jk> or <code>Object.<jk>class</jk></code>, object type is based on what's being parsed. 103 * For example, when parsing JSON text, it may return a <code>String</code>, <code>Number</code>, 104 * <code>ObjectMap</code>, etc... 105 * @param <T> The class type of the object to create. 106 * @return The parsed object. 107 * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. 108 */ 109 protected abstract <T> T doParse(ParserPipe pipe, ClassMeta<T> type) throws Exception; 110 111 /** 112 * Returns <jk>true</jk> if this parser subclasses from {@link ReaderParser}. 113 * 114 * @return <jk>true</jk> if this parser subclasses from {@link ReaderParser}. 115 */ 116 public abstract boolean isReaderParser(); 117 118 119 //-------------------------------------------------------------------------------- 120 // Other methods 121 //-------------------------------------------------------------------------------- 122 123 /** 124 * Wraps the specified input object into a {@link ParserPipe} object so that it can be easily converted into 125 * a stream or reader. 126 * 127 * @param input 128 * The input. 129 * <br>For character-based parsers, this can be any of the following types: 130 * <ul> 131 * <li><jk>null</jk> 132 * <li>{@link Reader} 133 * <li>{@link CharSequence} 134 * <li>{@link InputStream} containing UTF-8 encoded text (or whatever the encoding specified by 135 * {@link Parser#PARSER_inputStreamCharset}). 136 * <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or whatever the encoding specified by 137 * {@link Parser#PARSER_inputStreamCharset}). 138 * <li>{@link File} containing system encoded text (or whatever the encoding specified by 139 * {@link Parser#PARSER_fileCharset}). 140 * </ul> 141 * <br>For byte-based parsers, this can be any of the following types: 142 * <ul> 143 * <li><jk>null</jk> 144 * <li>{@link InputStream} 145 * <li><code><jk>byte</jk>[]</code> 146 * <li>{@link File} 147 * </ul> 148 * @return 149 * A new {@link ParserPipe} wrapper around the specified input object. 150 */ 151 public final ParserPipe createPipe(Object input) { 152 return new ParserPipe(input, isDebug(), strict, autoCloseStreams, unbuffered, fileCharset, inputStreamCharset); 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 * Returns the {@link Parser#PARSER_trimStrings} setting value for this session. 214 * 215 * @return The {@link Parser#PARSER_trimStrings} setting value for this session. 216 */ 217 protected final boolean isTrimStrings() { 218 return trimStrings; 219 } 220 221 /** 222 * Returns the {@link Parser#PARSER_strict} setting value for this session. 223 * 224 * @return The {@link Parser#PARSER_strict} setting value for this session. 225 */ 226 protected final boolean isStrict() { 227 return strict; 228 } 229 230 /** 231 * Trims the specified object if it's a <code>String</code> and {@link #isTrimStrings()} returns <jk>true</jk>. 232 * 233 * @param o The object to trim. 234 * @return The trimmed string if it's a string. 235 */ 236 @SuppressWarnings("unchecked") 237 protected final <K> K trim(K o) { 238 if (trimStrings && o instanceof String) 239 return (K)o.toString().trim(); 240 return o; 241 242 } 243 244 /** 245 * Trims the specified string if {@link ParserSession#isTrimStrings()} returns <jk>true</jk>. 246 * 247 * @param s The input string to trim. 248 * @return The trimmed string, or <jk>null</jk> if the input was <jk>null</jk>. 249 */ 250 protected final String trim(String s) { 251 if (trimStrings && s != null) 252 return s.trim(); 253 return s; 254 } 255 256 /** 257 * Converts the specified <code>ObjectMap</code> into a bean identified by the <js>"_type"</js> property in the map. 258 * 259 * @param m The map to convert to a bean. 260 * @param pMeta The current bean property being parsed. 261 * @param eType The current expected type being parsed. 262 * @return 263 * The converted bean, or the same map if the <js>"_type"</js> entry wasn't found or didn't resolve to a bean. 264 */ 265 protected final Object cast(ObjectMap m, BeanPropertyMeta pMeta, ClassMeta<?> eType) { 266 267 String btpn = getBeanTypePropertyName(eType); 268 269 Object o = m.get(btpn); 270 if (o == null) 271 return m; 272 String typeName = o.toString(); 273 274 ClassMeta<?> cm = getClassMeta(typeName, pMeta, eType); 275 276 if (cm != null) { 277 BeanMap<?> bm = m.getBeanSession().newBeanMap(cm.getInnerClass()); 278 279 // Iterate through all the entries in the map and set the individual field values. 280 for (Map.Entry<String,Object> e : m.entrySet()) { 281 String k = e.getKey(); 282 Object v = e.getValue(); 283 if (! k.equals(btpn)) { 284 // Attempt to recursively cast child maps. 285 if (v instanceof ObjectMap) 286 v = cast((ObjectMap)v, pMeta, eType); 287 bm.put(k, v); 288 } 289 } 290 return bm.getBean(); 291 } 292 293 return m; 294 } 295 296 /** 297 * Give the specified dictionary name, resolve it to a class. 298 * 299 * @param typeName The dictionary name to resolve. 300 * @param pMeta The bean property we're currently parsing. 301 * @param eType The expected type we're currently parsing. 302 * @return The resolved class, or <jk>null</jk> if the type name could not be resolved. 303 */ 304 protected final ClassMeta<?> getClassMeta(String typeName, BeanPropertyMeta pMeta, ClassMeta<?> eType) { 305 BeanRegistry br = null; 306 307 // Resolve via @BeanProperty(beanDictionary={}) 308 if (pMeta != null) { 309 br = pMeta.getBeanRegistry(); 310 if (br != null && br.hasName(typeName)) 311 return br.getClassMeta(typeName); 312 } 313 314 // Resolve via @Bean(beanDictionary={}) on the expected type where the 315 // expected type is an interface with subclasses. 316 if (eType != null) { 317 br = eType.getBeanRegistry(); 318 if (br != null && br.hasName(typeName)) 319 return br.getClassMeta(typeName); 320 } 321 322 // Last resort, resolve using the session registry. 323 return getBeanRegistry().getClassMeta(typeName); 324 } 325 326 /** 327 * Method that gets called when an unknown bean property name is encountered. 328 * 329 * @param pipe The parser input. 330 * @param propertyName The unknown bean property name. 331 * @param beanMap The bean that doesn't have the expected property. 332 * @param line The line number where the property was found. <code>-1</code> if line numbers are not available. 333 * @param col The column number where the property was found. <code>-1</code> if column numbers are not available. 334 * @throws ParseException 335 * Automatically thrown if {@link BeanContext#BEAN_ignoreUnknownBeanProperties} setting on this parser is 336 * <jk>false</jk> 337 * @param <T> The class type of the bean map that doesn't have the expected property. 338 */ 339 protected final <T> void onUnknownProperty(ParserPipe pipe, String propertyName, BeanMap<T> beanMap, int line, int col) throws ParseException { 340 if (propertyName.equals(getBeanTypePropertyName(beanMap.getClassMeta()))) 341 return; 342 if (! isIgnoreUnknownBeanProperties()) 343 throw new ParseException(getLastLocation(), 344 "Unknown property ''{0}'' encountered while trying to parse into class ''{1}''", propertyName, 345 beanMap.getClassMeta()); 346 if (listener != null) 347 listener.onUnknownBeanProperty(this, pipe, propertyName, beanMap.getClassMeta().getInnerClass(), beanMap.getBean(), 348 line, col); 349 } 350 351 /** 352 * Parses input into the specified object type. 353 * 354 * <p> 355 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 356 * 357 * <h5 class='section'>Examples:</h5> 358 * <p class='bcode'> 359 * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; 360 * 361 * <jc>// Parse into a linked-list of strings.</jc> 362 * List l = p.parse(json, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 363 * 364 * <jc>// Parse into a linked-list of beans.</jc> 365 * List l = p.parse(json, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 366 * 367 * <jc>// Parse into a linked-list of linked-lists of strings.</jc> 368 * List l = p.parse(json, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 369 * 370 * <jc>// Parse into a map of string keys/values.</jc> 371 * Map m = p.parse(json, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 372 * 373 * <jc>// Parse into a map containing string keys and values of lists containing beans.</jc> 374 * Map m = p.parse(json, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 375 * </p> 376 * 377 * <p> 378 * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type. 379 * 380 * <p> 381 * <code>Map</code> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 382 * 383 * <p> 384 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 385 * 386 * <h5 class='section'>Notes:</h5> 387 * <ul class='spaced-list'> 388 * <li> 389 * Use the {@link #parse(Object, Class)} method instead if you don't need a parameterized map/collection. 390 * </ul> 391 * 392 * @param <T> The class type of the object to create. 393 * @param input 394 * The input. 395 * <br>Character-based parsers can handle the following input class types: 396 * <ul> 397 * <li><jk>null</jk> 398 * <li>{@link Reader} 399 * <li>{@link CharSequence} 400 * <li>{@link InputStream} containing UTF-8 encoded text (or charset defined by 401 * {@link Parser#PARSER_inputStreamCharset} property value). 402 * <li><code><jk>byte</jk>[]</code> containing UTF-8 encoded text (or charset defined by 403 * {@link Parser#PARSER_inputStreamCharset} property value). 404 * <li>{@link File} containing system encoded text (or charset defined by 405 * {@link Parser#PARSER_fileCharset} property value). 406 * </ul> 407 * <br>Stream-based parsers can handle the following input class types: 408 * <ul> 409 * <li><jk>null</jk> 410 * <li>{@link InputStream} 411 * <li><code><jk>byte</jk>[]</code> 412 * <li>{@link File} 413 * </ul> 414 * @param type 415 * The object type to create. 416 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 417 * @param args 418 * The type arguments of the class if it's a collection or map. 419 * <br>Can be any of the following: {@link ClassMeta}, {@link Class}, {@link ParameterizedType}, {@link GenericArrayType} 420 * <br>Ignored if the main type is not a map or collection. 421 * @return The parsed object. 422 * @throws ParseException 423 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 424 * @see BeanSession#getClassMeta(Type,Type...) for argument syntax for maps and collections. 425 */ 426 @SuppressWarnings("unchecked") 427 public final <T> T parse(Object input, Type type, Type...args) throws ParseException { 428 try (ParserPipe pipe = createPipe(input)) { 429 return (T)parseInner(pipe, getClassMeta(type, args)); 430 } 431 } 432 433 /** 434 * Same as {@link #parse(Object, Type, Type...)} except optimized for a non-parameterized class. 435 * 436 * <p> 437 * This is the preferred parse method for simple types since you don't need to cast the results. 438 * 439 * <h5 class='section'>Examples:</h5> 440 * <p class='bcode'> 441 * ReaderParser p = JsonParser.<jsf>DEFAULT</jsf>; 442 * 443 * <jc>// Parse into a string.</jc> 444 * String s = p.parse(json, String.<jk>class</jk>); 445 * 446 * <jc>// Parse into a bean.</jc> 447 * MyBean b = p.parse(json, MyBean.<jk>class</jk>); 448 * 449 * <jc>// Parse into a bean array.</jc> 450 * MyBean[] ba = p.parse(json, MyBean[].<jk>class</jk>); 451 * 452 * <jc>// Parse into a linked-list of objects.</jc> 453 * List l = p.parse(json, LinkedList.<jk>class</jk>); 454 * 455 * <jc>// Parse into a map of object keys/values.</jc> 456 * Map m = p.parse(json, TreeMap.<jk>class</jk>); 457 * </p> 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, Class<T> type) throws ParseException { 469 try (ParserPipe pipe = createPipe(input)) { 470 return parseInner(pipe, getClassMeta(type)); 471 } 472 } 473 474 /** 475 * Same as {@link #parse(Object, Type, Type...)} except the type has already been converted into a {@link ClassMeta} 476 * object. 477 * 478 * <p> 479 * This is mostly an internal method used by the framework. 480 * 481 * @param <T> The class type of the object being created. 482 * @param input 483 * The input. 484 * See {@link #parse(Object, Type, Type...)} for details. 485 * @param type The object type to create. 486 * @return The parsed object. 487 * @throws ParseException 488 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 489 */ 490 public final <T> T parse(Object input, ClassMeta<T> type) throws ParseException { 491 try (ParserPipe pipe = createPipe(input)) { 492 return parseInner(pipe, type); 493 } 494 } 495 496 /** 497 * Entry point for all parsing calls. 498 * 499 * <p> 500 * Calls the {@link #doParse(ParserPipe, ClassMeta)} implementation class and catches/re-wraps any exceptions 501 * thrown. 502 * 503 * @param pipe The parser input. 504 * @param type The class type of the object to create. 505 * @param <T> The class type of the object to create. 506 * @return The parsed object. 507 * @throws ParseException 508 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 509 */ 510 private <T> T parseInner(ParserPipe pipe, ClassMeta<T> type) throws ParseException { 511 if (type.isVoid()) 512 return null; 513 try { 514 return doParse(pipe, type); 515 } catch (ParseException e) { 516 throw e; 517 } catch (StackOverflowError e) { 518 throw new ParseException(getLastLocation(), "Depth too deep. Stack overflow occurred."); 519 } catch (IOException e) { 520 throw new ParseException(getLastLocation(), "I/O exception occurred. exception={0}, message={1}.", 521 e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); 522 } catch (Exception e) { 523 throw new ParseException(getLastLocation(), "Exception occurred. exception={0}, message={1}.", 524 e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); 525 } finally { 526 checkForWarnings(); 527 } 528 } 529 530 /** 531 * Parses the contents of the specified reader and loads the results into the specified map. 532 * 533 * <p> 534 * Reader must contain something that serializes to a map (such as text containing a JSON object). 535 * 536 * <p> 537 * Used in the following locations: 538 * <ul class='spaced-list'> 539 * <li> 540 * The various character-based constructors in {@link ObjectMap} (e.g. 541 * {@link ObjectMap#ObjectMap(CharSequence,Parser)}). 542 * </ul> 543 * 544 * @param <K> The key class type. 545 * @param <V> The value class type. 546 * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. 547 * @param m The map being loaded. 548 * @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>. 549 * @param valueType The class type of the values, or <jk>null</jk> to default to whatever is being parsed. 550 * @return The same map that was passed in to allow this method to be chained. 551 * @throws ParseException If the input contains a syntax error or is malformed, or is not valid for the specified type. 552 * @throws UnsupportedOperationException If not implemented. 553 */ 554 public final <K,V> Map<K,V> parseIntoMap(Object input, Map<K,V> m, Type keyType, Type valueType) throws ParseException { 555 try (ParserPipe pipe = createPipe(input)) { 556 return doParseIntoMap(pipe, m, keyType, valueType); 557 } catch (ParseException e) { 558 throw e; 559 } catch (Exception e) { 560 throw new ParseException(getLastLocation(), e); 561 } finally { 562 checkForWarnings(); 563 } 564 } 565 566 /** 567 * Implementation method. 568 * 569 * <p> 570 * Default implementation throws an {@link UnsupportedOperationException}. 571 * 572 * @param pipe The parser input. 573 * @param m The map being loaded. 574 * @param keyType The class type of the keys, or <jk>null</jk> to default to <code>String.<jk>class</jk></code>. 575 * @param valueType The class type of the values, or <jk>null</jk> to default to whatever is being parsed. 576 * @return The same map that was passed in to allow this method to be chained. 577 * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. 578 */ 579 protected <K,V> Map<K,V> doParseIntoMap(ParserPipe pipe, Map<K,V> m, Type keyType, Type valueType) throws Exception { 580 throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); 581 } 582 583 /** 584 * Parses the contents of the specified reader and loads the results into the specified collection. 585 * 586 * <p> 587 * Used in the following locations: 588 * <ul class='spaced-list'> 589 * <li> 590 * The various character-based constructors in {@link ObjectList} (e.g. 591 * {@link ObjectList#ObjectList(CharSequence,Parser)}. 592 * </ul> 593 * 594 * @param <E> The element class type. 595 * @param input The input. See {@link #parse(Object, ClassMeta)} for supported input types. 596 * @param c The collection being loaded. 597 * @param elementType The class type of the elements, or <jk>null</jk> to default to whatever is being parsed. 598 * @return The same collection that was passed in to allow this method to be chained. 599 * @throws ParseException 600 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 601 * @throws UnsupportedOperationException If not implemented. 602 */ 603 public final <E> Collection<E> parseIntoCollection(Object input, Collection<E> c, Type elementType) throws ParseException { 604 try (ParserPipe pipe = createPipe(input)) { 605 return doParseIntoCollection(pipe, c, elementType); 606 } catch (ParseException e) { 607 throw e; 608 } catch (StackOverflowError e) { 609 throw new ParseException(getLastLocation(), "Depth too deep. Stack overflow occurred."); 610 } catch (IOException e) { 611 throw new ParseException(getLastLocation(), "I/O exception occurred. exception={0}, message={1}.", 612 e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); 613 } catch (Exception e) { 614 throw new ParseException(getLastLocation(), "Exception occurred. exception={0}, message={1}.", 615 e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); 616 } finally { 617 checkForWarnings(); 618 } 619 } 620 621 /** 622 * Implementation method. 623 * 624 * <p> 625 * Default implementation throws an {@link UnsupportedOperationException}. 626 * 627 * @param pipe The parser input. 628 * @param c The collection being loaded. 629 * @param elementType The class type of the elements, or <jk>null</jk> to default to whatever is being parsed. 630 * @return The same collection that was passed in to allow this method to be chained. 631 * @throws Exception If thrown from underlying stream, or if the input contains a syntax error or is malformed. 632 */ 633 protected <E> Collection<E> doParseIntoCollection(ParserPipe pipe, Collection<E> c, Type elementType) throws Exception { 634 throw new UnsupportedOperationException("Parser '"+getClass().getName()+"' does not support this method."); 635 } 636 637 /** 638 * Parses the specified array input with each entry in the object defined by the {@code argTypes} 639 * argument. 640 * 641 * <p> 642 * Used for converting arrays (e.g. <js>"[arg1,arg2,...]"</js>) into an {@code Object[]} that can be passed 643 * to the {@code Method.invoke(target, args)} method. 644 * 645 * <p> 646 * Used in the following locations: 647 * <ul class='spaced-list'> 648 * <li> 649 * Used to parse argument strings in the {@link PojoIntrospector#invokeMethod(Method, Reader)} method. 650 * </ul> 651 * 652 * @param input The input. Subclasses can support different input types. 653 * @param argTypes Specifies the type of objects to create for each entry in the array. 654 * @return An array of parsed objects. 655 * @throws ParseException 656 * If the input contains a syntax error or is malformed, or is not valid for the specified type. 657 */ 658 public final Object[] parseArgs(Object input, Type[] argTypes) throws ParseException { 659 try (ParserPipe pipe = createPipe(input)) { 660 return doParse(pipe, getArgsClassMeta(argTypes)); 661 } catch (ParseException e) { 662 throw e; 663 } catch (StackOverflowError e) { 664 throw new ParseException(getLastLocation(), "Depth too deep. Stack overflow occurred."); 665 } catch (IOException e) { 666 throw new ParseException(getLastLocation(), "I/O exception occurred. exception={0}, message={1}.", 667 e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); 668 } catch (Exception e) { 669 throw new ParseException(getLastLocation(), "Exception occurred. exception={0}, message={1}.", 670 e.getClass().getSimpleName(), e.getLocalizedMessage()).initCause(e); 671 } finally { 672 checkForWarnings(); 673 } 674 } 675 676 /** 677 * Converts the specified string to the specified type. 678 * 679 * @param outer 680 * The outer object if we're converting to an inner object that needs to be created within the context 681 * of an outer object. 682 * @param s The string to convert. 683 * @param type The class type to convert the string to. 684 * @return The string converted as an object of the specified type. 685 * @throws Exception If the input contains a syntax error or is malformed, or is not valid for the specified type. 686 * @param <T> The class type to convert the string to. 687 */ 688 @SuppressWarnings({ "unchecked", "rawtypes" }) 689 protected final <T> T convertAttrToType(Object outer, String s, ClassMeta<T> type) throws Exception { 690 if (s == null) 691 return null; 692 693 if (type == null) 694 type = (ClassMeta<T>)object(); 695 PojoSwap swap = type.getPojoSwap(this); 696 ClassMeta<?> sType = swap == null ? type : swap.getSwapClassMeta(this); 697 698 Object o = s; 699 if (sType.isChar()) 700 o = s.charAt(0); 701 else if (sType.isNumber()) 702 if (type.canCreateNewInstanceFromNumber(outer)) 703 o = type.newInstanceFromNumber(this, outer, parseNumber(s, type.getNewInstanceFromNumberClass())); 704 else 705 o = parseNumber(s, (Class<? extends Number>)sType.getInnerClass()); 706 else if (sType.isBoolean()) 707 o = Boolean.parseBoolean(s); 708 else if (! (sType.isCharSequence() || sType.isObject())) { 709 if (sType.canCreateNewInstanceFromString(outer)) 710 o = sType.newInstanceFromString(outer, s); 711 else 712 throw new ParseException(getLastLocation(), "Invalid conversion from string to class ''{0}''", type); 713 } 714 715 if (swap != null) 716 o = swap.unswap(this, o, type); 717 718 return (T)o; 719 } 720 721 /** 722 * Convenience method for calling the {@link ParentProperty @ParentProperty} method on the specified object if it 723 * exists. 724 * 725 * @param cm The class type of the object. 726 * @param o The object. 727 * @param parent The parent to set. 728 * @throws Exception 729 */ 730 protected static final void setParent(ClassMeta<?> cm, Object o, Object parent) throws Exception { 731 Setter m = cm.getParentProperty(); 732 if (m != null) 733 m.set(o, parent); 734 } 735 736 /** 737 * Convenience method for calling the {@link NameProperty @NameProperty} method on the specified object if it exists. 738 * 739 * @param cm The class type of the object. 740 * @param o The object. 741 * @param name The name to set. 742 * @throws Exception 743 */ 744 protected static final void setName(ClassMeta<?> cm, Object o, Object name) throws Exception { 745 if (cm != null) { 746 Setter m = cm.getNameProperty(); 747 if (m != null) 748 m.set(o, name); 749 } 750 } 751 752 /** 753 * Returns the listener associated with this session. 754 * 755 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 756 */ 757 public ParserListener getListener() { 758 return listener; 759 } 760 761 /** 762 * Returns the listener associated with this session. 763 * 764 * @param c The listener class to cast to. 765 * @return The listener associated with this session, or <jk>null</jk> if there is no listener. 766 */ 767 @SuppressWarnings("unchecked") 768 public <T extends ParserListener> T getListener(Class<T> c) { 769 return (T)listener; 770 } 771}