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