    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    }