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