http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java 
b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java
index ce948c6..194a39f 100644
--- a/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java
+++ b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroup.java
@@ -15,7 +15,7 @@ package org.apache.juneau.encoders;
 import java.util.*;
 import java.util.concurrent.*;
 
-import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 
 /**
  * Represents the group of {@link Encoder encoders} keyed by codings.
@@ -53,9 +53,12 @@ import org.apache.juneau.*;
 public final class EncoderGroup {
 
        // Maps Accept-Encoding headers to matching encoders.
-       private final Map<String,EncoderMatch> cache = new 
ConcurrentHashMap<String,EncoderMatch>();
+       private final ConcurrentHashMap<String,EncoderMatch> cache = new 
ConcurrentHashMap<String,EncoderMatch>();
 
-       final Encoder[] encoders;
+       private final String[] encodings;
+       private final List<String> encodingsList;
+       private final Encoder[] encodingsEncoders;
+       private final List<Encoder> encoders;
 
        /**
         * Constructor
@@ -63,9 +66,21 @@ public final class EncoderGroup {
         * @param encoders The encoders to add to this group.
         */
        public EncoderGroup(Encoder[] encoders) {
-               this.encoders = Arrays.copyOf(encoders, encoders.length);
-       }
+               this.encoders = Collections.unmodifiableList(new 
ArrayList<Encoder>(Arrays.asList(encoders)));
+
+               List<String> lc = new ArrayList<String>();
+               List<Encoder> l = new ArrayList<Encoder>();
+               for (Encoder e : encoders) {
+                       for (String c: e.getCodings()) {
+                               lc.add(c);
+                               l.add(e);
+                       }
+               }
 
