001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.marshall.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.Shorts.*; 023 024import java.util.*; 025import java.util.concurrent.*; 026import java.util.function.*; 027import java.util.stream.*; 028 029import org.apache.juneau.commons.http.*; 030import org.apache.juneau.commons.inject.*; 031import org.apache.juneau.marshall.*; 032 033/** 034 * Represents a group of {@link 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> -> <jv>x</jv>.calendarFormat(<jsf>ISO_LOCAL_DATE_TIME</jsf>)) 066 * .forEachWS(<jv>x</jv> -> <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 * .orElseThrow(); <jc>// Throws if no serializer matched the Accept header.</jc> 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>.write(<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 */ 088@SuppressWarnings({ 089 "rawtypes", // Raw Consumer type required for generic builder dispatch across parameterized builder hierarchy. 090 "unchecked" // Builder forEach casts raw (Consumer)/(Class) actions to the builder type selected by the class argument 091}) 092public class SerializerSet { 093 094 /** 095 * Builder class. 096 */ 097 @SuppressWarnings({ 098 "java:S115" // Constants use UPPER_snakeCase convention (e.g., CLASS_Inherit) 099 }) 100 public static class Builder implements BeanStoreOverridable<Builder> { 101 102 private static final String CLASS_Inherit = "Inherit"; 103 104 private BeanStore beanStore; 105 private SerializerSet impl; 106 107 List<Object> entries; 108 private MarshallingContext.Builder bcBuilder; 109 110 /** 111 * Create an empty serializer group builder. 112 * 113 * @param beanStore The bean store to use for creating beans. 114 */ 115 protected Builder(BeanStore beanStore) { 116 this.beanStore = beanStore; 117 this.entries = list(); 118 } 119 120 /** 121 * Clone an existing serializer group builder. 122 * 123 * <p> 124 * Serializer builders will be cloned during this process. 125 * 126 * @param copyFrom The serializer group that we're copying settings and serializers from. 127 * <br>Cannot be <jk>null</jk>. 128 */ 129 protected Builder(Builder copyFrom) { 130 this.beanStore = copyFrom.beanStore; 131 this.impl = copyFrom.impl; 132 bcBuilder = MarshallingContext.Builder.copyOrNull(copyFrom.bcBuilder); 133 entries = list(); 134 copyFrom.entries.stream().map(this::copyBuilder).forEach(entries::add); 135 } 136 137 /** 138 * Clone an existing serializer group. 139 * 140 * @param copyFrom The serializer group that we're copying settings and serializers from. 141 * <br>Cannot be <jk>null</jk>. 142 */ 143 protected Builder(SerializerSet copyFrom) { 144 this.beanStore = BasicBeanStore.INSTANCE; 145 this.entries = list((Object[])copyFrom.entries); 146 } 147 148 /** 149 * Returns the bean store used by this builder. 150 * 151 * @return The bean store used by this builder. 152 */ 153 public BeanStore beanStore() { 154 return beanStore; 155 } 156 157 /** 158 * Installs the supplied {@link BeanStore} as the {@code overridingParent} of this builder's bean store. 159 * 160 * <p> 161 * Wraps the current {@link #beanStore()} in a fresh {@link BasicBeanStore} whose {@code overridingParent} 162 * is the supplied store, so test-time overrides win over the builder's regular bean lookups during 163 * construction-time reflective injection. Passing {@code null} is a no-op. 164 * 165 * @param store The override layer. Can be <jk>null</jk> (no-op). 166 * @return This object. 167 * @since 10.0.0 168 */ 169 @Override 170 @SuppressWarnings({ 171 "resource" // BasicBeanStore lifecycle is owned by the enclosing builder; closed externally when the builder is disposed. 172 }) 173 public Builder overridingBeanStore(BeanStore store) { 174 if (store != null) 175 this.beanStore = new BasicBeanStore(this.beanStore, store); 176 return this; 177 } 178 179 /** 180 * Adds the specified serializers to this group. 181 * 182 * <p> 183 * Entries are added in-order to the beginning of the list in the group. 184 * 185 * <p> 186 * The {@link NoInherit} class can be used to clear out the existing list of serializers before adding the new entries. 187 * 188 * <h5 class='section'>Example:</h5> 189 * <p class='bjava'> 190 * SerializerSet.Builder <jv>builder</jv> = SerializerSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 191 * 192 * <jv>builder</jv>.add(FooSerializer.<jk>class</jk>); <jc>// Now contains: [FooSerializer]</jc> 193 * 194 * <jv>builder</jv>.add(BarSerializer.<jk>class</jk>, BazSerializer.<jk>class</jk>); <jc>// Now contains: [BarParser,BazSerializer,FooSerializer]</jc> 195 * 196 * <jv>builder</jv>.add(NoInherit.<jk>class</jk>, QuxSerializer.<jk>class</jk>); <jc>// Now contains: [QuxSerializer]</jc> 197 * </p> 198 * 199 * @param values The serializers to add to this group. 200 * @return This object. 201 * @throws IllegalArgumentException If one or more values do not extend from {@link Serializer}. 202 */ 203 public Builder add(Class<?>...values) { 204 List<Object> l = list(); 205 for (var e : values) { 206 if (Serializer.class.isAssignableFrom(e)) { 207 l.add(createBuilder(e)); 208 } else { 209 throw rex("Invalid type passed to SerializeGroup.Builder.add(): %s", cn(e)); 210 } 211 } 212 entries.addAll(0, l); 213 return this; 214 } 215 216 /** 217 * Registers the specified serializers with this group. 218 * 219 * <p> 220 * When passing in pre-instantiated serializers to this group, applying properties and transforms to the group 221 * do not affect them. 222 * 223 * @param s The serializers to append to this group. 224 * @return This object. 225 */ 226 public Builder add(Serializer...s) { 227 prependAll(entries, (Object[])s); 228 return this; 229 } 230 231 /** 232 * Applies the specified annotations to all applicable serializer builders in this group. 233 * 234 * @param work The annotations to apply. 235 * @return This object. 236 */ 237 public Builder apply(AnnotationWorkList work) { 238 return forEach(x -> x.apply(work)); 239 } 240 241 /** 242 * Associates an existing bean context builder with all serializer builders in this group. 243 * 244 * @param value The bean context builder to associate. 245 * <br>Can be <jk>null</jk> (clears any previously-associated bean context builder). 246 * @return This object. 247 */ 248 public Builder marshallingContext(MarshallingContext.Builder value) { 249 bcBuilder = value; 250 forEach(x -> x.marshallingContext(value)); 251 return this; 252 } 253 254 /** 255 * Applies an operation to the bean context builder. 256 * 257 * @param operation The operation to apply. 258 * @return This object. 259 */ 260 public final Builder marshallingContext(Consumer<MarshallingContext.Builder> operation) { 261 if (nn(bcBuilder)) 262 operation.accept(bcBuilder); 263 return this; 264 } 265 266 /** 267 * Returns <jk>true</jk> if at least one of the specified annotations can be applied to at least one serializer builder in this group. 268 * 269 * @param work The work to check. 270 * @return <jk>true</jk> if at least one of the specified annotations can be applied to at least one serializer builder in this group. 271 */ 272 public boolean canApply(AnnotationWorkList work) { 273 for (var o : entries) 274 if (o instanceof Serializer.Builder<?> o2 && o2.canApply(work)) 275 return true; 276 return false; 277 } 278 279 /** 280 * Clears out any existing serializers in this group. 281 * 282 * @return This object. 283 */ 284 public Builder clear() { 285 entries.clear(); 286 return this; 287 } 288 289 /** 290 * Makes a copy of this builder. 291 * 292 * @return A new copy of this builder. 293 */ 294 public Builder copy() { 295 return new Builder(this); 296 } 297 298 /** 299 * Performs an action on all serializer builders of the specified type in this group. 300 * 301 * @param <T> The serializer builder type. 302 * @param type The serializer builder type. 303 * @param action The action to perform. 304 * @return This object. 305 */ 306 public <T extends Serializer.Builder<?>> Builder forEach(Class<T> type, Consumer<T> action) { 307 builders(type).forEach(action); 308 return this; 309 } 310 311 /** 312 * Performs an action on all serializer builders in this group. 313 * 314 * @param action The action to perform. 315 * @return This object. 316 */ 317 public Builder forEach(Consumer<Serializer.Builder<?>> action) { 318 builders(Serializer.Builder.class).forEach((Consumer)action); 319 return this; 320 } 321 322 /** 323 * Performs an action on all output stream serializer builders in this group. 324 * 325 * @param action The action to perform. 326 * @return This object. 327 */ 328 public Builder forEachOSS(Consumer<OutputStreamSerializer.Builder<?>> action) { 329 return forEach((Class)OutputStreamSerializer.Builder.class, (Consumer)action); 330 } 331 332 /** 333 * Performs an action on all writer serializer builders in this group. 334 * 335 * @param action The action to perform. 336 * @return This object. 337 */ 338 public Builder forEachWS(Consumer<WriterSerializer.Builder<?>> action) { 339 return forEach((Class)WriterSerializer.Builder.class, (Consumer)action); 340 } 341 342 /** 343 * Overrides the bean returned by the {@link #build()} method with a pre-built instance. 344 * 345 * @param value The pre-built instance. 346 * <br>Can be <jk>null</jk> (clears the override; {@link #build()} constructs a new instance). 347 * @return This object. 348 */ 349 public Builder impl(Object value) { 350 impl = (SerializerSet) value; 351 return this; 352 } 353 354 /** 355 * Returns direct access to the {@link Serializer} and {@link Serializer.Builder}objects in this builder. 356 * 357 * <p> 358 * Provided to allow for any extraneous modifications to the list not accomplishable via other methods on this builder such 359 * as re-ordering/adding/removing entries. 360 * 361 * <p> 362 * Note that it is up to the user to ensure that the list only contains {@link Serializer} and {@link Serializer.Builder}objects. 363 * 364 * @return The inner list of entries in this builder. 365 */ 366 public List<Object> inner() { 367 return entries; 368 } 369 370 /** 371 * Sets the specified serializers for this group. 372 * 373 * <p> 374 * Existing values are overwritten. 375 * 376 * <p> 377 * The {@link Inherit} class can be used to insert existing entries in this group into the position specified. 378 * 379 * <h5 class='section'>Example:</h5> 380 * <p class='bjava'> 381 * SerializerSet.Builder <jv>builder</jv> = SerializerSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 382 * 383 * <jv>builder</jv>.set(FooSerializer.<jk>class</jk>); <jc>// Now contains: [FooSerializer]</jc> 384 * 385 * <jv>builder</jv>.set(BarSerializer.<jk>class</jk>, BazSerializer.<jk>class</jk>); <jc>// Now contains: [BarParser,BazSerializer]</jc> 386 * 387 * <jv>builder</jv>.set(Inherit.<jk>class</jk>, QuxSerializer.<jk>class</jk>); <jc>// Now contains: [BarParser,BazSerializer,QuxSerializer]</jc> 388 * </p> 389 * 390 * @param values The serializers to set in this group. 391 * @return This object. 392 * @throws IllegalArgumentException If one or more values do not extend from {@link Serializer} or named <js>"Inherit"</js>. 393 */ 394 public Builder set(Class<?>...values) { 395 List<Object> l = list(); 396 for (var e : values) { 397 if (e.getSimpleName().equals(CLASS_Inherit)) { 398 l.addAll(entries); 399 } else if (Serializer.class.isAssignableFrom(e)) { 400 l.add(createBuilder(e)); 401 } else { 402 throw rex("Invalid type passed to SerializeGroup.Builder.set(): %s", cn(e)); 403 } 404 } 405 entries = l; 406 return this; 407 } 408 409 @Override /* Overridden from Object */ 410 public String toString() { 411 return entries.stream().map(this::toString).collect(joining(",", "[", "]")); 412 } 413 414 415 private <T extends Serializer.Builder<?>> Stream<T> builders(Class<T> type) { 416 return entries.stream().filter(type::isInstance).map(type::cast); 417 } 418 419 private Object copyBuilder(Object o) { 420 if (o instanceof Serializer.Builder<?> x) { 421 Serializer.Builder<?> x2 = x.copy(); 422 if (neq(x.getClass(), x2.getClass())) 423 throw rex("Copy method not implemented on class %s", cn(x)); 424 x = x2; 425 if (nn(bcBuilder)) 426 x.marshallingContext(bcBuilder); 427 return x; 428 } 429 return o; 430 } 431 432 private Object createBuilder(Object o) { 433 if (o instanceof Class<?> o2) { 434 435 // Check for no-arg constructor. 436 var ci = info(o2).getPublicConstructor(c -> c.getParameterCount() == 0).orElse(null); 437 if (nn(ci)) 438 return ci.newInstance(); 439 440 // Check for builder create method. 441 Serializer.Builder<?> b = Serializer.createSerializerBuilder((Class<? extends Serializer>)o); 442 if (nn(bcBuilder)) 443 b.marshallingContext(bcBuilder); 444 o = b; 445 } 446 return o; 447 } 448 449 private String toString(Object o) { 450 if (o == null) 451 return "null"; 452 if (o instanceof Serializer.Builder<?>) 453 return "builder:" + cn(o); 454 return "serializer:" + cn(o); 455 } 456 457 /** 458 * Builds the serializer set. 459 * 460 * @return A new {@link SerializerSet}. 461 */ 462 public SerializerSet build() { 463 if (nn(impl)) 464 return impl; 465 return new SerializerSet(this); 466 } 467 } 468 469 /** 470 * An identifier that the previous entries in this group should be inherited. 471 * <p> 472 * Used by {@link SerializerSet.Builder#set(Class...)} 473 */ 474 public abstract static class Inherit extends Serializer { 475 protected Inherit(Serializer.Builder<?> builder) { 476 super(builder); 477 } 478 } 479 480 /** 481 * An identifier that the previous entries in this group should not be inherited. 482 * <p> 483 * Used by {@link SerializerSet.Builder#add(Class...)} 484 */ 485 public abstract static class NoInherit extends Serializer { 486 protected NoInherit(Serializer.Builder<?> builder) { 487 super(builder); 488 } 489 } 490 491 /** 492 * Static creator. 493 * 494 * @return A new builder for this object. 495 */ 496 public static Builder create() { 497 return new Builder(BasicBeanStore.INSTANCE); 498 } 499 500 /** 501 * Static creator. 502 * 503 * @param beanStore The bean store to use for creating beans. 504 * @return A new builder for this object. 505 */ 506 public static Builder create(BeanStore beanStore) { 507 return new Builder(beanStore); 508 } 509 510 // Maps Accept headers to matching serializers. 511 private final ConcurrentHashMap<String,SerializerMatch> cache = new ConcurrentHashMap<>(); 512 513 private final MediaRange[] mediaRanges; 514 private final List<MediaRange> mediaRangesList; 515 private final Serializer[] mediaTypeRangeSerializers; 516 517 private final MediaType[] mediaTypes; 518 private final List<MediaType> mediaTypesList; 519 final Serializer[] entries; 520 private final List<Serializer> entriesList; 521 522 /** 523 * Constructor. 524 * 525 * @param builder The builder for this bean. 526 * <br>Cannot be <jk>null</jk>. 527 */ 528 protected SerializerSet(Builder builder) { 529 530 this.entries = builder.entries.stream().map(this::build).toArray(Serializer[]::new); 531 this.entriesList = u(l(entries)); 532 533 List<MediaRange> lmtr = list(); 534 Set<MediaType> lmt = set(); 535 List<Serializer> l = list(); 536 for (var e : entries) { 537 e.getMediaTypeRanges().forEachRange(x -> { 538 lmtr.add(x); 539 l.add(e); 540 }); 541 e.forEachAcceptMediaType(lmt::add); 542 } 543 544 this.mediaRanges = lmtr.toArray(new MediaRange[lmtr.size()]); 545 this.mediaRangesList = u(l(mediaRanges)); 546 this.mediaTypes = lmt.toArray(new MediaType[lmt.size()]); 547 this.mediaTypesList = u(l(mediaTypes)); 548 this.mediaTypeRangeSerializers = l.toArray(new Serializer[l.size()]); 549 } 550 551 /** 552 * Creates a copy of this serializer group. 553 * 554 * @return A new copy of this serializer group. 555 */ 556 public Builder copy() { 557 return new Builder(this); 558 } 559 560 /** 561 * Same as {@link #getSerializerMatch(MediaType)} but returns just the matched serializer. 562 * 563 * @param mediaType The HTTP media type. 564 * <br>Can be <jk>null</jk> (returns {@link Optional#empty()}). 565 * @return The serializer that matched the accept header, or {@link Optional#empty()} if no match was made. 566 */ 567 public Optional<Serializer> getSerializer(MediaType mediaType) { 568 return getSerializerMatch(mediaType).map(SerializerMatch::getSerializer); 569 } 570 571 /** 572 * Same as {@link #getSerializerMatch(String)} but returns just the matched serializer. 573 * 574 * @param acceptHeader The HTTP <l>Accept</l> header string. 575 * <br>Can be <jk>null</jk> (returns {@link Optional#empty()}). 576 * @return The serializer that matched the accept header, or {@link Optional#empty()} if no match was made. 577 */ 578 public Optional<Serializer> getSerializer(String acceptHeader) { 579 return getSerializerMatch(acceptHeader).map(SerializerMatch::getSerializer); 580 } 581 582 /** 583 * Same as {@link #getSerializerMatch(String)} but matches using a {@link MediaType} instance. 584 * 585 * @param mediaType The HTTP media type. Can be <jk>null</jk> (returns {@link Optional#empty()}). 586 * @return The serializer and media type that matched the media type, or {@link Optional#empty()} if no match was made. 587 */ 588 public Optional<SerializerMatch> getSerializerMatch(MediaType mediaType) { 589 return mediaType == null ? oe() : getSerializerMatch(mediaType.toString()); 590 } 591 592 /** 593 * Searches the group for a serializer that can handle the specified <c>Accept</c> value. 594 * 595 * <p> 596 * The <c>accept</c> value complies with the syntax described in RFC2616, Section 14.1, as described below: 597 * <p class='bcode'> 598 * Accept = "Accept" ":" 599 * #( media-range [ accept-params ] ) 600 * 601 * media-range = ( "*\/*" 602 * | ( type "/" "*" ) 603 * | ( type "/" subtype ) 604 * ) *( ";" parameter ) 605 * accept-params = ";" "q" "=" qvalue *( accept-extension ) 606 * accept-extension = ";" token [ "=" ( token | quoted-string ) ] 607 * </p> 608 * 609 * <p> 610 * The returned object includes both the serializer and media type that matched. 611 * 612 * @param acceptHeader The HTTP <l>Accept</l> header string. 613 * <br>Can be <jk>null</jk> (returns {@link Optional#empty()}). 614 * @return The serializer and media type that matched the accept header, or {@link Optional#empty()} if no match was made. 615 */ 616 public Optional<SerializerMatch> getSerializerMatch(String acceptHeader) { 617 if (acceptHeader == null) 618 return oe(); 619 SerializerMatch sm = cache.get(acceptHeader); 620 if (nn(sm)) 621 return o(sm); 622 623 var a = MediaRanges.of(acceptHeader); 624 int match = a.match(mediaRangesList); 625 if (match >= 0) { 626 sm = new SerializerMatch(mediaRanges[match], mediaTypeRangeSerializers[match]); 627 cache.putIfAbsent(acceptHeader, sm); 628 } 629 630 return o(cache.get(acceptHeader)); 631 } 632 633 /** 634 * Returns a copy of the serializers in this group. 635 * 636 * @return An unmodifiable list of serializers in this group. 637 */ 638 public List<Serializer> getSerializers() { return entriesList; } 639 640 /** 641 * Same as {@link #getSerializer(MediaType)}, but casts it to a {@link OutputStreamSerializer}. 642 * 643 * @param mediaType The HTTP media type. 644 * <br>Can be <jk>null</jk> (returns {@link Optional#empty()}). 645 * @return The serializer that matched the accept header, or {@link Optional#empty()} if no match was made. 646 */ 647 public Optional<OutputStreamSerializer> getStreamSerializer(MediaType mediaType) { 648 return getSerializer(mediaType).map(OutputStreamSerializer.class::cast); 649 } 650 651 /** 652 * Same as {@link #getSerializer(String)}, but casts it to an {@link OutputStreamSerializer}. 653 * 654 * @param acceptHeader The HTTP <l>Accept</l> header string. 655 * <br>Can be <jk>null</jk> (returns {@link Optional#empty()}). 656 * @return The serializer that matched the accept header, or {@link Optional#empty()} if no match was made. 657 */ 658 public Optional<OutputStreamSerializer> getStreamSerializer(String acceptHeader) { 659 return getSerializer(acceptHeader).map(OutputStreamSerializer.class::cast); 660 } 661 662 /** 663 * Returns the media types that all serializers in this group can handle. 664 * 665 * <p> 666 * Entries are ordered in the same order as the serializers in the group. 667 * 668 * @return An unmodifiable list of media types. 669 */ 670 public List<MediaType> getSupportedMediaTypes() { return mediaTypesList; } 671 672 /** 673 * Same as {@link #getSerializer(MediaType)}, but casts it to a {@link WriterSerializer}. 674 * 675 * @param mediaType The HTTP media type. 676 * <br>Can be <jk>null</jk> (returns {@link Optional#empty()}). 677 * @return The serializer that matched the accept header, or {@link Optional#empty()} if no match was made. 678 */ 679 public Optional<WriterSerializer> getWriterSerializer(MediaType mediaType) { 680 return getSerializer(mediaType).map(WriterSerializer.class::cast); 681 } 682 683 /** 684 * Same as {@link #getSerializer(String)}, but casts it to a {@link WriterSerializer}. 685 * 686 * @param acceptHeader The HTTP <l>Accept</l> header string. 687 * <br>Can be <jk>null</jk> (returns {@link Optional#empty()}). 688 * @return The serializer that matched the accept header, or {@link Optional#empty()} if no match was made. 689 */ 690 public Optional<WriterSerializer> getWriterSerializer(String acceptHeader) { 691 return getSerializer(acceptHeader).map(WriterSerializer.class::cast); 692 } 693 694 /** 695 * Returns <jk>true</jk> if this group contains no serializers. 696 * 697 * @return <jk>true</jk> if this group contains no serializers. 698 */ 699 public boolean isEmpty() { return entries.length == 0; } 700 701 private Serializer build(Object o) { 702 if (o instanceof Serializer o2) 703 return o2; 704 return ((Serializer.Builder<?>)o).build(); 705 } 706}