This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 4729255  org.apache.juneau.http improvements.
4729255 is described below

commit 47292551a79094f11f7949b47ae4eee53fb64950
Author: JamesBognar <[email protected]>
AuthorDate: Tue Jul 14 11:49:03 2020 -0400

    org.apache.juneau.http improvements.
---
 .../java/org/apache/juneau/http/MediaRange.java    | 144 +++++++++++
 .../java/org/apache/juneau/http/MediaRanges.java   | 242 ++++++++++++++++++
 .../java/org/apache/juneau/http/MediaType.java     |  65 +++--
 .../org/apache/juneau/http/MediaTypeRange.java     | 272 ---------------------
 .../java/org/apache/juneau/http/header/Accept.java | 115 ++-------
 .../org/apache/juneau/http/header/StringRange.java |   2 +-
 .../org/apache/juneau/serializer/Serializer.java   |   6 +-
 .../apache/juneau/serializer/SerializerGroup.java  |  10 +-
 .../org/apache/juneau/http/MediaType_Test.java     |  47 ++++
 .../juneau/http/header/AcceptExtensionsTest.java   |  76 +++---
 .../org/apache/juneau/http/header/AcceptTest.java  |   5 +-
 .../apache/juneau/http/header/MediaRangeTest.java  |  36 +--
 .../org/apache/juneau/rest/jaxrs/BaseProvider.java |   6 +-
 .../java/org/apache/juneau/rest/RestRequest.java   |  16 +-
 .../java/org/apache/juneau/rest/RestResponse.java  |   9 +-
 15 files changed, 575 insertions(+), 476 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaRange.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaRange.java
