Coverage Report - org.melati.util.AcceptCharset
 
Classes in this File Line Coverage Branch Coverage Complexity
AcceptCharset
96%
55/57
90%
27/30
2.6
AcceptCharset$1
100%
5/5
100%
2/2
2.6
AcceptCharset$CharsetAndQValue
100%
18/18
100%
2/2
2.6
AcceptCharset$CharsetAndQValueIterator
100%
2/2
N/A
2.6
AcceptCharset$Comparator
100%
7/7
100%
4/4
2.6
 
 1  
 /*
 2  
  * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/util/AcceptCharset.java,v $
 3  
  * $Revision: 1.9 $
 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.Arrays;
 54  
 import java.util.HashMap;
 55  
 import java.util.Iterator;
 56  
 import java.util.List;
 57  
 
 58  
 /**
 59  
  * Representation of the Accept-Charset header fields.
 60  
  * <p>
 61  
  * Provides features for choosing a charset according to client or server
 62  
  * preferences.
 63  
  *
 64  
  * @author  jimw@paneris.org
 65  
  */
 66  
 public class AcceptCharset extends HttpHeader {
 67  
 
 68  
   /**
 69  
    * Preferred and supported charsets.
 70  
    * <p>
 71  
    * These are supported and either explicitly accepted by the client or
 72  
    * preferred by the server.
 73  
    * There may be others that are supported and accepted if the client
 74  
    * included the wildcard * in its acceptable charsets.
 75  
    */
 76  26
   protected HashMap supportedPreferred = new HashMap();
 77  
 
 78  
   /**
 79  
    * Client wildcard * specification if any.
 80  
    */
 81  26
   CharsetAndQValue wildcard = null;
 82  
 
 83  
   /**
 84  
    * The name of the first server preferred charset that is not acceptable
 85  
    * but is supported.
 86  
    * <p>
 87  
    * This may be worth checking by the caller if there are no acceptable
 88  
    * charsets, or the caller can respond with a 406 error code.
 89  
    * <p>
 90  
    * Note that if there is a wildcard then this will be null.
 91  
    */
 92  26
   String firstOther = null;
 93  
 
 94  
   /**
 95  
    * Create an instance from the Accept-Charset header field values and
 96  
    * a set of server preferred charset names given as an array for testing.
 97  
    */
 98  
   public AcceptCharset(String values, String[] serverPreference)
 99  
       throws HttpHeaderException {
 100  5
     this(values, Arrays.asList(serverPreference));
 101  4
   }
 102  
   
 103  
   /**
 104  
    * Create an instance from the Accept-Charset header field values and
 105  
    * a set of server preferred charset names.
 106  
    * <p>
 107  
    * The field values might have appeared in a single Accept-Charset header
 108  
    * or in several that were concatenated with comma separator in order.
 109  
    * This concatenation is often done for the caller, by a servlet
 110  
    * container or something, but it must be done.
 111  
    * <p>
 112  
    * <code>null</code> is taken to mean there were no Accept-Charset header
 113  
    * fields.
 114  
    * <p>
 115  
    * If there are any unsupported charsets they are just ignored.
 116  
    * If the caller wants to ensure the there are not any then it must
 117  
    * check for itself.
 118  
    * <p>
 119  
    * If the same charset is specified more than once (perhaps under
 120  
    * different names or aliases) then the first occurrence is significant.
 121  
    * <p>
 122  
    * The server preferences also provides a list of charsets
 123  
    * used if there is a wildcard specification.
 124  
    * This class does not currently try other available charsets so
 125  
    * to avoid 406 errors to reasonable clients, enough reasonable charsets
 126  
    * must be specified.
 127  
    */
 128  
   public AcceptCharset(String values, List serverPreference) throws HttpHeaderException {
 129  26
     super(values);
 130  26
     int position = 0;
 131  26
     for (CharsetAndQValueIterator i = charsetAndQValueIterator(); 
 132  35
          i.hasNext();) {
 133  11
       CharsetAndQValue c = i.nextCharsetAndQValue();
 134  9
       if (c.isWildcard()) {
 135  2
         wildcard = c;
 136  
         // System.err.println("Tested 1");
 137  
       } else {
 138  
         try {
 139  7
           String n = c.charset.name();
 140  7
           if (supportedPreferred.get(c) == null) {
 141  7
             supportedPreferred.put(n, c);
 142  7
             c.position = position++;
 143  
             // System.err.println("Tested 2");
 144  
           }
 145  
         }
 146  0
         catch (UnsupportedCharsetException uce) {
 147  
           // Continue with next one
 148  0
           uce = null; // shut PMD up          
 149  7
         }
 150  
       }
 151  9
     }
 152  24
     if (wildcard == null) {
 153  22
       Charset latin1 = Charset.forName("ISO-8859-1");
 154  22
       if (supportedPreferred.get(latin1.name()) == null) {
 155  20
         CharsetAndQValue c = new CharsetAndQValue(latin1, 1.0f);
 156  20
         supportedPreferred.put(latin1.name(), c);
 157  
         // System.err.println("Tested 3");
 158  
       }
 159  
     }
 160  99
     for (int i = 0; i < serverPreference.size(); i++) {
 161  
       try {
 162  75
         Charset charset = Charset.forName((String)serverPreference.get(i));
 163  72
         CharsetAndQValue acceptable =
 164  
           (CharsetAndQValue)supportedPreferred.get(charset.name());
 165  72
         if (acceptable == null) {
 166  48
           if (wildcard == null) {
 167  43
             if (firstOther == null) {
 168  22
               firstOther = charset.name();
 169  
               // System.err.println("Tested 4");
 170  
             }
 171  
           } else {
 172  5
             CharsetAndQValue c = new CharsetAndQValue(charset, wildcard);
 173  5
             supportedPreferred.put(charset.name(), c);
 174  5
             c.serverPreferability = i;
 175  
             // System.err.println("Tested 5");
 176  5
           }
 177  
         } else {
 178  24
           if (i < acceptable.serverPreferability) {
 179  24
             acceptable.serverPreferability = i;
 180  
             // System.err.println("Tested 6");
 181  
           }
 182  
         }
 183  
       }
 184  3
       catch (UnsupportedCharsetException uce) {
 185  
         // Ignore this charset
 186  
         // System.err.println("Tested 7");
 187  3
         uce = null; // shut PMD up          
 188  72
       }
 189  
       
 190  
     }
 191  24
   }
 192  
 
 193  
   /**
 194  
    * Enumeration of {@link AcceptCharset.CharsetAndQValue}.
 195  
    */
 196  26
   public class CharsetAndQValueIterator extends TokenAndQValueIterator {
 197  
 
 198  
     /**
 199  
      * @return the next one
 200  
      */
 201  
     public CharsetAndQValue nextCharsetAndQValue() throws HttpHeaderException {
 202  
       // System.err.println("Tested 7a");
 203  11
       return (CharsetAndQValue)AcceptCharset.this.nextTokenAndQValue();
 204  
     }
 205  
   }
 206  
 
 207  
   /**
 208  
    * {@inheritDoc}
 209  
    * @see org.melati.util.HttpHeader#nextTokenAndQValue()
 210  
    */
 211  
   public TokenAndQValue nextTokenAndQValue() throws HttpHeaderException {
 212  
     // System.err.println("Tested 7b");
 213  11
     return new CharsetAndQValue(tokenizer);
 214  
   }
 215  
 
 216  
   /**
 217  
    * Factory method to create and return the next
 218  
    * {@link HttpHeader.TokenAndQValue}.
 219  
    * @return a new Iterator
 220  
    */
 221  
   public CharsetAndQValueIterator charsetAndQValueIterator() {
 222  
     // System.err.println("Tested 7c");
 223  26
     return new CharsetAndQValueIterator();
 224  
   }
 225  
 
 226  26
   private final Comparator clientComparator = new Comparator();
 227  
 
 228  
   /**
 229  
    * @return the first supported charset that is also acceptable to the
 230  
    * client in order of client preference.
 231  
    *  
 232  
    */
 233  
   public String clientChoice() {
 234  
     // System.err.println("Tested 8");
 235  7
     return choice(clientComparator);
 236  
   }
 237  
 
 238  26
   private final Comparator serverComparator = new Comparator() {
 239  26
       protected int compareCharsetAndQValue(CharsetAndQValue one,
 240  
                                    CharsetAndQValue two) {
 241  
         int result;
 242  8
         result = two.serverPreferability - one.serverPreferability;
 243  8
         if (result == 0) {
 244  1
           result = super.compareCharsetAndQValue(one, two);
 245  
           // System.err.println("Tested 9");
 246  
         }
 247  8
         return result;   
 248  
       }
 249  
     };
 250  
 
 251  
   /**
 252  
    * @return the first supported charset also acceptable to the client
 253  
    * in order of server preference.
 254  
    */
 255  
   public String serverChoice() {
 256  
     // System.err.println("Tested 10");
 257  21
     return choice(serverComparator);
 258  
   }
 259  
 
 260  
   /**
 261  
    * If there is none, return null, and the caller can either use an
 262  
    * unacceptable character set or generate a 406 error.
 263  
    *
 264  
    * see #firstOther
 265  
    * @return the first supported charset also acceptable to the client
 266  
    * in order defined by the given {@link Comparator}
 267  
    */
 268  
   public String choice(Comparator comparator) {
 269  28
     CharsetAndQValue best = null;
 270  28
     for (Iterator i = supportedPreferred.values().iterator(); i.hasNext();) {
 271  44
       CharsetAndQValue c = (CharsetAndQValue)i.next();
 272  44
       if (best == null || comparator.compare(c, best) > 0) {
 273  31
         best = c;
 274  
         // System.err.println("Tested 11");
 275  
       }
 276  44
     }
 277  28
     if (best == null || best.q == 0.0) {
 278  
       // System.err.println("Tested 12");
 279  2
       return null;
 280  
     } else {
 281  
       // System.err.println("Tested 13");
 282  26
       return best.charset.name();
 283  
     }
 284  
   }
 285  
 
 286  
   /**
 287  
    * Comparator for comparing {@link AcceptCharset.CharsetAndQValue} objects.
 288  
    */
 289  52
   protected static class Comparator implements java.util.Comparator {
 290  
     
 291  
     /**
 292  
      * {@inheritDoc}
 293  
      * @see java.util.Comparator#compare(T, T)
 294  
      */
 295  
     public final int compare(Object one, Object two) {
 296  
       // System.err.println("Tested 14");
 297  16
       return compareCharsetAndQValue((CharsetAndQValue)one, (CharsetAndQValue)two);
 298  
     }
 299  
     
 300  
     /**
 301  
      * The same as {@link java.util.Comparator#compare(Object, Object)} except
 302  
      * for the type of arguments.
 303  
      * <p>
 304  
      * This default compares according to client requirements.
 305  
      */
 306  
     protected int compareCharsetAndQValue(CharsetAndQValue one, CharsetAndQValue two) {
 307  9
       if (one.q == two.q) {
 308  
         // System.err.println("Tested 15");
 309  5
         return two.position - one.position;
 310  4
       } else if (one.q > two.q) {
 311  
         // System.err.println("Tested 16");
 312  1
         return 1;
 313  
       } else {
 314  
         // System.err.println("Tested 17");
 315  
         //assert one.q < two.q : "Only this possibility";
 316  3
         return -1;
 317  
       }
 318  
     }
 319  
   }
 320  
   
 321  
   /**
 322  
    * A charset and associated qvalue.
 323  
    */
 324  
   public static class CharsetAndQValue extends TokenAndQValue {
 325  
 
 326  
     /**
 327  
      * Java platform charset or null if this is the wildcard.
 328  
      */
 329  36
     Charset charset = null;
 330  
 
 331  
     /**
 332  
      * An integer that is less for more preferable instances from
 333  
      * server point of view.
 334  
      * <p>
 335  
      * It might be the index of the array of supported server
 336  
      * preferences or <code>Integer.MAX_VALUE</code>.
 337  
      */
 338  36
     public int serverPreferability = Integer.MAX_VALUE;
 339  
     
 340  
     /**
 341  
      * An integer that indicates where this charset was explicitly
 342  
      * specified in Accept-Charset relative to others.
 343  
      * <p>
 344  
      * This increases left to right so it could be the actual position
 345  
      * but need not be.
 346  
      * <p>
 347  
      * It is <code>Integer.MAX_VALUE</code> if the charset was not
 348  
      * explicitly specified, regardless of the position of any wildcard.
 349  
      */
 350  36
     public int position = Integer.MAX_VALUE;
 351  
     
 352  
     /**
 353  
      * Create an instance and initialize it by reading a tokenizer.
 354  
      * @param t tokenizer
 355  
      */
 356  
     public CharsetAndQValue(Tokenizer t) throws HttpHeaderException {
 357  11
       super(t);
 358  11
       if (! isWildcard()) {
 359  
         try {
 360  9
           charset = Charset.forName(token);
 361  2
         } catch (UnsupportedCharsetException e) {
 362  2
           throw new HttpHeaderException("Unsupported Character set:", e);
 363  7
         }
 364  
       }
 365  9
     }
 366  
 
 367  
     /**
 368  
      * Creates an instance for the given charset and q value.
 369  
      */
 370  
     public CharsetAndQValue(Charset charset, float q) {
 371  25
       super();
 372  25
       this.token = charset.name();
 373  25
       this.charset = charset;        
 374  25
       this.q = q;
 375  
       // System.err.println("Tested 19");
 376  25
     }
 377  
 
 378  
     /**
 379  
      * Creates an instance for the given <code>Charset</code>
 380  
      * using the q value from a parsed wildcard Accept-Charset field.
 381  
      */
 382  
     public CharsetAndQValue(Charset charset, CharsetAndQValue wildcard) {
 383  5
       this(charset, wildcard.q);
 384  
       // System.err.println("Tested 20");
 385  5
     }
 386  
 
 387  
     /**
 388  
      * @return whether the given charset token is an asterix
 389  
      */
 390  
     public boolean isWildcard() {
 391  
       // System.err.println("Tested 20a");
 392  20
       return token.equals("*");
 393  
     }
 394  
 
 395  
   }
 396  
 
 397  
 } // AcceptCharset
 398  
 
 399  
 /*
 400  
  * MODIFICATIONS
 401  
  * $Log: AcceptCharset.java,v $
 402  
  * Revision 1.9  2007/01/16 10:28:26  timp
 403  
  * Throw HttpHeaderException if charset unrecognised
 404  
  *
 405  
  * Revision 1.8  2007/01/11 13:23:34  timp
 406  
  * Javadoc
 407  
  *
 408  
  * Revision 1.7  2006/05/16 12:42:01  timp
 409  
  * Javadoc - do not refer to private methods
 410  
  *
 411  
  * Revision 1.6  2006/05/05 12:44:06  timp
 412  
  * Shut PMD up
 413  
  *
 414  
  * Revision 1.5  2005/11/19 11:13:22  timp
 415  
  * Comment out assert
 416  
  *
 417  
  * Revision 1.4  2005/01/14 13:15:51  timp
 418  
  * Stop barfing about empty catch blocks
 419  
  *
 420  
  * Revision 1.3  2004/11/25 18:44:34  timp
 421  
  * Avoid * imports
 422  
  *
 423  
  * Revision 1.2  2003/11/19 04:14:06  jimw
 424  
  * Added standard header.
 425  
  *
 426  
  * Revision 1.1  2003/11/14 03:47:41  jimw
 427  
  * Representation of the Accept-Charset header fields.
 428  
  *
 429  
  */