Coverage Report - com.mattunderscore.http.headers.content.type.QContentType
 
Classes in this File Line Coverage Branch Coverage Complexity
QContentType
94%
88/93
96%
73/76
6.273
 
 1  
 /* Copyright © 2012, 2013 Matthew Champion
 2  
 All rights reserved.
 3  
 
 4  
 Redistribution and use in source and binary forms, with or without
 5  
 modification, are permitted provided that the following conditions are met:
 6  
  * Redistributions of source code must retain the above copyright
 7  
       notice, this list of conditions and the following disclaimer.
 8  
  * Redistributions in binary form must reproduce the above copyright
 9  
       notice, this list of conditions and the following disclaimer in the
 10  
       documentation and/or other materials provided with the distribution.
 11  
  * Neither the name of mattunderscore.com nor the
 12  
       names of its contributors may be used to endorse or promote products
 13  
       derived from this software without specific prior written permission.
 14  
 
 15  
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 16  
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 17  
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 18  
 DISCLAIMED. IN NO EVENT SHALL MATTHEW CHAMPION BE LIABLE FOR ANY
 19  
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 20  
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 21  
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 22  
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 23  
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 24  
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
 25  
 
 26  
 package com.mattunderscore.http.headers.content.type;
 27  
 
 28  
 import java.util.ArrayList;
 29  
 import java.util.Collections;
 30  
 import java.util.HashMap;
 31  
 import java.util.List;
 32  
 import java.util.Map;
 33  
 import java.util.Set;
 34  
 
 35  
 import javax.servlet.http.HttpServletRequest;
 36  
 
 37  
 import com.mattunderscore.http.headers.HeaderFieldElement;
 38  
 import com.mattunderscore.http.headers.Qualified;
 39  
 import com.mattunderscore.http.headers.UnParsableHeaderException;
 40  
 
 41  
 /**
 42  
  * A parsed accept header field value with a qualification value.
 43  
  * <P>
 44  
  * The to string value is calculated when first accessed and cached subsequently. The value is
 45  
  * checked and set atomically. Concurrent access may cause different String objects to be cached and
 46  
  * returned but they will be equal. A happens before relationship is not established by calls to
 47  
  * {@link #toString()}.
 48  
  * 
 49  
  * @author Matt Champion
 50  
  * @since 0.0.13
 51  
  */
 52  0
 public class QContentType implements HeaderFieldElement, Qualified, Cloneable
 53  
 {
 54  
 
 55  2
     public static final QContentType TEXT_HTML = new QContentType("text", "html", null, 1.0);
 56  2
     public static final QContentType APP_XHTML = new QContentType("application", "xhtml+xml", null,
 57  
             1.0);
 58  2
     public static final QContentType APP_RDF =
 59  
             new QContentType("application", "rdf+xml", null, 1.0);
 60  
 
 61  
     private static final int HASH_SEED = 13;
 62  
     private static final int HASH_MULT = 5;
 63  
     private static final String WILDCARD = "*";
 64  
 
 65  
     private final String type;
 66  
     private final String subType;
 67  
     private final Map<String, String> parameters;
 68  
     private final double qualifier;
 69  
 
 70  
     private String toString;
 71  
 
 72  
     /**
 73  
      * Constructor for qualified content types.
 74  
      * <P>
 75  
      * Creates an immutable object. The type and subtype may not be null. The value of the qualifier
 76  
      * must be between 0.0 and 1.0.
 77  
      * 
 78  
      * @param type
 79  
      *            Type of the content type
 80  
      * @param subType
 81  
      *            Subtype of the content type
 82  
      * @param parameters
 83  
      *            Map of additional parameters
 84  
      * @param qualifier
 85  
      *            The qualifier of the content type
 86  
      * @throws IllegalArgumentException
 87  
      *             If type or subtype are null or if the qualifier is less than zero or greater than
 88  
      *             one
 89  
      */
 90  
     public QContentType(String type, String subType, Map<String, String> parameters,
 91  
             double qualifier)
 92  1266
     {
 93  1266
         if (type == null)
 94  
         {
 95  2
             throw new IllegalArgumentException("Type must not be null.");
 96  
         }
 97  1264
         else if (subType == null)
 98  
         {
 99  2
             throw new IllegalArgumentException("SubType must not be null.");
 100  
         }
 101  1262
         else if (qualifier < 0.0 || qualifier > 1.0)
 102  
         {
 103  4
             throw new IllegalArgumentException("Qualifier must be between 0.0 and 1.0");
 104  
         }
 105  
         else
 106  
         {
 107  1258
             this.type = type;
 108  1258
             this.subType = subType;
 109  1258
             if (parameters != null)
 110  
             {
 111  692
                 this.parameters = Collections.unmodifiableMap(new HashMap<String, String>(
 112  
                         parameters));
 113  
             }
 114  
             else
 115  
             {
 116  566
                 this.parameters = null;
 117  
             }
 118  1258
             this.qualifier = qualifier;
 119  
         }
 120  1258
     }
 121  
 
 122  
     /**
 123  
      * Parser for HttpServletRequest to QContentType
 124  
      * 
 125  
      * @param request
 126  
      *            The HTTP request to parse
 127  
      * @return The List of content types in the accept header.
 128  
      * @throws UnParsableHeaderException
 129  
      * @since 0.0.13
 130  
      */
 131  
     public static List<QContentType> getRequestContentTypes(HttpServletRequest request)
 132  
             throws UnParsableHeaderException
 133  
     {
 134  8
         final ContentTypeParser parser = new ContentTypeParser();
 135  8
         final List<QContentType> types = new ArrayList<QContentType>();
 136  8
         final String headerField = request.getHeader(parser.getHeaderFieldName());
 137  8
         types.addAll(parser.parseElements(headerField));
 138  8
         return types;
 139  
     }
 140  
 
 141  
     /**
 142  
      * Returns a string representation of the object.
 143  
      * <P>
 144  
      * The string representation of the object is constructed lazily, it may be initialised multiple
 145  
      * times and may not be aware of other threads setting it. The objects returned will be equal
 146  
      * regardless of the thread returning the value.
 147  
      * 
 148  
      * @return A string representation of this object
 149  
      * @see java.lang.Object#toString()
 150  
      */
 151  
     @Override
 152  
     public String toString()
 153  
     {
 154  30
         String theToString = this.toString;
 155  30
         if (theToString != null)
 156  
         {
 157  0
             return theToString;
 158  
         }
 159  
         else
 160  
         {
 161  30
             theToString = type + "/" + subType + ";q=" + getQualifier();
 162  30
             if (parameters != null)
 163  
             {
 164  22
                 for (final String name : parameters.keySet())
 165  
                 {
 166  6
                     theToString += ";" + name + "=" + parameters.get(name);
 167  6
                 }
 168  
             }
 169  30
             this.toString = theToString;
 170  30
             return theToString;
 171  
         }
 172  
     }
 173  
 
 174  
     @Override
 175  
     public final double getQualifier()
 176  
     {
 177  30
         return qualifier;
 178  
     }
 179  
 
 180  
     /**
 181  
      * Method that returns true if the two content types can be matched against one another. Takes
 182  
      * into account wildcard matching.
 183  
      * 
 184  
      * @param qct
 185  
      * @return True if they match
 186  
      * @since 0.0.13
 187  
      */
 188  
     public final boolean sameType(QContentType qct)
 189  
     {
 190  864
         if (type.equals(qct.type) && subType.equals(qct.subType))
 191  
         {
 192  132
             return true;
 193  
         }
 194  732
         else if (type.equals(qct.type)
 195  280
                 && (WILDCARD.equals(subType) || WILDCARD.equals(qct.subType)))
 196  
         {
 197  240
             return true;
 198  
         }
 199  492
         else if ((WILDCARD.equals(type) || WILDCARD.equals(qct.type))
 200  132
                 && subType.equals(qct.subType))
 201  
         {
 202  12
             return true;
 203  
         }
 204  
         else
 205  
         {
 206  480
             return ((WILDCARD.equals(type) || WILDCARD.equals(qct.type)) && (WILDCARD
 207  120
                     .equals(subType) || WILDCARD.equals(qct.subType)));
 208  
         }
 209  
     }
 210  
 
 211  
     /**
 212  
      * Test to see if the subtype is a wildcard.
 213  
      * 
 214  
      * @return True if the sub type is a wildcard
 215  
      * @since 0.0.13
 216  
      */
 217  
     public final boolean isAnySubType()
 218  
     {
 219  6
         return WILDCARD.equals(subType);
 220  
     }
 221  
 
 222  
     /**
 223  
      * Test to see if the type is a wildcard.
 224  
      * 
 225  
      * @return True if the type is a wild card.
 226  
      * @since 0.0.13
 227  
      */
 228  
     public boolean isAnyType()
 229  
     {
 230  10
         return WILDCARD.equals(type);
 231  
     }
 232  
 
 233  
     @Override
 234  
     public int hashCode()
 235  
     {
 236  72
         int hashCode = HASH_SEED;
 237  72
         hashCode = (hashCode * HASH_MULT) + type.hashCode();
 238  72
         hashCode = (hashCode * HASH_MULT) + subType.hashCode();
 239  72
         if (parameters != null)
 240  
         {
 241  40
             for (String key : parameters.keySet())
 242  
             {
 243  8
                 hashCode = (hashCode * HASH_MULT) + key.hashCode();
 244  8
                 hashCode = (hashCode * HASH_MULT) + parameters.get(key).hashCode();
 245  8
             }
 246  
         }
 247  72
         long temp = Double.doubleToLongBits(qualifier);
 248  72
         hashCode = (hashCode * HASH_MULT) + (int) (temp ^ (temp >>> 32));
 249  72
         return hashCode;
 250  
     }
 251  
 
 252  
     @Override
 253  
     public boolean equals(Object object)
 254  
     {
 255  222
         if (object == null)
 256  
         {
 257  4
             return false;
 258  
         }
 259  218
         else if (this == object)
 260  
         {
 261  0
             return true;
 262  
         }
 263  218
         else if (object.getClass().equals(getClass()))
 264  
         {
 265  214
             final QContentType contentType = (QContentType) object;
 266  214
             return equalObjects(contentType);
 267  
         }
 268  
         else
 269  
         {
 270  4
             return false;
 271  
         }
 272  
     }
 273  
 
 274  
     /**
 275  
      * Equality check for a different, non-null object of the correct type.
 276  
      * 
 277  
      * @param that
 278  
      *            Other object
 279  
      * @return True if are equal objects
 280  
      */
 281  
     private boolean equalObjects(QContentType that)
 282  
     {
 283  214
         if (!(this.type.equals(that.type) &&
 284  196
                 this.subType.equals(that.subType) &&
 285  
                 this.qualifier == that.qualifier))
 286  
         {
 287  78
             return false;
 288  
         }
 289  
         else
 290  
         {
 291  136
             if (parameters == null)
 292  
             {
 293  48
                 if (that.parameters == null || that.parameters.size() == 0)
 294  
                 {
 295  44
                     return true;
 296  
                 }
 297  
                 else
 298  
                 {
 299  4
                     return false;
 300  
                 }
 301  
             }
 302  88
             else if (parameters.size() == 0)
 303  
             {
 304  48
                 if (that.parameters == null || that.parameters.size() == 0)
 305  
                 {
 306  38
                     return true;
 307  
                 }
 308  
                 else
 309  
                 {
 310  10
                     return false;
 311  
                 }
 312  
             }
 313  40
             else if (that.parameters == null)
 314  
             {
 315  4
                 return false;
 316  
             }
 317  36
             else if (parameters.size() == that.parameters.size())
 318  
             {
 319  26
                 final Set<String> keys = parameters.keySet();
 320  26
                 for (final String key : keys)
 321  
                 {
 322  26
                     final String value0 = parameters.get(key);
 323  26
                     final String value1 = that.parameters.get(key);
 324  26
                     final boolean objectsEqual = value0.equals(value1);
 325  26
                     if (!objectsEqual)
 326  
                     {
 327  12
                         return false;
 328  
                     }
 329  14
                 }
 330  14
                 return true;
 331  
             }
 332  
             else
 333  
             {
 334  10
                 return false;
 335  
             }
 336  
         }
 337  
     }
 338  
 
 339  
     /**
 340  
      * @return Clone object
 341  
      * @see java.lang.Object#clone()
 342  
      * @deprecated There is not real value in cloning this object, the only mutable value is the.
 343  
      *             cached toString value
 344  
      */
 345  
     @Override
 346  
     @Deprecated
 347  
     public QContentType clone()
 348  
     {
 349  
         try
 350  
         {
 351  20
             final QContentType newObject = (QContentType) super.clone();
 352  20
             return newObject;
 353  
         }
 354  0
         catch (final CloneNotSupportedException e)
 355  
         {
 356  0
             throw new AssertionError(e);
 357  
         }
 358  
     }
 359  
 }