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}