org.apache.juneau.parser.ParserGroup 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 java.util.*;
import java.util.concurrent.*;
import org.apache.juneau.*;
import org.apache.juneau.http.*;
/**
* 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
* ParserGroupBuilder b = ParserGroup.create ();
*
* // Add some parsers to it
* b.append(JsonParser.class , XmlParser.class );
*
* // Change settings on parsers simultaneously
* b.set(BeanContext.BEAN_beansRequireSerializable , true )
* .pojoSwaps(CalendarSwap.ISO8601DT.class );
*
* ParserGroup g = b.build();
*
* // Find the appropriate parser by Content-Type
* ReaderParser p = (ReaderParser)g.getParser("text/json" );
*
* // Parse a bean from JSON
* String json = "{...}" ;
* AddressBook addressBook = p.parse(json, AddressBook.class );
*
*/
public final class ParserGroup extends BeanContext {
/**
* An unmodifiable empty parser group.
*/
public static final ParserGroup EMPTY = create().build();
// Maps Content-Type headers to matches.
private final ConcurrentHashMap cache = new ConcurrentHashMap<>();
private final MediaType[] mediaTypes; // List of media types
private final List mediaTypesList;
private final Parser[] mediaTypeParsers;
private final List parsers;
/**
* Instantiates a new clean-slate {@link ParserGroupBuilder} object.
*
*
* This is equivalent to simply calling new ParserGroupBuilder()
.
*
* @return A new {@link ParserGroupBuilder} object.
*/
public static ParserGroupBuilder create() {
return new ParserGroupBuilder();
}
/**
* Returns a builder that's a copy of the settings on this parser group.
*
* @return A new {@link ParserGroupBuilder} initialized to this group.
*/
@Override /* Context */
public ParserGroupBuilder builder() {
return new ParserGroupBuilder(this);
}
/**
* Constructor.
*
* @param ps
* The modifiable properties that were used to initialize the parsers.
* A snapshot of these will be made so that we can clone and modify this group.
* @param parsers
* The parsers defined in this group.
* The order is important because they will be tried in reverse order (e.g. newer first) in which they will be
* tried to match against media types.
*/
public ParserGroup(PropertyStore ps, Parser[] parsers) {
super(ps);
this.parsers = immutableList(parsers);
List lmt = new ArrayList<>();
List l = new ArrayList<>();
for (Parser p : parsers) {
for (MediaType m: p.getMediaTypes()) {
lmt.add(m);
l.add(p);
}
}
this.mediaTypes = lmt.toArray(new MediaType[lmt.size()]);
this.mediaTypesList = unmodifiableList(lmt);
this.mediaTypeParsers = l.toArray(new Parser[l.size()]);
}
/**
* 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;
ContentType ct = ContentType.forString(contentTypeHeader);
int match = ct.findMatch(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 mediaTypesList;
}
/**
* Returns the parsers in this group.
*
* @return An unmodifiable list of parsers in this group.
*/
public List getParsers() {
return parsers;
}
}