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    }