All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.parser.ParserSet Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.parser;

import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;
import static java.util.stream.Collectors.*;

import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.*;

import org.apache.juneau.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.reflect.*;

/**
 * Represents a group of {@link Parser Parsers} that can be looked up by media type.
 *
 * 
Description
* * Provides the following features: *
    *
  • * Finds parsers based on HTTP Content-Type header values. *
  • * Sets common properties on all parsers in a single method call. *
  • * Locks all parsers in a single method call. *
  • * Clones existing groups and all parsers within the group in a single method call. *
* *
Match ordering
* * Parsers are matched against Content-Type strings in the order they exist in this group. * *

* Adding new entries will cause the entries to be prepended to the group. * This allows for previous parsers to be overridden through subsequent calls. * *

* For example, calling g.append(P1.class,P2.class).append(P3.class,P4.class) * will result in the order P3, P4, P1, P2. * *

Example:
*

* // Construct a new parser group builder * ParserSet parsers = ParserSet.create(); * .add(JsonParser.class, XmlParser.class); // Add some parsers to it * .forEach(x -> x.swaps(CalendarSwap.IsoLocalDateTime.class)) * .forEach(x -> x.beansRequireSerializable()) * .build(); * * // Find the appropriate parser by Content-Type * ReaderParser parser = (ReaderParser)parsers.getParser("text/json"); * * // Parse a bean from JSON * String json = "{...}"; * AddressBook addressBook = parser.parse(json, AddressBook.class); *

* *
Notes:
    *
  • This class is thread safe and reusable. *
* *
See Also:
*/ public final class ParserSet { //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- /** * An identifier that the previous entries in this group should be inherited. *

* Used by {@link ParserSet.Builder#set(Class...)} */ @SuppressWarnings("javadoc") public static abstract class Inherit extends Parser { protected Inherit(Parser.Builder builder) { super(builder); } } /** * An identifier that the previous entries in this group should not be inherited. *

* Used by {@link ParserSet.Builder#add(Class...)} */ @SuppressWarnings("javadoc") public static abstract class NoInherit extends Parser { protected NoInherit(Parser.Builder builder) { super(builder); } } /** * Static creator. * * @param beanStore The bean store to use for creating beans. * @return A new builder for this object. */ public static Builder create(BeanStore beanStore) { return new Builder(beanStore); } /** * Static creator. * * @return A new builder for this object. */ public static Builder create() { return new Builder(BeanStore.INSTANCE); } //----------------------------------------------------------------------------------------------------------------- // Builder //----------------------------------------------------------------------------------------------------------------- /** * Builder class. */ @FluentSetters public static class Builder extends BeanBuilder { List entries; private BeanContext.Builder bcBuilder; /** * Create an empty parser group builder. * * @param beanStore The bean store to use for creating beans. */ protected Builder(BeanStore beanStore) { super(ParserSet.class, beanStore); this.entries = list(); } /** * Clone an existing parser group. * * @param copyFrom The parser group that we're copying settings and parsers from. */ protected Builder(ParserSet copyFrom) { super(copyFrom.getClass(), BeanStore.INSTANCE); this.entries = list((Object[])copyFrom.entries); } /** * Clone an existing parser group builder. * *

* Parser builders will be cloned during this process. * * @param copyFrom The parser group that we're copying settings and parsers from. */ protected Builder(Builder copyFrom) { super(copyFrom); bcBuilder = copyFrom.bcBuilder == null ? null : copyFrom.bcBuilder.copy(); entries = list(); copyFrom.entries.stream().map(this::copyBuilder).forEach(x -> entries.add(x)); } private Object copyBuilder(Object o) { if (o instanceof Parser.Builder) { Parser.Builder x = (Parser.Builder)o; Parser.Builder x2 = x.copy(); if (ne(x.getClass(), x2.getClass())) throw new BasicRuntimeException("Copy method not implemented on class {0}", x.getClass().getName()); x = x2; if (bcBuilder != null) x.beanContext(bcBuilder); return x; } return o; } @Override /* BeanBuilder */ protected ParserSet buildDefault() { return new ParserSet(this); } /** * Makes a copy of this builder. * * @return A new copy of this builder. */ public Builder copy() { return new Builder(this); } //------------------------------------------------------------------------------------------------------------- // Properties //------------------------------------------------------------------------------------------------------------- /** * Associates an existing bean context builder with all parser builders in this group. * * @param value The bean contest builder to associate. * @return This object. */ public Builder beanContext(BeanContext.Builder value) { bcBuilder = value; forEach(x -> x.beanContext(value)); return this; } /** * Applies an operation to the bean context builder. * * @param operation The operation to apply. * @return This object. */ public final Builder beanContext(Consumer operation) { if (bcBuilder != null) operation.accept(bcBuilder); return this; } /** * Adds the specified parsers to this group. * *

* Entries are added in-order to the beginning of the list in the group. * *

* The {@link NoInherit} class can be used to clear out the existing list of parsers before adding the new entries. * *

Example:
*

* ParserSet.Builder builder = ParserSet.create(); // Create an empty builder. * * builder.add(FooParser.class); // Now contains: [FooParser] * * builder.add(BarParser.class, BazParser.class); // Now contains: [BarParser,BazParser,FooParser] * * builder.add(NoInherit.class, QuxParser.class); // Now contains: [QuxParser] *

* * @param values The parsers to add to this group. * @return This object. * @throws IllegalArgumentException If one or more values do not extend from {@link Parser}. */ public Builder add(Class...values) { List l = list(); for (Class v : values) if (v.getSimpleName().equals("NoInherit")) clear(); for (Class v : values) { if (Parser.class.isAssignableFrom(v)) { l.add(createBuilder(v)); } else if (! v.getSimpleName().equals("NoInherit")) { throw new BasicRuntimeException("Invalid type passed to ParserSet.Builder.add(): {0}", v.getName()); } } entries.addAll(0, l); return this; } /** * Sets the specified parsers for this group. * *

* Existing values are overwritten. * *

* The {@link Inherit} class can be used to insert existing entries in this group into the position specified. * *

Example:
*

* ParserSet.Builder builder = ParserSet.create(); // Create an empty builder. * * builder.set(FooParser.class); // Now contains: [FooParser] * * builder.set(BarParser.class, BazParser.class); // Now contains: [BarParser,BazParser] * * builder.set(Inherit.class, QuxParser.class); // Now contains: [BarParser,BazParser,QuxParser] *

* * @param values The parsers to set in this group. * @return This object. * @throws IllegalArgumentException If one or more values do not extend from {@link Parser} or named "Inherit". */ public Builder set(Class...values) { List l = list(); for (Class v : values) { if (v.getSimpleName().equals("Inherit")) { l.addAll(entries); } else if (Parser.class.isAssignableFrom(v)) { l.add(createBuilder(v)); } else { throw new BasicRuntimeException("Invalid type passed to ParserGrouup.Builder.set(): {0}", v.getName()); } } entries = l; return this; } private Object createBuilder(Object o) { if (o instanceof Class) { // Check for no-arg constructor. ConstructorInfo ci = ClassInfo.of((Class)o).getPublicConstructor(ConstructorInfo::hasNoParams); if (ci != null) return ci.invoke(); // Check for builder. @SuppressWarnings("unchecked") Parser.Builder b = Parser.createParserBuilder((Class)o); if (bcBuilder != null) b.beanContext(bcBuilder); o = b; } return o; } /** * Registers the specified parsers with this group. * *

* When passing in pre-instantiated parsers to this group, applying properties and transforms to the group * do not affect them. * * @param s The parsers to append to this group. * @return This object. */ public Builder add(Parser...s) { prependAll(entries, (Object[])s); return this; } /** * Clears out any existing parsers in this group. * * @return This object. */ public Builder clear() { entries.clear(); return this; } /** * Returns true if at least one of the specified annotations can be applied to at least one parser builder in this group. * * @param work The work to check. * @return true if at least one of the specified annotations can be applied to at least one parser builder in this group. */ public boolean canApply(AnnotationWorkList work) { for (Object o : entries) if (o instanceof Parser.Builder) if (((Parser.Builder)o).canApply(work)) return true; return false; } /** * Applies the specified annotations to all applicable parser builders in this group. * * @param work The annotations to apply. * @return This object. */ public Builder apply(AnnotationWorkList work) { return forEach(x -> x.apply(work)); } /** * Performs an action on all parser builders in this group. * * @param action The action to perform. * @return This object. */ public Builder forEach(Consumer action) { builders(Parser.Builder.class).forEach(action); return this; } /** * Performs an action on all writer parser builders in this group. * * @param action The action to perform. * @return This object. */ public Builder forEachRP(Consumer action) { return forEach(ReaderParser.Builder.class, action); } /** * Performs an action on all output stream parser builders in this group. * * @param action The action to perform. * @return This object. */ public Builder forEachISP(Consumer action) { return forEach(InputStreamParser.Builder.class, action); } /** * Performs an action on all parser builders of the specified type in this group. * * @param The parser builder type. * @param type The parser builder type. * @param action The action to perform. * @return This object. */ public Builder forEach(Class type, Consumer action) { builders(type).forEach(action); return this; } /** * Returns direct access to the {@link Parser} and {@link Parser.Builder} objects in this builder. * *

* Provided to allow for any extraneous modifications to the list not accomplishable via other methods on this builder such * as re-ordering/adding/removing entries. * *

* Note that it is up to the user to ensure that the list only contains {@link Parser} and {@link Parser.Builder} objects. * * @return The inner list of entries in this builder. */ public List inner() { return entries; } private Stream builders(Class type) { return entries.stream().filter(x -> type.isInstance(x)).map(x -> type.cast(x)); } // @Override /* GENERATED - org.apache.juneau.BeanBuilder */ public Builder impl(Object value) { super.impl(value); return this; } @Override /* GENERATED - org.apache.juneau.BeanBuilder */ public Builder type(Class value) { super.type(value); return this; } // @Override /* Object */ public String toString() { return entries.stream().map(this::toString).collect(joining(",","[","]")); } private String toString(Object o) { if (o == null) return "null"; if (o instanceof Parser.Builder) return "builder:" + o.getClass().getName(); return "parser:" + o.getClass().getName(); } } //----------------------------------------------------------------------------------------------------------------- // Instance //----------------------------------------------------------------------------------------------------------------- // Maps Content-Type headers to matches. private final ConcurrentHashMap cache = new ConcurrentHashMap<>(); private final MediaType[] mediaTypes; private final Parser[] mediaTypeParsers; final Parser[] entries; /** * Constructor. * * @param builder The builder for this bean. */ public ParserSet(Builder builder) { this.entries = builder.entries.stream().map(this::build).toArray(Parser[]::new); List lmt = list(); List l = list(); for (Parser e : entries) { e.getMediaTypes().forEach(x -> { lmt.add(x); l.add(e); }); } this.mediaTypes = array(lmt, MediaType.class); this.mediaTypeParsers = array(l, Parser.class); } private Parser build(Object o) { if (o instanceof Parser) return (Parser)o; return ((Parser.Builder)o).build(); } /** * Creates a copy of this parser group. * * @return A new copy of this parser group. */ public Builder copy() { return new Builder(this); } /** * Searches the group for a parser that can handle the specified Content-Type header value. * *

* The returned object includes both the parser and media type that matched. * * @param contentTypeHeader The HTTP Content-Type header value. * @return The parser and media type that matched the content type header, or null if no match was made. */ public ParserMatch getParserMatch(String contentTypeHeader) { ParserMatch pm = cache.get(contentTypeHeader); if (pm != null) return pm; MediaType ct = MediaType.of(contentTypeHeader); int match = ct.match(ulist(mediaTypes)); if (match >= 0) { pm = new ParserMatch(mediaTypes[match], mediaTypeParsers[match]); cache.putIfAbsent(contentTypeHeader, pm); } return cache.get(contentTypeHeader); } /** * Same as {@link #getParserMatch(String)} but matches using a {@link MediaType} instance. * * @param mediaType The HTTP Content-Type header value as a media type. * @return The parser and media type that matched the media type, or null if no match was made. */ public ParserMatch getParserMatch(MediaType mediaType) { return getParserMatch(mediaType.toString()); } /** * Same as {@link #getParserMatch(String)} but returns just the matched parser. * * @param contentTypeHeader The HTTP Content-Type header string. * @return The parser that matched the content type header, or null if no match was made. */ public Parser getParser(String contentTypeHeader) { ParserMatch pm = getParserMatch(contentTypeHeader); return pm == null ? null : pm.getParser(); } /** * Same as {@link #getParserMatch(MediaType)} but returns just the matched parser. * * @param mediaType The HTTP media type. * @return The parser that matched the media type, or null if no match was made. */ public Parser getParser(MediaType mediaType) { ParserMatch pm = getParserMatch(mediaType); return pm == null ? null : pm.getParser(); } /** * Returns the media types that all parsers in this group can handle * *

* Entries are ordered in the same order as the parsers in the group. * * @return An unmodifiable list of media types. */ public List getSupportedMediaTypes() { return ulist(mediaTypes); } /** * Returns the parsers in this group. * * @return An unmodifiable list of parsers in this group. */ public List getParsers() { return ulist(entries); } /** * Returns true if this group contains no parsers. * * @return true if this group contains no parsers. */ public boolean isEmpty() { return entries.length == 0; } }