| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package org.apache.tapestry5.ioc.util; |
| |
| import org.apache.tapestry5.ioc.internal.util.InternalUtils; |
| |
| import java.util.Iterator; |
| import java.util.Locale; |
| import java.util.NoSuchElementException; |
| |
| /** |
| * Generates name variations for a given file name or path and a locale. The name variations |
| * are provided in most-specific to least-specific order, so for a path of "Base.ext" and a Locale |
| * of "en_US", the generated names would be "Base_en_US.ext", "Base_en.ext", "Base.ext". |
| * |
| * Implements Iterable, so a LocalizedNameGenerator may be used directly in a for loop. |
| * |
| * This class is not threadsafe. |
| * |
| * @since 5.3 |
| */ |
| public class LocalizedNameGenerator implements Iterator<String>, Iterable<String> |
| { |
| private final int baseNameLength; |
| |
| private final String suffix; |
| |
| private final StringBuilder builder; |
| |
| private final String language; |
| |
| private final String country; |
| |
| private final String variant; |
| |
| private int state; |
| |
| private int prevState; |
| |
| private static final int INITIAL = 0; |
| |
| private static final int LCV = 1; |
| |
| private static final int LC = 2; |
| |
| private static final int LV = 3; |
| |
| private static final int L = 4; |
| |
| private static final int BARE = 5; |
| |
| private static final int EXHAUSTED = 6; |
| |
| /** |
| * Creates a new generator for the given path and locale. |
| * |
| * @param path |
| * non-blank path |
| * @param locale |
| * non-null locale |
| */ |
| public LocalizedNameGenerator(String path, Locale locale) |
| { |
| assert InternalUtils.isNonBlank(path); |
| assert locale != null; |
| |
| int dotx = path.lastIndexOf('.'); |
| |
| // When there is no dot in the name, pretend it exists after the |
| // end of the string. The locale extensions will be tacked on there. |
| |
| if (dotx == -1) |
| dotx = path.length(); |
| |
| String baseName = path.substring(0, dotx); |
| |
| suffix = path.substring(dotx); |
| |
| baseNameLength = dotx; |
| |
| language = locale.getLanguage(); |
| country = locale.getCountry(); |
| variant = locale.getVariant(); |
| |
| state = INITIAL; |
| prevState = INITIAL; |
| |
| builder = new StringBuilder(baseName); |
| |
| advance(); |
| } |
| |
| private void advance() |
| { |
| prevState = state; |
| |
| while (state != EXHAUSTED) |
| { |
| state++; |
| |
| switch (state) |
| { |
| case LCV: |
| |
| if (InternalUtils.isBlank(variant)) |
| continue; |
| |
| return; |
| |
| case LC: |
| |
| if (InternalUtils.isBlank(country)) |
| continue; |
| |
| return; |
| |
| case LV: |
| |
| // If country is null, then we've already generated this string |
| // as state LCV and we can continue directly to state L |
| |
| if (InternalUtils.isBlank(variant) || InternalUtils.isBlank(country)) |
| continue; |
| |
| return; |
| |
| case L: |
| |
| if (InternalUtils.isBlank(language)) |
| continue; |
| |
| return; |
| |
| case BARE: |
| default: |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Returns true if there are more name variants to be returned, false otherwise. |
| */ |
| |
| @Override |
| public boolean hasNext() |
| { |
| return state != EXHAUSTED; |
| } |
| |
| /** |
| * Returns the next localized variant. |
| * |
| * @throws NoSuchElementException |
| * if all variants have been returned. |
| */ |
| |
| @Override |
| public String next() |
| { |
| if (state == EXHAUSTED) |
| throw new NoSuchElementException(); |
| |
| String result = build(); |
| |
| advance(); |
| |
| return result; |
| } |
| |
| private String build() |
| { |
| builder.setLength(baseNameLength); |
| |
| if (state == LC || state == LCV || state == L || state == LV) |
| { |
| builder.append('_'); |
| builder.append(language); |
| } |
| |
| // For LV, we want two underscores between language |
| // and variant. |
| |
| if (state == LC || state == LCV || state == LV) |
| { |
| builder.append('_'); |
| |
| if (state != LV) |
| builder.append(country); |
| } |
| |
| if (state == LV || state == LCV) |
| { |
| builder.append('_'); |
| builder.append(variant); |
| } |
| |
| if (suffix != null) |
| builder.append(suffix); |
| |
| return builder.toString(); |
| } |
| |
| public Locale getCurrentLocale() |
| { |
| switch (prevState) |
| { |
| case LCV: |
| |
| return new Locale(language, country, variant); |
| |
| case LC: |
| |
| return new Locale(language, country, ""); |
| |
| case LV: |
| |
| return new Locale(language, "", variant); |
| |
| case L: |
| |
| return new Locale(language, "", ""); |
| |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * @throws UnsupportedOperationException |
| */ |
| @Override |
| public void remove() |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| /** |
| * So that LNG may be used with the for loop. |
| */ |
| @Override |
| public Iterator<String> iterator() |
| { |
| return this; |
| } |
| |
| } |