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