View Javadoc
1   package org.codehaus.plexus.i18n;
2   
3   /*
4    * Copyright 2001-2007 Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.Iterator;
22  import java.util.Locale;
23  import java.util.NoSuchElementException;
24  import java.util.StringTokenizer;
25  
26  /**
27   * Parses the HTTP <code>Accept-Language</code> header as per section
28   * 14.4 of RFC 2068 (HTTP 1.1 header field definitions).
29   *
30   * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
31   * @version $Id: I18NTokenizer.java 6675 2007-07-20 23:05:53Z olamy $
32   *
33   * @todo Move this class out of here as its purely web related.
34   */
35  public class I18NTokenizer
36      implements Iterator
37  {
38      /**
39       * Separates elements of the <code>Accept-Language</code> HTTP
40       * header.
41       */
42      private static final String LOCALE_SEPARATOR = ",";
43  
44      /**
45       * Separates locale from quality within elements.
46       */
47      private static final char QUALITY_SEPARATOR = ';';
48  
49      /**
50       * The default quality value for an <code>AcceptLanguage</code>
51       * object.
52       */
53      private static final Float DEFAULT_QUALITY = new Float(1.0f);
54  
55      /**
56       * The parsed locales.
57       */
58      private ArrayList locales = new ArrayList(3);
59  
60      /**
61       * Parses the <code>Accept-Language</code> header.
62       *
63       * @param header The <code>Accept-Language</code> header
64       * (i.e. <code>en, es;q=0.8, zh-TW;q=0.1</code>).
65       */
66      public I18NTokenizer(String header)
67      {
68          StringTokenizer tok = new StringTokenizer(header, LOCALE_SEPARATOR);
69          while (tok.hasMoreTokens())
70          {
71              AcceptLanguage acceptLang = new AcceptLanguage();
72              String element = tok.nextToken().trim();
73              int index;
74  
75              // Record and cut off any quality value that comes after a
76              // semi-colon.
77              if ( (index = element.indexOf(QUALITY_SEPARATOR)) != -1 )
78              {
79                  String q = element.substring(index);
80                  element = element.substring(0, index);
81                  if ( (index = q.indexOf('=')) != -1 )
82                  {
83                      try
84                      {
85                          acceptLang.quality =
86                              Float.valueOf(q.substring(index + 1));
87                      }
88                      catch (NumberFormatException useDefault)
89                      {
90                      }
91                  }
92              }
93  
94              element = element.trim();
95  
96              // Create a Locale from the language.  A dash may separate the
97              // language from the country.
98              if ( (index = element.indexOf('-')) == -1 )
99              {
100                 // No dash means no country.
101                 acceptLang.locale = new Locale(element, "");
102             }
103             else
104             {
105                 acceptLang.locale = new Locale(element.substring(0, index),
106                                                element.substring(index + 1));
107             }
108 
109             locales.add(acceptLang);
110         }
111 
112         // Sort by quality in descending order.
113         Collections.sort(locales, Collections.reverseOrder());
114     }
115 
116     /**
117      * @return Whether there are more locales.
118      */
119     public boolean hasNext()
120     {
121         return !locales.isEmpty();
122     }
123 
124     /**
125      * Creates a <code>Locale</code> from the next element of the
126      * <code>Accept-Language</code> header.
127      *
128      * @return The next highest-rated <code>Locale</code>.
129      * @throws NoSuchElementException No more locales.
130      */
131     public Object next()
132     {
133         if (locales.isEmpty())
134         {
135             throw new NoSuchElementException();
136         }
137         return ((AcceptLanguage) locales.remove(0)).locale;
138     }
139 
140     /**
141      * Not implemented.
142      */
143     public final void remove()
144     {
145         throw new UnsupportedOperationException(getClass().getName() +
146                                                 " does not support remove()");
147     }
148 
149     /**
150      * Struct representing an element of the HTTP
151      * <code>Accept-Language</code> header.
152      */
153     private class AcceptLanguage implements Comparable
154     {
155         /**
156          * The language and country.
157          */
158         Locale locale;
159 
160         /**
161          * The quality of our locale (as values approach
162          * <code>1.0</code>, they indicate increased user preference).
163          */
164         Float quality = DEFAULT_QUALITY;
165 
166         public final int compareTo(Object acceptLang)
167         {
168             return quality.compareTo( ((AcceptLanguage) acceptLang).quality );
169         }
170     }
171 }