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.*;
016
017import org.apache.juneau.*;
018import org.apache.juneau.annotation.*;
019import org.apache.juneau.httppart.*;
020import org.apache.juneau.parser.*;
021import org.apache.juneau.urlencoding.*;
022
023/**
024 * Parses UON (a notation for URL-encoded query parameter values) text into POJO models.
025 *
026 * <h5 class='topic'>Media types</h5>
027 *
028 * Handles <c>Content-Type</c> types:  <bc>text/uon</bc>
029 *
030 * <h5 class='topic'>Description</h5>
031 *
032 * This parser uses a state machine, which makes it very fast and efficient.
033 */
034@ConfigurableContext
035public class UonParser extends ReaderParser implements HttpPartParser {
036
037   //-------------------------------------------------------------------------------------------------------------------
038   // Configurable properties
039   //-------------------------------------------------------------------------------------------------------------------
040
041   static final String PREFIX = "UonParser";
042
043   /**
044    * Configuration property: Decode <js>"%xx"</js> sequences.
045    *
046    * <h5 class='section'>Property:</h5>
047    * <ul>
048    *    <li><b>Name:</b>  <js>"UonParser.decoding.b"</js>
049    *    <li><b>Data type:</b>  <c>Boolean</c>
050    *    <li><b>Default:</b>  <jk>false</jk> for {@link UonParser}, <jk>true</jk> for {@link UrlEncodingParser}
051    *    <li><b>Session property:</b>  <jk>false</jk>
052    *    <li><b>Methods:</b>
053    *       <ul>
054    *          <li class='jm'>{@link UonParserBuilder#decoding(boolean)}
055    *          <li class='jm'>{@link UonParserBuilder#decoding()}
056    *       </ul>
057    * </ul>
058    *
059    * <h5 class='section'>Description:</h5>
060    * <p>
061    * Specify <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded
062    * before being passed to this parser.
063    *
064    * <h5 class='section'>Example:</h5>
065    * <p class='bcode w800'>
066    *    <jc>// Create a decoding UON parser.</jc>
067    *    ReaderParser p = UonParser.
068    *       .<jsm>create</jsm>()
069    *       .decoding()
070    *       .build();
071    *
072    *    <jc>// Same, but use property.</jc>
073    *    ReaderParser p = UonParser.
074    *       .<jsm>create</jsm>()
075    *       .set(<jsf>UON_decoding</jsf>, <jk>true</jk>)
076    *       .build();
077    *
078    * <jc>// Produces: ["foo bar", "baz quz"].</jc>
079    *    String[] foo = p.parse(<js>"@(foo%20bar,baz%20qux)"</js>, String[].<jk>class</jk>);
080    * </p>
081    */
082   public static final String UON_decoding = PREFIX + ".decoding.b";
083
084   /**
085    * Configuration property:  Validate end.
086    *
087    * <h5 class='section'>Property:</h5>
088    * <ul>
089    *    <li><b>Name:</b>  <js>"UonParser.validateEnd.b"</js>
090    *    <li><b>Data type:</b>  <c>Boolean</c>
091    *    <li><b>Default:</b>  <jk>false</jk>
092    *    <li><b>Session property:</b>  <jk>false</jk>
093    *    <li><b>Methods:</b>
094    *       <ul>
095    *          <li class='jm'>{@link UonParserBuilder#validateEnd(boolean)}
096    *          <li class='jm'>{@link UonParserBuilder#validateEnd()}
097    *       </ul>
098    * </ul>
099    *
100    * <h5 class='section'>Description:</h5>
101    * <p>
102    * If <jk>true</jk>, after parsing a POJO from the input, verifies that the remaining input in
103    * the stream consists of only comments or whitespace.
104    *
105    * <h5 class='section'>Example:</h5>
106    * <p class='bcode w800'>
107    *    <jc>// Create a parser using strict mode.</jc>
108    *    ReaderParser p = UonParser.
109    *       .<jsm>create</jsm>()
110    *       .validateEnd()
111    *       .build();
112    *
113    *    <jc>// Same, but use property.</jc>
114    *    ReaderParser p = UonParser.
115    *       .<jsm>create</jsm>()
116    *       .set(<jsf>UON_validateEnd</jsf>, <jk>true</jk>)
117    *       .build();
118    *
119    *    <jc>// Should fail because input has multiple POJOs.</jc>
120    *    String in = <js>"(foo=bar)(baz=qux)"</js>;
121    *    MyBean myBean = p.parse(in, MyBean.<jk>class</jk>);
122    * </p>
123    */
124   public static final String UON_validateEnd = PREFIX + ".validateEnd.b";
125
126   //-------------------------------------------------------------------------------------------------------------------
127   // Predefined instances
128   //-------------------------------------------------------------------------------------------------------------------
129
130   /** Reusable instance of {@link UonParser}, all default settings. */
131   public static final UonParser DEFAULT = new UonParser(PropertyStore.DEFAULT);
132
133   /** Reusable instance of {@link UonParser} with decodeChars set to true. */
134   public static final UonParser DEFAULT_DECODING = new UonParser.Decoding(PropertyStore.DEFAULT);
135
136
137   //-------------------------------------------------------------------------------------------------------------------
138   // Predefined subclasses
139   //-------------------------------------------------------------------------------------------------------------------
140
141   /** Default parser, decoding. */
142   public static class Decoding extends UonParser {
143
144      /**
145       * Constructor.
146       *
147       * @param ps The property store containing all the settings for this object.
148       */
149      public Decoding(PropertyStore ps) {
150         super(ps.builder().set(UON_decoding, true).build());
151      }
152   }
153
154
155   //-------------------------------------------------------------------------------------------------------------------
156   // Instance
157   //-------------------------------------------------------------------------------------------------------------------
158
159   private final boolean
160      decoding, validateEnd;
161
162   /**
163    * Constructor.
164    *
165    * @param ps
166    *    The property store containing all the settings for this object.
167    */
168   public UonParser(PropertyStore ps) {
169      this(ps, "text/uon");
170   }
171
172   /**
173    * Constructor.
174    *
175    * @param ps
176    *    The property store containing all the settings for this object.
177    * @param consumes
178    *    The list of media types that this parser consumes (e.g. <js>"application/json"</js>, <js>"*&#8203;/json"</js>).
179    */
180   public UonParser(PropertyStore ps, String...consumes) {
181      super(ps, consumes);
182      this.decoding = getBooleanProperty(UON_decoding, false);
183      this.validateEnd = getBooleanProperty(UON_validateEnd, false);
184   }
185
186   @Override /* Context */
187   public UonParserBuilder builder() {
188      return new UonParserBuilder(getPropertyStore());
189   }
190
191   /**
192    * Instantiates a new clean-slate {@link UonParserBuilder} object.
193    *
194    * <p>
195    * This is equivalent to simply calling <code><jk>new</jk> UonParserBuilder()</code>.
196    *
197    * <p>
198    * Note that this method creates a builder initialized to all default settings, whereas {@link #builder()} copies
199    * the settings of the object called on.
200    *
201    * @return A new {@link UonParserBuilder} object.
202    */
203   public static UonParserBuilder create() {
204      return new UonParserBuilder();
205   }
206
207   /**
208    * Create a UON parser session for parsing parameter values.
209    *
210    * @return A new parser session.
211    */
212   protected final UonParserSession createParameterSession() {
213      return new UonParserSession(this, createDefaultSessionArgs(), false);
214   }
215
216   //-----------------------------------------------------------------------------------------------------------------
217   // Entry point methods
218   //-----------------------------------------------------------------------------------------------------------------
219
220   @Override /* Parser */
221   public UonParserSession createSession(ParserSessionArgs args) {
222      return new UonParserSession(this, args);
223   }
224
225   @Override /* HttpPartParser */
226   public UonParserSession createSession() {
227      return createSession(null);
228   }
229
230   @Override /* HttpPartParser */
231   public UonParserSession createPartSession(ParserSessionArgs args) {
232      return new UonParserSession(this, args);
233   }
234
235   @Override /* HttpPartParser */
236   public UonParserSession createPartSession() {
237      return createPartSession(null);
238   }
239
240   @Override /* HttpPartParser */
241   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException {
242      return createPartSession().parse(partType, schema, in, toType);
243   }
244
245   @Override /* HttpPartParser */
246   public <T> T parse(HttpPartType partType, HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException {
247      return createPartSession().parse(partType, schema, in, toType, toTypeArgs);
248   }
249
250   @Override /* HttpPartParser */
251   public <T> T parse(HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException {
252      return createPartSession().parse(null, schema, in, toType);
253   }
254
255   @Override /* HttpPartParser */
256   public <T> T parse(HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException {
257      return createPartSession().parse(null, schema, in, toType, toTypeArgs);
258   }
259
260   //-----------------------------------------------------------------------------------------------------------------
261   // Properties
262   //-----------------------------------------------------------------------------------------------------------------
263
264   /**
265    * Configuration property: Decode <js>"%xx"</js> sequences.
266    *
267    * @see #UON_decoding
268    * @return
269    *    <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded
270    *    before being passed to this parser.
271    */
272   protected final boolean isDecoding() {
273      return decoding;
274   }
275
276   /**
277    * Configuration property:  Validate end.
278    *
279    * @see #UON_validateEnd
280    * @return
281    *    <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in
282    *    the stream consists of only comments or whitespace.
283    */
284   protected final boolean isValidateEnd() {
285      return validateEnd;
286   }
287
288   //-----------------------------------------------------------------------------------------------------------------
289   // Other methods
290   //-----------------------------------------------------------------------------------------------------------------
291
292   @Override /* Context */
293   public ObjectMap toMap() {
294      return super.toMap()
295         .append("UonParser", new DefaultFilteringObjectMap()
296            .append("decoding", decoding)
297            .append("validateEnd", validateEnd)
298         );
299   }
300}