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 }