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.CollectionUtils.*;
016import static org.apache.juneau.internal.ObjectUtils.*;
017import static java.util.stream.Collectors.*;
018
019import java.util.*;
020import java.util.concurrent.*;
021import java.util.function.*;
022import java.util.stream.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.cp.*;
026import org.apache.juneau.internal.*;
027import org.apache.juneau.reflect.*;
028
029/**
030 * Represents a group of {@link Parser Parsers} that can be looked up by media type.
031 *
032 * <h5 class='topic'>Description</h5>
033 *
034 * Provides the following features:
035 * <ul class='spaced-list'>
036 *    <li>
037 *       Finds parsers based on HTTP <c>Content-Type</c> header values.
038 *    <li>
039 *       Sets common properties on all parsers in a single method call.
040 *    <li>
041 *       Locks all parsers in a single method call.
042 *    <li>
043 *       Clones existing groups and all parsers within the group in a single method call.
044 * </ul>
045 *
046 * <h5 class='topic'>Match ordering</h5>
047 *
048 * Parsers are matched against <c>Content-Type</c> strings in the order they exist in this group.
049 *
050 * <p>
051 * Adding new entries will cause the entries to be prepended to the group.
052 * This allows for previous parsers to be overridden through subsequent calls.
053 *
054 * <p>
055 * For example, calling <code>g.append(P1.<jk>class</jk>,P2.<jk>class</jk>).append(P3.<jk>class</jk>,P4.<jk>class</jk>)</code>
056 * will result in the order <c>P3, P4, P1, P2</c>.
057 *
058 * <h5 class='section'>Example:</h5>
059 * <p class='bjava'>
060 *    <jc>// Construct a new parser group builder</jc>
061 *    ParserSet <jv>parsers</jv> = ParserSet.<jsm>create</jsm>();
062 *       .add(JsonParser.<jk>class</jk>, XmlParser.<jk>class</jk>); <jc>// Add some parsers to it</jc>
063 *    .forEach(<jv>x</jv> -&gt; <jv>x</jv>.swaps(CalendarSwap.IsoLocalDateTime.<jk>class</jk>))
064 *    .forEach(<jv>x</jv> -&gt; <jv>x</jv>.beansRequireSerializable())
065 *       .build();
066 *
067 *    <jc>// Find the appropriate parser by Content-Type</jc>
068 *    ReaderParser <jv>parser</jv> = (ReaderParser)<jv>parsers</jv>.getParser(<js>"text/json"</js>);
069 *
070 *    <jc>// Parse a bean from JSON</jc>
071 *    String <jv>json</jv> = <js>"{...}"</js>;
072 *    AddressBook <jv>addressBook</jv> = <jv>parser</jv>.parse(<jv>json</jv>, AddressBook.<jk>class</jk>);
073 * </p>
074 *
075 * <h5 class='section'>Notes:</h5><ul>
076 *    <li class='note'>This class is thread safe and reusable.
077 * </ul>
078 *
079 * <h5 class='section'>See Also:</h5><ul>
080 *    <li class='link'><a class="doclink" href="../../../../index.html#jm.SerializersAndParsers">Serializers and Parsers</a>
081
082 * </ul>
083 */
084public final class ParserSet {
085
086   //-----------------------------------------------------------------------------------------------------------------
087   // Static
088   //-----------------------------------------------------------------------------------------------------------------
089
090   /**
091    * An identifier that the previous entries in this group should be inherited.
092    * <p>
093    * Used by {@link ParserSet.Builder#set(Class...)}
094    */
095   @SuppressWarnings("javadoc")
096   public static abstract class Inherit extends Parser {
097      protected Inherit(Parser.Builder builder) {
098         super(builder);
099      }
100   }
101
102   /**
103    * An identifier that the previous entries in this group should not be inherited.
104    * <p>
105    * Used by {@link ParserSet.Builder#add(Class...)}
106    */
107   @SuppressWarnings("javadoc")
108   public static abstract class NoInherit extends Parser {
109      protected NoInherit(Parser.Builder builder) {
110         super(builder);
111      }
112   }
113
114   /**
115    * Static creator.
116    *
117    * @param beanStore The bean store to use for creating beans.
118    * @return A new builder for this object.
119    */
120   public static Builder create(BeanStore beanStore) {
121      return new Builder(beanStore);
122   }
123
124   /**
125    * Static creator.
126    *
127    * @return A new builder for this object.
128    */
129   public static Builder create() {
130      return new Builder(BeanStore.INSTANCE);
131   }
132
133   //-----------------------------------------------------------------------------------------------------------------
134   // Builder
135   //-----------------------------------------------------------------------------------------------------------------
136
137   /**
138    * Builder class.
139    */
140   @FluentSetters
141   public static class Builder extends BeanBuilder<ParserSet> {
142
143      List<Object> entries;
144      private BeanContext.Builder bcBuilder;
145
146      /**
147       * Create an empty parser group builder.
148       *
149       * @param beanStore The bean store to use for creating beans.
150       */
151      protected Builder(BeanStore beanStore) {
152         super(ParserSet.class, beanStore);
153         this.entries = list();
154      }
155
156      /**
157       * Clone an existing parser group.
158       *
159       * @param copyFrom The parser group that we're copying settings and parsers from.
160       */
161      protected Builder(ParserSet copyFrom) {
162         super(copyFrom.getClass(), BeanStore.INSTANCE);
163         this.entries = list((Object[])copyFrom.entries);
164      }
165
166      /**
167       * Clone an existing parser group builder.
168       *
169       * <p>
170       * Parser builders will be cloned during this process.
171       *
172       * @param copyFrom The parser group that we're copying settings and parsers from.
173       */
174      protected Builder(Builder copyFrom) {
175         super(copyFrom);
176         bcBuilder = copyFrom.bcBuilder == null ? null : copyFrom.bcBuilder.copy();
177         entries = list();
178         copyFrom.entries.stream().map(this::copyBuilder).forEach(x -> entries.add(x));
179      }
180
181      private Object copyBuilder(Object o) {
182         if (o instanceof Parser.Builder) {
183            Parser.Builder x = (Parser.Builder)o;
184            Parser.Builder x2 = x.copy();
185            if (ne(x.getClass(), x2.getClass()))
186               throw new BasicRuntimeException("Copy method not implemented on class {0}", x.getClass().getName());
187            x = x2;
188            if (bcBuilder != null)
189               x.beanContext(bcBuilder);
190            return x;
191         }
192         return o;
193      }
194
195      @Override /* BeanBuilder */
196      protected ParserSet buildDefault() {
197         return new ParserSet(this);
198      }
199
200      /**
201       * Makes a copy of this builder.
202       *
203       * @return A new copy of this builder.
204       */
205      public Builder copy() {
206         return new Builder(this);
207      }
208
209      //-------------------------------------------------------------------------------------------------------------
210      // Properties
211      //-------------------------------------------------------------------------------------------------------------
212
213      /**
214       * Associates an existing bean context builder with all parser builders in this group.
215       *
216       * @param value The bean contest builder to associate.
217       * @return This object.
218       */
219      public Builder beanContext(BeanContext.Builder value) {
220         bcBuilder = value;
221         forEach(x -> x.beanContext(value));
222         return this;
223      }
224
225      /**
226       * Applies an operation to the bean context builder.
227       *
228       * @param operation The operation to apply.
229       * @return This object.
230       */
231      public final Builder beanContext(Consumer<BeanContext.Builder> operation) {
232         if (bcBuilder != null)
233            operation.accept(bcBuilder);
234         return this;
235      }
236
237      /**
238       * Adds the specified parsers to this group.
239       *
240       * <p>
241       * Entries are added in-order to the beginning of the list in the group.
242       *
243       * <p>
244       * The {@link NoInherit} class can be used to clear out the existing list of parsers before adding the new entries.
245       *
246       * <h5 class='section'>Example:</h5>
247       * <p class='bjava'>
248       *    ParserSet.Builder <jv>builder</jv> = ParserSet.<jsm>create</jsm>();  <jc>// Create an empty builder.</jc>
249       *
250       *    <jv>builder</jv>.add(FooParser.<jk>class</jk>);  <jc>// Now contains:  [FooParser]</jc>
251       *
252       *    <jv>builder</jv>.add(BarParser.<jk>class</jk>, BazParser.<jk>class</jk>);  <jc>// Now contains:  [BarParser,BazParser,FooParser]</jc>
253       *
254       *    <jv>builder</jv>.add(NoInherit.<jk>class</jk>, QuxParser.<jk>class</jk>);  <jc>// Now contains:  [QuxParser]</jc>
255       * </p>
256       *
257       * @param values The parsers to add to this group.
258       * @return This object.
259       * @throws IllegalArgumentException If one or more values do not extend from {@link Parser}.
260       */
261      public Builder add(Class<?>...values) {
262         List<Object> l = list();
263         for (Class<?> v : values)
264            if (v.getSimpleName().equals("NoInherit"))
265               clear();
266         for (Class<?> v : values) {
267            if (Parser.class.isAssignableFrom(v)) {
268               l.add(createBuilder(v));
269            } else if (! v.getSimpleName().equals("NoInherit")) {
270               throw new BasicRuntimeException("Invalid type passed to ParserSet.Builder.add(): {0}", v.getName());
271            }
272         }
273         entries.addAll(0, l);
274         return this;
275      }
276
277      /**
278       * Sets the specified parsers for this group.
279       *
280       * <p>
281       * Existing values are overwritten.
282       *
283       * <p>
284       * The {@link Inherit} class can be used to insert existing entries in this group into the position specified.
285       *
286       * <h5 class='section'>Example:</h5>
287       * <p class='bjava'>
288       *    ParserSet.Builder <jv>builder</jv> = ParserSet.<jsm>create</jsm>();  <jc>// Create an empty builder.</jc>
289       *
290       *    <jv>builder</jv>.set(FooParser.<jk>class</jk>);  <jc>// Now contains:  [FooParser]</jc>
291       *
292       *    <jv>builder</jv>.set(BarParser.<jk>class</jk>, BazParser.<jk>class</jk>);  <jc>// Now contains:  [BarParser,BazParser]</jc>
293       *
294       *    <jv>builder</jv>.set(Inherit.<jk>class</jk>, QuxParser.<jk>class</jk>);  <jc>// Now contains:  [BarParser,BazParser,QuxParser]</jc>
295       * </p>
296       *
297       * @param values The parsers to set in this group.
298       * @return This object.
299       * @throws IllegalArgumentException If one or more values do not extend from {@link Parser} or named <js>"Inherit"</js>.
300       */
301      public Builder set(Class<?>...values) {
302         List<Object> l = list();
303         for (Class<?> v : values) {
304            if (v.getSimpleName().equals("Inherit")) {
305               l.addAll(entries);
306            } else if (Parser.class.isAssignableFrom(v)) {
307               l.add(createBuilder(v));
308            } else {
309               throw new BasicRuntimeException("Invalid type passed to ParserGrouup.Builder.set(): {0}", v.getName());
310            }
311         }
312         entries = l;
313         return this;
314      }
315
316      private Object createBuilder(Object o) {
317         if (o instanceof Class) {
318
319            // Check for no-arg constructor.
320            ConstructorInfo ci = ClassInfo.of((Class<?>)o).getPublicConstructor(ConstructorInfo::hasNoParams);
321            if (ci != null)
322               return ci.invoke();
323
324            // Check for builder.
325            @SuppressWarnings("unchecked")
326            Parser.Builder b = Parser.createParserBuilder((Class<? extends Parser>)o);
327            if (bcBuilder != null)
328               b.beanContext(bcBuilder);
329            o = b;
330         }
331         return o;
332      }
333
334      /**
335       * Registers the specified parsers with this group.
336       *
337       * <p>
338       * When passing in pre-instantiated parsers to this group, applying properties and transforms to the group
339       * do not affect them.
340       *
341       * @param s The parsers to append to this group.
342       * @return This object.
343       */
344      public Builder add(Parser...s) {
345         prependAll(entries, (Object[])s);
346         return this;
347      }
348
349      /**
350       * Clears out any existing parsers in this group.
351       *
352       * @return This object.
353       */
354      public Builder clear() {
355         entries.clear();
356         return this;
357      }
358
359      /**
360       * Returns <jk>true</jk> if at least one of the specified annotations can be applied to at least one parser builder in this group.
361       *
362       * @param work The work to check.
363       * @return <jk>true</jk> if at least one of the specified annotations can be applied to at least one parser builder in this group.
364       */
365      public boolean canApply(AnnotationWorkList work) {
366         for (Object o : entries)
367            if (o instanceof Parser.Builder)
368               if (((Parser.Builder)o).canApply(work))
369                  return true;
370         return false;
371      }
372
373      /**
374       * Applies the specified annotations to all applicable parser builders in this group.
375       *
376       * @param work The annotations to apply.
377       * @return This object.
378       */
379      public Builder apply(AnnotationWorkList work) {
380         return forEach(x -> x.apply(work));
381      }
382
383      /**
384       * Performs an action on all parser builders in this group.
385       *
386       * @param action The action to perform.
387       * @return This object.
388       */
389      public Builder forEach(Consumer<Parser.Builder> action) {
390         builders(Parser.Builder.class).forEach(action);
391         return this;
392      }
393
394      /**
395       * Performs an action on all writer parser builders in this group.
396       *
397       * @param action The action to perform.
398       * @return This object.
399       */
400      public Builder forEachRP(Consumer<ReaderParser.Builder> action) {
401         return forEach(ReaderParser.Builder.class, action);
402      }
403
404      /**
405       * Performs an action on all output stream parser builders in this group.
406       *
407       * @param action The action to perform.
408       * @return This object.
409       */
410      public Builder forEachISP(Consumer<InputStreamParser.Builder> action) {
411         return forEach(InputStreamParser.Builder.class, action);
412      }
413
414      /**
415       * Performs an action on all parser builders of the specified type in this group.
416       *
417       * @param <B> The parser builder type.
418       * @param type The parser builder type.
419       * @param action The action to perform.
420       * @return This object.
421       */
422      public <B extends Parser.Builder> Builder forEach(Class<B> type, Consumer<B> action) {
423         builders(type).forEach(action);
424         return this;
425      }
426
427      /**
428       * Returns direct access to the {@link Parser} and {@link Parser.Builder} objects in this builder.
429       *
430       * <p>
431       * Provided to allow for any extraneous modifications to the list not accomplishable via other methods on this builder such
432       * as re-ordering/adding/removing entries.
433       *
434       * <p>
435       * Note that it is up to the user to ensure that the list only contains {@link Parser} and {@link Parser.Builder} objects.
436       *
437       * @return The inner list of entries in this builder.
438       */
439      public List<Object> inner() {
440         return entries;
441      }
442
443      private <T extends Parser.Builder> Stream<T> builders(Class<T> type) {
444         return entries.stream().filter(x -> type.isInstance(x)).map(x -> type.cast(x));
445      }
446
447      // <FluentSetters>
448
449      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
450      public Builder impl(Object value) {
451         super.impl(value);
452         return this;
453      }
454
455      @Override /* GENERATED - org.apache.juneau.BeanBuilder */
456      public Builder type(Class<?> value) {
457         super.type(value);
458         return this;
459      }
460
461      // </FluentSetters>
462
463      @Override /* Object */
464      public String toString() {
465         return entries.stream().map(this::toString).collect(joining(",","[","]"));
466      }
467
468      private String toString(Object o) {
469         if (o == null)
470            return "null";
471         if (o instanceof Parser.Builder)
472            return "builder:" + o.getClass().getName();
473         return "parser:" + o.getClass().getName();
474      }
475   }
476
477   //-----------------------------------------------------------------------------------------------------------------
478   // Instance
479   //-----------------------------------------------------------------------------------------------------------------
480
481   // Maps Content-Type headers to matches.
482   private final ConcurrentHashMap<String,ParserMatch> cache = new ConcurrentHashMap<>();
483
484   private final MediaType[] mediaTypes;
485   private final Parser[] mediaTypeParsers;
486
487   final Parser[] entries;
488
489   /**
490    * Constructor.
491    *
492    * @param builder The builder for this bean.
493    */
494   public ParserSet(Builder builder) {
495
496      this.entries = builder.entries.stream().map(this::build).toArray(Parser[]::new);
497
498      List<MediaType> lmt = list();
499      List<Parser> l = list();
500      for (Parser e : entries) {
501         e.getMediaTypes().forEach(x -> {
502            lmt.add(x);
503            l.add(e);
504         });
505      }
506
507      this.mediaTypes = array(lmt, MediaType.class);
508      this.mediaTypeParsers = array(l, Parser.class);
509   }
510
511   private Parser build(Object o) {
512      if (o instanceof Parser)
513         return (Parser)o;
514      return ((Parser.Builder)o).build();
515   }
516
517   /**
518    * Creates a copy of this parser group.
519    *
520    * @return A new copy of this parser group.
521    */
522   public Builder copy() {
523      return new Builder(this);
524   }
525
526   /**
527    * Searches the group for a parser that can handle the specified <l>Content-Type</l> header value.
528    *
529    * <p>
530    * The returned object includes both the parser and media type that matched.
531    *
532    * @param contentTypeHeader The HTTP <l>Content-Type</l> header value.
533    * @return The parser and media type that matched the content type header, or <jk>null</jk> if no match was made.
534    */
535   public ParserMatch getParserMatch(String contentTypeHeader) {
536      ParserMatch pm = cache.get(contentTypeHeader);
537      if (pm != null)
538         return pm;
539
540      MediaType ct = MediaType.of(contentTypeHeader);
541      int match = ct.match(ulist(mediaTypes));
542
543      if (match >= 0) {
544         pm = new ParserMatch(mediaTypes[match], mediaTypeParsers[match]);
545         cache.putIfAbsent(contentTypeHeader, pm);
546      }
547
548      return cache.get(contentTypeHeader);
549   }
550
551   /**
552    * Same as {@link #getParserMatch(String)} but matches using a {@link MediaType} instance.
553    *
554    * @param mediaType The HTTP <l>Content-Type</l> header value as a media type.
555    * @return The parser and media type that matched the media type, or <jk>null</jk> if no match was made.
556    */
557   public ParserMatch getParserMatch(MediaType mediaType) {
558      return getParserMatch(mediaType.toString());
559   }
560
561   /**
562    * Same as {@link #getParserMatch(String)} but returns just the matched parser.
563    *
564    * @param contentTypeHeader The HTTP <l>Content-Type</l> header string.
565    * @return The parser that matched the content type header, or <jk>null</jk> if no match was made.
566    */
567   public Parser getParser(String contentTypeHeader) {
568      ParserMatch pm = getParserMatch(contentTypeHeader);
569      return pm == null ? null : pm.getParser();
570   }
571
572   /**
573    * Same as {@link #getParserMatch(MediaType)} but returns just the matched parser.
574    *
575    * @param mediaType The HTTP media type.
576    * @return The parser that matched the media type, or <jk>null</jk> if no match was made.
577    */
578   public Parser getParser(MediaType mediaType) {
579      ParserMatch pm = getParserMatch(mediaType);
580      return pm == null ? null : pm.getParser();
581   }
582
583   /**
584    * Returns the media types that all parsers in this group can handle
585    *
586    * <p>
587    * Entries are ordered in the same order as the parsers in the group.
588    *
589    * @return An unmodifiable list of media types.
590    */
591   public List<MediaType> getSupportedMediaTypes() {
592      return ulist(mediaTypes);
593   }
594
595   /**
596    * Returns the parsers in this group.
597    *
598    * @return An unmodifiable list of parsers in this group.
599    */
600   public List<Parser> getParsers() {
601      return ulist(entries);
602   }
603
604   /**
605    * Returns <jk>true</jk> if this group contains no parsers.
606    *
607    * @return <jk>true</jk> if this group contains no parsers.
608    */
609   public boolean isEmpty() {
610      return entries.length == 0;
611   }
612}