View Javadoc

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