001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.uon;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.SystemUtils.*;
021
022import java.io.*;
023import java.lang.reflect.*;
024import java.util.*;
025import java.util.concurrent.*;
026
027import org.apache.juneau.commons.bean.*;
028import org.apache.juneau.commons.collections.*;
029import org.apache.juneau.commons.httppart.*;
030import org.apache.juneau.marshall.*;
031import org.apache.juneau.marshall.httppart.*;
032import org.apache.juneau.marshall.parser.*;
033import org.apache.juneau.marshall.stream.*;
034
035/**
036 * Parses UON (a notation for URL-encoded query parameter values) text into POJO models.
037 *
038 * <h5 class='topic'>Media types</h5>
039 * <p>
040 * Handles <c>Content-Type</c> types:  <bc>text/uon</bc>
041 *
042 * <h5 class='topic'>Description</h5>
043 * <p>
044 * This parser uses a state machine, which makes it very fast and efficient.
045 *
046 * <h5 class='figure'>Example input (Map of name/age):</h5>
047 * <p class='bcode'>
048 *    (name=Alice,age=30)
049 * </p>
050 *
051 * <h5 class='figure'>Complex (nested object + array):</h5>
052 * <p class='bcode'>
053 *    (name=Alice,age=30,address=(street=123+Main+St,city=Boston,state=MA),tags=@(a,b,c))
054 * </p>
055 *
056 * <h5 class='section'>Notes:</h5><ul>
057 *    <li class='note'>This class is thread safe and reusable.
058 * </ul>
059 *
060 * <h5 class='section'>See Also:</h5><ul>
061 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/UonSupport">UON</a>
062
063 * </ul>
064 */
065@SuppressWarnings({
066   "java:S110", // UonParser extends ReaderParser on the shared Parser base-class chain, and UonParser.Decoding subclasses it; the depth comes from that format-family design.
067   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
068   "java:S9149", // Per-format static factories intentionally shadow the parent's.
069   "resource" // Closeable resources are owned by the caller's parser session; Eclipse JDT @Owning warning is by design.
070})
071public class UonParser extends ReaderParser implements HttpPartParser, UonMetaProvider, RecordReadable {
072
073   /**
074    * Builder class.
075    */
076   @SuppressWarnings({
077      "java:S119" // 'SELF' (CRTP self-type) is intentional and clearer than a single-letter name.
078   })
079   public abstract static class Builder<SELF extends Builder<SELF>> extends ReaderParser.Builder<SELF> {
080
081      private static final Cache<HashKey,UonParser> CACHE = Cache.of(HashKey.class, UonParser.class).build();
082
083      private boolean decoding;
084      private boolean validateEnd;
085
086      /**
087       * Constructor, default settings.
088       */
089      protected Builder() {
090         consumes("text/uon");
091         decoding = env("UonParser.decoding", false);
092         validateEnd = env("UonParser.validateEnd", false);
093      }
094
095      /**
096       * Copy constructor.
097       *
098       * @param copyFrom The builder to copy from.
099       *    <br>Cannot be <jk>null</jk>.
100       */
101      protected Builder(Builder<?> copyFrom) {
102         super(reqnn("copyFrom", copyFrom));
103         decoding = copyFrom.decoding;
104         validateEnd = copyFrom.validateEnd;
105      }
106
107      /**
108       * Copy constructor.
109       *
110       * @param copyFrom The bean to copy from.
111       *    <br>Cannot be <jk>null</jk>.
112       */
113      protected Builder(UonParser copyFrom) {
114         super(reqnn("copyFrom", copyFrom));
115         decoding = copyFrom.decoding;
116         validateEnd = copyFrom.validateEnd;
117      }
118
119      @Override /* Overridden from Context.Builder<?> */
120      public UonParser build() {
121         return cache(CACHE).build(UonParser.class);
122      }
123
124      @Override /* Overridden from Context.Builder<?> */
125      public abstract SELF copy();
126
127      /**
128       * Decode <js>"%xx"</js> sequences.
129       *
130       * <p>
131       * When enabled, URI encoded characters will be decoded.  Otherwise it's assumed that they've already been decoded
132       * before being passed to this parser.
133       *
134       * <h5 class='section'>Example:</h5>
135       * <p class='bjava'>
136       *    <jc>// Create a decoding UON parser.</jc>
137       *    ReaderParser <jv>parser</jv> = UonParser.
138       *       .<jsm>create</jsm>()
139       *       .decoding()
140       *       .build();
141       *
142       *  <jc>// Produces: ["foo bar", "baz quz"].</jc>
143       *    String[] <jv>foo</jv> = <jv>parser</jv>.read(<js>"@(foo%20bar,baz%20qux)"</js>, String[].<jk>class</jk>);
144       * </p>
145       *
146       * @return This object.
147       */
148      public SELF decoding() {
149         return decoding(true);
150      }
151
152      /**
153       * Same as {@link #decoding()} but allows you to explicitly specify the value.
154       *
155       * @param value The value for this setting.
156       * @return This object.
157       */
158      public SELF decoding(boolean value) {
159         decoding = value;
160         return self();
161      }
162
163      @Override /* Overridden from Context.Builder<?> */
164      public HashKey hashKey() {
165         // @formatter:off
166         return HashKey.of(
167            super.hashKey(),
168            decoding,
169            validateEnd
170         );
171         // @formatter:on
172      }
173
174
175      /**
176       * Validate end.
177       *
178       * <p>
179       * When enabled, after parsing a POJO from the input, verifies that the remaining input in
180       * the stream consists of only comments or whitespace.
181       *
182       * <h5 class='section'>Example:</h5>
183       * <p class='bjava'>
184       *    <jc>// Create a parser using strict mode.</jc>
185       *    ReaderParser <jv>parser</jv> = UonParser.
186       *       .<jsm>create</jsm>()
187       *       .validateEnd()
188       *       .build();
189       *
190       *    <jc>// Should fail because input has multiple POJOs.</jc>
191       *    String <jv>in</jv> = <js>"(foo=bar)(baz=qux)"</js>;
192       *    MyBean <jv>myBean</jv> = <jv>parser</jv>.read(<jv>in</jv>, MyBean.<jk>class</jk>);
193       * </p>
194       *
195       * @return This object.
196       */
197      public SELF validateEnd() {
198         return validateEnd(true);
199      }
200
201      /**
202       * Same as {@link #validateEnd()} but allows you to explicitly specify the value.
203       *
204       * @param value The value for this setting.
205       * @return This object.
206       */
207      public SELF validateEnd(boolean value) {
208         validateEnd = value;
209         return self();
210      }
211   }
212
213   /**
214    * Concrete default builder leaf for the non-subclassed {@link UonParser#create()} / {@link UonParser#copy()} path.
215    */
216   public static final class DefaultBuilder extends Builder<DefaultBuilder> {
217
218      DefaultBuilder() {}
219
220      DefaultBuilder(UonParser copyFrom) {
221         super(copyFrom);
222      }
223
224      DefaultBuilder(Builder<?> copyFrom) {
225         super(copyFrom);
226      }
227
228      @Override /* Overridden from Context.Builder<?> */
229      public DefaultBuilder copy() {
230         return new DefaultBuilder(this);
231      }
232   }
233
234   /** Default parser, decoding. */
235   public static class Decoding extends UonParser {
236
237      /**
238       * Constructor.
239       *
240       * @param builder The builder for this object.
241       */
242      public Decoding(Builder<?> builder) {
243         super(builder.decoding());
244      }
245   }
246
247   /** Reusable instance of {@link UonParser}, all default settings. */
248   public static final UonParser DEFAULT = new UonParser(create());
249   /** Reusable instance of {@link UonParser} with decodeChars set to true. */
250   public static final UonParser DEFAULT_DECODING = new UonParser.Decoding(create());
251
252   /**
253    * Creates a new builder for this object.
254    *
255    * @return A new builder.
256    */
257   @SuppressWarnings({
258      "java:S1452" // Builder<?> wildcard return intentional; callers chain via fluent API without needing the concrete type
259   })
260   public static Builder<?> create() {
261      return new DefaultBuilder();
262   }
263
264   protected final boolean decoding;
265   protected final boolean validateEnd;
266
267   private final Map<BeanPropertyMeta,UonBeanPropertyMeta> uonBeanPropertyMetas = new ConcurrentHashMap<>();
268   private final Map<ClassMeta<?>,UonClassMeta> uonClassMetas = new ConcurrentHashMap<>();
269
270   /**
271    * Constructor.
272    *
273    * @param builder The builder for this object.
274    */
275   public UonParser(Builder<?> builder) {
276      super(builder);
277      decoding = builder.decoding;
278      validateEnd = builder.validateEnd;
279   }
280
281   @Override /* Overridden from Context */
282   public Builder<?> copy() {
283      return new DefaultBuilder(this);
284   }
285
286   @Override /* Overridden from Context */
287   public UonParserSession.Builder<?> createSession() {
288      return UonParserSession.create(this);
289   }
290
291   @Override
292   public <T> ClassMeta<T> getClassMeta(Class<T> c) {
293      return getMarshallingContext().getClassMeta(c);
294   }
295
296   @Override
297   public <T> ClassMeta<T> getClassMeta(Type t, Type...args) {
298      return getMarshallingContext().getClassMeta(t, args);
299   }
300
301   @Override /* Overridden from HttpPartParser */
302   public UonParserSession getPartSession() { return UonParserSession.create(this).build(); }
303
304   @Override /* Overridden from Context */
305   public UonParserSession getSession() { return createSession().build(); }
306
307   /**
308    * Convenience delegator that opens a whole-value {@link RecordReader} over the input using
309    * <b>default session arguments</b> (mirrors {@link #read(Object, Class)}).
310    *
311    * <p>
312    * The real implementation lives on {@link UonParserSession#readRecords(Object)}.  Callers
313    * that need request-derived configuration (locale, timezone, schema, swaps) should call
314    * {@link #createSession()} and invoke {@link UonParserSession#readRecords(Object)} on the
315    * built session instead.
316    *
317    * @param input The input.
318    * @return A new {@link RecordReader} cursor.
319    * @throws IOException If a problem occurred opening the underlying input.
320    */
321   @Override /* RecordReadable */
322   public RecordReader readRecords(Object input) throws IOException {
323      return getSession().readRecords(input);
324   }
325
326   @Override /* RecordReadable */
327   public boolean isRecordStreaming() {
328      return false;
329   }
330
331   @Override /* Overridden from UonMetaProvider */
332   public UonBeanPropertyMeta getUonBeanPropertyMeta(BeanPropertyMeta bpm) {
333      if (bpm == null)
334         return UonBeanPropertyMeta.DEFAULT;
335      return uonBeanPropertyMetas.computeIfAbsent(bpm, k -> new UonBeanPropertyMeta(k.getDelegateFor(), this));
336   }
337
338   @Override /* Overridden from UonMetaProvider */
339   public UonClassMeta getUonClassMeta(ClassMeta<?> cm) {
340      return uonClassMetas.computeIfAbsent(cm, k -> new UonClassMeta(k, this));
341   }
342
343   /**
344    * Converts the specified input to the specified class type.
345    *
346    * @param <T> The POJO type to transform the input into.
347    * @param partType The part type being parsed.
348    * @param schema
349    *    Schema information about the part.
350    *    <br>May be <jk>null</jk>.
351    *    <br>Not all part parsers use the schema information.
352    * @param in The input being parsed.
353    * @param toType The POJO type to transform the input into.
354    * @return The parsed value.
355    * @throws ParseException Malformed input encountered.
356    * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation.
357    */
358   public <T> T read(HttpPartType partType, HttpPartSchema schema, String in, Class<T> toType) throws ParseException, SchemaValidationException {
359      return getPartSession().read(partType, schema, in, getClassMeta(toType));
360   }
361
362   /**
363    * Converts the specified input to the specified class type.
364    *
365    * @param <T> The POJO type to transform the input into.
366    * @param partType The part type being parsed.
367    * @param schema
368    *    Schema information about the part.
369    *    <br>May be <jk>null</jk>.
370    *    <br>Not all part parsers use the schema information.
371    * @param in The input being parsed.
372    * @param toType The POJO type to transform the input into.
373    * @return The parsed value.
374    * @throws ParseException Malformed input encountered.
375    * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation.
376    */
377   public <T> T read(HttpPartType partType, HttpPartSchema schema, String in, ClassMeta<T> toType) throws ParseException, SchemaValidationException {
378      return getPartSession().read(partType, schema, in, toType);
379   }
380
381   /**
382    * Converts the specified input to the specified class type.
383    *
384    * @param <T> The POJO type to transform the input into.
385    * @param partType The part type being parsed.
386    * @param schema
387    *    Schema information about the part.
388    *    <br>May be <jk>null</jk>.
389    *    <br>Not all part parsers use the schema information.
390    * @param in The input being parsed.
391    * @param toType The POJO type to transform the input into.
392    * @param toTypeArgs The generic type arguments of the POJO type to transform the input into.
393    * @return The parsed value.
394    * @throws ParseException Malformed input encountered.
395    * @throws SchemaValidationException If the input or resulting HTTP part object fails schema validation.
396    */
397   public <T> T read(HttpPartType partType, HttpPartSchema schema, String in, Type toType, Type...toTypeArgs) throws ParseException, SchemaValidationException {
398      return getPartSession().read(partType, schema, in, getClassMeta(toType, toTypeArgs));
399   }
400
401   /**
402    * Decode <js>"%xx"</js> sequences enabled
403    *
404    * @see Builder#decoding()
405    * @return
406    *    <jk>true</jk> if URI encoded characters should be decoded, <jk>false</jk> if they've already been decoded
407    *    before being passed to this parser.
408    */
409   protected final boolean isDecoding() { return decoding; }
410
411   /**
412    * Validate end enabled.
413    *
414    * @see Builder#validateEnd()
415    * @return
416    *    <jk>true</jk> if after parsing a POJO from the input, verifies that the remaining input in
417    *    the stream consists of only comments or whitespace.
418    */
419   protected final boolean isValidateEnd() { return validateEnd; }
420
421   @Override /* Overridden from ReaderParser */
422   protected FluentMap<String,Object> properties() {
423      return super.properties()
424         .a("decoding", decoding)
425         .a("validateEnd", validateEnd);
426   }
427}