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 }