001 package org.apache.fulcrum.localization;
002
003
004 /*
005 * Licensed to the Apache Software Foundation (ASF) under one
006 * or more contributor license agreements. See the NOTICE file
007 * distributed with this work for additional information
008 * regarding copyright ownership. The ASF licenses this file
009 * to you under the Apache License, Version 2.0 (the
010 * "License"); you may not use this file except in compliance
011 * with the License. You may obtain a copy of the License at
012 *
013 * http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing,
016 * software distributed under the License is distributed on an
017 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
018 * KIND, either express or implied. See the License for the
019 * specific language governing permissions and limitations
020 * under the License.
021 */
022
023
024 import java.util.ArrayList;
025 import java.util.Collections;
026 import java.util.Iterator;
027 import java.util.Locale;
028 import java.util.NoSuchElementException;
029 import java.util.StringTokenizer;
030
031 /**
032 * Parses the HTTP <code>Accept-Language</code> header as per section
033 * 14.4 of RFC 2068 (HTTP 1.1 header field definitions).
034 *
035 * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
036 * @version $Id: LocaleTokenizer.java 670330 2008-06-22 09:37:21Z tv $
037 */
038 public class LocaleTokenizer
039 implements Iterator
040 {
041 /**
042 * Separates elements of the <code>Accept-Language</code> HTTP
043 * header.
044 */
045 private static final String LOCALE_SEPARATOR = ",";
046
047 /**
048 * Separates locale from quality within elements.
049 */
050 private static final char QUALITY_SEPARATOR = ';';
051
052 /**
053 * The default quality value for an <code>AcceptLanguage</code>
054 * object.
055 */
056 protected static final Float DEFAULT_QUALITY = new Float(1.0f);
057
058 /**
059 * The parsed locales.
060 */
061 private ArrayList locales = new ArrayList(3);
062
063 /**
064 * Parses the <code>Accept-Language</code> header.
065 *
066 * @param header The <code>Accept-Language</code> header
067 * (i.e. <code>en, es;q=0.8, zh-TW;q=0.1</code>).
068 */
069 public LocaleTokenizer(String header)
070 {
071 StringTokenizer tok = new StringTokenizer(header, LOCALE_SEPARATOR);
072 while (tok.hasMoreTokens())
073 {
074 AcceptLanguage acceptLang = new AcceptLanguage();
075 String element = tok.nextToken().trim();
076 int index;
077
078 // Record and cut off any quality value that comes after a
079 // semi-colon.
080 if ( (index = element.indexOf(QUALITY_SEPARATOR)) != -1 )
081 {
082 String q = element.substring(index);
083 element = element.substring(0, index);
084 if ( (index = q.indexOf('=')) != -1 )
085 {
086 try
087 {
088 acceptLang.quality =
089 Float.valueOf(q.substring(index + 1));
090 }
091 catch (NumberFormatException useDefault)
092 {
093 // ignore
094 }
095 }
096 }
097
098 element = element.trim();
099
100 // Create a Locale from the language. A dash may separate the
101 // language from the country.
102 if ( (index = element.indexOf('-')) == -1 )
103 {
104 // No dash means no country.
105 acceptLang.locale = new Locale(element, "");
106 }
107 else
108 {
109 acceptLang.locale = new Locale(element.substring(0, index),
110 element.substring(index + 1));
111 }
112
113 locales.add(acceptLang);
114 }
115
116 // Sort by quality in descending order.
117 Collections.sort(locales, Collections.reverseOrder());
118 }
119
120 /**
121 * @return Whether there are more locales.
122 */
123 public boolean hasNext()
124 {
125 return !locales.isEmpty();
126 }
127
128 /**
129 * Creates a <code>Locale</code> from the next element of the
130 * <code>Accept-Language</code> header.
131 *
132 * @return The next highest-rated <code>Locale</code>.
133 * @throws NoSuchElementException No more locales.
134 */
135 public Object next()
136 {
137 if (locales.isEmpty())
138 {
139 throw new NoSuchElementException();
140 }
141 return ((AcceptLanguage) locales.remove(0)).locale;
142 }
143
144 /**
145 * Not implemented.
146 */
147 public final void remove()
148 {
149 throw new UnsupportedOperationException(getClass().getName() +
150 " does not support remove()");
151 }
152
153 /**
154 * Struct representing an element of the HTTP
155 * <code>Accept-Language</code> header.
156 */
157 protected static class AcceptLanguage implements Comparable
158 {
159 /**
160 * The language and country.
161 */
162 Locale locale;
163
164 /**
165 * The quality of our locale (as values approach
166 * <code>1.0</code>, they indicate increased user preference).
167 */
168 Float quality = DEFAULT_QUALITY;
169
170 public final int compareTo(Object acceptLang)
171 {
172 return quality.compareTo( ((AcceptLanguage) acceptLang).quality );
173 }
174 }
175 }