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}