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