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.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.ThrowableUtils.*; 023import static org.apache.juneau.commons.utils.Utils.*; 024 025import java.util.*; 026import java.util.concurrent.*; 027import java.util.function.*; 028import java.util.stream.*; 029 030import org.apache.juneau.*; 031import org.apache.juneau.cp.*; 032 033/** 034 * Represents a group of {@link 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>); 073 * 074 * <jc>// Parse a bean from JSON</jc> 075 * String <jv>json</jv> = <js>"{...}"</js>; 076 * AddressBook <jv>addressBook</jv> = <jv>parser</jv>.parse(<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 */ 088public class ParserSet { 089 /** 090 * Builder class. 091 */ 092 public static class Builder extends BeanBuilder<ParserSet> { 093 094 List<Object> entries; 095 private BeanContext.Builder bcBuilder; 096 097 /** 098 * Create an empty parser group builder. 099 * 100 * @param beanStore The bean store to use for creating beans. 101 */ 102 protected Builder(BeanStore beanStore) { 103 super(ParserSet.class, beanStore); 104 this.entries = list(); 105 } 106 107 /** 108 * Clone an existing parser group builder. 109 * 110 * <p> 111 * Parser builders will be cloned during this process. 112 * 113 * @param copyFrom The parser group that we're copying settings and parsers from. 114 */ 115 protected Builder(Builder copyFrom) { 116 super(copyFrom); 117 bcBuilder = copyFrom.bcBuilder == null ? null : copyFrom.bcBuilder.copy(); 118 entries = list(); 119 copyFrom.entries.stream().map(this::copyBuilder).forEach(x -> entries.add(x)); 120 } 121 122 /** 123 * Clone an existing parser group. 124 * 125 * @param copyFrom The parser group that we're copying settings and parsers from. 126 */ 127 protected Builder(ParserSet copyFrom) { 128 super(copyFrom.getClass(), BeanStore.INSTANCE); 129 this.entries = list((Object[])copyFrom.entries); 130 } 131 132 /** 133 * Adds the specified parsers to this group. 134 * 135 * <p> 136 * Entries are added in-order to the beginning of the list in the group. 137 * 138 * <p> 139 * The {@link NoInherit} class can be used to clear out the existing list of parsers before adding the new entries. 140 * 141 * <h5 class='section'>Example:</h5> 142 * <p class='bjava'> 143 * ParserSet.Builder <jv>builder</jv> = ParserSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 144 * 145 * <jv>builder</jv>.add(FooParser.<jk>class</jk>); <jc>// Now contains: [FooParser]</jc> 146 * 147 * <jv>builder</jv>.add(BarParser.<jk>class</jk>, BazParser.<jk>class</jk>); <jc>// Now contains: [BarParser,BazParser,FooParser]</jc> 148 * 149 * <jv>builder</jv>.add(NoInherit.<jk>class</jk>, QuxParser.<jk>class</jk>); <jc>// Now contains: [QuxParser]</jc> 150 * </p> 151 * 152 * @param values The parsers to add to this group. 153 * @return This object. 154 * @throws IllegalArgumentException If one or more values do not extend from {@link Parser}. 155 */ 156 public Builder add(Class<?>...values) { 157 List<Object> l = list(); 158 for (var v : values) 159 if (v.getSimpleName().equals("NoInherit")) 160 clear(); 161 for (var v : values) { 162 if (Parser.class.isAssignableFrom(v)) { 163 l.add(createBuilder(v)); 164 } else if (! v.getSimpleName().equals("NoInherit")) { 165 throw rex("Invalid type passed to ParserSet.Builder.add(): {0}", cn(v)); 166 } 167 } 168 entries.addAll(0, l); 169 return this; 170 } 171 172 /** 173 * Registers the specified parsers with this group. 174 * 175 * <p> 176 * When passing in pre-instantiated parsers to this group, applying properties and transforms to the group 177 * do not affect them. 178 * 179 * @param s The parsers to append to this group. 180 * @return This object. 181 */ 182 public Builder add(Parser...s) { 183 prependAll(entries, (Object[])s); 184 return this; 185 } 186 187 /** 188 * Applies the specified annotations to all applicable parser builders in this group. 189 * 190 * @param work The annotations to apply. 191 * @return This object. 192 */ 193 public Builder apply(AnnotationWorkList work) { 194 return forEach(x -> x.apply(work)); 195 } 196 197 /** 198 * Associates an existing bean context builder with all parser builders in this group. 199 * 200 * @param value The bean contest builder to associate. 201 * @return This object. 202 */ 203 public Builder beanContext(BeanContext.Builder value) { 204 bcBuilder = value; 205 forEach(x -> x.beanContext(value)); 206 return this; 207 } 208 209 /** 210 * Applies an operation to the bean context builder. 211 * 212 * @param operation The operation to apply. 213 * @return This object. 214 */ 215 public final Builder beanContext(Consumer<BeanContext.Builder> operation) { 216 if (nn(bcBuilder)) 217 operation.accept(bcBuilder); 218 return this; 219 } 220 221 /** 222 * Returns <jk>true</jk> if at least one of the specified annotations can be applied to at least one parser builder in this group. 223 * 224 * @param work The work to check. 225 * @return <jk>true</jk> if at least one of the specified annotations can be applied to at least one parser builder in this group. 226 */ 227 public boolean canApply(AnnotationWorkList work) { 228 for (var o : entries) 229 if (o instanceof Parser.Builder) 230 if (((Parser.Builder)o).canApply(work)) 231 return true; 232 return false; 233 } 234 235 /** 236 * Clears out any existing parsers in this group. 237 * 238 * @return This object. 239 */ 240 public Builder clear() { 241 entries.clear(); 242 return this; 243 } 244 245 /** 246 * Makes a copy of this builder. 247 * 248 * @return A new copy of this builder. 249 */ 250 public Builder copy() { 251 return new Builder(this); 252 } 253 254 /** 255 * Performs an action on all parser builders of the specified type in this group. 256 * 257 * @param <B> The parser builder type. 258 * @param type The parser builder type. 259 * @param action The action to perform. 260 * @return This object. 261 */ 262 public <B extends Parser.Builder> Builder forEach(Class<B> type, Consumer<B> action) { 263 builders(type).forEach(action); 264 return this; 265 } 266 267 /** 268 * Performs an action on all parser builders in this group. 269 * 270 * @param action The action to perform. 271 * @return This object. 272 */ 273 public Builder forEach(Consumer<Parser.Builder> action) { 274 builders(Parser.Builder.class).forEach(action); 275 return this; 276 } 277 278 /** 279 * Performs an action on all output stream parser builders in this group. 280 * 281 * @param action The action to perform. 282 * @return This object. 283 */ 284 public Builder forEachISP(Consumer<InputStreamParser.Builder> action) { 285 return forEach(InputStreamParser.Builder.class, action); 286 } 287 288 /** 289 * Performs an action on all writer parser builders in this group. 290 * 291 * @param action The action to perform. 292 * @return This object. 293 */ 294 public Builder forEachRP(Consumer<ReaderParser.Builder> action) { 295 return forEach(ReaderParser.Builder.class, action); 296 } 297 298 @Override /* Overridden from BeanBuilder */ 299 public Builder impl(Object value) { 300 super.impl(value); 301 return this; 302 } 303 304 /** 305 * Returns direct access to the {@link Parser} and {@link Parser.Builder} objects in this builder. 306 * 307 * <p> 308 * Provided to allow for any extraneous modifications to the list not accomplishable via other methods on this builder such 309 * as re-ordering/adding/removing entries. 310 * 311 * <p> 312 * Note that it is up to the user to ensure that the list only contains {@link Parser} and {@link Parser.Builder} objects. 313 * 314 * @return The inner list of entries in this builder. 315 */ 316 public List<Object> inner() { 317 return entries; 318 } 319 320 /** 321 * Sets the specified parsers for this group. 322 * 323 * <p> 324 * Existing values are overwritten. 325 * 326 * <p> 327 * The {@link Inherit} class can be used to insert existing entries in this group into the position specified. 328 * 329 * <h5 class='section'>Example:</h5> 330 * <p class='bjava'> 331 * ParserSet.Builder <jv>builder</jv> = ParserSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 332 * 333 * <jv>builder</jv>.set(FooParser.<jk>class</jk>); <jc>// Now contains: [FooParser]</jc> 334 * 335 * <jv>builder</jv>.set(BarParser.<jk>class</jk>, BazParser.<jk>class</jk>); <jc>// Now contains: [BarParser,BazParser]</jc> 336 * 337 * <jv>builder</jv>.set(Inherit.<jk>class</jk>, QuxParser.<jk>class</jk>); <jc>// Now contains: [BarParser,BazParser,QuxParser]</jc> 338 * </p> 339 * 340 * @param values The parsers to set in this group. 341 * @return This object. 342 * @throws IllegalArgumentException If one or more values do not extend from {@link Parser} or named <js>"Inherit"</js>. 343 */ 344 public Builder set(Class<?>...values) { 345 List<Object> l = list(); 346 for (var v : values) { 347 if (v.getSimpleName().equals("Inherit")) { 348 l.addAll(entries); 349 } else if (Parser.class.isAssignableFrom(v)) { 350 l.add(createBuilder(v)); 351 } else { 352 throw rex("Invalid type passed to ParserGrouup.Builder.set(): {0}", cn(v)); 353 } 354 } 355 entries = l; 356 return this; 357 } 358 359 @Override /* Overridden from Object */ 360 public String toString() { 361 return entries.stream().map(this::toString).collect(joining(",", "[", "]")); 362 } 363 364 @Override /* Overridden from BeanBuilder */ 365 public Builder type(Class<?> value) { 366 super.type(value); 367 return this; 368 } 369 370 private <T extends Parser.Builder> Stream<T> builders(Class<T> type) { 371 return entries.stream().filter(x -> type.isInstance(x)).map(x -> type.cast(x)); 372 } 373 374 private Object copyBuilder(Object o) { 375 if (o instanceof Parser.Builder x) { 376 Parser.Builder x2 = x.copy(); 377 if (neq(x.getClass(), x2.getClass())) 378 throw rex("Copy method not implemented on class {0}", cn(x)); 379 x = x2; 380 if (nn(bcBuilder)) 381 x.beanContext(bcBuilder); 382 return x; 383 } 384 return o; 385 } 386 387 private Object createBuilder(Object o) { 388 if (o instanceof Class<?> o2) { 389 390 // Check for no-arg constructor. 391 var ci = info(o2).getPublicConstructor(c -> c.getParameterCount() == 0).orElse(null); 392 if (nn(ci)) 393 return ci.newInstance(); 394 395 // Check for builder. 396 @SuppressWarnings("unchecked") 397 Parser.Builder b = Parser.createParserBuilder((Class<? extends Parser>)o); 398 if (nn(bcBuilder)) 399 b.beanContext(bcBuilder); 400 o = b; 401 } 402 return o; 403 } 404 405 private String toString(Object o) { 406 if (o == null) 407 return "null"; 408 if (o instanceof Parser.Builder) 409 return "builder:" + cn(o); 410 return "parser:" + cn(o); 411 } 412 413 @Override /* Overridden from BeanBuilder */ 414 protected ParserSet buildDefault() { 415 return new ParserSet(this); 416 } 417 } 418 419 /** 420 * An identifier that the previous entries in this group should be inherited. 421 * <p> 422 * Used by {@link ParserSet.Builder#set(Class...)} 423 */ 424 public static abstract class Inherit extends Parser { 425 protected Inherit(Parser.Builder builder) { 426 super(builder); 427 } 428 } 429 430 /** 431 * An identifier that the previous entries in this group should not be inherited. 432 * <p> 433 * Used by {@link ParserSet.Builder#add(Class...)} 434 */ 435 public static abstract class NoInherit extends Parser { 436 protected NoInherit(Parser.Builder builder) { 437 super(builder); 438 } 439 } 440 441 /** 442 * Static creator. 443 * 444 * @return A new builder for this object. 445 */ 446 public static Builder create() { 447 return new Builder(BeanStore.INSTANCE); 448 } 449 450 /** 451 * Static creator. 452 * 453 * @param beanStore The bean store to use for creating beans. 454 * @return A new builder for this object. 455 */ 456 public static Builder create(BeanStore beanStore) { 457 return new Builder(beanStore); 458 } 459 460 // Maps Content-Type headers to matches. 461 private final ConcurrentHashMap<String,ParserMatch> cache = new ConcurrentHashMap<>(); 462 463 private final MediaType[] mediaTypes; 464 private final Parser[] mediaTypeParsers; 465 466 final Parser[] entries; 467 468 /** 469 * Constructor. 470 * 471 * @param builder The builder for this bean. 472 */ 473 public ParserSet(Builder builder) { 474 475 this.entries = builder.entries.stream().map(this::build).toArray(Parser[]::new); 476 477 List<MediaType> lmt = list(); 478 List<Parser> l = list(); 479 for (var e : entries) { 480 e.getMediaTypes().forEach(x -> { 481 lmt.add(x); 482 l.add(e); 483 }); 484 } 485 486 this.mediaTypes = array(lmt, MediaType.class); 487 this.mediaTypeParsers = array(l, Parser.class); 488 } 489 490 /** 491 * Creates a copy of this parser group. 492 * 493 * @return A new copy of this parser group. 494 */ 495 public Builder copy() { 496 return new Builder(this); 497 } 498 499 /** 500 * Same as {@link #getParserMatch(MediaType)} but returns just the matched parser. 501 * 502 * @param mediaType The HTTP media type. 503 * @return The parser that matched the media type, or <jk>null</jk> if no match was made. 504 */ 505 public Parser getParser(MediaType mediaType) { 506 ParserMatch pm = getParserMatch(mediaType); 507 return pm == null ? null : pm.getParser(); 508 } 509 510 /** 511 * Same as {@link #getParserMatch(String)} but returns just the matched parser. 512 * 513 * @param contentTypeHeader The HTTP <l>Content-Type</l> header string. 514 * @return The parser that matched the content type header, or <jk>null</jk> if no match was made. 515 */ 516 public Parser getParser(String contentTypeHeader) { 517 ParserMatch pm = getParserMatch(contentTypeHeader); 518 return pm == null ? null : pm.getParser(); 519 } 520 521 /** 522 * Same as {@link #getParserMatch(String)} but matches using a {@link MediaType} instance. 523 * 524 * @param mediaType The HTTP <l>Content-Type</l> header value as a media type. 525 * @return The parser and media type that matched the media type, or <jk>null</jk> if no match was made. 526 */ 527 public ParserMatch getParserMatch(MediaType mediaType) { 528 return getParserMatch(mediaType.toString()); 529 } 530 531 /** 532 * Searches the group for a parser that can handle the specified <l>Content-Type</l> header value. 533 * 534 * <p> 535 * The returned object includes both the parser and media type that matched. 536 * 537 * @param contentTypeHeader The HTTP <l>Content-Type</l> header value. 538 * @return The parser and media type that matched the content type header, or <jk>null</jk> if no match was made. 539 */ 540 public ParserMatch getParserMatch(String contentTypeHeader) { 541 ParserMatch pm = cache.get(contentTypeHeader); 542 if (nn(pm)) 543 return pm; 544 545 var ct = MediaType.of(contentTypeHeader); 546 int match = ct.match(l(mediaTypes)); 547 548 if (match >= 0) { 549 pm = new ParserMatch(mediaTypes[match], mediaTypeParsers[match]); 550 cache.putIfAbsent(contentTypeHeader, pm); 551 } 552 553 return cache.get(contentTypeHeader); 554 } 555 556 /** 557 * Returns the parsers in this group. 558 * 559 * @return An unmodifiable list of parsers in this group. 560 */ 561 public List<Parser> getParsers() { return u(l(entries)); } 562 563 /** 564 * Returns the media types that all parsers in this group can handle 565 * 566 * <p> 567 * Entries are ordered in the same order as the parsers in the group. 568 * 569 * @return An unmodifiable list of media types. 570 */ 571 public List<MediaType> getSupportedMediaTypes() { return u(l(mediaTypes)); } 572 573 /** 574 * Returns <jk>true</jk> if this group contains no parsers. 575 * 576 * @return <jk>true</jk> if this group contains no parsers. 577 */ 578 public boolean isEmpty() { return entries.length == 0; } 579 580 private Parser build(Object o) { 581 if (o instanceof Parser o2) 582 return o2; 583 return ((Parser.Builder)o).build(); 584 } 585}