Coverage Report - org.melati.util.AcceptCharset
 
Classes in this File Line Coverage Branch Coverage Complexity
AcceptCharset
100%
56/56
90%
27/30
2.714
AcceptCharset$1
100%
5/5
100%
2/2
2.714
AcceptCharset$CharsetAndQValue
100%
15/15
100%
2/2
2.714
AcceptCharset$CharsetAndQValueIterator
100%
2/2
N/A
2.714
AcceptCharset$Comparator
85%
6/7
75%
3/4
2.714
 
 1  
 /*
 2  
  * $Source$
 3  
  * $Revision$
 4  
  *
 5  
  * Copyright (C) 2003 Jim Wright
 6  
  *
 7  
  * Part of Melati (http://melati.org), a framework for the rapid
 8  
  * development of clean, maintainable web applications.
 9  
  *
 10  
  * Melati is free software; Permission is granted to copy, distribute
 11  
  * and/or modify this software under the terms either:
 12  
  *
 13  
  * a) the GNU General Public License as published by the Free Software
 14  
  *    Foundation; either version 2 of the License, or (at your option)
 15  
  *    any later version,
 16  
  *
 17  
  *    or
 18  
  *
 19  
  * b) any version of the Melati Software License, as published
 20  
  *    at http://melati.org
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License and
 23  
  * the Melati Software License along with this program;
 24  
  * if not, write to the Free Software Foundation, Inc.,
 25  
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 26  
  * GNU General Public License and visit http://melati.org to obtain the
 27  
  * Melati Software License.
 28  
  *
 29  
  * Feel free to contact the Developers of Melati (http://melati.org),
 30  
  * if you would like to work out a different arrangement than the options
 31  
  * outlined here.  It is our intention to allow Melati to be used by as
 32  
  * wide an audience as possible.
 33  
  *
 34  
  * This program is distributed in the hope that it will be useful,
 35  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 36  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 37  
  * GNU General Public License for more details.
 38  
  *
 39  
  * Contact details for copyright holder:
 40  
  *
 41  
  *     Jim Wright <jimw At paneris.org>
 42  
  *     Bohemian Enterprise
 43  
  *     Predmerice nad Jizerou 77
 44  
  *     294 74
 45  
  *     Mlada Boleslav
 46  
  *     Czech Republic
 47  
  */
 48  
 
 49  
 package org.melati.util;
 50  
 
 51  
 import java.nio.charset.Charset;
 52  
 import java.nio.charset.UnsupportedCharsetException;
 53  
 import java.util.HashMap;
 54  
 import java.util.Iterator;
 55  
 import java.util.List;
 56  
 
 57  
 /**
 58  
  * Representation of the Accept-Charset header fields.
 59  
  * <p>
 60  
  * Provides features for choosing a charset according to client or server
 61  
  * preferences.
 62  
  * 
 63  
  * @author jimw At paneris.org
 64  
  */
 65  
 public class AcceptCharset extends HttpHeader {
 66  
 
 67  
 
 68  
   /**
 69  
    * Charsets supported by the jvm and accepted by the client or preferred by
 70  
    * the server.
 71  
    */
 72  575
   protected HashMap<String, CharsetAndQValue> supportedAcceptedOrPreferred = new HashMap<String, CharsetAndQValue>();
 73  
 
 74  
   /**
 75  
    * Client wildcard * specification if any.
 76  
    */
 77  575
   CharsetAndQValue wildcard = null;
 78  
 
 79  
   /**
 80  
    * The name of the first server preferred charset that is not acceptable to
 81  
    * the client but is supported by the jvm.
 82  
    * <p>
 83  
    * This may be worth checking by the caller if there are no acceptable
 84  
    * charsets, or the caller can respond with a 406 error code.
 85  
    * <p>
 86  
    * Note that if there is a wildcard then this will be null.
 87  
    */
 88  575
   String firstOther = null;
 89  
 
 90  
   /**
 91  
    * Create an instance from the Accept-Charset header field values and a set of
 92  
    * server preferred charset names.
 93  
    * <p>
 94  
    * The field values might have appeared in a single Accept-Charset header or
 95  
    * in several that were concatenated with comma separator in order. This
 96  
    * concatenation is often done for the caller, by a servlet container or
 97  
    * something, but it must be done.
 98  
    * <p>
 99  
    * <code>null</code> is taken to mean there were no Accept-Charset header
 100  
    * fields.
 101  
    * <p>
 102  
    * If a client supported charset is unsupported by the JVM it is ignored. If
 103  
    * the caller wants to ensure that there are none then it must check for
 104  
    * itself.
 105  
    * <p>
 106  
    * If the same charset is specified more than once (perhaps under different
 107  
    * names or aliases) then the first occurrence is significant.
 108  
    * <p>
 109  
    * The server preferences provides a list of charsets used if there is a
 110  
    * wildcard specification.
 111  
    * 
 112  
    * This class does not currently try other available charsets so to avoid 406
 113  
    * errors to reasonable clients, enough reasonable charsets must be listed in
 114  
    * serverPreferences.
 115  
    */
 116  
   public AcceptCharset(String values, List<String> serverPreference) {
 117  575
     super(values);
 118  575
     int position = 0;
 119  575
     for (CharsetAndQValueIterator i = charsetAndQValueIterator(); i.hasNext();) {
 120  
       try {
 121  14
         CharsetAndQValue c = i.nextCharsetAndQValue();
 122  11
         if (c.isWildcard()) {
 123  3
           wildcard = c;
 124  
         } else {
 125  8
           String n = c.charset.name();
 126  8
           if (supportedAcceptedOrPreferred.get(c) == null) {
 127  8
             supportedAcceptedOrPreferred.put(n, c);
 128  8
             c.position = position++;
 129  
           }
 130  
         }
 131  3
       } catch (UnsupportedCharsetException uce) {
 132  
         // Continue with next one
 133  3
         uce = null; // shut PMD up
 134  14
       }
 135  
     }
 136  575
     if (wildcard == null) {
 137  572
       Charset latin1 = Charset.forName("ISO-8859-1");
 138  572
       if (supportedAcceptedOrPreferred.get(latin1.name()) == null) {
 139  570
         CharsetAndQValue c = new CharsetAndQValue(latin1, 1.0f);
 140  570
         supportedAcceptedOrPreferred.put(latin1.name(), c);
 141  
       }
 142  
     }
 143  2303
     for (int i = 0; i < serverPreference.size(); i++) {
 144  
       try {
 145  1728
         Charset charset = Charset.forName(serverPreference.get(i));
 146  1725
         CharsetAndQValue acceptable = (CharsetAndQValue) supportedAcceptedOrPreferred
 147  1725
             .get(charset.name());
 148  1725
         if (acceptable == null) {
 149  1150
           if (wildcard == null) {
 150  1143
             if (firstOther == null) {
 151  572
               firstOther = charset.name();
 152  
             }
 153  
           } else {
 154  7
             CharsetAndQValue c = new CharsetAndQValue(charset, wildcard);
 155  7
             supportedAcceptedOrPreferred.put(charset.name(), c);
 156  7
             c.serverPreferability = i;
 157  7
           }
 158  
         } else {
 159  575
           supportedAcceptedOrPreferred.put(charset.name(), acceptable);
 160  575
           if (i < acceptable.serverPreferability) {
 161  575
             acceptable.serverPreferability = i;
 162  
           }
 163  
         }
 164  3
       } catch (UnsupportedCharsetException uce) {
 165  
         // Ignore this charset, go on to next
 166  3
         uce = null; // shut PMD up
 167  1725
       }
 168  
     }
 169  575
   }
 170  
 
 171  
   /**
 172  
    * Enumeration of {@link AcceptCharset.CharsetAndQValue}.
 173  
    */
 174  575
   public class CharsetAndQValueIterator extends TokenAndQValueIterator {
 175  
 
 176  
     /**
 177  
      * @return the next one
 178  
      */
 179  
     public CharsetAndQValue nextCharsetAndQValue() throws HttpHeaderException {
 180  14
       return (CharsetAndQValue) AcceptCharset.this.nextTokenAndQValue();
 181  
     }
 182  
   }
 183  
 
 184  
   /**
 185  
    * {@inheritDoc}
 186  
    * 
 187  
    * @see org.melati.util.HttpHeader#nextTokenAndQValue()
 188  
    */
 189  
   public TokenAndQValue nextTokenAndQValue() {
 190  14
     return new CharsetAndQValue(tokenizer);
 191  
   }
 192  
 
 193  
   /**
 194  
    * Factory method to create and return the next
 195  
    * {@link HttpHeader.TokenAndQValue}.
 196  
    * 
 197  
    * @return a new Iterator
 198  
    */
 199  
   public CharsetAndQValueIterator charsetAndQValueIterator() {
 200  575
     return new CharsetAndQValueIterator();
 201  
   }
 202  
 
 203  575
   private final Comparator<CharsetAndQValue> clientComparator = new Comparator<CharsetAndQValue>();
 204  
 
 205  
   /**
 206  
    * @return the first supported charset that is also acceptable to the client
 207  
    *         in order of client preference.
 208  
    * 
 209  
    */
 210  
   public String clientChoice() {
 211  555
     return choice(clientComparator);
 212  
   }
 213  
 
 214  575
   private final Comparator<CharsetAndQValue> serverComparator = new Comparator<CharsetAndQValue>() {
 215  
     protected int compareCharsetAndQValue(CharsetAndQValue one,
 216  
         CharsetAndQValue two) {
 217  
       int result;
 218  10
       result = two.serverPreferability - one.serverPreferability;
 219  10
       if (result == 0) {
 220  1
         result = super.compareCharsetAndQValue(one, two);
 221  
       }
 222  10
       return result;
 223  
     }
 224  
   };
 225  
 
 226  
   /**
 227  
    * @return the first supported charset also acceptable to the client in order
 228  
    *         of server preference.
 229  
    */
 230  
   public String serverChoice() {
 231  26
     return choice(serverComparator);
 232  
   }
 233  
 
 234  
   /**
 235  
    * If there is none, return null, and the caller can either use an
 236  
    * unacceptable character set or generate a 406 error.
 237  
    * 
 238  
    * see #firstOther
 239  
    * 
 240  
    * @return the first supported charset also acceptable to the client in order
 241  
    *         defined by the given {@link Comparator}
 242  
    */
 243  
   public String choice(Comparator<CharsetAndQValue> comparator) {
 244  581
     CharsetAndQValue best = null;
 245  581
     for (Iterator<CharsetAndQValue> i = supportedAcceptedOrPreferred.values()
 246  581
         .iterator(); i.hasNext();) {
 247  599
       CharsetAndQValue c = (CharsetAndQValue) i.next();
 248  599
       if (best == null || comparator.compare(c, best) > 0) {
 249  588
         best = c;
 250  
       }
 251  599
     }
 252  581
     if (best == null || best.q == 0.0) {
 253  2
       return null;
 254  
     } else {
 255  579
       return best.charset.name();
 256  
     }
 257  
   }
 258  
 
 259  
   /**
 260  
    * Comparator for comparing {@link AcceptCharset.CharsetAndQValue} objects.
 261  
    */
 262  1150
   protected static class Comparator<T> implements java.util.Comparator<T> {
 263  
 
 264  
     @Override
 265  
     public final int compare(Object one, Object two) {
 266  18
       return compareCharsetAndQValue((CharsetAndQValue) one,
 267  
           (CharsetAndQValue) two);
 268  
     }
 269  
 
 270  
     /**
 271  
      * This default compares according to client requirements.
 272  
      */
 273  
     protected int compareCharsetAndQValue(CharsetAndQValue one,
 274  
         CharsetAndQValue two) {
 275  9
       if (one.q == two.q) {
 276  5
         return two.position - one.position;
 277  4
       } else if (one.q > two.q) {
 278  0
         return 1;
 279  
       } else {
 280  
         // assert one.q < two.q : "Only this possibility";
 281  4
         return -1;
 282  
       }
 283  
     }
 284  
   }
 285  
 
 286  
   /**
 287  
    * A charset and associated qvalue.
 288  
    */
 289  
   public static class CharsetAndQValue extends TokenAndQValue {
 290  
 
 291  
     /**
 292  
      * Java platform charset or null if this is the wildcard.
 293  
      */
 294  591
     Charset charset = null;
 295  
 
 296  
     /**
 297  
      * An integer that is less for more preferable instances from server point
 298  
      * of view.
 299  
      * <p>
 300  
      * It might be the index of the array of supported server preferences or
 301  
      * <code>Integer.MAX_VALUE</code>.
 302  
      */
 303  591
     public int serverPreferability = Integer.MAX_VALUE;
 304  
 
 305  
     /**
 306  
      * An integer that indicates where this charset was explicitly specified in
 307  
      * Accept-Charset relative to others.
 308  
      * <p>
 309  
      * This increases left to right so it could be the actual position but need
 310  
      * not be.
 311  
      * <p>
 312  
      * It is <code>Integer.MAX_VALUE</code> if the charset was not explicitly
 313  
      * specified, regardless of the position of any wildcard.
 314  
      */
 315  591
     public int position = Integer.MAX_VALUE;
 316  
 
 317  
     /**
 318  
      * Create an instance and initialize it by reading a tokenizer.
 319  
      * 
 320  
      * @param t
 321  
      *          tokenizer
 322  
      */
 323  
     public CharsetAndQValue(Tokenizer t) {
 324  14
       super(t);
 325  14
       if (!isWildcard()) {
 326  11
         charset = Charset.forName(token);
 327  
       }
 328  11
     }
 329  
 
 330  
     /**
 331  
      * Creates an instance for the given charset and q value.
 332  
      */
 333  
     public CharsetAndQValue(Charset charset, float q) {
 334  577
       super();
 335  577
       this.token = charset.name();
 336  577
       this.charset = charset;
 337  577
       this.q = q;
 338  577
     }
 339  
 
 340  
     /**
 341  
      * Creates an instance for the given <code>Charset</code> using the q value
 342  
      * from a parsed wildcard Accept-Charset field.
 343  
      */
 344  
     public CharsetAndQValue(Charset charset, CharsetAndQValue wildcard) {
 345  7
       this(charset, wildcard.q);
 346  7
     }
 347  
 
 348  
     /**
 349  
      * @return whether the given charset token is an asterix
 350  
      */
 351  
     public boolean isWildcard() {
 352  25
       return token.equals("*");
 353  
     }
 354  
 
 355  
   }
 356  
 
 357  
 }