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