##### Coverage Report - org.melati.util.AcceptCharset

Classes in this File | Line Coverage | Branch Coverage | Complexity
AcceptCharset |
96% | 63/65
76% | 35/46
| 3.4285714285714284;3.429
AcceptCharset$1 |
80% | 4/5
50% | 1/2
| 3.4285714285714284;3.429
AcceptCharset$CharsetAndQValue |
100% | 18/18
100% | 2/2
| 3.4285714285714284;3.429
AcceptCharset$CharsetAndQValueIterator |
100% | 2/2
N/A | N/A
| 3.4285714285714284;3.429
AcceptCharset$Comparator |
85% | 6/7
75% | 3/4
| 3.4285714285714284;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 |  |