    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}