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