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    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}