Coverage Report - org.melati.util.AcceptCharset
 
Classes in this File Line Coverage Branch Coverage Complexity
AcceptCharset
96%
63/65
76%
35/46
3.429
AcceptCharset$1
80%
4/5
50%
1/2
3.429
AcceptCharset$CharsetAndQValue
100%
18/18
100%
2/2
3.429
AcceptCharset$CharsetAndQValueIterator
100%
2/2
N/A
3.429
AcceptCharset$Comparator
85%
6/7
75%
3/4
3.429
 
 1  
 /*
 2  
  * $Source: /usr/cvsroot/melati/melati/src/site/resources/withWebmacro/org.melati.util.AcceptCharset.html,v $
 3  
  * $Revision: 1.1 $
 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  1189
   private boolean debug = false;
 68  
   
 69  
   /**
 70  
    * Charsets supported by the jvm and accepted by the client 
 71  
    * or preferred by the server.
 72  
    */
 73  1189
   protected HashMap<String, CharsetAndQValue> supportedAcceptedOrPreferred = new HashMap<String, CharsetAndQValue>();
 74  
 
 75  
   /**
 76  
    * Client wildcard * specification if any.
 77  
    */
 78  1189
   CharsetAndQValue wildcard = null;
 79  
 
 80  
   /**
 81  
    * The name of the first server preferred charset that is not acceptable
 82  
    * to the client but is supported by the jvm.
 83  
    * <p>
 84  
    * This may be worth checking by the caller if there are no acceptable
 85  
    * charsets, or the caller can respond with a 406 error code.
 86  
    * <p>
 87  
    * Note that if there is a wildcard then this will be null.
 88  
    */
 89  1189
   String firstOther = null;
 90  
 
 91  
   /**
 92  
    * Create an instance from the Accept-Charset header field values and
 93  
    * a set of server preferred charset names.
 94  
    * <p>
 95  
    * The field values might have appeared in a single Accept-Charset header
 96  
    * or in several that were concatenated with comma separator in order.
 97  
    * This concatenation is often done for the caller, by a servlet
 98  
    * container or something, but it must be done.
 99  
    * <p>
 100  
    * <code>null</code> is taken to mean there were no Accept-Charset header
 101  
    * fields.
 102  
    * <p>
 103  
    * If a client supported charset is unsupported by the JVM it is ignored.
 104  
    * If the caller wants to ensure that there are none then it must check 
 105  
    * for itself.
 106  
    * <p>
 107  
    * If the same charset is specified more than once (perhaps under
 108  
    * different names or aliases) then the first occurrence is significant.
 109  
    * <p>
 110  
    * The server preferences provides a list of charsets used if there is 
 111  
    * a wildcard specification.
 112  
    * 
 113  
    * This class does not currently try other available charsets so
 114  
    * to avoid 406 errors to reasonable clients, enough reasonable charsets
 115  
    * must be listed in serverPreferences.
 116  
    */
 117  
   public AcceptCharset(String values, List<String> serverPreference) throws HttpHeaderException {
 118  1189
     super(values);
 119  1189
     if (debug) System.err.println("values:" + values);
 120  1189
     if (debug) System.err.println("serverPreference:" + serverPreference);
 121  1189
     int position = 0;
 122  1189
     for (CharsetAndQValueIterator i = charsetAndQValueIterator(); 
 123  1207
          i.hasNext();) {
 124  22
       CharsetAndQValue c = i.nextCharsetAndQValue();
 125  18
       if (c.isWildcard()) {
 126  4
         wildcard = c;
 127  4
         if (debug) System.err.println("Tested 1");
 128  
       } else {
 129  
         try {
 130  14
           String n = c.charset.name();
 131  14
           if (supportedAcceptedOrPreferred.get(c) == null) {
 132  14
             supportedAcceptedOrPreferred.put(n, c);
 133  14
             c.position = position++;
 134  14
              if (debug) System.err.println("Tested 2:" + n);
 135  
           }
 136  
         }
 137  0
         catch (UnsupportedCharsetException uce) {
 138  
           // Continue with next one
 139  0
           uce = null; // shut PMD up          
 140  14
         }
 141  
       }
 142  18
     }
 143  1185
     if (wildcard == null) {
 144  1181
       Charset latin1 = Charset.forName("ISO-8859-1");
 145  1181
       if (supportedAcceptedOrPreferred.get(latin1.name()) == null) {
 146  1177
         CharsetAndQValue c = new CharsetAndQValue(latin1, 1.0f);
 147  1177
         supportedAcceptedOrPreferred.put(latin1.name(), c);
 148  1177
         if (debug) System.err.println("Tested 3 + " + latin1.name());
 149  
       }
 150  
     }
 151  4746
     for (int i = 0; i < serverPreference.size(); i++) {
 152  
       try {
 153  3561
         Charset charset = Charset.forName(serverPreference.get(i));
 154  3555
         CharsetAndQValue acceptable =
 155  
             (CharsetAndQValue)supportedAcceptedOrPreferred.get(charset.name());
 156  3555
         if (acceptable == null) {
 157  2370
           if (wildcard == null) {
 158  2360
             if (firstOther == null) {
 159  1181
               firstOther = charset.name();
 160  1181
               if (debug) System.err.println("Tested 4" + charset.name());
 161  
             }
 162  
           } else {
 163  10
             CharsetAndQValue c = new CharsetAndQValue(charset, wildcard);
 164  10
             supportedAcceptedOrPreferred.put(charset.name(), c);
 165  10
             c.serverPreferability = i;
 166  10
             if (debug) System.err.println("Tested 5:" + charset.name());
 167  10
           }
 168  
         } else {
 169  1185
           supportedAcceptedOrPreferred.put(charset.name(), acceptable);
 170  1185
           if (i < acceptable.serverPreferability) {
 171  1185
             acceptable.serverPreferability = i;
 172  1185
             if (debug) System.err.println("Tested 6");
 173  
           }
 174  
         }
 175  
       }
 176  6
       catch (UnsupportedCharsetException uce) {
 177  
         // Ignore this charset
 178  
         // if (debug) System.err.println("Tested 7");
 179  6
         uce = null; // shut PMD up          
 180  3555
       }
 181  
     }
 182  1185
   }
 183  
 
 184  
   /**
 185  
    * Enumeration of {@link AcceptCharset.CharsetAndQValue}.
 186  
    */
 187  1189
   public class CharsetAndQValueIterator extends TokenAndQValueIterator {
 188  
 
 189  
     /**
 190  
      * @return the next one
 191  
      */
 192  
     public CharsetAndQValue nextCharsetAndQValue() throws HttpHeaderException {
 193  
       // if (debug) System.err.println("Tested 7a");
 194  22
       return (CharsetAndQValue)AcceptCharset.this.nextTokenAndQValue();
 195  
     }
 196  
   }
 197  
 
 198  
   /**
 199  
    * {@inheritDoc}
 200  
    * @see org.melati.util.HttpHeader#nextTokenAndQValue()
 201  
    */
 202  
   public TokenAndQValue nextTokenAndQValue() throws HttpHeaderException {
 203  
     // if (debug) System.err.println("Tested 7b");
 204  22
     return new CharsetAndQValue(tokenizer);
 205  
   }
 206  
 
 207  
   /**
 208  
    * Factory method to create and return the next
 209  
    * {@link HttpHeader.TokenAndQValue}.
 210  
    * @return a new Iterator
 211  
    */
 212  
   public CharsetAndQValueIterator charsetAndQValueIterator() {
 213  
     // if (debug) System.err.println("Tested 7c");
 214  1189
     return new CharsetAndQValueIterator();
 215  
   }
 216  
 
 217  1189
   private final Comparator<CharsetAndQValue> clientComparator = new Comparator<CharsetAndQValue>();
 218  
 
 219  
   /**
 220  
    * @return the first supported charset that is also acceptable to the
 221  
    * client in order of client preference.
 222  
    *  
 223  
    */
 224  
   public String clientChoice() {
 225  
     // if (debug) System.err.println("Tested 8");
 226  1149
     return choice(clientComparator);
 227  
   }
 228  
 
 229  1189
   private final Comparator<CharsetAndQValue> serverComparator = new Comparator<CharsetAndQValue>() {
 230  
       protected int compareCharsetAndQValue(CharsetAndQValue one,
 231  
                                    CharsetAndQValue two) {
 232  
         int result;
 233  16
         result = two.serverPreferability - one.serverPreferability;
 234  16
         if (result == 0) {
 235  0
           result = super.compareCharsetAndQValue(one, two);
 236  
           // if (debug) System.err.println("Tested 9");
 237  
         }
 238  16
         return result;   
 239  
       }
 240  
     };
 241  
 
 242  
   /**
 243  
    * @return the first supported charset also acceptable to the client
 244  
    * in order of server preference.
 245  
    */
 246  
   public String serverChoice() {
 247  
     // if (debug) System.err.println("Tested 10");
 248  44
     return choice(serverComparator);
 249  
   }
 250  
 
 251  
   /**
 252  
    * If there is none, return null, and the caller can either use an
 253  
    * unacceptable character set or generate a 406 error.
 254  
    *
 255  
    * see #firstOther
 256  
    * @return the first supported charset also acceptable to the client
 257  
    * in order defined by the given {@link Comparator}
 258  
    */
 259  
   public String choice(Comparator<CharsetAndQValue> comparator) {
 260  1193
     CharsetAndQValue best = null;
 261  1193
     for (Iterator<CharsetAndQValue> i = supportedAcceptedOrPreferred.values().iterator(); i.hasNext();) {
 262  1225
       CharsetAndQValue c = (CharsetAndQValue)i.next();
 263  1225
       if (best == null || comparator.compare(c, best) > 0) {
 264  1203
         best = c;
 265  
         // if (debug) System.err.println("Tested 11");
 266  
       }
 267  1225
     }
 268  1193
     if (best == null || best.q == 0.0) {
 269  
       // if (debug) System.err.println("Tested 12");
 270  4
       return null;
 271  
     } else {
 272  
       // if (debug) System.err.println("Tested 13");
 273  1189
       return best.charset.name();
 274  
     }
 275  
   }
 276  
 
 277  
   /**
 278  
    * Comparator for comparing {@link AcceptCharset.CharsetAndQValue} objects.
 279  
    */
 280  2378
   protected static class Comparator<T> implements java.util.Comparator<T> {
 281  
     
 282  
     /**
 283  
      * {@inheritDoc}
 284  
      * @see java.util.Comparator#compare(T, T)
 285  
      */
 286  
     public final int compare(Object one, Object two) {
 287  
       // if (debug) System.err.println("Tested 14");
 288  32
       return compareCharsetAndQValue((CharsetAndQValue)one, (CharsetAndQValue)two);
 289  
     }
 290  
     
 291  
     /**
 292  
      * This default compares according to client requirements.
 293  
      */
 294  
     protected int compareCharsetAndQValue(CharsetAndQValue one, CharsetAndQValue two) {
 295  16
       if (one.q == two.q) {
 296  
         // if (debug) System.err.println("Tested 15");
 297  12
         return two.position - one.position;
 298  4
       } else if (one.q > two.q) {
 299  
         // if (debug) System.err.println("Tested 16");
 300  4
         return 1;
 301  
       } else {
 302  
         // if (debug) System.err.println("Tested 17");
 303  
         //assert one.q < two.q : "Only this possibility";
 304  0
         return -1;
 305  
       }
 306  
     }
 307  
   }
 308  
   
 309  
   /**
 310  
    * A charset and associated qvalue.
 311  
    */
 312  
   public static class CharsetAndQValue extends TokenAndQValue {
 313  
 
 314  
     /**
 315  
      * Java platform charset or null if this is the wildcard.
 316  
      */
 317  1209
     Charset charset = null;
 318  
 
 319  
     /**
 320  
      * An integer that is less for more preferable instances from
 321  
      * server point of view.
 322  
      * <p>
 323  
      * It might be the index of the array of supported server
 324  
      * preferences or <code>Integer.MAX_VALUE</code>.
 325  
      */
 326  1209
     public int serverPreferability = Integer.MAX_VALUE;
 327  
     
 328  
     /**
 329  
      * An integer that indicates where this charset was explicitly
 330  
      * specified in Accept-Charset relative to others.
 331  
      * <p>
 332  
      * This increases left to right so it could be the actual position
 333  
      * but need not be.
 334  
      * <p>
 335  
      * It is <code>Integer.MAX_VALUE</code> if the charset was not
 336  
      * explicitly specified, regardless of the position of any wildcard.
 337  
      */
 338  1209
     public int position = Integer.MAX_VALUE;
 339  
     
 340  
     /**
 341  
      * Create an instance and initialize it by reading a tokenizer.
 342  
      * @param t tokenizer
 343  
      */
 344  
     public CharsetAndQValue(Tokenizer t) throws HttpHeaderException {
 345  22
       super(t);
 346  22
       if (! isWildcard()) {
 347  
         try {
 348  18
           charset = Charset.forName(token);
 349  4
         } catch (UnsupportedCharsetException e) {
 350  4
           throw new HttpHeaderException("Unsupported Character set:", e);
 351  14
         }
 352  
       }
 353  18
     }
 354  
 
 355  
     /**
 356  
      * Creates an instance for the given charset and q value.
 357  
      */
 358  
     public CharsetAndQValue(Charset charset, float q) {
 359  1187
       super();
 360  1187
       this.token = charset.name();
 361  1187
       this.charset = charset;        
 362  1187
       this.q = q;
 363  
       // if (debug) System.err.println("Tested 19");
 364  1187
     }
 365  
 
 366  
     /**
 367  
      * Creates an instance for the given <code>Charset</code>
 368  
      * using the q value from a parsed wildcard Accept-Charset field.
 369  
      */
 370  
     public CharsetAndQValue(Charset charset, CharsetAndQValue wildcard) {
 371  10
       this(charset, wildcard.q);
 372  
       // if (debug) System.err.println("Tested 20");
 373  10
     }
 374  
 
 375  
     /**
 376  
      * @return whether the given charset token is an asterix
 377  
      */
 378  
     public boolean isWildcard() {
 379  
       // if (debug) System.err.println("Tested 20a");
 380  40
       return token.equals("*");
 381  
     }
 382  
 
 383  
   }
 384  
 
 385  
 } 
 386  
 
 387