new file mode 100644
index 0000000..662aa09
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaRange.java
@@ -0,0 +1,144 @@
+// 
***************************************************************************************************************************
+// * 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.http;
+
+import static org.apache.juneau.internal.ObjectUtils.*;
+
+import java.util.*;
+
+import org.apache.http.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.collections.*;
+
+
+/**
+ * Describes a single type used in content negotiation between an HTTP client 
and server, as described in
+ * Section 14.1 and 14.7 of RFC2616 (the HTTP/1.1 specification).
+ * 
+ * <ul class='seealso'>
+ *     <li class='extlink'>{@doc RFC2616}
+ * </ul>
+ */
+@BeanIgnore
+public class MediaRange extends MediaType {
+
+       private final NameValuePair[] extensions;
+       private final Float qValue;
+       private final String value;
+
+       /**
+        * Constructor.
+        *
+        * @param value The raw media range string.
+        */
+       public MediaRange(String value) {
+               this(parse(value));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param e The parsed media range element.
+        */
+       public MediaRange(HeaderElement e) {
+               super(e);
+
+               Float qValue = 1f;
+
+               // The media type consists of everything up to the q parameter.
+               // The q parameter and stuff after is part of the range.
+               List<NameValuePair> extensions = AList.of();
+               boolean foundQ = false;
+               for (NameValuePair p : e.getParameters()) {
+                       if (p.getName().equals("q")) {
+                               qValue = Float.parseFloat(p.getValue());
+                               foundQ = true;
+                       } else if (foundQ) {
+                               
extensions.add(BasicNameValuePair.of(p.getName(), p.getValue()));
+                       }
+               }
+
+               this.qValue = qValue;
+               this.extensions = extensions.toArray(new 
NameValuePair[extensions.size()]);
+
+               StringBuffer sb = new StringBuffer().append(super.toString());
+
+               // '1' is equivalent to specifying no qValue. If there's no 
extensions, then we won't include a qValue.
+               if (qValue.floatValue() == 1.0) {
+                       if (this.extensions.length > 0) {
+                               sb.append(";q=").append(qValue);
+                               for (NameValuePair p : extensions)
+                                       
sb.append(';').append(p.getName()).append('=').append(p.getValue());
+                       }
+               } else {
+                       sb.append(";q=").append(qValue);
+                       for (NameValuePair p : extensions)
+                               
sb.append(';').append(p.getName()).append('=').append(p.getValue());
+               }
+               value = sb.toString();
+       }
+
+       /**
+        * Returns the <js>'q'</js> (quality) value for this type, as described 
in Section 3.9 of RFC2616.
+        *
+        * <p>
+        * The quality value is a float between <c>0.0</c> (unacceptable) and 
<c>1.0</c> (most acceptable).
+        *
+        * <p>
+        * If 'q' value doesn't make sense for the context (e.g. this range was 
extracted from a <js>"content-*"</js>
+        * header, as opposed to <js>"accept-*"</js> header, its value will 
always be <js>"1"</js>.
+        *
+        * @return The 'q' value for this type, never <jk>null</jk>.
+        */
+       public Float getQValue() {
+               return qValue;
+       }
+
+       /**
+        * Returns the optional set of custom extensions defined for this type.
+        *
+        * <p>
+        * Values are lowercase and never <jk>null</jk>.
+        *
+        * @return The optional list of extensions, never <jk>null</jk>.
+        */
+       public List<NameValuePair> getExtensions() {
+               return Collections.unmodifiableList(Arrays.asList(extensions));
+       }
+
+       /**
+        * Returns <jk>true</jk> if the specified object is also a 
<c>MediaType</c>, and has the same qValue, type,
+        * parameters, and extensions.
+        *
+        * @return <jk>true</jk> if object is equivalent.
+        */
+       @Override /* Object */
+       public boolean equals(Object o) {
+               return (o instanceof MediaRange) && eq(this, (MediaRange)o, 
(x,y)->eq(x.value, y.value));
+       }
+
+       /**
+        * Returns a hash based on this instance's <c>media-type</c>.
+        *
+        * @return A hash based on this instance's <c>media-type</c>.
+        */
+       @Override /* Object */
+       public int hashCode() {
+               return value.hashCode();
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return value;
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaRanges.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaRanges.java
new file mode 100644
index 0000000..e94f06e
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaRanges.java
@@ -0,0 +1,242 @@
+// 
***************************************************************************************************************************
+// * 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.http;
+
+import static org.apache.juneau.http.Constants.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.util.*;
+
+import org.apache.http.*;
+import org.apache.http.message.*;
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * A parsed <c>Accept</c> or similar header value.
+ *
+ * <p>
+ * The returned media ranges are sorted such that the most acceptable media is 
available at ordinal position
+ * <js>'0'</js>, and the least acceptable at position n-1.
+ *
+ * <p>
+ * The syntax expected to be found in the referenced <c>value</c> complies 
with the syntax described in
+ * RFC2616, Section 14.1, as described below:
+ * <p class='bcode w800'>
+ *     Accept         = "Accept" ":"
+ *                       #( media-range [ accept-params ] )
+ *
+ *     media-range    = ( "*\/*"
+ *                       | ( type "/" "*" )
+ *                       | ( type "/" subtype )
+ *                       ) *( ";" parameter )
+ *     accept-params  = ";" "q" "=" qvalue *( accept-extension )
+ *     accept-extension = ";" token [ "=" ( token | quoted-string ) ]
+ * </p>
+ */
+@BeanIgnore
+public class MediaRanges {
+
+       private static final MediaRanges DEFAULT = new MediaRanges("*/*");
+       private static final Cache<String,MediaRanges> CACHE = new 
Cache<>(NOCACHE, CACHE_MAX_SIZE);
+
+       private final MediaRange[] ranges;
+       private final String value;
+
+       /**
+        * Returns a parsed <c>Accept</c> header value.
+        *
+        * @param value The raw <c>Accept</c> header value.
+        * @return A parsed <c>Accept</c> header value.
+        */
+       public static MediaRanges of(String value) {
+               if (value == null || value.length() == 0)
+                       return DEFAULT;
+
+               MediaRanges mr = CACHE.get(value);
+               if (mr == null)
+                       mr = CACHE.put(value, new MediaRanges(value));
+               return mr;
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param value The <c>Accept</c> header value.
+        */
+       public MediaRanges(String value) {
+               this(parse(value));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param e The parsed <c>Accept</c> header value.
+        */
+       public MediaRanges(HeaderElement[] e) {
+
+               List<MediaRange> l = AList.of();
+               for (HeaderElement e2 : e)
+                       l.add(new MediaRange(e2));
+
+               l.sort(RANGE_COMPARATOR);
+               ranges = l.toArray(new MediaRange[l.size()]);
+
+               this.value = ranges.length == 1 ? ranges[0].toString() : 
StringUtils.join(l, ',');
+       }
+
+       /**
+        * Compares two MediaRanges for equality.
+        *
+        * <p>
+        * The values are first compared according to <c>qValue</c> values.
+        * Should those values be equal, the <c>type</c> is then 
lexicographically compared (case-insensitive) in
+        * ascending order, with the <js>"*"</js> type demoted last in that 
order.
+        * <c>MediaRanges</c> with the same type but different sub-types are 
compared - a more specific subtype is
+        * promoted over the 'wildcard' subtype.
+        * <c>MediaRanges</c> with the same types but with extensions are 
promoted over those same types with no
+        * extensions.
+        */
+       private static final Comparator<MediaRange> RANGE_COMPARATOR = new 
Comparator<MediaRange>() {
+               @Override
+               public int compare(MediaRange o1, MediaRange o2) {
+                       // Compare q-values.
+                       int qCompare = Float.compare(o2.getQValue(), 
o1.getQValue());
+                       if (qCompare != 0)
+                               return qCompare;
+
+                       // Compare media-types.
+                       // Note that '*' comes alphabetically before letters, 
so just do a reverse-alphabetical comparison.
+                       int i = o2.toString().compareTo(o1.toString());
+                       return i;
+               }
+       };
+
+       /**
+        * Given a list of media types, returns the best match for this 
<c>Accept</c> header.
+        *
+        * <p>
+        * Note that fuzzy matching is allowed on the media types where the 
<c>Accept</c> header may
+        * contain additional subtype parts.
+        * <br>For example, given identical q-values and an <c>Accept</c> value 
of <js>"text/json+activity"</js>,
+        * the media type <js>"text/json"</js> will match if 
<js>"text/json+activity"</js> or <js>"text/activity+json"</js>
+        * isn't found.
+        * <br>The purpose for this is to allow serializers to match when 
artifacts such as <c>id</c> properties are
+        * present in the header.
+        *
+        * <p>
+        * See {@doc https://www.w3.org/TR/activitypub/#retrieving-objects 
ActivityPub / Retrieving Objects}
+        *
+        * @param mediaTypes The media types to match against.
+        * @return The index into the array of the best match, or <c>-1</c> if 
no suitable matches could be found.
+        */
+       public int findMatch(List<? extends MediaType> mediaTypes) {
+               int matchQuant = 0, matchIndex = -1;
+               float q = 0f;
+
+               // Media ranges are ordered by 'q'.
+               // So we only need to search until we've found a match.
+               for (MediaRange mr : ranges) {
+                       float q2 = mr.getQValue();
+
+                       if (q2 < q || q2 == 0)
+                               break;
+
+                       for (int i = 0; i < mediaTypes.size(); i++) {
+                               MediaType mt = mediaTypes.get(i);
+                               int matchQuant2 = mr.match(mt, false);
+
+                               if (matchQuant2 > matchQuant) {
+                                       matchIndex = i;
+                                       matchQuant = matchQuant2;
+                                       q = q2;
+                               }
+                       }
+               }
+
+               return matchIndex;
+       }
+
+       /**
+        * Same as {@link #findMatch(List)} but matching against media type 
ranges.
+        *
+        * <p>
+        * Note that the q-types on both the <c>mediaTypeRanges</c> parameter 
and this header
+        * are taken into account when trying to find the best match.
+        * <br>When both this header and the matching range have q-values, the 
q-value for the match is the result of multiplying them.
+        * <br>(e.g. Accept=<js>"text/html;q=0.9"</js> and 
mediaTypeRange=<js>"text/html;q=0.9"</js> ==>, q-value=<c>0.81</c>).
+        *
+        * @param mediaRanges The media type ranges to match against.
+        * @return The index into the array of the best match, or <c>-1</c> if 
no suitable matches could be found.
+        */
+       public int findMatch(MediaRanges mediaRanges) {
+               return findMatch(mediaRanges.getRanges());
+       }
+
+       /**
+        * Returns the {@link MediaRange} at the specified index.
+        *
+        * @param index The index position of the media range.
+        * @return The {@link MediaRange} at the specified index or 
<jk>null</jk> if the index is out of range.
+        */
+       public MediaRange getRange(int index) {
+               if (index < 0 || index >= ranges.length)
+                       return null;
+               return ranges[index];
+       }
+
+       /**
+        * Convenience method for searching through all of the subtypes of all 
the media ranges in this header for the
+        * presence of a subtype fragment.
+        *
+        * <p>
+        * For example, given the header <js>"text/json+activity"</js>, calling
+        * <code>hasSubtypePart(<js>"activity"</js>)</code> returns 
<jk>true</jk>.
+        *
+        * @param part The media type subtype fragment.
+        * @return <jk>true</jk> if subtype fragment exists.
+        */
+       public boolean hasSubtypePart(String part) {
+
+               for (MediaRange mr : ranges)
+                       if (mr.getQValue() > 0 && 
mr.getSubTypes().indexOf(part) >= 0)
+                               return true;
+
+               return false;
+       }
+
+       /**
+        * Parses the specified header element part.
+        *
+        * @param value The header element part.
+        * @return Thew parsed header element part.  Never <jk>null</jk>.
+        */
+       protected static HeaderElement[] parse(String value) {
+               return 
BasicHeaderValueParser.parseElements(emptyIfNull(trim(value)), null);
+       }
+
+       /**
+        * Returns the media ranges that make up this object.
+        *
+        * @return The media ranges that make up this object.
+        */
+       public List<MediaRange> getRanges() {
+               return Collections.unmodifiableList(Arrays.asList(ranges));
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return value;
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaType.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaType.java
index b861a02..aafeef6 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaType.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaType.java
@@ -14,12 +14,14 @@ package org.apache.juneau.http;
 
 import static org.apache.juneau.http.Constants.*;
 import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.internal.ObjectUtils.*;
 
 import java.util.*;
 
 import org.apache.http.*;
 import org.apache.http.message.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.collections.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 
@@ -122,20 +124,24 @@ public class MediaType implements Comparable<MediaType>  {
         * @param mt The media type string.
         */
        public MediaType(String mt) {
-               mt = trim(mt);
-               value = mt;
-
-               HeaderElement[] elements = 
BasicHeaderValueParser.parseElements(mt, null);
-               HeaderElement element = (elements.length > 0 ? elements[0] : 
DEFAULT_ELEMENT);
-
-               mediaType = element.getName();
-               parameters = element.getParameters();
+               this(parse(mt));
+       }
 
-               // Convert the parameters to BasicNameValuePair.
-               for (int j = 0; j < parameters.length; j++) {
-                       NameValuePair p = parameters[j];
-                       parameters[j] = BasicNameValuePair.of(p.getName(), 
p.getValue());
+       /**
+        * Constructor.
+        *
+        * @param e The parsed media type string.
+        */
+       public MediaType(HeaderElement e) {
+               mediaType = e.getName();
+
+               List<NameValuePair> parameters = AList.of();
+               for (NameValuePair p : e.getParameters()) {
+                       if (p.getName().equals("q"))
+                               break;
+                       parameters.add(BasicNameValuePair.of(p.getName(), 
p.getValue()));
                }
+               this.parameters= parameters.toArray(new 
NameValuePair[parameters.size()]);
 
                String x = mediaType.replace(' ', '+');
                int i = x.indexOf('/');
@@ -146,6 +152,23 @@ public class MediaType implements Comparable<MediaType>  {
                subTypesSorted = Arrays.copyOf(subTypes, subTypes.length);
                Arrays.sort(this.subTypesSorted);
                hasSubtypeMeta = ArrayUtils.contains("*", this.subTypes);
+
+               StringBuilder sb = new StringBuilder();
+               sb.append(mediaType);
+               for (NameValuePair p : parameters)
+                       
sb.append(';').append(p.getName()).append('=').append(p.getValue());
+               this.value = sb.toString();
+       }
+
+       /**
+        * Parses the specified header element part.
+        *
+        * @param value The header element part.
+        * @return Thew parsed header element part.  Never <jk>null</jk>.
+        */
+       protected static HeaderElement parse(String value) {
+               HeaderElement[] elements = 
BasicHeaderValueParser.parseElements(emptyIfNull(trim(value)), null);
+               return (elements.length > 0 ? elements[0] : DEFAULT_ELEMENT);
        }
 
        /**
@@ -300,29 +323,27 @@ public class MediaType implements Comparable<MediaType>  {
         *
         * @return The map of additional parameters, or an empty map if there 
are no parameters.
         */
-       public NameValuePair[] getParameters() {
-               return parameters;
+       public List<NameValuePair> getParameters() {
+               return Collections.unmodifiableList(Arrays.asList(parameters));
        }
 
        @Override /* Object */
-       public final String toString() {
+       public String toString() {
                return value;
        }
 
        @Override /* Object */
-       public final int hashCode() {
-               return mediaType.hashCode();
+       public int hashCode() {
+               return value.hashCode();
        }
 
        @Override /* Object */
-       public final boolean equals(Object o) {
-               if (o instanceof MediaType)
-                       return ObjectUtils.eq(mediaType, 
((MediaType)o).mediaType);
-               return false;
+       public boolean equals(Object o) {
+               return (o instanceof MediaType) && eq(this, (MediaType)o, 
(x,y)->eq(x.value, y.value));
        }
 
        @Override
        public final int compareTo(MediaType o) {
-               return mediaType.compareTo(o.mediaType);
+               return toString().compareTo(o.toString());
        }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaTypeRange.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaTypeRange.java
deleted file mode 100644
index 508392d..0000000
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/MediaTypeRange.java
+++ /dev/null
@@ -1,272 +0,0 @@
-// 
***************************************************************************************************************************
-// * 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.http;
-
-import static org.apache.juneau.http.Constants.*;
-import static org.apache.juneau.internal.ObjectUtils.*;
-
-import java.util.*;
-import java.util.Map.*;
-
-import org.apache.juneau.annotation.*;
-import org.apache.juneau.collections.*;
-import org.apache.juneau.internal.*;
-
-/**
- * Describes a single type used in content negotiation between an HTTP client 
and server, as described in
- * Section 14.1 and 14.7 of RFC2616 (the HTTP/1.1 specification).
- *
- * <ul class='seealso'>
- *     <li class='extlink'>{@doc RFC2616}
- * </ul>
- */
-@BeanIgnore
-public class MediaTypeRange implements Comparable<MediaTypeRange>  {
-
-       private static final MediaTypeRange[] DEFAULT = new 
MediaTypeRange[]{new MediaTypeRange("*/*")};
-       private static final Cache<String,MediaTypeRange[]> CACHE = new 
Cache<>(NOCACHE, CACHE_MAX_SIZE);
-
-       private final MediaType mediaType;
-       private final Float qValue;
-       private final Map<String,Set<String>> extensions;
-
-       /**
-        * Parses an <c>Accept</c> header value into an array of media ranges.
-        *
-        * <p>
-        * The returned media ranges are sorted such that the most acceptable 
media is available at ordinal position
-        * <js>'0'</js>, and the least acceptable at position n-1.
-        *
-        * <p>
-        * The syntax expected to be found in the referenced <c>value</c> 
complies with the syntax described in
-        * RFC2616, Section 14.1, as described below:
-        * <p class='bcode w800'>
-        *      Accept         = "Accept" ":"
-        *                        #( media-range [ accept-params ] )
-        *
-        *      media-range    = ( "*\/*"
-        *                        | ( type "/" "*" )
-        *                        | ( type "/" subtype )
-        *                        ) *( ";" parameter )
-        *      accept-params  = ";" "q" "=" qvalue *( accept-extension )
-        *      accept-extension = ";" token [ "=" ( token | quoted-string ) ]
-        * </p>
-        *
-        * @param value
-        *      The value to parse.
-        *      If <jk>null</jk> or empty, returns a single 
<c>MediaTypeRange</c> is returned that represents all types.
-        * @return
-        *      The media ranges described by the string.
-        *      The ranges are sorted such that the most acceptable media is 
available at ordinal position <js>'0'</js>, and
-        *      the least acceptable at position n-1.
-        */
-       public static MediaTypeRange[] parse(String value) {
-
-               if (value == null || value.length() == 0)
-                       return DEFAULT;
-
-               MediaTypeRange[] mtr = CACHE.get(value);
-               if (mtr != null)
-                       return mtr;
-
-               if (value.indexOf(',') == -1) {
-                       mtr = new MediaTypeRange[]{new MediaTypeRange(value)};
-               } else {
-                       Set<MediaTypeRange> ranges = new TreeSet<>();
-                       for (String r : StringUtils.split(value)) {
-                               r = r.trim();
-                               if (r.isEmpty())
-                                       continue;
-                               ranges.add(new MediaTypeRange(r));
-                       }
-                       mtr = ranges.toArray(new MediaTypeRange[ranges.size()]);
-               }
-               return CACHE.put(value, mtr);
-       }
-
-       private MediaTypeRange(String token) {
-               Builder b = new Builder(token);
-               this.mediaType = b.mediaType;
-               this.qValue = b.qValue;
-               this.extensions = AMap.unmodifiable(b.extensions);
-       }
-
-       static final class Builder {
-               MediaType mediaType;
-               Float qValue = 1f;
-               Map<String,Set<String>> extensions;
-
-               Builder(String token) {
-
-                       token = token.trim();
-
-                       int i = token.indexOf(";q=");
-
-                       if (i == -1) {
-                               mediaType = MediaType.of(token);
-                               return;
-                       }
-
-                       mediaType = MediaType.of(token.substring(0, i));
-
-                       String[] tokens = token.substring(i+1).split(";");
-
-                       // Only the type of the range is specified
-                       if (tokens.length > 0) {
-                               boolean isInExtensions = false;
-                               for (int j = 0; j < tokens.length; j++) {
-                                       String[] parm = tokens[j].split("=");
-                                       if (parm.length == 2) {
-                                               String k = parm[0], v = parm[1];
-                                               if (isInExtensions) {
-                                                       if (extensions == null)
-                                                               extensions = 
new TreeMap<>();
-                                                       if (! 
extensions.containsKey(k))
-                                                               
extensions.put(k, new TreeSet<String>());
-                                                       
extensions.get(k).add(v);
-                                               } else if (k.equals("q")) {
-                                                       qValue = new Float(v);
-                                                       isInExtensions = true;
-                                               }
-                                       }
-                               }
-                       }
-               }
-       }
-
-       /**
-        * Returns the media type enclosed by this media range.
-        *
-        * <h5 class='section'>Examples:</h5>
-        * <ul>
-        *      <li><js>"text/html"</js>
-        *      <li><js>"text/*"</js>
-        *      <li><js>"*\/*"</js>
-        * </ul>
-        *
-        * @return The media type of this media range, lowercased, never 
<jk>null</jk>.
-        */
-       public MediaType getMediaType() {
-               return mediaType;
-       }
-
-       /**
-        * Returns the <js>'q'</js> (quality) value for this type, as described 
in Section 3.9 of RFC2616.
-        *
-        * <p>
-        * The quality value is a float between <c>0.0</c> (unacceptable) and 
<c>1.0</c> (most acceptable).
-        *
-        * <p>
-        * If 'q' value doesn't make sense for the context (e.g. this range was 
extracted from a <js>"content-*"</js>
-        * header, as opposed to <js>"accept-*"</js> header, its value will 
always be <js>"1"</js>.
-        *
-        * @return The 'q' value for this type, never <jk>null</jk>.
-        */
-       public Float getQValue() {
-               return qValue;
-       }
-
-       /**
-        * Returns the optional set of custom extensions defined for this type.
-        *
-        * <p>
-        * Values are lowercase and never <jk>null</jk>.
-        *
-        * @return The optional list of extensions, never <jk>null</jk>.
-        */
-       public Map<String,Set<String>> getExtensions() {
-               return extensions;
-       }
-
-       /**
-        * Provides a string representation of this media range, suitable for 
use as an <c>Accept</c> header value.
-        *
-        * <p>
-        * The literal text generated will be all lowercase.
-        *
-        * @return A media range suitable for use as an Accept header value, 
never <c>null</c>.
-        */
-       @Override /* Object */
-       public String toString() {
-               StringBuffer sb = new StringBuffer().append(mediaType);
-
-               // '1' is equivalent to specifying no qValue. If there's no 
extensions, then we won't include a qValue.
-               if (qValue.floatValue() == 1.0) {
-                       if (! extensions.isEmpty()) {
-                               sb.append(";q=").append(qValue);
-                               for (Entry<String,Set<String>> e : 
extensions.entrySet()) {
-                                       String k = e.getKey();
-                                       for (String v : e.getValue())
-                                               
sb.append(';').append(k).append('=').append(v);
-                               }
-                       }
-               } else {
-                       sb.append(";q=").append(qValue);
-                       for (Entry<String,Set<String>> e : 
extensions.entrySet()) {
-                               String k = e.getKey();
-                               for (String v : e.getValue())
-                                       
sb.append(';').append(k).append('=').append(v);
-                       }
-               }
-               return sb.toString();
-       }
-
-       /**
-        * Returns <jk>true</jk> if the specified object is also a 
<c>MediaType</c>, and has the same qValue, type,
-        * parameters, and extensions.
-        *
-        * @return <jk>true</jk> if object is equivalent.
-        */
-       @Override /* Object */
-       public boolean equals(Object o) {
-               return (o instanceof MediaTypeRange) && eq(this, 
(MediaTypeRange)o, (x,y)->eq(x.qValue, y.qValue) && eq(x.mediaType, 
y.mediaType) && eq(x.extensions, y.extensions));
-       }
-
-       /**
-        * Returns a hash based on this instance's <c>media-type</c>.
-        *
-        * @return A hash based on this instance's <c>media-type</c>.
-        */
-       @Override /* Object */
-       public int hashCode() {
-               return mediaType.hashCode();
-       }
-
-       /**
-        * Compares two MediaRanges for equality.
-        *
-        * <p>
-        * The values are first compared according to <c>qValue</c> values.
-        * Should those values be equal, the <c>type</c> is then 
lexicographically compared (case-insensitive) in
-        * ascending order, with the <js>"*"</js> type demoted last in that 
order.
-        * <c>MediaRanges</c> with the same type but different sub-types are 
compared - a more specific subtype is
-        * promoted over the 'wildcard' subtype.
-        * <c>MediaRanges</c> with the same types but with extensions are 
promoted over those same types with no
-        * extensions.
-        *
-        * @param o The range to compare to.  Never <jk>null</jk>.
-        */
-       @Override /* Comparable */
-       public int compareTo(MediaTypeRange o) {
-
-               // Compare q-values.
-               int qCompare = Float.compare(o.qValue, qValue);
-               if (qCompare != 0)
-                       return qCompare;
-
-               // Compare media-types.
-               // Note that '*' comes alphabetically before letters, so just 
do a reverse-alphabetical comparison.
-               int i = o.mediaType.toString().compareTo(mediaType.toString());
-               return i;
-       }
-}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/Accept.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/Accept.java
index 990497e..2bd9bfe 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/Accept.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/Accept.java
@@ -17,7 +17,6 @@ import static org.apache.juneau.http.Constants.*;
 import java.util.*;
 import java.util.function.*;
 
-import org.apache.juneau.collections.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.internal.*;
@@ -206,7 +205,7 @@ public class Accept extends BasicParameterizedArrayHeader {
                return new Accept(value);
        }
 
-       private List<MediaTypeRange> parsed;
+       private MediaRanges ranges;
 
        /**
         * Constructor
@@ -223,7 +222,7 @@ public class Accept extends BasicParameterizedArrayHeader {
        public Accept(Object value) {
                super("Accept", value);
                if (! isSupplier(value))
-                       parsed = getParsedValue();
+                       ranges = getRanges();
        }
 
        /**
@@ -244,8 +243,18 @@ public class Accept extends BasicParameterizedArrayHeader {
         *
         * @return An unmodifiable list of media ranges.
         */
-       public List<MediaTypeRange> asRanges() {
-               return getParsedValue();
+       public MediaRanges asRanges() {
+               return getRanges();
+       }
+
+       /**
+        * Returns the {@link MediaRange} at the specified index.
+        *
+        * @param index The index position of the media range.
+        * @return The {@link MediaRange} at the specified index or 
<jk>null</jk> if the index is out of range.
+        */
+       public MediaRange getRange(int index) {
+               return getRanges().getRange(index);
        }
 
        /**
@@ -266,100 +275,16 @@ public class Accept extends 
BasicParameterizedArrayHeader {
         * @param mediaTypes The media types to match against.
         * @return The index into the array of the best match, or <c>-1</c> if 
no suitable matches could be found.
         */
-       public int findMatch(MediaType[] mediaTypes) {
-               int matchQuant = 0, matchIndex = -1;
-               float q = 0f;
-
-               // Media ranges are ordered by 'q'.
-               // So we only need to search until we've found a match.
-               for (MediaTypeRange mr : getParsedValue()) {
-                       float q2 = mr.getQValue();
-
-                       if (q2 < q || q2 == 0)
-                               break;
-
-                       for (int i = 0; i < mediaTypes.length; i++) {
-                               MediaType mt = mediaTypes[i];
-                               int matchQuant2 = mr.getMediaType().match(mt, 
false);
-
-                               if (matchQuant2 > matchQuant) {
-                                       matchIndex = i;
-                                       matchQuant = matchQuant2;
-                                       q = q2;
-                               }
-                       }
-               }
-
-               return matchIndex;
-       }
-
-       /**
-        * Same as {@link #findMatch(MediaType[])} but matching against media 
type ranges.
-        *
-        * <p>
-        * Note that the q-types on both the <c>mediaTypeRanges</c> parameter 
and this header
-        * are taken into account when trying to find the best match.
-        * <br>When both this header and the matching range have q-values, the 
q-value for the match is the result of multiplying them.
-        * <br>(e.g. Accept=<js>"text/html;q=0.9"</js> and 
mediaTypeRange=<js>"text/html;q=0.9"</js> ==>, q-value=<c>0.81</c>).
-        *
-        * @param mediaTypeRanges The media type ranges to match against.
-        * @return The index into the array of the best match, or <c>-1</c> if 
no suitable matches could be found.
-        */
-       public int findMatch(MediaTypeRange[] mediaTypeRanges) {
-               float matchQuant = 0;
-               int matchIndex = -1;
-               float q = 0f;
-
-               // Media ranges are ordered by 'q'.
-               // So we only need to search until we've found a match.
-               for (MediaTypeRange mr : getParsedValue()) {
-                       float q2 = mr.getQValue();
-
-                       if (q2 < q || q2 == 0)
-                               break;
-
-                       for (int i = 0; i < mediaTypeRanges.length; i++) {
-                               MediaTypeRange mt = mediaTypeRanges[i];
-                               float matchQuant2 = 
mr.getMediaType().match(mt.getMediaType(), false) * mt.getQValue();
-
-                               if (matchQuant2 > matchQuant) {
-                                       matchIndex = i;
-                                       matchQuant = matchQuant2;
-                                       q = q2;
-                               }
-                       }
-               }
-
-               return matchIndex;
-       }
-
-
-       /**
-        * Convenience method for searching through all of the subtypes of all 
the media ranges in this header for the
-        * presence of a subtype fragment.
-        *
-        * <p>
-        * For example, given the header <js>"text/json+activity"</js>, calling
-        * <code>hasSubtypePart(<js>"activity"</js>)</code> returns 
<jk>true</jk>.
-        *
-        * @param part The media type subtype fragment.
-        * @return <jk>true</jk> if subtype fragment exists.
-        */
-       public boolean hasSubtypePart(String part) {
-
-               for (MediaTypeRange mr : this.getParsedValue())
-                       if (mr.getQValue() > 0 && 
mr.getMediaType().getSubTypes().indexOf(part) >= 0)
-                               return true;
-
-               return false;
+       public int findMatch(List<? extends MediaType> mediaTypes) {
+               return getRanges().findMatch(mediaTypes);
        }
 
-       private List<MediaTypeRange> getParsedValue() {
-               if (parsed != null)
-                       return parsed;
+       private MediaRanges getRanges() {
+               if (ranges != null)
+                       return ranges;
                Object o = getRawValue();
                if (o == null)
                        return null;
-               return 
AList.of(MediaTypeRange.parse(o.toString())).unmodifiable();
+               return MediaRanges.of(o.toString());
        }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/StringRange.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/StringRange.java
index 802f566..49bbcb7 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/StringRange.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/StringRange.java
@@ -27,7 +27,7 @@ import org.apache.juneau.internal.*;
  * comparison and extension parameters.
  *
  * <p>
- * Similar in concept to {@link MediaTypeRange} except instead of media types 
(e.g. <js>"text/json"</js>),
+ * Similar in concept to {@link MediaRanges} except instead of media types 
(e.g. <js>"text/json"</js>),
  * it's a simple type (e.g. <js>"iso-8601"</js>).
  *
  * <p>
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/Serializer.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/Serializer.java
index 539bed1..2dc894c 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/Serializer.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/Serializer.java
@@ -847,7 +847,7 @@ public abstract class Serializer extends 
BeanTraverseContext {
        private final UriRelativity uriRelativity;
        private final Class<? extends SerializerListener> listener;
 
-       private final MediaTypeRange[] accept;
+       private final MediaRanges accept;
        private final MediaType[] accepts;
        private final MediaType produces;
 
@@ -894,7 +894,7 @@ public abstract class Serializer extends 
BeanTraverseContext {
                listener = getClassProperty(SERIALIZER_listener, 
SerializerListener.class, null);
 
                this.produces = MediaType.of(produces);
-               this.accept = accept == null ? MediaTypeRange.parse(produces) : 
MediaTypeRange.parse(accept);
+               this.accept = accept == null ? MediaRanges.of(produces) : 
MediaRanges.of(accept);
                this.accepts = accept == null ? new MediaType[] {this.produces} 
: MediaType.ofAll(StringUtils.split(accept, ','));
        }
 
@@ -1015,7 +1015,7 @@ public abstract class Serializer extends 
BeanTraverseContext {
         *
         * @return The list of media types.  Never <jk>null</jk>.
         */
-       public final MediaTypeRange[] getMediaTypeRanges() {
+       public final MediaRanges getMediaTypeRanges() {
                return accept;
        }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
index 1aefe8d..f5f7b68 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerGroup.java
@@ -75,7 +75,7 @@ public final class SerializerGroup extends 
BeanTraverseContext {
        // Maps Accept headers to matching serializers.
        private final ConcurrentHashMap<String,SerializerMatch> cache = new 
ConcurrentHashMap<>();
 
-       private final MediaTypeRange[] mediaTypeRanges;
+       private final List<MediaRange> mediaTypeRanges;
        private final Serializer[] mediaTypeRangeSerializers;
 
        private final List<MediaType> mediaTypesList;
@@ -118,11 +118,11 @@ public final class SerializerGroup extends 
BeanTraverseContext {
                super(ps);
                this.serializers = AList.unmodifiable(serializers);
 
-               AList<MediaTypeRange> lmtr = AList.of();
+               AList<MediaRange> lmtr = AList.of();
                ASet<MediaType> lmt = ASet.of();
                AList<Serializer> l = AList.of();
                for (Serializer s : serializers) {
-                       for (MediaTypeRange m: s.getMediaTypeRanges()) {
+                       for (MediaRange m: s.getMediaTypeRanges().getRanges()) {
                                lmtr.add(m);
                                l.add(s);
                        }
@@ -130,7 +130,7 @@ public final class SerializerGroup extends 
BeanTraverseContext {
                                lmt.add(mt);
                }
 
-               this.mediaTypeRanges = lmtr.asArrayOf(MediaTypeRange.class);
+               this.mediaTypeRanges = lmtr.unmodifiable();
                this.mediaTypesList = AList.of(lmt).unmodifiable();
                this.mediaTypeRangeSerializers = l.asArrayOf(Serializer.class);
        }
@@ -168,7 +168,7 @@ public final class SerializerGroup extends 
BeanTraverseContext {
                Accept a = Accept.of(acceptHeader);
                int match = a.findMatch(mediaTypeRanges);
                if (match >= 0) {
-                       sm = new 
SerializerMatch(mediaTypeRanges[match].getMediaType(), 
mediaTypeRangeSerializers[match]);
+                       sm = new SerializerMatch(mediaTypeRanges.get(match), 
mediaTypeRangeSerializers[match]);
                        cache.putIfAbsent(acceptHeader, sm);
                }
 
diff --git 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/MediaType_Test.java
 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/MediaType_Test.java
new file mode 100644
index 0000000..0f8c75c
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/MediaType_Test.java
@@ -0,0 +1,47 @@
+// 
***************************************************************************************************************************
+// * 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.http;
+
+import static org.apache.juneau.assertions.Assertions.*;
+import static org.junit.Assert.*;
+import static org.junit.runners.MethodSorters.*;
+
+import java.util.*;
+
+import org.junit.*;
+
+@FixMethodOrder(NAME_ASCENDING)
+public class MediaType_Test {
+
+       @Test
+       public void a01_basic() {
+               assertEquals(new MediaType("text/foo"), new 
MediaType("text/foo"));
+               assertNotEquals(new MediaType("text/foo"), "text/foo");
+
+               Set<MediaType> x = new TreeSet<>();
+               x.add(MediaType.of("text/foo"));
+               x.add(MediaType.of("text/bar"));
+               assertObject(x).json().is("['text/bar','text/foo']");
+
+               MediaType x2 = new MediaType((String)null);  // Interpreted as 
"/*"
+               assertString(x2.getType()).isEmpty();
+               assertString(x2.getSubType()).is("*");
+               assertObject(x2.getSubTypes()).json().is("['*']");
+               assertTrue(x2.isMeta());
+
+               MediaType x3 = MediaType.of("text/foo+bar");
+               assertTrue(x3.hasSubType("bar"));
+               assertFalse(x3.hasSubType("baz"));
+               assertFalse(x3.hasSubType(null));
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptExtensionsTest.java
 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptExtensionsTest.java
index 68f5af6..98e26c4 100644
--- 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptExtensionsTest.java
+++ 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptExtensionsTest.java
@@ -31,79 +31,70 @@ public class AcceptExtensionsTest {
        @Test
        public void testExtensions() throws Exception {
                Accept accept;
-               MediaTypeRange mr;
+               MediaRange mr;
 
                accept = Accept.of("text/json");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("text/json");
-               assertString(mr.getMediaType()).is("text/json");
-               assertObject(mr.getMediaType().getParameters()).json().is("[]");
+               assertObject(mr.getParameters()).json().is("[]");
                assertString(mr.getQValue()).is("1.0");
-               assertObject(mr.getExtensions()).json().is("{}");
+               assertObject(mr.getExtensions()).json().is("[]");
 
                accept = Accept.of("foo,bar");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("foo");
-               assertString(mr.getMediaType()).is("foo");
-               assertObject(mr.getMediaType().getParameters()).json().is("[]");
+               assertObject(mr.getParameters()).json().is("[]");
                assertString(mr.getQValue()).is("1.0");
-               assertObject(mr.getExtensions()).json().is("{}");
+               assertObject(mr.getExtensions()).json().is("[]");
 
                accept = Accept.of(" foo , bar ");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("foo");
-               assertString(mr.getMediaType()).is("foo");
-               assertObject(mr.getMediaType().getParameters()).json().is("[]");
+               assertObject(mr.getParameters()).json().is("[]");
                assertString(mr.getQValue()).is("1.0");
-               assertObject(mr.getExtensions()).json().is("{}");
+               assertObject(mr.getExtensions()).json().is("[]");
 
                accept = Accept.of("text/json;a=1;q=0.9;b=2");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("text/json;a=1;q=0.9;b=2");
-               assertString(mr.getMediaType()).is("text/json;a=1");
-               
assertObject(mr.getMediaType().getParameters()).json().is("['a=1']");
+               assertObject(mr.getParameters()).json().is("['a=1']");
                assertString(mr.getQValue()).is("0.9");
-               assertObject(mr.getExtensions()).json().is("{b:['2']}");
+               assertObject(mr.getExtensions()).json().is("['b=2']");
 
                accept = Accept.of("text/json;a=1;a=2;q=0.9;b=3;b=4");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("text/json;a=1;a=2;q=0.9;b=3;b=4");
-               assertString(mr.getMediaType()).is("text/json;a=1;a=2");
-               
assertObject(mr.getMediaType().getParameters()).json().is("['a=1','a=2']");
+               assertObject(mr.getParameters()).json().is("['a=1','a=2']");
                assertString(mr.getQValue()).is("0.9");
-               assertObject(mr.getExtensions()).json().is("{b:['3','4']}");
+               assertObject(mr.getExtensions()).json().is("['b=3','b=4']");
 
                accept = Accept.of("text/json;a=1");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("text/json;a=1");
-               assertString(mr.getMediaType()).is("text/json;a=1");
-               
assertObject(mr.getMediaType().getParameters()).json().is("['a=1']");
+               assertObject(mr.getParameters()).json().is("['a=1']");
                assertString(mr.getQValue()).is("1.0");
-               assertObject(mr.getExtensions()).json().is("{}");
+               assertObject(mr.getExtensions()).json().is("[]");
 
                accept = Accept.of("text/json;a=1;");
-               mr = accept.asRanges().get(0);
-               assertString(mr).is("text/json;a=1;");
-               assertString(mr.getMediaType()).is("text/json;a=1;");
-               
assertObject(mr.getMediaType().getParameters()).json().is("['a=1']");
+               mr = accept.getRange(0);
+               assertString(mr).is("text/json;a=1");
+               assertObject(mr.getParameters()).json().is("['a=1']");
                assertString(mr.getQValue()).is("1.0");
-               assertObject(mr.getExtensions()).json().is("{}");
+               assertObject(mr.getExtensions()).json().is("[]");
 
                accept = Accept.of("text/json;q=0.9");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("text/json;q=0.9");
-               assertString(mr.getMediaType()).is("text/json");
-               assertObject(mr.getMediaType().getParameters()).json().is("[]");
+               assertObject(mr.getParameters()).json().is("[]");
                assertString(mr.getQValue()).is("0.9");
-               assertObject(mr.getExtensions()).json().is("{}");
+               assertObject(mr.getExtensions()).json().is("[]");
 
                accept = Accept.of("text/json;q=0.9;");
-               mr = accept.asRanges().get(0);
+               mr = accept.getRange(0);
                assertString(mr).is("text/json;q=0.9");
-               assertString(mr.getMediaType()).is("text/json");
-               assertObject(mr.getMediaType().getParameters()).json().is("[]");
+               assertObject(mr.getParameters()).json().is("[]");
                assertString(mr.getQValue()).is("0.9");
-               assertObject(mr.getExtensions()).json().is("{}");
+               assertObject(mr.getExtensions()).json().is("[]");
        }
 
        
//-----------------------------------------------------------------------------------------------------------------
@@ -112,9 +103,10 @@ public class AcceptExtensionsTest {
        @Test
        public void testHasSubtypePart() {
                Accept accept = Accept.of("text/json+x,text/foo+y;q=0.0");
-               assertTrue(accept.hasSubtypePart("json"));
-               assertTrue(accept.hasSubtypePart("x"));
-               assertFalse(accept.hasSubtypePart("foo"));
-               assertFalse(accept.hasSubtypePart("y"));
+               MediaRanges mr = accept.asRanges();
+               assertTrue(mr.hasSubtypePart("json"));
+               assertTrue(mr.hasSubtypePart("x"));
+               assertFalse(mr.hasSubtypePart("foo"));
+               assertFalse(mr.hasSubtypePart("y"));
        }
 }
diff --git 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptTest.java
 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptTest.java
index cad873b..fa20da2 100644
--- 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptTest.java
+++ 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/AcceptTest.java
@@ -17,6 +17,7 @@ import static org.junit.runners.MethodSorters.*;
 
 import java.util.*;
 
+import org.apache.juneau.collections.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.json.*;
 import org.junit.*;
@@ -165,7 +166,7 @@ public class AcceptTest {
        public void test() throws Exception {
                Accept accept = Accept.of(this.accept);
                MediaType[] mt = JsonParser.DEFAULT.parse(mediaTypes, 
MediaType[].class);
-               int r = accept.findMatch(mt);
+               int r = accept.findMatch(AList.ofa(mt));
                assertInteger(r).msg("{0} failed", label).is(expected);
        }
 
@@ -174,7 +175,7 @@ public class AcceptTest {
                Accept accept = Accept.of(this.accept);
                MediaType[] mt = JsonParser.DEFAULT.parse(mediaTypes, 
MediaType[].class);
                Collections.reverse(Arrays.asList(mt));
-               int r = accept.findMatch(mt);
+               int r = accept.findMatch(AList.ofa(mt));
                int expected2 = expectedReverse == -1 ? -1 : 
mt.length-expectedReverse-1;
                assertInteger(r).msg("{0} failed", label).is(expected2);
        }
diff --git 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/MediaRangeTest.java
 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/MediaRangeTest.java
index cd8b946..654a8c7 100644
--- 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/MediaRangeTest.java
+++ 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/http/header/MediaRangeTest.java
@@ -32,23 +32,23 @@ public class MediaRangeTest {
        @Parameterized.Parameters
        public static Collection<Object[]> getParameters() {
                return Arrays.asList(new Object[][] {
-                       { "0", "text/json", "['text/json']" },
-                       { "1", "text/json,text/*", "['text/json','text/*']" },
-                       { "2", "text/*,text/json", "['text/json','text/*']" },
-                       { "3", "text/*,text/*", "['text/*']" },
-                       { "4", "*/text,text/*", "['text/*','*/text']" },
-                       { "5", "text/*,*/text", "['text/*','*/text']" },
-                       { "6", "a;q=0.9,b;q=0.1", "['a;q=0.9','b;q=0.1']" },
-                       { "7", "b;q=0.9,a;q=0.1", "['b;q=0.9','a;q=0.1']" },
-                       { "8", "a,b;q=0.9,c;q=0.1,d;q=0", 
"['a','b;q=0.9','c;q=0.1','d;q=0.0']" },
-                       { "9", "d;q=0,c;q=0.1,b;q=0.9,a", 
"['a','b;q=0.9','c;q=0.1','d;q=0.0']" },
-                       { "10", "a;q=1,b;q=0.9,c;q=0.1,d;q=0", 
"['a','b;q=0.9','c;q=0.1','d;q=0.0']" },
-                       { "11", "d;q=0,c;q=0.1,b;q=0.9,a;q=1", 
"['a','b;q=0.9','c;q=0.1','d;q=0.0']" },
-                       { "12", "a;q=0,b;q=0.1,c;q=0.9,d;q=1", 
"['d','c;q=0.9','b;q=0.1','a;q=0.0']" },
-                       { "13", "*", "['*']" },
-                       { "14", "", "['*/*']" },
-                       { "15", null, "['*/*']" },
-                       { "16", "foo/bar/baz", "['foo/bar/baz']" },
+                       { "0", "text/json", "'text/json'" },
+                       { "1", "text/json,text/*", "'text/json,text/*'" },
+                       { "2", "text/*,text/json", "'text/json,text/*'" },
+                       { "3", "text/*,text/*", "'text/*,text/*'" },
+                       { "4", "*/text,text/*", "'text/*,*/text'" },
+                       { "5", "text/*,*/text", "'text/*,*/text'" },
+                       { "6", "a;q=0.9,b;q=0.1", "'a;q=0.9,b;q=0.1'" },
+                       { "7", "b;q=0.9,a;q=0.1", "'b;q=0.9,a;q=0.1'" },
+                       { "8", "a,b;q=0.9,c;q=0.1,d;q=0", 
"'a,b;q=0.9,c;q=0.1,d;q=0.0'" },
+                       { "9", "d;q=0,c;q=0.1,b;q=0.9,a", 
"'a,b;q=0.9,c;q=0.1,d;q=0.0'" },
+                       { "10", "a;q=1,b;q=0.9,c;q=0.1,d;q=0", 
"'a,b;q=0.9,c;q=0.1,d;q=0.0'" },
+                       { "11", "d;q=0,c;q=0.1,b;q=0.9,a;q=1", 
"'a,b;q=0.9,c;q=0.1,d;q=0.0'" },
+                       { "12", "a;q=0,b;q=0.1,c;q=0.9,d;q=1", 
"'d,c;q=0.9,b;q=0.1,a;q=0.0'" },
+                       { "13", "*", "'*'" },
+                       { "14", "", "'*/*'" },
+                       { "15", null, "'*/*'" },
+                       { "16", "foo/bar/baz", "'foo/bar/baz'" },
                });
        }
 
@@ -62,7 +62,7 @@ public class MediaRangeTest {
 
        @Test
        public void test() {
-               MediaTypeRange[] r = MediaTypeRange.parse(mediaRange);
+               MediaRanges r = MediaRanges.of(mediaRange);
                assertEquals(label + " failed", expected, 
SimpleJsonSerializer.DEFAULT.toString(r));
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server-jaxrs/src/main/java/org/apache/juneau/rest/jaxrs/BaseProvider.java
 
b/juneau-rest/juneau-rest-server-jaxrs/src/main/java/org/apache/juneau/rest/jaxrs/BaseProvider.java
index 5cd1e34..229fbb4 100644
--- 
a/juneau-rest/juneau-rest-server-jaxrs/src/main/java/org/apache/juneau/rest/jaxrs/BaseProvider.java
+++ 
b/juneau-rest/juneau-rest-server-jaxrs/src/main/java/org/apache/juneau/rest/jaxrs/BaseProvider.java
@@ -176,9 +176,9 @@ public class BaseProvider implements 
MessageBodyReader<Object>, MessageBodyWrite
                if (headers.containsKey("Accept-Language") && 
headers.get("Accept-Language") != null) {
                        String h = 
String.valueOf(headers.get("Accept-Language"));
                        if (h != null) {
-                               MediaTypeRange[] mr = MediaTypeRange.parse(h);
-                               if (mr.length > 0)
-                                       return 
toLocale(mr[0].getMediaType().getType());
+                               MediaRanges mr = MediaRanges.of(h);
+                               if (! mr.getRanges().isEmpty())
+                                       return 
toLocale(mr.getRange(0).getType());
                        }
                }
                return null;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 7c43a4b..33b9039 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -454,9 +454,9 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
        public Locale getLocale() {
                String h = headers.getString("Accept-Language");
                if (h != null) {
-                       MediaTypeRange[] mr = MediaTypeRange.parse(h);
-                       if (mr.length > 0)
-                               return toLocale(mr[0].getMediaType().getType());
+                       MediaRanges mr = MediaRanges.of(h);
+                       if (! mr.getRanges().isEmpty())
+                               return toLocale(mr.getRange(0).getType());
                }
                return super.getLocale();
        }
@@ -465,11 +465,11 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
        public Enumeration<Locale> getLocales() {
                String h = headers.getString("Accept-Language");
                if (h != null) {
-                       MediaTypeRange[] mr = MediaTypeRange.parse(h);
-                       if (mr.length > 0) {
-                               List<Locale> l = new ArrayList<>(mr.length);
-                               for (MediaTypeRange r : mr)
-                                       
l.add(toLocale(r.getMediaType().getType()));
+                       MediaRanges mr = MediaRanges.of(h);
+                       if (! mr.getRanges().isEmpty()) {
+                               List<Locale> l = new 
ArrayList<>(mr.getRanges().size());
+                               for (MediaRange r : mr.getRanges())
+                                       l.add(toLocale(r.getType()));
                                return enumeration(l);
                        }
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
index 7a5dbfe..a3b23db 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -115,13 +115,12 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
                String charset = null;
                if (h == null)
                        charset = rjm.defaultCharset;
-               else for (MediaTypeRange r : MediaTypeRange.parse(h)) {
+               else for (MediaRange r : MediaRanges.of(h).getRanges()) {
                        if (r.getQValue() > 0) {
-                               MediaType mt = r.getMediaType();
-                               if (mt.getType().equals("*"))
+                               if (r.getType().equals("*"))
                                        charset = rjm.defaultCharset;
-                               else if (Charset.isSupported(mt.getType()))
-                                       charset = mt.getType();
+                               else if (Charset.isSupported(r.getType()))
+                                       charset = r.getType();
                                if (charset != null)
                                        break;
                        }

Reply via email to