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.httppart.*;
022import org.apache.juneau.parser.*;
023
024/**
025 * Parses UON (a notation for URL-encoded query parameter values) text into POJO models.
026 *
027 * <h5 class='topic'>Media types</h5>
028 *
029 * Handles <c>Content-Type</c> types:  <bc>text/uon</bc>
030 *
031 * <h5 class='topic'>Description</h5>
032 *
033 * This parser uses a state machine, which makes it very fast and efficient.
034 */
035@ConfigurableContext
036public class UonParser extends ReaderParser implements HttpPartParser, UonMetaProvider, UonCommon {
037
038   //-------------------------------------------------------------------------------------------------------------------
039   // Configurable properties
040   //-------------------------------------------------------------------------------------------------------------------
041
042   static final String PREFIX = "UonParser";
043
044   /**
045    * Configuration property: Decode <js>"%xx"</js> sequences.
046    *
047    * <h5 class='section'>Property:</h5>
048    * <ul class='spaced-list'>
049    *    <li><b>ID:</b>  {@link org.apache.juneau.uon.UonParser#UON_decoding UON_decoding}
050    *    <li><b>Name:</b>  <js>"UonParser.decoding.b"</js>
051    *    <li><b>Data type:</b>  <jk>boolean</jk>
052    *    <li><b>System property:</b>  <c>UonParser.decoding</c>
053    *    <li><b>Environment variable:</b>  <c>UONPARSER_DECODING</c>
054    *    <li><b>Default:</b>  <jk>false</jk> for {@link org.apache.juneau.uon.UonParser}, <jk>true</jk> for {@link org.apache.juneau.urlencoding.UrlEncodingParser}
055    *    <li><b>Session property:</b>  <jk>false</jk>
056    *    <li><b>Annotations:</b>
057    *       <ul>
058    *          <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#decoding()}
059    *       </ul>
060    *    <li><b>Methods:</b>
061    *       <ul>
062    *          <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding(boolean)}
063    *          <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#decoding()}
064    *       </ul>
065    * </ul>
066    *
067    * <h5 class='section'>Description:</h5>
068    * <p>
069    * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded
070    * before being passed to this parser.
071    *
072    * <h5 class='section'>Example:</h5>
073    * <p class='bcode w800'>
074    *    <jc>// Create a decoding UON parser.</jc>
075    *    ReaderParser p = UonParser.
076    *       .<jsm>create</jsm>()
077    *       .decoding()
078    *       .build();
079    *
080    *    <jc>// Same, but use property.</jc>
081    *    ReaderParser p = UonParser.
082    *       .<jsm>create</jsm>()
083    *       .set(<jsf>UON_decoding</jsf>, <jk>true</jk>)
084    *       .build();
085    *
086    *  <jc>// Produces: ["foo bar", "baz quz"].</jc>
087    *    String[] foo = p.parse(<js>"@(foo%20bar,baz%20qux)"</js>, String[].<jk>class</jk>);
088    * </p>
089    */
090   public static final String UON_decoding = PREFIX + ".decoding.b";
091
092   /**
093    * Configuration property:  Validate end.
094    *
095    * <h5 class='section'>Property:</h5>
096    * <ul class='spaced-list'>
097    *    <li><b>ID:</b>  {@link org.apache.juneau.uon.UonParser#UON_validateEnd UON_validateEnd}
098    *    <li><b>Name:</b>  <js>"UonParser.validateEnd.b"</js>
099    *    <li><b>Data type:</b>  <jk>boolean</jk>
100    *    <li><b>System property:</b>  <c>UonParser.validateEnd</c>
101    *    <li><b>Environment variable:</b>  <c>UONPARSER_VALIDATEEND</c>
102    *    <li><b>Default:</b>  <jk>false</jk>
103    *    <li><b>Session property:</b>  <jk>false</jk>
104    *    <li><b>Annotations:</b>
105    *       <ul>
106    *          <li class='ja'>{@link org.apache.juneau.uon.annotation.UonConfig#validateEnd()}
107    *       </ul>
108    *    <li><b>Methods:</b>
109    *       <ul>
110    *          <li class='jm'>{@link org.apache.juneau.uon.UonParserBuilder#validateEnd(boolean)}
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().set(UON_decoding, true).build());
166      }
167   }
168
169
170   //-------------------------------------------------------------------------------------------------------------------
171   // Instance
172   //-------------------------------------------------------------------------------------------------------------------
173
174   private final boolean
175      decoding, validateEnd;
176   private final Map<ClassMeta<?>,UonClassMeta> uonClassMetas = new ConcurrentHashMap<>();
177   private final Map<BeanPropertyMeta,UonBeanPropertyMeta> uonBeanPropertyMetas = new ConcurrentHashMap<>();
178
179   /**
180    * Constructor.
181    *
182    * @param ps
183    *    The property store containing all the settings for this object.
184    */
185   public UonParser(PropertyStore ps) {
186      this(ps, "text/uon");
187   }
188
189   /**
190    * Constructor.
191    *
192    * @param ps
193    *    The property store containing all the settings for this object.
194    * @param consumes
195    *    The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*&#8203;/json"</js>).
196    */
197   public UonParser(PropertyStore ps, String...consumes) {
198      super(ps, consumes);
199      this.decoding = getBooleanProperty(UON_decoding, false);
200      this.validateEnd = getBooleanProperty(UON_validateEnd, false);
201   }
202
203   @Override /* Context */
204   public UonParserBuilder builder() {
205      return new UonParserBuilder(getPropertyStore());
206   }
207
208   /**
209    * Instantiates a new clean-slate {@link UonParserBuilder} object.
210    *
211    * <p>
212    * This is equivalent to simply calling <code><jk>new</jk> UonParserBuilder()</code>.
213    *
214    * <p>
215    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
216    * the settings of the object called on.
217    *
218    * @return A new {@link UonParserBuilder} object.
219    */
220   public static UonParserBuilder create() {
221      return new UonParserBuilder();
222   }
223
224   /**
225    * Create a UON parser session for parsing parameter values.
226    *
227    * @return A new parser session.
228    */
229   protected final UonParserSession createParameterSession() {
230      return new UonParserSession(this, createDefaultSessionArgs(), false);
231   }
232
233   //-----------------------------------------------------------------------------------------------------------------
234   // Entry point methods
235   //-----------------------------------------------------------------------------------------------------------------
236
237   @Override /* Parser */
238   public UonParserSession createSession(ParserSessionArgs args) {
239      return new UonParserSession(this, args);
240   }
241
242   @Override /* HttpPartParser */
243   public UonParserSession createSession() {
244      return createSession(null);
245   }
246
247   @Override /* HttpPartParser */
248   public UonParserSession createPartSession(ParserSessionArgs args) {
249      return new UonParserSession(this, args);
250   }
251
252   @Override /* HttpPartParser */
253   public UonParserSession createPartSession() {
254      return createPartSession(null);
255   }
256
257   @Override /* HttpPartParser */
258   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException {
259      return createPartSession().parse(partType, schema, in, toType);
260   }
261
262   @Override /* HttpPartParser */
263   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException {
264      return createPartSession().parse(partType, schema, in, toType, toTypeArgs);
265   }
266
267   @Override /* HttpPartParser */
268   public <T> T parse(HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException {
269      return createPartSession().parse(null, schema, in, toType);
270   }
271
272   @Override /* HttpPartParser */
273   public <T> T parse(HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException {
274      return createPartSession().parse(null, schema, in, toType, toTypeArgs);
275   }
276
277   //-----------------------------------------------------------------------------------------------------------------
278   // Extended metadata
279   //-----------------------------------------------------------------------------------------------------------------
280
281   @Override /* UonMetaProvider */
282   public UonClassMeta getUonClassMeta(ClassMeta<?> cm) {
283      UonClassMeta m = uonClassMetas.get(cm);
284      if (m == null) {
285         m = new UonClassMeta(cm, this);
286         uonClassMetas.put(cm, m);
287      }
288      return m;
289   }
290
291   @Override /* UonMetaProvider */
292   public UonBeanPropertyMeta getUonBeanPropertyMeta(BeanPropertyMeta bpm) {
293      if (bpm == null)
294         return UonBeanPropertyMeta.DEFAULT;
295      UonBeanPropertyMeta m = uonBeanPropertyMetas.get(bpm);
296      if (m == null) {
297         m = new UonBeanPropertyMeta(bpm.getDelegateFor(), this);
298         uonBeanPropertyMetas.put(bpm, m);
299      }
300      return m;
301   }
302
303   //-----------------------------------------------------------------------------------------------------------------
304   // Properties
305   //-----------------------------------------------------------------------------------------------------------------
306
307   /**
308    * Configuration property: Decode <js>"%xx"</js> sequences.
309    *
310    * @see #UON_decoding
311    * @return
312    *    <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded
313    *    before being passed to this parser.
314    */
315   protected final boolean isDecoding() {
316      return decoding;
317   }
318
319   /**
320    * Configuration property:  Validate end.
321    *
322    * @see #UON_validateEnd
323    * @return
324    *    <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in
325    *    the stream consists of only comments or whitespace.
326    */
327   protected final boolean isValidateEnd() {
328      return validateEnd;
329   }
330
331   //-----------------------------------------------------------------------------------------------------------------
332   // Other methods
333   //-----------------------------------------------------------------------------------------------------------------
334
335   @Override /* Context */
336   public ObjectMap toMap() {
337      return super.toMap()
338         .append("UonParser", new DefaultFilteringObjectMap()
339            .append("decoding", decoding)
340            .append("validateEnd", validateEnd)
341         );
342   }
343}