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.*;
20  
21  /**
22   * Parses the HTTP <code>Accept-Language</code> header as per section
23   * 14.4 of RFC 2068 (HTTP 1.1 header field definitions).
24   *
25   * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
26   * @version $Id: I18NTokenizer.java 6675 2007-07-20 23:05:53Z olamy $
27   *
28   * TODO Move this class out of here as its purely web related.
29   */
30  public class I18NTokenizer implements Iterator<Locale> {
31      /**
32       * Separates elements of the <code>Accept-Language</code> HTTP
33       * header.
34       */
35      private static final String LOCALE_SEPARATOR = ",";
36  
37      /**
38       * Separates locale from quality within elements.
39       */
40      private static final char QUALITY_SEPARATOR = ';';
41  
42      /**
43       * The default quality value for an <code>AcceptLanguage</code>
44       * object.
45       */
46      private static final float DEFAULT_QUALITY = 1.0f;
47  
48      /**
49       * The parsed locales.
50       */
51      private final List<AcceptLanguage> locales = new ArrayList<>(3);
52  
53      /**
54       * Parses the <code>Accept-Language</code> header.
55       *
56       * @param header The <code>Accept-Language</code> header
57       * (i.e. <code>en, es;q=0.8, zh-TW;q=0.1</code>).
58       */
59      public I18NTokenizer(String header) {
60          StringTokenizer tok = new StringTokenizer(header, LOCALE_SEPARATOR);
61          while (tok.hasMoreTokens()) {
62              AcceptLanguage acceptLang = new AcceptLanguage();
63              String element = tok.nextToken().trim();
64              int index;
65  
66              // Record and cut off any quality value that comes after a
67              // semi-colon.
68              if ((index = element.indexOf(QUALITY_SEPARATOR)) != -1) {
69                  String q = element.substring(index);
70                  element = element.substring(0, index);
71                  if ((index = q.indexOf('=')) != -1) {
72                      try {
73                          acceptLang.quality = Float.valueOf(q.substring(index + 1));
74                      } catch (NumberFormatException ignored) {
75                      }
76                  }
77              }
78  
79              element = element.trim();
80  
81              // Create a Locale from the language.  A dash may separate the
82              // language from the country.
83              if ((index = element.indexOf('-')) == -1) {
84                  // No dash means no country.
85                  acceptLang.locale = new Locale(element, "");
86              } else {
87                  acceptLang.locale = new Locale(element.substring(0, index), element.substring(index + 1));
88              }
89  
90              locales.add(acceptLang);
91          }
92  
93          // Sort by quality in descending order.
94          locales.sort(Collections.reverseOrder());
95      }
96  
97      /**
98       * @return Whether there are more locales.
99       */
100     public boolean hasNext() {
101         return !locales.isEmpty();
102     }
103 
104     /**
105      * Creates a <code>Locale</code> from the next element of the
106      * <code>Accept-Language</code> header.
107      *
108      * @return The next highest-rated <code>Locale</code>.
109      * @throws NoSuchElementException No more locales.
110      */
111     public Locale next() {
112         if (locales.isEmpty()) {
113             throw new NoSuchElementException();
114         }
115         return locales.remove(0).locale;
116     }
117 
118     /**
119      * Not implemented.
120      */
121     public final void remove() {
122         throw new UnsupportedOperationException(getClass().getName() + " does not support remove()");
123     }
124 
125     /**
126      * Struct representing an element of the HTTP
127      * <code>Accept-Language</code> header.
128      */
129     private static class AcceptLanguage implements Comparable<AcceptLanguage> {
130         /**
131          * The language and country.
132          */
133         Locale locale;
134 
135         /**
136          * The quality of our locale (as values approach
137          * <code>1.0</code>, they indicate increased user preference).
138          */
139         Float quality = DEFAULT_QUALITY;
140 
141         public final int compareTo(AcceptLanguage acceptLang) {
142             return quality.compareTo(acceptLang.quality);
143         }
144     }
145 }