001 // Copyright 2006, 2007, 2008, 2011 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry5.ioc.util; 016 017 import java.util.Iterator; 018 import java.util.Locale; 019 import java.util.NoSuchElementException; 020 021 import org.apache.tapestry5.ioc.internal.util.InternalUtils; 022 023 /** 024 * Generates name variations for a given file name or path and a locale. The name variations 025 * are provided in most-specific to least-specific order, so for a path of "Base.ext" and a Locale 026 * of "en_US", the generated names would be "Base_en_US.ext", "Base_en.ext", "Base.ext". 027 * <p> 028 * Implements Iterable, so a LocalizedNameGenerator may be used directly in a for loop. 029 * <p/> 030 * This class is not threadsafe. 031 * 032 * @since 5.3 033 */ 034 public class LocalizedNameGenerator implements Iterator<String>, Iterable<String> 035 { 036 private final int baseNameLength; 037 038 private final String suffix; 039 040 private final StringBuilder builder; 041 042 private final String language; 043 044 private final String country; 045 046 private final String variant; 047 048 private int state; 049 050 private int prevState; 051 052 private static final int INITIAL = 0; 053 054 private static final int LCV = 1; 055 056 private static final int LC = 2; 057 058 private static final int LV = 3; 059 060 private static final int L = 4; 061 062 private static final int BARE = 5; 063 064 private static final int EXHAUSTED = 6; 065 066 /** 067 * Creates a new generator for the given path and locale. 068 * 069 * @param path 070 * non-blank path 071 * @param locale 072 * non-null locale 073 */ 074 public LocalizedNameGenerator(String path, Locale locale) 075 { 076 assert InternalUtils.isNonBlank(path); 077 assert locale != null; 078 079 int dotx = path.lastIndexOf('.'); 080 081 // When there is no dot in the name, pretend it exists after the 082 // end of the string. The locale extensions will be tacked on there. 083 084 if (dotx == -1) 085 dotx = path.length(); 086 087 String baseName = path.substring(0, dotx); 088 089 suffix = path.substring(dotx); 090 091 baseNameLength = dotx; 092 093 language = locale.getLanguage(); 094 country = locale.getCountry(); 095 variant = locale.getVariant(); 096 097 state = INITIAL; 098 prevState = INITIAL; 099 100 builder = new StringBuilder(baseName); 101 102 advance(); 103 } 104 105 private void advance() 106 { 107 prevState = state; 108 109 while (state != EXHAUSTED) 110 { 111 state++; 112 113 switch (state) 114 { 115 case LCV: 116 117 if (InternalUtils.isBlank(variant)) 118 continue; 119 120 return; 121 122 case LC: 123 124 if (InternalUtils.isBlank(country)) 125 continue; 126 127 return; 128 129 case LV: 130 131 // If country is null, then we've already generated this string 132 // as state LCV and we can continue directly to state L 133 134 if (InternalUtils.isBlank(variant) || InternalUtils.isBlank(country)) 135 continue; 136 137 return; 138 139 case L: 140 141 if (InternalUtils.isBlank(language)) 142 continue; 143 144 return; 145 146 case BARE: 147 default: 148 return; 149 } 150 } 151 } 152 153 /** 154 * Returns true if there are more name variants to be returned, false otherwise. 155 */ 156 157 public boolean hasNext() 158 { 159 return state != EXHAUSTED; 160 } 161 162 /** 163 * Returns the next localized variant. 164 * 165 * @throws NoSuchElementException 166 * if all variants have been returned. 167 */ 168 169 public String next() 170 { 171 if (state == EXHAUSTED) 172 throw new NoSuchElementException(); 173 174 String result = build(); 175 176 advance(); 177 178 return result; 179 } 180 181 private String build() 182 { 183 builder.setLength(baseNameLength); 184 185 if (state == LC || state == LCV || state == L) 186 { 187 builder.append('_'); 188 builder.append(language); 189 } 190 191 // For LV, we want two underscores between language 192 // and variant. 193 194 if (state == LC || state == LCV || state == LV) 195 { 196 builder.append('_'); 197 198 if (state != LV) 199 builder.append(country); 200 } 201 202 if (state == LV || state == LCV) 203 { 204 builder.append('_'); 205 builder.append(variant); 206 } 207 208 if (suffix != null) 209 builder.append(suffix); 210 211 return builder.toString(); 212 } 213 214 public Locale getCurrentLocale() 215 { 216 switch (prevState) 217 { 218 case LCV: 219 220 return new Locale(language, country, variant); 221 222 case LC: 223 224 return new Locale(language, country, ""); 225 226 case LV: 227 228 return new Locale(language, "", variant); 229 230 case L: 231 232 return new Locale(language, "", ""); 233 234 default: 235 return null; 236 } 237 } 238 239 /** 240 * @throws UnsupportedOperationException 241 */ 242 public void remove() 243 { 244 throw new UnsupportedOperationException(); 245 } 246 247 /** 248 * So that LNG may be used with the for loop. 249 */ 250 public Iterator<String> iterator() 251 { 252 return this; 253 } 254 255 }