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 }