    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
    015package org.apache.tapestry5.ioc.util;
    016
    017import java.util.Iterator;
    018import java.util.Locale;
    019import java.util.NoSuchElementException;
    020
    021import 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 */
    034public 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    @Override
    158    public boolean hasNext()
    159    {
    160        return state != EXHAUSTED;
    161    }
    162
    163    /**
    164     * Returns the next localized variant.
    165     *
    166     * @throws NoSuchElementException
    167     *             if all variants have been returned.
    168     */
    169
    170    @Override
    171    public String next()
    172    {
    173        if (state == EXHAUSTED)
    174            throw new NoSuchElementException();
    175
    176        String result = build();
    177
    178        advance();
    179
    180        return result;
    181    }
    182
    183    private String build()
    184    {
    185        builder.setLength(baseNameLength);
    186
    187        if (state == LC || state == LCV || state == L)
    188        {
    189            builder.append('_');
    190            builder.append(language);
    191        }
    192
    193        // For LV, we want two underscores between language
    194        // and variant.
    195
    196        if (state == LC || state == LCV || state == LV)
    197        {
    198            builder.append('_');
    199
    200            if (state != LV)
    201                builder.append(country);
    202        }
    203
    204        if (state == LV || state == LCV)
    205        {
    206            builder.append('_');
    207            builder.append(variant);
    208        }
    209
    210        if (suffix != null)
    211            builder.append(suffix);
    212
    213        return builder.toString();
    214    }
    215
    216    public Locale getCurrentLocale()
    217    {
    218        switch (prevState)
    219        {
    220            case LCV:
    221
    222                return new Locale(language, country, variant);
    223
    224            case LC:
    225
    226                return new Locale(language, country, "");
    227
    228            case LV:
    229
    230                return new Locale(language, "", variant);
    231
    232            case L:
    233
    234                return new Locale(language, "", "");
    235
    236            default:
    237                return null;
    238        }
    239    }
    240
    241    /**
    242     * @throws UnsupportedOperationException
    243     */
    244    @Override
    245    public void remove()
    246    {
    247        throw new UnsupportedOperationException();
    248    }
    249
    250    /**
    251     * So that LNG may be used with the for loop.
    252     */
    253    @Override
    254    public Iterator<String> iterator()
    255    {
    256        return this;
    257    }
    258
    259}