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.parser; 014 015import static org.apache.juneau.internal.CollectionUtils.*; 016import static org.apache.juneau.internal.ObjectUtils.*; 017import static java.util.stream.Collectors.*; 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 Parser Parsers} 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 parsers based on HTTP <c>Content-Type</c> header values. 038 * <li> 039 * Sets common properties on all parsers in a single method call. 040 * <li> 041 * Locks all parsers in a single method call. 042 * <li> 043 * Clones existing groups and all parsers within the group in a single method call. 044 * </ul> 045 * 046 * <h5 class='topic'>Match ordering</h5> 047 * 048 * Parsers are matched against <c>Content-Type</c> strings in the order they exist in this group. 049 * 050 * <p> 051 * Adding new entries will cause the entries to be prepended to the group. 052 * This allows for previous parsers to be overridden through subsequent calls. 053 * 054 * <p> 055 * For example, calling <code>g.append(P1.<jk>class</jk>,P2.<jk>class</jk>).append(P3.<jk>class</jk>,P4.<jk>class</jk>)</code> 056 * will result in the order <c>P3, P4, P1, P2</c>. 057 * 058 * <h5 class='section'>Example:</h5> 059 * <p class='bjava'> 060 * <jc>// Construct a new parser group builder</jc> 061 * ParserSet <jv>parsers</jv> = ParserSet.<jsm>create</jsm>(); 062 * .add(JsonParser.<jk>class</jk>, XmlParser.<jk>class</jk>); <jc>// Add some parsers to it</jc> 063 * .forEach(<jv>x</jv> -> <jv>x</jv>.swaps(CalendarSwap.IsoLocalDateTime.<jk>class</jk>)) 064 * .forEach(<jv>x</jv> -> <jv>x</jv>.beansRequireSerializable()) 065 * .build(); 066 * 067 * <jc>// Find the appropriate parser by Content-Type</jc> 068 * ReaderParser <jv>parser</jv> = (ReaderParser)<jv>parsers</jv>.getParser(<js>"text/json"</js>); 069 * 070 * <jc>// Parse a bean from JSON</jc> 071 * String <jv>json</jv> = <js>"{...}"</js>; 072 * AddressBook <jv>addressBook</jv> = <jv>parser</jv>.parse(<jv>json</jv>, AddressBook.<jk>class</jk>); 073 * </p> 074 * 075 * <h5 class='section'>Notes:</h5><ul> 076 * <li class='note'>This class is thread safe and reusable. 077 * </ul> 078 * 079 * <h5 class='section'>See Also:</h5><ul> 080 * <li class='link'><a class="doclink" href="../../../../index.html#jm.SerializersAndParsers">Serializers and Parsers</a> 081 082 * </ul> 083 */ 084public final class ParserSet { 085 086 //----------------------------------------------------------------------------------------------------------------- 087 // Static 088 //----------------------------------------------------------------------------------------------------------------- 089 090 /** 091 * An identifier that the previous entries in this group should be inherited. 092 * <p> 093 * Used by {@link ParserSet.Builder#set(Class...)} 094 */ 095 @SuppressWarnings("javadoc") 096 public static abstract class Inherit extends Parser { 097 protected Inherit(Parser.Builder builder) { 098 super(builder); 099 } 100 } 101 102 /** 103 * An identifier that the previous entries in this group should not be inherited. 104 * <p> 105 * Used by {@link ParserSet.Builder#add(Class...)} 106 */ 107 @SuppressWarnings("javadoc") 108 public static abstract class NoInherit extends Parser { 109 protected NoInherit(Parser.Builder builder) { 110 super(builder); 111 } 112 } 113 114 /** 115 * Static creator. 116 * 117 * @param beanStore The bean store to use for creating beans. 118 * @return A new builder for this object. 119 */ 120 public static Builder create(BeanStore beanStore) { 121 return new Builder(beanStore); 122 } 123 124 /** 125 * Static creator. 126 * 127 * @return A new builder for this object. 128 */ 129 public static Builder create() { 130 return new Builder(BeanStore.INSTANCE); 131 } 132 133 //----------------------------------------------------------------------------------------------------------------- 134 // Builder 135 //----------------------------------------------------------------------------------------------------------------- 136 137 /** 138 * Builder class. 139 */ 140 @FluentSetters 141 public static class Builder extends BeanBuilder<ParserSet> { 142 143 List<Object> entries; 144 private BeanContext.Builder bcBuilder; 145 146 /** 147 * Create an empty parser group builder. 148 * 149 * @param beanStore The bean store to use for creating beans. 150 */ 151 protected Builder(BeanStore beanStore) { 152 super(ParserSet.class, beanStore); 153 this.entries = list(); 154 } 155 156 /** 157 * Clone an existing parser group. 158 * 159 * @param copyFrom The parser group that we're copying settings and parsers from. 160 */ 161 protected Builder(ParserSet copyFrom) { 162 super(copyFrom.getClass(), BeanStore.INSTANCE); 163 this.entries = list((Object[])copyFrom.entries); 164 } 165 166 /** 167 * Clone an existing parser group builder. 168 * 169 * <p> 170 * Parser builders will be cloned during this process. 171 * 172 * @param copyFrom The parser group that we're copying settings and parsers from. 173 */ 174 protected Builder(Builder copyFrom) { 175 super(copyFrom); 176 bcBuilder = copyFrom.bcBuilder == null ? null : copyFrom.bcBuilder.copy(); 177 entries = list(); 178 copyFrom.entries.stream().map(x -> copyBuilder(x)).forEach(x -> entries.add(x)); 179 } 180 181 private Object copyBuilder(Object o) { 182 if (o instanceof Parser.Builder) { 183 Parser.Builder x = (Parser.Builder)o; 184 Parser.Builder x2 = x.copy(); 185 if (ne(x.getClass(), x2.getClass())) 186 throw new BasicRuntimeException("Copy method not implemented on class {0}", x.getClass().getName()); 187 x = x2; 188 if (bcBuilder != null) 189 x.beanContext(bcBuilder); 190 return x; 191 } 192 return o; 193 } 194 195 @Override /* BeanBuilder */ 196 protected ParserSet buildDefault() { 197 return new ParserSet(this); 198 } 199 200 /** 201 * Makes a copy of this builder. 202 * 203 * @return A new copy of this builder. 204 */ 205 public Builder copy() { 206 return new Builder(this); 207 } 208 209 //------------------------------------------------------------------------------------------------------------- 210 // Properties 211 //------------------------------------------------------------------------------------------------------------- 212 213 /** 214 * Associates an existing bean context builder with all parser builders in this group. 215 * 216 * @param value The bean contest builder to associate. 217 * @return This object. 218 */ 219 public Builder beanContext(BeanContext.Builder value) { 220 bcBuilder = value; 221 forEach(x -> x.beanContext(value)); 222 return this; 223 } 224 225 /** 226 * Applies an operation to the bean context builder. 227 * 228 * @param operation The operation to apply. 229 * @return This object. 230 */ 231 public final Builder beanContext(Consumer<BeanContext.Builder> operation) { 232 if (bcBuilder != null) 233 operation.accept(bcBuilder); 234 return this; 235 } 236 237 /** 238 * Adds the specified parsers to this group. 239 * 240 * <p> 241 * Entries are added in-order to the beginning of the list in the group. 242 * 243 * <p> 244 * The {@link NoInherit} class can be used to clear out the existing list of parsers before adding the new entries. 245 * 246 * <h5 class='section'>Example:</h5> 247 * <p class='bjava'> 248 * ParserSet.Builder <jv>builder</jv> = ParserSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 249 * 250 * <jv>builder</jv>.add(FooParser.<jk>class</jk>); <jc>// Now contains: [FooParser]</jc> 251 * 252 * <jv>builder</jv>.add(BarParser.<jk>class</jk>, BazParser.<jk>class</jk>); <jc>// Now contains: [BarParser,BazParser,FooParser]</jc> 253 * 254 * <jv>builder</jv>.add(NoInherit.<jk>class</jk>, QuxParser.<jk>class</jk>); <jc>// Now contains: [QuxParser]</jc> 255 * </p> 256 * 257 * @param values The parsers to add to this group. 258 * @return This object. 259 * @throws IllegalArgumentException If one or more values do not extend from {@link Parser}. 260 */ 261 public Builder add(Class<?>...values) { 262 List<Object> l = list(); 263 for (Class<?> v : values) 264 if (v.getSimpleName().equals("NoInherit")) 265 clear(); 266 for (Class<?> v : values) { 267 if (Parser.class.isAssignableFrom(v)) { 268 l.add(createBuilder(v)); 269 } else if (! v.getSimpleName().equals("NoInherit")) { 270 throw new BasicRuntimeException("Invalid type passed to ParserSet.Builder.add(): {0}", v.getName()); 271 } 272 } 273 entries.addAll(0, l); 274 return this; 275 } 276 277 /** 278 * Sets the specified parsers 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 * ParserSet.Builder <jv>builder</jv> = ParserSet.<jsm>create</jsm>(); <jc>// Create an empty builder.</jc> 289 * 290 * <jv>builder</jv>.set(FooParser.<jk>class</jk>); <jc>// Now contains: [FooParser]</jc> 291 * 292 * <jv>builder</jv>.set(BarParser.<jk>class</jk>, BazParser.<jk>class</jk>); <jc>// Now contains: [BarParser,BazParser]</jc> 293 * 294 * <jv>builder</jv>.set(Inherit.<jk>class</jk>, QuxParser.<jk>class</jk>); <jc>// Now contains: [BarParser,BazParser,QuxParser]</jc> 295 * </p> 296 * 297 * @param values The parsers to set in this group. 298 * @return This object. 299 * @throws IllegalArgumentException If one or more values do not extend from {@link Parser} or named <js>"Inherit"</js>. 300 */ 301 public Builder set(Class<?>...values) { 302 List<Object> l = list(); 303 for (Class<?> v : values) { 304 if (v.getSimpleName().equals("Inherit")) { 305 l.addAll(entries); 306 } else if (Parser.class.isAssignableFrom(v)) { 307 l.add(createBuilder(v)); 308 } else { 309 throw new BasicRuntimeException("Invalid type passed to ParserGrouup.Builder.set(): {0}", v.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(x -> x.hasNoParams()); 321 if (ci != null) 322 return ci.invoke(); 323 324 // Check for builder. 325 @SuppressWarnings("unchecked") 326 Parser.Builder b = Parser.createParserBuilder((Class<? extends Parser>)o); 327 if (bcBuilder != null) 328 b.beanContext(bcBuilder); 329 o = b; 330 } 331 return o; 332 } 333 334 /** 335 * Registers the specified parsers with this group. 336 * 337 * <p> 338 * When passing in pre-instantiated parsers to this group, applying properties and transforms to the group 339 * do not affect them. 340 * 341 * @param s The parsers to append to this group. 342 * @return This object. 343 */ 344 public Builder add(Parser...s) { 345 prependAll(entries, (Object[])s); 346 return this; 347 } 348 349 /** 350 * Clears out any existing parsers 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 parser 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 parser builder in this group. 364 */ 365 public boolean canApply(AnnotationWorkList work) { 366 for (Object o : entries) 367 if (o instanceof Parser.Builder) 368 if (((Parser.Builder)o).canApply(work)) 369 return true; 370 return false; 371 } 372 373 /** 374 * Applies the specified annotations to all applicable parser 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 parser builders in this group. 385 * 386 * @param action The action to perform. 387 * @return This object. 388 */ 389 public Builder forEach(Consumer<Parser.Builder> action) { 390 builders(Parser.Builder.class).forEach(action); 391 return this; 392 } 393 394 /** 395 * Performs an action on all writer parser builders in this group. 396 * 397 * @param action The action to perform. 398 * @return This object. 399 */ 400 public Builder forEachRP(Consumer<ReaderParser.Builder> action) { 401 return forEach(ReaderParser.Builder.class, action); 402 } 403 404 /** 405 * Performs an action on all output stream parser builders in this group. 406 * 407 * @param action The action to perform. 408 * @return This object. 409 */ 410 public Builder forEachISP(Consumer<InputStreamParser.Builder> action) { 411 return forEach(InputStreamParser.Builder.class, action); 412 } 413 414 /** 415 * Performs an action on all parser builders of the specified type in this group. 416 * 417 * @param <B> The parser builder type. 418 * @param type The parser builder type. 419 * @param action The action to perform. 420 * @return This object. 421 */ 422 public <B extends Parser.Builder> Builder forEach(Class<B> type, Consumer<B> action) { 423 builders(type).forEach(action); 424 return this; 425 } 426 427 /** 428 * Returns direct access to the {@link Parser} and {@link Parser.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 Parser} and {@link Parser.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 Parser.Builder> Stream<T> builders(Class<T> type) { 444 return entries.stream().filter(x -> type.isInstance(x)).map(x -> type.cast(x)); 445 } 446 447 // <FluentSetters> 448 449 @Override /* GENERATED - org.apache.juneau.BeanBuilder */ 450 public Builder impl(Object value) { 451 super.impl(value); 452 return this; 453 } 454 455 @Override /* GENERATED - org.apache.juneau.BeanBuilder */ 456 public Builder type(Class<?> value) { 457 super.type(value); 458 return this; 459 } 460 461 // </FluentSetters> 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 Parser.Builder) 472 return "builder:" + o.getClass().getName(); 473 return "parser:" + o.getClass().getName(); 474 } 475 } 476 477 //----------------------------------------------------------------------------------------------------------------- 478 // Instance 479 //----------------------------------------------------------------------------------------------------------------- 480 481 // Maps Content-Type headers to matches. 482 private final ConcurrentHashMap<String,ParserMatch> cache = new ConcurrentHashMap<>(); 483 484 private final MediaType[] mediaTypes; 485 private final Parser[] mediaTypeParsers; 486 487 final Parser[] entries; 488 489 /** 490 * Constructor. 491 * 492 * @param builder The builder for this bean. 493 */ 494 public ParserSet(Builder builder) { 495 496 this.entries = builder.entries.stream().map(x -> build(x)).toArray(Parser[]::new); 497 498 List<MediaType> lmt = list(); 499 List<Parser> l = list(); 500 for (Parser e : entries) { 501 e.getMediaTypes().forEach(x -> { 502 lmt.add(x); 503 l.add(e); 504 }); 505 } 506 507 this.mediaTypes = array(lmt, MediaType.class); 508 this.mediaTypeParsers = array(l, Parser.class); 509 } 510 511 private Parser build(Object o) { 512 if (o instanceof Parser) 513 return (Parser)o; 514 return ((Parser.Builder)o).build(); 515 } 516 517 /** 518 * Creates a copy of this parser group. 519 * 520 * @return A new copy of this parser group. 521 */ 522 public Builder copy() { 523 return new Builder(this); 524 } 525 526 /** 527 * Searches the group for a parser that can handle the specified <l>Content-Type</l> header value. 528 * 529 * <p> 530 * The returned object includes both the parser and media type that matched. 531 * 532 * @param contentTypeHeader The HTTP <l>Content-Type</l> header value. 533 * @return The parser and media type that matched the content type header, or <jk>null</jk> if no match was made. 534 */ 535 public ParserMatch getParserMatch(String contentTypeHeader) { 536 ParserMatch pm = cache.get(contentTypeHeader); 537 if (pm != null) 538 return pm; 539 540 MediaType ct = MediaType.of(contentTypeHeader); 541 int match = ct.match(ulist(mediaTypes)); 542 543 if (match >= 0) { 544 pm = new ParserMatch(mediaTypes[match], mediaTypeParsers[match]); 545 cache.putIfAbsent(contentTypeHeader, pm); 546 } 547 548 return cache.get(contentTypeHeader); 549 } 550 551 /** 552 * Same as {@link #getParserMatch(String)} but matches using a {@link MediaType} instance. 553 * 554 * @param mediaType The HTTP <l>Content-Type</l> header value as a media type. 555 * @return The parser and media type that matched the media type, or <jk>null</jk> if no match was made. 556 */ 557 public ParserMatch getParserMatch(MediaType mediaType) { 558 return getParserMatch(mediaType.toString()); 559 } 560 561 /** 562 * Same as {@link #getParserMatch(String)} but returns just the matched parser. 563 * 564 * @param contentTypeHeader The HTTP <l>Content-Type</l> header string. 565 * @return The parser that matched the content type header, or <jk>null</jk> if no match was made. 566 */ 567 public Parser getParser(String contentTypeHeader) { 568 ParserMatch pm = getParserMatch(contentTypeHeader); 569 return pm == null ? null : pm.getParser(); 570 } 571 572 /** 573 * Same as {@link #getParserMatch(MediaType)} but returns just the matched parser. 574 * 575 * @param mediaType The HTTP media type. 576 * @return The parser that matched the media type, or <jk>null</jk> if no match was made. 577 */ 578 public Parser getParser(MediaType mediaType) { 579 ParserMatch pm = getParserMatch(mediaType); 580 return pm == null ? null : pm.getParser(); 581 } 582 583 /** 584 * Returns the media types that all parsers in this group can handle 585 * 586 * <p> 587 * Entries are ordered in the same order as the parsers in the group. 588 * 589 * @return An unmodifiable list of media types. 590 */ 591 public List<MediaType> getSupportedMediaTypes() { 592 return ulist(mediaTypes); 593 } 594 595 /** 596 * Returns the parsers in this group. 597 * 598 * @return An unmodifiable list of parsers in this group. 599 */ 600 public List<Parser> getParsers() { 601 return ulist(entries); 602 } 603 604 /** 605 * Returns <jk>true</jk> if this group contains no parsers. 606 * 607 * @return <jk>true</jk> if this group contains no parsers. 608 */ 609 public boolean isEmpty() { 610 return entries.length == 0; 611 } 612}