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