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    }