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.uon;
014
015import java.lang.reflect.*;
016import java.util.*;
017import java.util.concurrent.*;
018
019import org.apache.juneau.*;
020import org.apache.juneau.annotation.*;
021import org.apache.juneau.collections.*;
022import org.apache.juneau.httppart.*;
023import org.apache.juneau.parser.*;
024
025/**
026 * Parses UON (a notation for URL-encoded query parameter values) text into POJO models.
027 *
028 * <h5 class='topic'>Media types</h5>
029 *
030 * Handles <c>Content-Type</c> types:  <bc>text/uon</bc>
031 *
032 * <h5 class='topic'>Description</h5>
033 *
034 * This parser uses a state machine, which makes it very fast and efficient.
035 */
036@ConfigurableContext
037public class UonParser extends ReaderParser implements HttpPartParser, UonMetaProvider, UonCommon {
038
039   //-------------------------------------------------------------------------------------------------------------------
040   // Configurable properties
041   //-------------------------------------------------------------------------------------------------------------------
042
043   static final String PREFIX = "UonParser";
044
045   /**
046    * Configuration property: Decode <js>"%xx"</js> sequences.
047    *
048    * <h5 class='section'>Property:</h5>
049    * <ul class='spaced-list'>
050    *    <li><b>ID:</b>  {@link org.apache.juneau.uon.UonParser#UON_decoding UON_decoding}
051    *    <li><b>Name:</b>  <js>"UonParser.decoding.b"</js>
052    *    <li><b>Data type:</b>  <jk>boolean</jk>
053    *    <li><b>System property:</b>  <c>UonParser.decoding</c>
054    *    <li><b>Environment variable:</b>  <c>UONPARSER_DECODING</c>
055    *    <li><b>Default:</b>  <jk>false</jk> for {@link org.apache.juneau.uon.UonParser}, <jk>true</jk> for {@link org.apache.juneau.urlencoding.UrlEncodingParser}
056    *    <li><b>Session property:</b>  <jk>false</jk>
057    *    <li><b>Annotations:</b>
058    *       <ul>
059    *          <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#decoding()}
060    *       </ul>
061    *    <li><b>Methods:</b>
062    *       <ul>
063    *          <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding(boolean)}
064    *          <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding()}
065    *       </ul>
066    * </ul>
067    *
068    * <h5 class='section'>Description:</h5>
069    * <p>
070    * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded
071    * before being passed to this parser.
072    *
073    * <h5 class='section'>Example:</h5>
074    * <p class='bcode w800'>
075    *    <jc>// Create a decoding UON parser.</jc>
076    *    ReaderParser p = UonParser.
077    *       .<jsm>create</jsm>()
078    *       .decoding()
079    *       .build();
080    *
081    *    <jc>// Same, but use property.</jc>
082    *    ReaderParser p = UonParser.
083    *       .<jsm>create</jsm>()
084    *       .set(<jsf>UON_decoding</jsf>, <jk>true</jk>)
085    *       .build();
086    *
087    *  <jc>// Produces: ["foo bar", "baz quz"].</jc>
088    *    String[] foo = p.parse(<js>"@(foo%20bar,baz%20qux)"</js>, String[].<jk>class</jk>);
089    * </p>
090    */
091   public static final String UON_decoding = PREFIX + ".decoding.b";
092
093   /**
094    * Configuration property:  Validate end.
095    *
096    * <h5 class='section'>Property:</h5>
097    * <ul class='spaced-list'>
098    *    <li><b>ID:</b>  {@link org.apache.juneau.uon.UonParser#UON_validateEnd UON_validateEnd}
099    *    <li><b>Name:</b>  <js>"UonParser.validateEnd.b"</js>
100    *    <li><b>Data type:</b>  <jk>boolean</jk>
101    *    <li><b>System property:</b>  <c>UonParser.validateEnd</c>
102    *    <li><b>Environment variable:</b>  <c>UONPARSER_VALIDATEEND</c>
103    *    <li><b>Default:</b>  <jk>false</jk>
104    *    <li><b>Session property:</b>  <jk>false</jk>
105    *    <li><b>Annotations:</b>
106    *       <ul>
107    *          <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#validateEnd()}
108    *       </ul>
109    *    <li><b>Methods:</b>
110    *       <ul>
111    *          <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#validateEnd()}
112    *       </ul>
113    * </ul>
114    *
115    * <h5 class='section'>Description:</h5>
116    * <p>
117    * If <jk>true</jk>, after parsing a POJO from the input, verifies that the remaining input in
118    * the stream consists of only comments or whitespace.
119    *
120    * <h5 class='section'>Example:</h5>
121    * <p class='bcode w800'>
122    *    <jc>// Create a parser using strict mode.</jc>
123    *    ReaderParser p = UonParser.
124    *       .<jsm>create</jsm>()
125    *       .validateEnd()
126    *       .build();
127    *
128    *    <jc>// Same, but use property.</jc>
129    *    ReaderParser p = UonParser.
130    *       .<jsm>create</jsm>()
131    *       .set(<jsf>UON_validateEnd</jsf>, <jk>true</jk>)
132    *       .build();
133    *
134    *    <jc>// Should fail because input has multiple POJOs.</jc>
135    *    String in = <js>"(foo=bar)(baz=qux)"</js>;
136    *    MyBean myBean = p.parse(in, MyBean.<jk>class</jk>);
137    * </p>
138    */
139   public static final String UON_validateEnd = PREFIX + ".validateEnd.b";
140
141   //-------------------------------------------------------------------------------------------------------------------
142   // Predefined instances
143   //-------------------------------------------------------------------------------------------------------------------
144
145   /** Reusable instance of {@link UonParser}, all default settings. */
146   public static final UonParser DEFAULT = new UonParser(PropertyStore.DEFAULT);
147
148   /** Reusable instance of {@link UonParser} with decodeChars set to true. */
149   public static final UonParser DEFAULT_DECODING = new UonParser.Decoding(PropertyStore.DEFAULT);
150
151
152   //-------------------------------------------------------------------------------------------------------------------
153   // Predefined subclasses
154   //-------------------------------------------------------------------------------------------------------------------
155
156   /** Default parser, decoding. */
157   public static class Decoding extends UonParser {
158
159      /**
160       * Constructor.
161       *
162       * @param ps The property store containing all the settings for this object.
163       */
164      public Decoding(PropertyStore ps) {
165         super(ps.builder().setDefault(UON_decoding, true).build());
166      }
167   }
168
169   /**
170    * Converts the specified input to the specified class type.
171    *
172    * @param partType The part type being parsed.
173    * @param schema
174    *    Schema information about the part.
175    *    <br>May be <jk>null</jk>.
176    *    <br>Not all part parsers use the schema information.
177    * @param in The input being parsed.
178    * @param toType The POJO type to transform the input into.
179    * @return The parsed value.
180    * @throws ParseException Malformed input encountered.
181    * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation.
182    */
183   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> toType) throws ParseException, SchemaValidationException {
184      return createPartSession(null).parse(partType, schema, in, toType);
185   }
186
187   /**
188    * Converts the specified input to the specified class type.
189    *
190    * @param partType The part type being parsed.
191    * @param schema
192    *    Schema information about the part.
193    *    <br>May be <jk>null</jk>.
194    *    <br>Not all part parsers use the schema information.
195    * @param in The input being parsed.
196    * @param toType The POJO type to transform the input into.
197    * @return The parsed value.
198    * @throws ParseException Malformed input encountered.
199    * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation.
200    */
201   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException {
202      return createPartSession(null).parse(partType, schema, in, getClassMeta(toType));
203   }
204
205   /**
206    * Converts the specified input to the specified class type.
207    *
208    * @param partType The part type being parsed.
209    * @param schema
210    *    Schema information about the part.
211    *    <br>May be <jk>null</jk>.
212    *    <br>Not all part parsers use the schema information.
213    * @param in The input being parsed.
214    * @param toType The POJO type to transform the input into.
215    * @param toTypeArgs The generic type arguments of the POJO type to transform the input into.
216    * @return The parsed value.
217    * @throws ParseException Malformed input encountered.
218    * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation.
219    */
220   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException {
221      return createPartSession(null).parse(partType, schema, in, getClassMeta(toType, toTypeArgs));
222   }
223
224   //-------------------------------------------------------------------------------------------------------------------
225   // Instance
226   //-------------------------------------------------------------------------------------------------------------------
227
228   private final boolean
229      decoding, validateEnd;
230   private final Map<ClassMeta<?>,UonClassMeta> uonClassMetas = new ConcurrentHashMap<>();
231   private final Map<BeanPropertyMeta,UonBeanPropertyMeta> uonBeanPropertyMetas = new ConcurrentHashMap<>();
232
233   /**
234    * Constructor.
235    *
236    * @param ps
237    *    The property store containing all the settings for this object.
238    */
239   public UonParser(PropertyStore ps) {
240      this(ps, "text/uon");
241   }
242
243   /**
244    * No-arg constructor.
245    */
246   public UonParser() {
247      this(PropertyStore.DEFAULT, "text/uon");
248   }
249
250   /**
251    * Constructor.
252    *
253    * @param ps
254    *    The property store containing all the settings for this object.
255    * @param consumes
256    *    The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*&#8203;/json"</js>).
257    */
258   public UonParser(PropertyStore ps, String...consumes) {
259      super(ps, consumes);
260      this.decoding = getBooleanProperty(UON_decoding, false);
261      this.validateEnd = getBooleanProperty(UON_validateEnd, false);
262   }
263
264   @Override /* Context */
265   public UonParserBuilder builder() {
266      return new UonParserBuilder(getPropertyStore());
267   }
268
269   /**
270    * Instantiates a new clean-slate {@link UonParserBuilder} object.
271    *
272    * <p>
273    * This is equivalent to simply calling <code><jk>new</jk> UonParserBuilder()</code>.
274    *
275    * <p>
276    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
277    * the settings of the object called on.
278    *
279    * @return A new {@link UonParserBuilder} object.
280    */
281   public static UonParserBuilder create() {
282      return new UonParserBuilder();
283   }
284
285   /**
286    * Create a UON parser session for parsing parameter values.
287    *
288    * @return A new parser session.
289    */
290   protected final UonParserSession createParameterSession() {
291      return new UonParserSession(this, createDefaultSessionArgs(), false);
292   }
293
294   //-----------------------------------------------------------------------------------------------------------------
295   // Entry point methods
296   //-----------------------------------------------------------------------------------------------------------------
297
298   @Override /* Parser */
299   public UonParserSession createSession(ParserSessionArgs args) {
300      return new UonParserSession(this, args);
301   }
302
303   @Override /* HttpPartParser */
304   public UonParserSession createSession() {
305      return createSession(null);
306   }
307
308   @Override /* HttpPartParser */
309   public UonParserSession createPartSession(ParserSessionArgs args) {
310      return new UonParserSession(this, args);
311   }
312
313   //-----------------------------------------------------------------------------------------------------------------
314   // Extended metadata
315   //-----------------------------------------------------------------------------------------------------------------
316
317   @Override /* UonMetaProvider */
318   public UonClassMeta getUonClassMeta(ClassMeta<?> cm) {
319      UonClassMeta m = uonClassMetas.get(cm);
320      if (m == null) {
321         m = new UonClassMeta(cm, this);
322         uonClassMetas.put(cm, m);
323      }
324      return m;
325   }
326
327   @Override /* UonMetaProvider */
328   public UonBeanPropertyMeta getUonBeanPropertyMeta(BeanPropertyMeta bpm) {
329      if (bpm == null)
330         return UonBeanPropertyMeta.DEFAULT;
331      UonBeanPropertyMeta m = uonBeanPropertyMetas.get(bpm);
332      if (m == null) {
333         m = new UonBeanPropertyMeta(bpm.getDelegateFor(), this);
334         uonBeanPropertyMetas.put(bpm, m);
335      }
336      return m;
337   }
338
339   //-----------------------------------------------------------------------------------------------------------------
340   // Properties
341   //-----------------------------------------------------------------------------------------------------------------
342
343   /**
344    * Decode <js>"%xx"</js> sequences enabled
345    *
346    * @see #UON_decoding
347    * @return
348    *    <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded
349    *    before being passed to this parser.
350    */
351   protected final boolean isDecoding() {
352      return decoding;
353   }
354
355   /**
356    * Validate end enabled.
357    *
358    * @see #UON_validateEnd
359    * @return
360    *    <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in
361    *    the stream consists of only comments or whitespace.
362    */
363   protected final boolean isValidateEnd() {
364      return validateEnd;
365   }
366
367   //-----------------------------------------------------------------------------------------------------------------
368   // Other methods
369   //-----------------------------------------------------------------------------------------------------------------
370
371   @Override /* Context */
372   public OMap toMap() {
373      return super.toMap()
374         .a("UonParser", new DefaultFilteringOMap()
375            .a("decoding", decoding)
376            .a("validateEnd", validateEnd)
377         );
378   }
379}