+               this.encodings = lc.toArray(new String[lc.size()]);
+               this.encodingsList = Collections.unmodifiableList(lc);
+               this.encodingsEncoders = l.toArray(new Encoder[l.size()]);
+       }
 
        /**
         * Returns the coding string for the matching encoder that can handle 
the specified <code>Accept-Encoding</code>
@@ -79,62 +94,47 @@ public final class EncoderGroup {
         * @return The coding value (e.g. <js>"gzip"</js>).
         */
        public EncoderMatch getEncoderMatch(String acceptEncoding) {
-               if (encoders.length == 0)
-                       return null;
-
                EncoderMatch em = cache.get(acceptEncoding);
                if (em != null)
                        return em;
 
-               MediaRange[] ae = MediaRange.parse(acceptEncoding);
-
-               if (ae.length == 0)
-                       ae = MediaRange.parse("*/*");
-
-               Map<Float,EncoderMatch> m = null;
+               AcceptEncoding ae = AcceptEncoding.forString(acceptEncoding);
+               int match = ae.findMatch(encodings);
 
-               for (MediaRange a : ae) {
-                       for (Encoder e : encoders) {
-                               for (String c : e.getCodings()) {
-                                       MediaType mt = MediaType.forString(c);
-                                       float q = a.matches(mt);
-                                       if (q == 1) {
-                                               em = new EncoderMatch(mt, e);
-                                               cache.put(acceptEncoding, em);
-                                               return em;
-                                       } else if (q > 0) {
-                                               if (m == null)
-                                                       m = new 
TreeMap<Float,EncoderMatch>(Collections.reverseOrder());
-                                               m.put(q, new EncoderMatch(mt, 
e));
-                                       }
-                               }
-                       }
+               if (match >= 0) {
+                       em = new EncoderMatch(encodings[match], 
encodingsEncoders[match]);
+                       cache.putIfAbsent(acceptEncoding, em);
                }
-               return (m == null ? null : m.values().iterator().next());
+
+               return cache.get(acceptEncoding);
        }
 
        /**
         * Returns the encoder registered with the specified coding (e.g. 
<js>"gzip"</js>).
         *
-        * @param coding The coding string.
+        * @param encoding The coding string.
         * @return The encoder, or <jk>null</jk> if encoder isn't registered 
with that coding.
         */
-       public Encoder getEncoder(String coding) {
-               EncoderMatch em = getEncoderMatch(coding);
+       public Encoder getEncoder(String encoding) {
+               EncoderMatch em = getEncoderMatch(encoding);
                return (em == null ? null : em.getEncoder());
        }
 
        /**
         * Returns the set of codings supported by all encoders in this group.
         *
-        * @return The set of codings supported by all encoders in this group.  
Never <jk>null</jk>.
+        * @return An unmodifiable list of codings supported by all encoders in 
this group.  Never <jk>null</jk>.
         */
        public List<String> getSupportedEncodings() {
-               List<String> l = new ArrayList<String>();
-               for (Encoder e : encoders)
-                       for (String enc : e.getCodings())
-                               if (! l.contains(enc))
-                                       l.add(enc);
-               return l;
+               return encodingsList;
+       }
+
+       /**
+        * Returns the encoders in this group.
+        *
+        * @return An unmodifiable list of encoders in this group.
+        */
+       public List<Encoder> getEncoders() {
+               return encoders;
        }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroupBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroupBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroupBuilder.java
index 7a965c6..2180fd1 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroupBuilder.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderGroupBuilder.java
@@ -12,6 +12,7 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.encoders;
 
+import static org.apache.juneau.internal.CollectionUtils.*;
 import java.util.*;
 
 /**
@@ -33,7 +34,8 @@ public class EncoderGroupBuilder {
         * @param copyFrom The encoder group that we're copying settings and 
encoders from.
         */
        public EncoderGroupBuilder(EncoderGroup copyFrom) {
-               this.encoders = new 
ArrayList<Encoder>(Arrays.asList(copyFrom.encoders));
+               this.encoders = new ArrayList<Encoder>();
+               addReverse(encoders, copyFrom.getEncoders());
        }
 
        /**
@@ -43,9 +45,9 @@ public class EncoderGroupBuilder {
         * @return This object (for method chaining).
         */
        public EncoderGroupBuilder append(Class<?>...e) {
-               for (Class<?> ee : e) {
+               for (int i = e.length-1; i >= 0; i--) {
                        try {
-                               
encoders.add((Encoder)((Class<?>)ee).newInstance());
+                               
encoders.add((Encoder)((Class<?>)e[i]).newInstance());
                        } catch (Exception x) {
                                throw new RuntimeException(x);
                        }
@@ -60,7 +62,7 @@ public class EncoderGroupBuilder {
         * @return This object (for method chaining).
         */
        public EncoderGroupBuilder append(Encoder...e) {
-               encoders.addAll(Arrays.asList(e));
+               addReverse(encoders, e);
                return this;
        }
 
@@ -70,8 +72,8 @@ public class EncoderGroupBuilder {
         * @param e The encoders to append to this group.
         * @return This object (for method chaining).
         */
-       public EncoderGroupBuilder append(Collection<Encoder> e) {
-               encoders.addAll(e);
+       public EncoderGroupBuilder append(List<Encoder> e) {
+               addReverse(encoders, e);
                return this;
        }
 
@@ -82,7 +84,7 @@ public class EncoderGroupBuilder {
         * @return This object (for method chaining).
         */
        public EncoderGroupBuilder append(EncoderGroup eg) {
-               append(eg.encoders);
+               append(eg.getEncoders());
                return this;
        }
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderMatch.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderMatch.java 
b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderMatch.java
index e05b3e7..f5ecb3d 100644
--- a/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderMatch.java
+++ b/juneau-core/src/main/java/org/apache/juneau/encoders/EncoderMatch.java
@@ -12,18 +12,16 @@
 // 
***************************************************************************************************************************
 package org.apache.juneau.encoders;
 
-import org.apache.juneau.*;
-
 /**
  * Represents a encoder and encoding that matches an HTTP 
<code>Accept-Encoding</code> header value.
  */
 public final class EncoderMatch {
 
-       private final MediaType mediaType;
+       private final String encoding;
        private final Encoder encoder;
 
-       EncoderMatch(MediaType mediaType, Encoder encoder) {
-               this.mediaType = mediaType;
+       EncoderMatch(String encoding, Encoder encoder) {
+               this.encoding = encoding;
                this.encoder = encoder;
        }
 
@@ -33,7 +31,7 @@ public final class EncoderMatch {
         * @return The encoding of the match.
         */
        public String getEncoding() {
-               return mediaType.getType();
+               return encoding;
        }
 
        /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
index b7030e5..d97afe4 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializer.java
@@ -18,6 +18,7 @@ import java.util.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.dto.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.serializer.*;
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
index 440c80b..0e334d1 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlDocSerializerSession.java
@@ -18,6 +18,7 @@ import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.serializer.*;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
index 304bfab..2095968 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParser.java
@@ -23,6 +23,7 @@ import javax.xml.stream.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.transform.*;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserBuilder.java
index be8cc9c..6c9f27c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserBuilder.java
@@ -18,6 +18,7 @@ import javax.xml.stream.*;
 import javax.xml.stream.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.xml.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserSession.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserSession.java
index 5ebef14..2bf5e25 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlParserSession.java
@@ -22,6 +22,7 @@ import java.util.*;
 import javax.xml.stream.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.xml.*;
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
index eea6616..bc17387 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSchemaDocSerializer.java
@@ -20,6 +20,7 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.transform.*;
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
index 168ac5c..c3d1088 100644
--- a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializer.java
@@ -21,6 +21,7 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.transform.*;
 import org.apache.juneau.xml.*;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerBuilder.java
index fe0a7c5..6190c14 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerBuilder.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerBuilder.java
@@ -17,6 +17,7 @@ import static org.apache.juneau.html.HtmlSerializerContext.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.xml.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
index 4c1ee2f..3faf271 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
@@ -20,6 +20,7 @@ import java.util.*;
 import java.util.regex.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
 import org.apache.juneau.xml.*;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/http/Accept.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/http/Accept.java 
b/juneau-core/src/main/java/org/apache/juneau/http/Accept.java
new file mode 100644
index 0000000..ce7119f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/http/Accept.java
@@ -0,0 +1,231 @@
+// 
***************************************************************************************************************************
+// * 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 java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * Represents a parsed <code>Accept:</code> HTTP header.
+ * <p>
+ * The formal RFC2616 header field definition is as follows:
+ * <p class='bcode'>
+ *     14.1 Accept
+ *
+ *     The Accept request-header field can be used to specify certain media
+ *     types which are acceptable for the response. Accept headers can be
+ *     used to indicate that the request is specifically limited to a small
+ *     set of desired types, as in the case of a request for an in-line
+ *     image.
+ *
+ *      Accept         = "Accept" ":
+ *                                                     #( media-range [ 
accept-params ] )
+ *
+ *      media-range    = ( "* /*"
+ *                                                     | ( type "/" "*" )
+ *                                                     | ( type "/" subtype )
+ *                                                     ) *( ";" parameter )
+ *      accept-params  = ";" "q" "=" qvalue *( accept-extension )
+ *      accept-extension = ";" token [ "=" ( token | quoted-string ) ]
+ *
+ *     The asterisk "*" character is used to group media types into ranges,
+ *     with "* /*" indicating all media types and "type/*" indicating all
+ *     subtypes of that type. The media-range MAY include media type
+ *     parameters that are applicable to that range.
+ *
+ *     Each media-range MAY be followed by one or more accept-params,
+ *     beginning with the "q" parameter for indicating a relative quality
+ *     factor. The first "q" parameter (if any) separates the media-range
+ *     parameter(s) from the accept-params. Quality factors allow the user
+ *     or user agent to indicate the relative degree of preference for that
+ *     media-range, using the qvalue scale from 0 to 1 (section 3.9). The
+ *     default value is q=1.
+ *
+ *     Note: Use of the "q" parameter name to separate media type
+ *     parameters from Accept extension parameters is due to historical
+ *     practice. Although this prevents any media type parameter named
+ *     "q" from being used with a media range, such an event is believed
+ *     to be unlikely given the lack of any "q" parameters in the IANA
+ *     media type registry and the rare usage of any media type
+ *     parameters in Accept. Future media types are discouraged from
+ *     registering any parameter named "q".
+ *
+ *     The example
+ *
+ *             Accept: audio/*; q=0.2, audio/basic
+ *
+ *     SHOULD be interpreted as "I prefer audio/basic, but send me any audio
+ *     type if it is the best available after an 80% mark-down in quality."
+ *
+ *     If no Accept header field is present, then it is assumed that the
+ *     client accepts all media types. If an Accept header field is present,
+ *     and if the server cannot send a response which is acceptable
+ *     according to the combined Accept field value, then the server SHOULD
+ *     send a 406 (not acceptable) response.
+ *
+ *     A more elaborate example is
+ *
+ *         Accept: text/plain; q=0.5, text/html,
+ *                 text/x-dvi; q=0.8, text/x-c
+ *
+ *     Verbally, this would be interpreted as "text/html and text/x-c are
+ *     the preferred media types, but if they do not exist, then send the
+ *     text/x-dvi entity, and if that does not exist, send the text/plain
+ *     entity."
+ *
+ *     Media ranges can be overridden by more specific media ranges or
+ *     specific media types. If more than one media range applies to a given
+ *     type, the most specific reference has precedence. For example,
+ *
+ *         Accept: text/ *, text/html, text/html;level=1, * /*
+ *
+ *     have the following precedence:
+ *
+ *         1) text/html;level=1
+ *         2) text/html
+ *         3) text/*
+ *         4) * /*
+ *
+ *     The media type quality factor associated with a given type is
+ *     determined by finding the media range with the highest precedence
+ *     which matches that type. For example,
+ *
+ *         Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1,
+ *                 text/html;level=2;q=0.4, * /*;q=0.5
+ *
+ *     would cause the following values to be associated:
+ *
+ *         text/html;level=1         = 1
+ *         text/html                 = 0.7
+ *         text/plain                = 0.3
+ *         image/jpeg                = 0.5
+ *         text/html;level=2         = 0.4
+ *         text/html;level=3         = 0.7
+ *
+ *        Note: A user agent might be provided with a default set of quality
+ *        values for certain media ranges. However, unless the user agent is
+ *        a closed system which cannot interact with other rendering agents,
+ *        this default set ought to be configurable by the user.
+ * </p>
+ */
+public final class Accept {
+
+       private static final boolean nocache = 
Boolean.getBoolean("juneau.nocache");
+       private static final ConcurrentHashMap<String,Accept> cache = new 
ConcurrentHashMap<String,Accept>();
+
+       private final MediaTypeRange[] mediaRanges;
+       private final List<MediaTypeRange> mediaRangesList;
+
+       /**
+        * Returns a parsed <code>Accept</code> header.
+        *
+        * @param s The <code>Accept</code> header string.
+        * @return The parsed <code>Accept</code> header.
+        */
+       public static Accept forString(String s) {
+               if (s == null)
+                       s = "null";
+               Accept a = cache.get(s);
+               if (a == null) {
+                       a = new Accept(s);
+                       if (nocache)
+                               return a;
+                       cache.putIfAbsent(s, a);
+               }
+               return cache.get(s);
+       }
+
+       private Accept(String raw) {
+               this.mediaRanges = MediaTypeRange.parse(raw);
+               this.mediaRangesList = 
Collections.unmodifiableList(Arrays.asList(mediaRanges));
+       }
+
+       /**
+        * Returns the list of the media ranges that make up this header.
+        * <p>
+        * The media ranges in the list are sorted by their q-value in 
descending order.
+        *
+        * @return An unmodifiable list of media ranges.
+        */
+       public List<MediaTypeRange> getMediaRanges() {
+               return mediaRangesList;
+       }
+
+       /**
+        * Given a list of media types, returns the best match for this 
<code>Accept</code> header.
+        * <p>
+        * Note that fuzzy matching is allowed on the media types where the 
<code>Accept</code> header may
+        * contain additional subtype parts.
+        * <br>For example, given identical q-values and an <code>Accept</code> 
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 <code>id</code> properties are present
+        * in the header.
+        * <p>
+        * See <a class='doclink' 
href='https://www.w3.org/TR/activitypub/#retrieving-objects'>ActivityPub / 
Retrieving Objects</a>
+        * <p>
+        *
+        * @param mediaTypes The media types to match against.
+        * @return The index into the array of the best match, or 
<code>-1</code> 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 : mediaRanges) {
+                       float q2 = mr.getQValue();
+
+                       if (q2 < q || q2 == 0)
+                               break;
+
+                       for (int i = 0; i < mediaTypes.length; i++) {
+                               MediaType mt = mediaTypes[i];
+                               int matchQuant2 = mt.match(mr.getMediaType());
+                               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 fragement exists.
+        */
+       public boolean hasSubtypePart(String part) {
+
+               for (MediaTypeRange mr : this.mediaRanges)
+                       if (mr.getQValue() > 0 && 
mr.getMediaType().getSubTypes().indexOf(part) >= 0)
+                               return true;
+
+               return false;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return StringUtils.join(mediaRanges, ',');
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/http/AcceptEncoding.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/http/AcceptEncoding.java 
b/juneau-core/src/main/java/org/apache/juneau/http/AcceptEncoding.java
new file mode 100644
index 0000000..e8c6886
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/http/AcceptEncoding.java
@@ -0,0 +1,147 @@
+// 
***************************************************************************************************************************
+// * 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 java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * Represents a parsed <code>Accept-Encoding:</code> HTTP header.
+ * <p>
+ * The formal RFC2616 header field definition is as follows:
+ * <p class='bcode'>
+ *     The Accept-Encoding request-header field is similar to Accept, but 
restricts
+ *     the content-codings (section 3.5) that are acceptable in the response.
+ *
+ *             Accept-Encoding  = "Accept-Encoding" ":"
+ *                                1#( codings [ ";" "q" "=" qvalue ] )
+ *             codings          = ( content-coding | "*" )
+ *
+ *     Examples of its use are:
+ *
+ *             Accept-Encoding: compress, gzip
+ *             Accept-Encoding:
+ *             Accept-Encoding: *
+ *             Accept-Encoding: compress;q=0.5, gzip;q=1.0
+ *             Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0
+ *
+ *     A server tests whether a content-coding is acceptable, according to an
+ *     Accept-Encoding field, using these rules:
+ *
+ *        1. If the content-coding is one of the content-codings listed in
+ *           the Accept-Encoding field, then it is acceptable, unless it is
+ *           accompanied by a qvalue of 0. (As defined in section 3.9, a
+ *           qvalue of 0 means "not acceptable.")
+ *        2. The special "*" symbol in an Accept-Encoding field matches any
+ *           available content-coding not explicitly listed in the header
+ *           field.
+ *        3. If multiple content-codings are acceptable, then the acceptable
+ *           content-coding with the highest non-zero qvalue is preferred.
+ *        4. The "identity" content-coding is always acceptable, unless
+ *           specifically refused because the Accept-Encoding field includes
+ *           "identity;q=0", or because the field includes "*;q=0" and does
+ *           not explicitly include the "identity" content-coding. If the
+ *           Accept-Encoding field-value is empty, then only the "identity"
+ *           encoding is acceptable.
+ *
+ *     If an Accept-Encoding field is present in a request, and if the server 
cannot
+ *     send a response which is acceptable according to the Accept-Encoding 
header,
+ *     then the server SHOULD send an error response with the 406 (Not 
Acceptable) status code.
+ *
+ *     If no Accept-Encoding field is present in a request, the server MAY 
assume
+ *     that the client will accept any content coding. In this case, if 
"identity"
+ *     is one of the available content-codings, then the server SHOULD use the 
"identity"
+ *     content-coding, unless it has additional information that a different 
content-coding
+ *     is meaningful to the client.
+ *
+ *           Note: If the request does not include an Accept-Encoding field,
+ *           and if the "identity" content-coding is unavailable, then
+ *           content-codings commonly understood by HTTP/1.0 clients (i.e.,
+ *           "gzip" and "compress") are preferred; some older clients
+ *           improperly display messages sent with other content-codings.  The
+ *           server might also make this decision based on information about
+ *           the particular user-agent or client.
+ *           Note: Most HTTP/1.0 applications do not recognize or obey qvalues
+ *           associated with content-codings. This means that qvalues will not
+ *           work and are not permitted with x-gzip or x-compress.
+ * </p>
+ */
+public final class AcceptEncoding {
+
+       private static final boolean nocache = 
Boolean.getBoolean("juneau.nocache");
+       private static final ConcurrentHashMap<String,AcceptEncoding> cache = 
new ConcurrentHashMap<String,AcceptEncoding>();
+
+       private final TypeRange[] typeRanges;
+       private final List<TypeRange> typeRangesList;
+
+       /**
+        * Returns a parsed <code>Accept-Encoding</code> header.
+        *
+        * @param s The <code>Accept-Encoding</code> header string.
+        * @return The parsed <code>Accept-Encoding</code> header.
+        */
+       public static AcceptEncoding forString(String s) {
+               if (s == null)
+                       s = "null";
+               AcceptEncoding a = cache.get(s);
+               if (a == null) {
+                       a = new AcceptEncoding(s);
+                       if (nocache)
+                               return a;
+                       cache.putIfAbsent(s, a);
+               }
+               return cache.get(s);
+       }
+
+       private AcceptEncoding(String raw) {
+               this.typeRanges = TypeRange.parse(raw);
+               this.typeRangesList = 
Collections.unmodifiableList(Arrays.asList(typeRanges));
+       }
+
+       /**
+        * Returns the list of the types ranges that make up this header.
+        * <p>
+        * The types ranges in the list are sorted by their q-value in 
descending order.
+        *
+        * @return An unmodifiable list of type ranges.
+        */
+       public List<TypeRange> getTypeRanges() {
+               return typeRangesList;
+       }
+
+       /**
+        * Given a list of content codings, returns the best match for this 
<code>Accept-Encoding</code> header.
+        * <p>
+        *
+        * @param contentCodings The codings to match against.
+        * @return The index into the array of the best match, or 
<code>-1</code> if no suitable matches could be found.
+        */
+       public int findMatch(String[] contentCodings) {
+
+               // Type ranges are ordered by 'q'.
+               // So we only need to search until we've found a match.
+               for (TypeRange mr : typeRanges)
+                       for (int i = 0; i < contentCodings.length; i++)
+                               if (mr.matches(contentCodings[i]))
+                                       return i;
+
+               return -1;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               return StringUtils.join(typeRanges, ',');
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/http/ContentType.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/http/ContentType.java 
b/juneau-core/src/main/java/org/apache/juneau/http/ContentType.java
new file mode 100644
index 0000000..7f5570f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/http/ContentType.java
@@ -0,0 +1,90 @@
+// 
***************************************************************************************************************************
+// * 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 java.util.concurrent.*;
+
+/**
+ * Represents a parsed <code>Content-Type:</code> HTTP header.
+ * <p>
+ * The formal RFC2616 header field definition is as follows:
+ * <p class='bcode'>
+ *     14.17 Content-Type
+ *
+ *     The Content-Type entity-header field indicates the media type of the
+ *     entity-body sent to the recipient or, in the case of the HEAD method,
+ *     the media type that would have been sent had the request been a GET.
+ *
+ *             Content-Type   = "Content-Type" ":" media-type
+ *
+ *     Media types are defined in section 3.7. An example of the field is
+ *
+ *     Content-Type: text/html; charset=ISO-8859-4
+ * </p>
+ */
+public class ContentType extends MediaType {
+
+       private static final boolean nocache = 
Boolean.getBoolean("juneau.nocache");
+       private static final ConcurrentHashMap<String,ContentType> cache = new 
ConcurrentHashMap<String,ContentType>();
+
+       /**
+        * Returns a parsed <code>Content-Type</code> header.
+        *
+        * @param s The <code>Content-Type</code> header string.
+        * @return The parsed <code>Content-Type</code> header.
+        */
+       public static ContentType forString(String s) {
+               if (s == null)
+                       return null;
+               ContentType mt = cache.get(s);
+               if (mt == null) {
+                       mt = new ContentType(s);
+                       if (nocache)
+                               return mt;
+                       cache.putIfAbsent(s, mt);
+               }
+               return cache.get(s);
+       }
+
+       private ContentType(String s) {
+               super(s);
+       }
+
+       /**
+        * Given a list of media types, returns the best match for this 
<code>Content-Type</code> header.
+        * <p>
+        * Note that fuzzy matching is allowed on the media types where the 
<code>Content-Types</code> header may
+        * contain additional subtype parts.
+        * <br>For example, given a <code>Content-Type</code> 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 parsers to match when artifacts 
such as <code>id</code> properties are present
+        * in the header.
+        *
+        * @param mediaTypes The media types to match against.
+        * @return The index into the array of the best match, or 
<code>-1</code> if no suitable matches could be found.
+        */
+       public int findMatch(MediaType[] mediaTypes) {
+               int matchQuant = 0, matchIndex = -1;
+
+               for (int i = 0; i < mediaTypes.length; i++) {
+                       MediaType mt = mediaTypes[i];
+                       int matchQuant2 = mt.match(this);
+                       if (matchQuant2 > matchQuant) {
+                               matchIndex = i;
+                       }
+               }
+
+               return matchIndex;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/http/MediaType.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/http/MediaType.java 
b/juneau-core/src/main/java/org/apache/juneau/http/MediaType.java
new file mode 100644
index 0000000..74f298f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/http/MediaType.java
@@ -0,0 +1,277 @@
+// 
***************************************************************************************************************************
+// * 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 java.util.*;
+import java.util.concurrent.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+
+
+/**
+ * Describes a single media 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).
+ */
+@BeanIgnore
+@SuppressWarnings("unchecked")
+public class MediaType {
+
+       private static final boolean nocache = 
Boolean.getBoolean("juneau.nocache");
+       private static final ConcurrentHashMap<String,MediaType> cache = new 
ConcurrentHashMap<String,MediaType>();
+
+       /** Reusable predefined media type */
+       @SuppressWarnings("javadoc")
+       public static final MediaType
+               CSV = forString("text/csv"),
+               HTML = forString("text/html"),
+               JSON = forString("application/json"),
+               MSGPACK = forString("octal/msgpack"),
+               PLAIN = forString("text/plain"),
+               UON = forString("text/uon"),
+               URLENCODING = forString("application/x-www-form-urlencoded"),
+               XML = forString("text/xml"),
+               XMLSOAP = forString("text/xml+soap"),
+
+               RDF = forString("text/xml+rdf"),
+               RDFABBREV = forString("text/xml+rdf+abbrev"),
+               NTRIPLE = forString("text/n-triple"),
+               TURTLE = forString("text/turtle"),
+               N3 = forString("text/n3")
+       ;
+
+       private final String mediaType;
+       private final String type;                                              
                     // The media type (e.g. "text" for Accept, "utf-8" for 
Accept-Charset)
+       private final String subType;                        // The media 
sub-type (e.g. "json" for Accept, not used for Accept-Charset)
+       private final String[] subTypes;                     // The media 
sub-type (e.g. "json" for Accept, not used for Accept-Charset)
+       private final List<String> subTypesList;             // The media 
sub-type (e.g. "json" for Accept, not used for Accept-Charset)
+       private final Map<String,Set<String>> parameters;    // The media type 
parameters (e.g. "text/html;level=1").  Does not include q!
+
+
+       /**
+        * Returns the media type for the specified string.
+        * The same media type strings always return the same objects so that 
these objects
+        * can be compared for equality using '=='.
+        * <p>
+        * <h5 class='section'>Notes:</h5>
+        * <ul>
+        *      <li>Spaces are replaced with <js>'+'</js> characters.
+        *              This gets around the issue where passing media type 
strings with <js>'+'</js> as HTTP GET parameters
+        *              get replaced with spaces by your browser.  Since spaces 
aren't supported by the spec, this
+        *              is doesn't break anything.
+        *      <li>Anything including and following the <js>';'</js> character 
is ignored (e.g. <js>";charset=X"</js>).
+        * </ul>
+        *
+        * @param s The media type string.  Will be lowercased.
+        *      <br>Returns <jk>null</jk> if input is null.
+        * @return A cached media type object.
+        */
+       public static MediaType forString(String s) {
+               if (s == null)
+                       return null;
+               MediaType mt = cache.get(s);
+               if (mt == null) {
+                       mt = new MediaType(s);
+                       if (nocache)
+                               return mt;
+                       cache.putIfAbsent(s, mt);
+               }
+               return cache.get(s);
+       }
+
+       MediaType(String mt) {
+               Builder b = new Builder(mt);
+               this.mediaType = b.mediaType;
+               this.type = b.type;
+               this.subType = b.subType;
+               this.subTypes = b.subTypes;
+               this.subTypesList = 
Collections.unmodifiableList(Arrays.asList(subTypes));
+               this.parameters = (b.parameters == null ? Collections.EMPTY_MAP 
: Collections.unmodifiableMap(b.parameters));
+       }
+
+       private static class Builder {
+               private String mediaType, type, subType;
+               private String[] subTypes;
+               private Map<String,Set<String>> parameters;
+
+               private Builder(String mt) {
+                       mt = mt.trim();
+
+                       int i = mt.indexOf(';');
+                       if (i == -1) {
+                               this.parameters = Collections.EMPTY_MAP;
+                       } else {
+                               this.parameters = new 
TreeMap<String,Set<String>>();
+                               String[] tokens = mt.substring(i+1).split(";");
+
+                               for (int j = 0; j < tokens.length; j++) {
+                                       String[] parm = tokens[j].split("=");
+                                       if (parm.length == 2) {
+                                               String k = parm[0].trim(), v = 
parm[1].trim();
+                                               if (! parameters.containsKey(k))
+                                                       parameters.put(k, new 
TreeSet<String>());
+                                               parameters.get(k).add(v);
+                                       }
+                               }
+
+                               mt = mt.substring(0, i);
+                       }
+
+                       this.mediaType = mt;
+                       if (mt != null) {
+                               mt = mt.replace(' ', '+');
+                               i = mt.indexOf('/');
+                               type = (i == -1 ? mt : mt.substring(0, i));
+                               subType = (i == -1 ? "*" : mt.substring(i+1));
+                       }
+                       this.subTypes = StringUtils.split(subType, '+');
+               }
+       }
+
+       /**
+        * Returns the <js>'type'</js> fragment of the <js>'type/subType'</js> 
string.
+        *
+        * @return The media type.
+        */
+       public String getType() {
+               return type;
+       }
+
+       /**
+        * Returns the <js>'subType'</js> fragment of the 
<js>'type/subType'</js> string.
+        *
+        * @return The media subtype.
+        */
+       public String getSubType() {
+               return subType;
+       }
+
+       /**
+        * Returns the subtypes broken down by fragments delimited by 
<js>"'"</js>.
+        * For example, the media type <js>"text/foo+bar"</js> will return a 
list of
+        * <code>[<js>'foo'</js>,<js>'bar'</js>]</code>
+        *
+        * @return An unmodifiable list of subtype fragments.  Never 
<jk>null</jk>.
+        */
+       public List<String> getSubTypes() {
+               return subTypesList;
+       }
+
+       /**
+        * Returns <jk>true</jk> if this media type is a match for the 
specified media type.
+        * <p>
+        * Matches if any of the following is true:
+        * <ul>
+        *      <li>Both type and subtype are the same.
+        *      <li>One or both types are <js>'*'</js> and the subtypes are the 
same.
+        *      <li>One or both subtypes are <js>'*'</js> and the types are the 
same.
+        *      <li>Either is <js>'*\/*'</js>.
+        * </ul>
+        *
+        * @param o The media type to compare with.
+        * @return <jk>true</jk> if the media types match.
+        */
+       public final boolean matches(MediaType o) {
+               return match(o) > 0;
+       }
+
+       /**
+        * Returns a match metric against the specified media type where a 
larger number represents a better match.
+        * <p>
+        * <ul>
+        *      <li>Exact matches (e.g. 
<js>"text/json"<js>/</js>"text/json"</js>) should match
+        *              better than metacharacter matches (e.g. 
<js>"text/*"<js>/</js>"text/json"</js>)
+        *      <li>The comparison media type can have additional subtype 
tokens (e.g. <js>"text/json+foo"</js>)
+        *              that will not prevent a match.  The reverse is not 
true, e.g. the comparison media type
+        *              must contain all subtype tokens found in the comparing 
media type.
+        *              <ul>
+        *                      <li>We want the {@link JsonSerializer} 
(<js>"text/json"</js>) class to be able to handle requests for 
<js>"text/json+foo"</js>.
+        *                      <li>We want to make sure {@link 
org.apache.juneau.json.JsonSerializer.Simple} (<js>"text/json+simple"</js>) 
does not handle
+        *                              requests for <js>"text/json"</js>.
+        *              </ul>
+        *              More token matches should result in a higher match 
number.
+        * </ul>
+        *
+        * @param o The media type to compare with.
+        * @return <jk>true</jk> if the media types match.
+        */
+       public final int match(MediaType o) {
+
+               // Perfect match
+               if (this == o || (type.equals(o.type) && 
subType.equals(o.subType)))
+                       return Integer.MAX_VALUE;
+
+               int c1 = 0, c2 = 0;
+
+               if (type.equals(o.type))
+                       c1 += 10000;
+               else if ("*".equals(type) || "*".equals(o.type))
+                       c1 += 5000;
+
+               if (c1 == 0)
+                       return 0;
+
+               // Give type slightly higher comparison value than subtype 
simply for deterministic results.
+               if (subType.equals(o.subType))
+                       return c1 + 9999;
+
+               int c3 = 0;
+
+               for (String st1 : subTypes) {
+                       if ("*".equals(st1))
+                               c1++;
+                       else if (ArrayUtils.contains(st1, o.subTypes))
+                               c1 += 100;
+                       else if (ArrayUtils.contains("*", o.subTypes))
+                               c1 += 10;
+                       else
+                               return 0;
+               }
+
+               return c1 + c2 + c3;
+       }
+
+       /**
+        * Returns the additional parameters on this media type.
+        * <p>
+        * For example, given the media type string 
<js>"text/html;level=1"</js>, will return a map
+        * with the single entry <code>{level:[<js>'1'</js>]}</code>.
+        *
+        * @return The map of additional parameters, or an empty map if there 
are no parameters.
+        */
+       public Map<String,Set<String>> getParameters() {
+               return parameters;
+       }
+
+       @Override /* Object */
+       public String toString() {
+               if (parameters.isEmpty())
+                       return mediaType;
+               StringBuilder sb = new StringBuilder(mediaType);
+               for (Map.Entry<String,Set<String>> e : parameters.entrySet())
+                       for (String value : e.getValue())
+                               
sb.append(';').append(e.getKey()).append('=').append(value);
+               return sb.toString();
+       }
+
+       @Override /* Object */
+       public int hashCode() {
+               return mediaType.hashCode();
+       }
+
+       @Override /* Object */
+       public boolean equals(Object o) {
+               return this == o;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/http/MediaTypeRange.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/http/MediaTypeRange.java 
b/juneau-core/src/main/java/org/apache/juneau/http/MediaTypeRange.java
new file mode 100644
index 0000000..9ff7c4a
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/http/MediaTypeRange.java
@@ -0,0 +1,270 @@
+// 
***************************************************************************************************************************
+// * 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 java.util.*;
+import java.util.Map.*;
+
+import org.apache.juneau.annotation.*;
+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).
+ */
+@BeanIgnore
+public final class MediaTypeRange implements Comparable<MediaTypeRange>  {
+
+       private static final MediaTypeRange[] DEFAULT = new 
MediaTypeRange[]{new MediaTypeRange("*/*")};
+
+       private final MediaType mediaType;
+       private final Float qValue;
+       private final Map<String,Set<String>> extensions;
+
+       /**
+        * Parses an <code>Accept</code> 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 <code>value</code> 
complies with the syntax described in RFC2616, Section 14.1, as described below:
+        * <p class='bcode'>
+        *      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 <code>MediaTypeRange</code> 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;
+
+               if (value.indexOf(',') == -1)
+                       return new MediaTypeRange[]{new MediaTypeRange(value)};
+
+               Set<MediaTypeRange> ranges = new TreeSet<MediaTypeRange>();
+
+               for (String r : StringUtils.split(value, ',')) {
+                       r = r.trim();
+
+                       if (r.isEmpty())
+                               continue;
+
+                       ranges.add(new MediaTypeRange(r));
+               }
+
+               return ranges.toArray(new MediaTypeRange[ranges.size()]);
+       }
+
+       @SuppressWarnings("unchecked")
+       private MediaTypeRange(String token) {
+               Builder b = new Builder(token);
+               this.mediaType = b.mediaType;
+               this.qValue = b.qValue;
+               this.extensions = (b.extensions == null ? Collections.EMPTY_MAP 
: Collections.unmodifiableMap(b.extensions));
+       }
+
+       private static class Builder {
+               private MediaType mediaType;
+               private Float qValue = 1f;
+               private Map<String,Set<String>> extensions;
+
+               private Builder(String token) {
+
+                       token = token.trim();
+
+                       int i = token.indexOf(";q=");
+
+                       if (i == -1) {
+                               mediaType = MediaType.forString(token);
+                               return;
+                       }
+
+                       mediaType = MediaType.forString(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<String,Set<String>>();
+                                                       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 <code>0.0</code> (unacceptable) 
and <code>1.0</code> (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 <code>Accept</code> header value.
+        * <p>
+        * The literal text generated will be all lowercase.
+        *
+        * @return A media range suitable for use as an Accept header value, 
never <code>null</code>.
+        */
+       @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 
<code>MediaType</code>, and has the same qValue, type, parameters, and 
extensions.
+        *
+        * @return <jk>true</jk> if object is equivalent.
+        */
+       @Override /* Object */
+       public boolean equals(Object o) {
+
+               if (o == null || !(o instanceof MediaTypeRange))
+                       return false;
+
+               if (this == o)
+                       return true;
+
+               MediaTypeRange o2 = (MediaTypeRange) o;
+               return qValue.equals(o2.qValue)
+                       && mediaType.equals(o2.mediaType)
+                       && extensions.equals(o2.extensions);
+       }
+
+       /**
+        * Returns a hash based on this instance's <code>media-type</code>.
+        *
+        * @return A hash based on this instance's <code>media-type</code>.
+        */
+       @Override /* Object */
+       public int hashCode() {
+               return mediaType.hashCode();
+       }
+
+       /**
+        * Compares two MediaRanges for equality.
+        * <p>
+        * The values are first compared according to <code>qValue</code> 
values.
+        * Should those values be equal, the <code>type</code> is then 
lexicographically compared (case-insensitive) in ascending order,
+        *      with the <js>"*"</js> type demoted last in that order.
+        * <code>MediaRanges</code> with the same type but different sub-types 
are compared - a more specific subtype is
+        *      promoted over the 'wildcard' subtype.
+        * <code>MediaRanges</code> 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;
+       }
+
+       /**
+        * Matches the specified media type against this range and returns a 
q-value
+        * between 0 and 1 indicating the quality of the match.
+        *
+        * @param o The media type to match against.
+        * @return A float between 0 and 1.  1 is a perfect match.  0 is no 
match at all.
+        */
+       public float matches(MediaType o) {
+               if (this.mediaType == o || mediaType.matches(o))
+                       return qValue;
+               return 0;
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/http/TypeRange.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/http/TypeRange.java 
b/juneau-core/src/main/java/org/apache/juneau/http/TypeRange.java
new file mode 100644
index 0000000..a05189f
--- /dev/null
+++ b/juneau-core/src/main/java/org/apache/juneau/http/TypeRange.java
@@ -0,0 +1,276 @@
+// 
***************************************************************************************************************************
+// * 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 java.util.*;
+import java.util.Map.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.internal.*;
+
+/**
+ * Represents a single value in a comma-delimited header value that optionally 
contains a quality
+ * metric for comparison and extension parameters.
+ * <p>
+ * Similar in concept to {@link MediaTypeRange} except instead of media types 
(e.g. <js>"text/json"</js>),
+ * it's a simple type (e.g. <js>"iso-8601"</js>).
+ * <p>
+ * An example of a type range is a value in an <code>Accept-Encoding</code> 
header.
+ */
+@BeanIgnore
+public final class TypeRange implements Comparable<TypeRange>  {
+
+       private static final TypeRange[] DEFAULT = new TypeRange[]{new 
TypeRange("*")};
+
+       private final String type;
+       private final Float qValue;
+       private final Map<String,Set<String>> extensions;
+
+       /**
+        * Parses a header such as an <code>Accept-Encoding</code> header value 
into an array of type ranges.
+        * <p>
+        * The syntax expected to be found in the referenced <code>value</code> 
complies with the syntax described in RFC2616, Section 14.1, as described below:
+        * <p class='bcode'>
+        *      Accept-Encoding  = "Accept-Encoding" ":"
+        *                         1#( codings [ ";" "q" "=" qvalue ] )
+        *      codings          = ( content-coding | "*" )
+        * </p>
+        * <p>
+        * Examples of its use are:
+        * <p class='bcode'>
+        *      Accept-Encoding: compress, gzip
+        *      Accept-Encoding:
+        *      Accept-Encoding: *
+        *      Accept-Encoding: compress;q=0.5, gzip;q=1.0
+        *      Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0
+        * </p>
+        *
+        * @param value The value to parse.  If <jk>null</jk> or empty, returns 
a single <code>TypeRange</code> is returned that represents all types.
+        * @return The type ranges described by the string.
+        * <br>The ranges are sorted such that the most acceptable type is 
available at ordinal position <js>'0'</js>, and the least acceptable at 
position n-1.
+        */
+       public static TypeRange[] parse(String value) {
+
+               if (value == null || value.length() == 0)
+                       return DEFAULT;
+
+               if (value.indexOf(',') == -1)
+                       return new TypeRange[]{new TypeRange(value)};
+
+               Set<TypeRange> ranges = new TreeSet<TypeRange>();
+
+               for (String r : StringUtils.split(value, ',')) {
+                       r = r.trim();
+
+                       if (r.isEmpty())
+                               continue;
+
+                       ranges.add(new TypeRange(r));
+               }
+
+               return ranges.toArray(new TypeRange[ranges.size()]);
+       }
+
+       @SuppressWarnings("unchecked")
+       private TypeRange(String token) {
+               Builder b = new Builder(token);
+               this.type = b.type;
+               this.qValue = b.qValue;
+               this.extensions = (b.extensions == null ? Collections.EMPTY_MAP 
: Collections.unmodifiableMap(b.extensions));
+       }
+
+       private static class Builder {
+               private String type;
+               private Float qValue = 1f;
+               private Map<String,Set<String>> extensions;
+
+               private Builder(String token) {
+
+                       token = token.trim();
+
+                       int i = token.indexOf(";q=");
+
+                       if (i == -1) {
+                               type = token;
+                               return;
+                       }
+
+                       type = 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<String,Set<String>>();
+                                                       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 type enclosed by this type range.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <ul>
+        *      <li><js>"compress"</js>
+        *      <li><js>"gzip"</js>
+        *      <li><js>"*"</js>
+        * </ul>
+        *
+        * @return The type of this type range, lowercased, never <jk>null</jk>.
+        */
+       public String getType() {
+               return type;
+       }
+
+       /**
+        * 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 <code>0.0</code> (unacceptable) 
and <code>1.0</code> (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 <code>Accept</code> header value.
+        * <p>
+        * The literal text generated will be all lowercase.
+        *
+        * @return A media range suitable for use as an Accept header value, 
never <code>null</code>.
+        */
+       @Override /* Object */
+       public String toString() {
+               StringBuffer sb = new StringBuffer().append(type);
+
+               // '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 
<code>MediaType</code>, and has the same qValue, type, parameters, and 
extensions.
+        *
+        * @return <jk>true</jk> if object is equivalent.
+        */
+       @Override /* Object */
+       public boolean equals(Object o) {
+
+               if (o == null || !(o instanceof TypeRange))
+                       return false;
+
+               if (this == o)
+                       return true;
+
+               TypeRange o2 = (TypeRange) o;
+               return qValue.equals(o2.qValue)
+                       && type.equals(o2.type)
+                       && extensions.equals(o2.extensions);
+       }
+
+       /**
+        * Returns a hash based on this instance's <code>media-type</code>.
+        *
+        * @return A hash based on this instance's <code>media-type</code>.
+        */
+       @Override /* Object */
+       public int hashCode() {
+               return type.hashCode();
+       }
+
+       /**
+        * Compares two MediaRanges for equality.
+        * <p>
+        * The values are first compared according to <code>qValue</code> 
values.
+        * Should those values be equal, the <code>type</code> is then 
lexicographically compared (case-insensitive) in ascending order,
+        *      with the <js>"*"</js> type demoted last in that order.
+        * <code>TypeRanges</code> 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(TypeRange 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.type.toString().compareTo(type.toString());
+               return i;
+       }
+
+       /**
+        * Checks if the specified type matches this range.
+        * <p>
+        * The type will match this range if the range type string is the same 
or <js>"*"</js>.
+        *
+        * @param type The type to match against this range.
+        * @return <jk>true</jk> if the specified type matches this range.
+        */
+       @SuppressWarnings("hiding")
+       public boolean matches(String type) {
+               if (qValue == 0)
+                       return false;
+               return this.type.equals(type) || this.type.equals("*");
+       }
+}

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWritable.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWritable.java 
b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWritable.java
index 5bc7009..b8c504c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWritable.java
+++ b/juneau-core/src/main/java/org/apache/juneau/ini/ConfigFileWritable.java
@@ -15,6 +15,7 @@ package org.apache.juneau.ini;
 import java.io.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 
 /**
  * Wraps a {@link ConfigFile} in a {@link Writable} to be rendered as plain 
text.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
index effd363..5199af1 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/ArrayUtils.java
@@ -271,6 +271,39 @@ public final class ArrayUtils {
        }
 
        /**
+        * Returns <jk>true</jk> if the specified array contains the specified 
element
+        *      using the {@link String#equals(Object)} method.
+        *
+        * @param element The element to check for.
+        * @param array The array to check.
+        * @return <jk>true</jk> if the specified array contains the specified 
element,
+        *      <jk>false</jk> if the array or element is <jk>null</jk>.
+        */
+       public static boolean contains(String element, String[] array) {
+               return indexOf(element, array) != -1;
+       }
+
+       /**
+        * Returns the index position of the element in the specified array
+        *      using the {@link String#equals(Object)} method.
+        *
+        * @param element The element to check for.
+        * @param array The array to check.
+        * @return The index position of the element in the specified array, or
+        *      <code>-1</code> if the array doesn't contain the element, or 
the array or element is <jk>null</jk>.
+        */
+       public static int indexOf(String element, String[] array) {
+               if (element == null)
+                       return -1;
+               if (array == null)
+                       return -1;
+               for (int i = 0; i < array.length; i++)
+                       if (element.equals(array[i]))
+                               return i;
+               return -1;
+       }
+
+       /**
         * Converts a primitive wrapper array (e.g. <code>Integer[]</code>) to 
a primitive array (e.g. <code><jk>int</jk>[]</code>).
         *
         * @param o The array to convert.  Must be a primitive wrapper array.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/internal/CollectionUtils.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/CollectionUtils.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/CollectionUtils.java
index c413427..6c12246 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/CollectionUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/CollectionUtils.java
@@ -52,4 +52,36 @@ public class CollectionUtils {
                        l.add(o);
                return l;
        }
+
+       /**
+        * Adds the contents of one list to the other in reverse order.
+        * <p>
+        * i.e. add values from 2nd list from end-to-start order to the end of 
the 1st list.
+        *
+        * @param list The list to append to.
+        * @param append Contains the values to append to the list.
+        * @return The same list.
+        */
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       public static List<?> addReverse(List list, List append) {
+               for (ListIterator i = append.listIterator(append.size()); 
i.hasPrevious();)
+                       list.add(i.previous());
+               return list;
+       }
+
+       /**
+        * Adds the contents of the array to the list in reverse order.
+        * <p>
+        * i.e. add values from the array from end-to-start order to the end of 
the list.
+        *
+        * @param list The list to append to.
+        * @param append Contains the values to append to the list.
+        * @return The same list.
+        */
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       public static List<?> addReverse(List list, Object[] append) {
+               for (int i = append.length - 1; i >= 0; i--)
+                       list.add(append[i]);
+               return list;
+       }
 }

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java 
b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
index a5bc00e..bee95d9 100644
--- a/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
+++ b/juneau-core/src/main/java/org/apache/juneau/internal/StringUtils.java
@@ -480,6 +480,8 @@ public final class StringUtils {
                        return null;
                if (isEmpty(s))
                        return new String[0];
+               if (s.indexOf(c) == -1)
+                       return new String[]{s};
 
                List<String> l = new LinkedList<String>();
                char[] sArray = s.toCharArray();
@@ -522,6 +524,83 @@ public final class StringUtils {
        }
 
        /**
+        * Splits a list of key-value pairs into an ordered map.
+        * <p>
+        * Example:
+        * <p class='bcode'>
+        *      String in = <js>"foo=1;bar=2"</js>;
+        *      Map m = StringUtils.<jsm>splitMap</jsm>(in, <js>';'</js>, 
<js>'='</js>, <jk>true</jk>);
+        * </p>
+        *
+        * @param s The string to split.
+        * @param delim The delimiter between the key-value pairs.
+        * @param eq The delimiter between the key and value.
+        * @param trim Trim strings after parsing.
+        * @return The parsed map.  Never <jk>null</jk>.
+        */
+       @SuppressWarnings("unchecked")
+       public static Map<String,String> splitMap(String s, char delim, char 
eq, boolean trim) {
+
+               char[] unEscapeChars = new char[]{'\\', delim, eq};
+
+               if (s == null)
+                       return null;
+               if (isEmpty(s))
+                       return Collections.EMPTY_MAP;
+
+               Map<String,String> m = new LinkedHashMap<String,String>();
+
+               int
+                       S1 = 1,  // Found start of key, looking for equals.
+                       S2 = 2;  // Found equals, looking for delimiter (or 
end).
+
+               int state = S1;
+
+               char[] sArray = s.toCharArray();
+               int x1 = 0, escapeCount = 0;
+               String key = null;
+               for (int i = 0; i < sArray.length + 1; i++) {
+                       char c = i == sArray.length ? delim : sArray[i];
+                       if (c == '\\')
+                               escapeCount++;
+                       if (escapeCount % 2 == 0) {
+                               if (state == S1) {
+                                       if (c == eq) {
+                                               key = s.substring(x1, i);
+                                               if (trim)
+                                                       key = trim(key);
+                                               key = unEscapeChars(key, 
unEscapeChars);
+                                               state = S2;
+                                               x1 = i+1;
+                                       } else if (c == delim) {
+                                               key = s.substring(x1, i);
+                                               if (trim)
+                                                       key = trim(key);
+                                               key = unEscapeChars(key, 
unEscapeChars);
+                                               m.put(key, "");
+                                               state = S1;
+                                               x1 = i+1;
+                                       }
+                               } else if (state == S2) {
+                                       if (c == delim) {
+                                               String val = s.substring(x1, i);
+                                               if (trim)
+                                                       val = trim(val);
+                                               val = unEscapeChars(val, 
unEscapeChars);
+                                               m.put(key, val);
+                                               key = null;
+                                               x1 = i+1;
+                                               state = S1;
+                                       }
+                               }
+                       }
+                       if (c != '\\') escapeCount = 0;
+               }
+
+               return m;
+       }
+
+       /**
         * Returns <jk>true</jk> if specified string is <jk>null</jk> or empty.
         *
         * @param s The string to check.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserBuilder.java
index 2608028..210460b 100644
--- a/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoParserBuilder.java
@@ -15,6 +15,7 @@ package org.apache.juneau.jso;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.parser.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerBuilder.java
index c4d7d40..88d89c5 100644
--- a/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/jso/JsoSerializerBuilder.java
@@ -15,6 +15,7 @@ package org.apache.juneau.jso;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
----------------------------------------------------------------------
diff --git a/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
index d362112..c07c8b7 100644
--- a/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonParser.java
@@ -20,6 +20,7 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.transform.*;

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonParserBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonParserBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonParserBuilder.java
index ed550cc..1d6a3ad 100644
--- a/juneau-core/src/main/java/org/apache/juneau/json/JsonParserBuilder.java
+++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonParserBuilder.java
@@ -15,6 +15,7 @@ package org.apache.juneau.json;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.parser.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java
index 02ae190..f495bca 100644
--- a/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java
+++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonParserSession.java
@@ -17,6 +17,7 @@ import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.parser.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java
index 3e28eab..df974ea 100644
--- a/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializer.java
@@ -20,6 +20,7 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.transform.*;
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerBuilder.java
 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerBuilder.java
index 9a8616f..eb4a1ea 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerBuilder.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSchemaSerializerBuilder.java
@@ -15,6 +15,7 @@ package org.apache.juneau.json;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 
 /**
  * Builder class for building instances of JSON Schema serializers.

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java
index 91945d4..71859f4 100644
--- a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializer.java
@@ -19,6 +19,7 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.transform.*;
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerBuilder.java
index 97323aa..0715dd1 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerBuilder.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerBuilder.java
@@ -17,6 +17,7 @@ import static org.apache.juneau.json.JsonSerializerContext.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
index dd8b8a1..ef1a975 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
@@ -18,6 +18,7 @@ import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
index 8895f33..bd9128c 100644
--- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
+++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParser.java
@@ -19,6 +19,7 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.transform.*;
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserBuilder.java 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserBuilder.java
index 3e68801..26df53b 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserBuilder.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserBuilder.java
@@ -15,6 +15,7 @@ package org.apache.juneau.msgpack;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.parser.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java
index 24a40e7..57beda3 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackParserSession.java
@@ -17,6 +17,7 @@ import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.parser.*;
 
 /**

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java
index 89e736b..e0108ea 100644
--- a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java
+++ b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializer.java
@@ -17,6 +17,7 @@ import java.util.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.transform.*;
 

http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/c3609d05/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerBuilder.java
----------------------------------------------------------------------
diff --git 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerBuilder.java
 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerBuilder.java
index f909e14..2c29538 100644
--- 
a/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerBuilder.java
+++ 
b/juneau-core/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerBuilder.java
@@ -15,6 +15,7 @@ package org.apache.juneau.msgpack;
 import java.util.*;
 
 import org.apache.juneau.*;
+import org.apache.juneau.http.*;
 import org.apache.juneau.serializer.*;
 
 /**


Reply via email to