001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.codec.language.bm;
019    
020    import java.io.InputStream;
021    import java.util.Collections;
022    import java.util.EnumMap;
023    import java.util.HashSet;
024    import java.util.Map;
025    import java.util.NoSuchElementException;
026    import java.util.Scanner;
027    import java.util.Set;
028    
029    /**
030     * Language codes.
031     * <p>
032     * Language codes are typically loaded from resource files. These are UTF-8 encoded text files. They are
033     * systematically named following the pattern:
034     * <blockquote>org/apache/commons/codec/language/bm/${{@link NameType#getName()} languages.txt</blockquote>
035     * <p>
036     * The format of these resources is the following:
037     * <ul>
038     * <li><b>Language:</b> a single string containing no whitespace</li>
039     * <li><b>End-of-line comments:</b> Any occurrence of '//' will cause all text following on that line to be
040     * discarded as a comment.</li>
041     * <li><b>Multi-line comments:</b> Any line starting with '/*' will start multi-line commenting mode.
042     * This will skip all content until a line ending in '*' and '/' is found.</li>
043     * <li><b>Blank lines:</b> All blank lines will be skipped.</li>
044     * </ul>
045     * <p>
046     * Ported from language.php
047     * <p>
048     * This class is immutable and thread-safe.
049     *
050     * @since 1.6
051     * @version $Id: Languages.html 889935 2013-12-11 05:05:13Z ggregory $
052     */
053    public class Languages {
054        // Iimplementation note: This class is divided into two sections. The first part is a static factory interface that
055        // exposes org/apache/commons/codec/language/bm/%s_languages.txt for %s in NameType.* as a list of supported
056        // languages, and a second part that provides instance methods for accessing this set fo supported languages.
057    
058        /**
059         * A set of languages.
060         */
061        public static abstract class LanguageSet {
062    
063            public static LanguageSet from(Set<String> langs) {
064                return langs.isEmpty() ? NO_LANGUAGES : new SomeLanguages(langs);
065            }
066    
067            public abstract boolean contains(String language);
068    
069            public abstract String getAny();
070    
071            public abstract boolean isEmpty();
072    
073            public abstract boolean isSingleton();
074    
075            public abstract LanguageSet restrictTo(LanguageSet other);
076        }
077    
078        /**
079         * Some languages, explicitly enumerated.
080         */
081        public static final class SomeLanguages extends LanguageSet {
082            private final Set<String> languages;
083    
084            private SomeLanguages(Set<String> languages) {
085                this.languages = Collections.unmodifiableSet(languages);
086            }
087    
088            @Override
089            public boolean contains(String language) {
090                return this.languages.contains(language);
091            }
092    
093            @Override
094            public String getAny() {
095                return this.languages.iterator().next();
096            }
097    
098            public Set<String> getLanguages() {
099                return this.languages;
100            }
101    
102            @Override
103            public boolean isEmpty() {
104                return this.languages.isEmpty();
105            }
106    
107            @Override
108            public boolean isSingleton() {
109                return this.languages.size() == 1;
110            }
111    
112            @Override
113            public LanguageSet restrictTo(LanguageSet other) {
114                if (other == NO_LANGUAGES) {
115                    return other;
116                } else if (other == ANY_LANGUAGE) {
117                    return this;
118                } else {
119                    SomeLanguages sl = (SomeLanguages) other;
120                    if (sl.languages.containsAll(languages)) {
121                        return this;
122                    } else {
123                        Set<String> ls = new HashSet<String>(this.languages);
124                        ls.retainAll(sl.languages);
125                        return from(ls);
126                    }
127                }
128            }
129    
130            @Override
131            public String toString() {
132                return "Languages(" + languages.toString() + ")";
133            }
134    
135        }
136    
137        public static final String ANY = "any";
138    
139        private static final Map<NameType, Languages> LANGUAGES = new EnumMap<NameType, Languages>(NameType.class);
140    
141        static {
142            for (NameType s : NameType.values()) {
143                LANGUAGES.put(s, getInstance(langResourceName(s)));
144            }
145        }
146    
147        public static Languages getInstance(NameType nameType) {
148            return LANGUAGES.get(nameType);
149        }
150    
151        public static Languages getInstance(String languagesResourceName) {
152            // read languages list
153            Set<String> ls = new HashSet<String>();
154            InputStream langIS = Languages.class.getClassLoader().getResourceAsStream(languagesResourceName);
155    
156            if (langIS == null) {
157                throw new IllegalArgumentException("Unable to resolve required resource: " + languagesResourceName);
158            }
159    
160            Scanner lsScanner = new Scanner(langIS, ResourceConstants.ENCODING);
161            boolean inExtendedComment = false;
162            while (lsScanner.hasNextLine()) {
163                String line = lsScanner.nextLine().trim();
164                if (inExtendedComment) {
165                    if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
166                        inExtendedComment = false;
167                    }
168                } else {
169                    if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
170                        inExtendedComment = true;
171                    } else if (line.length() > 0) {
172                        ls.add(line);
173                    }
174                }
175            }
176    
177            return new Languages(Collections.unmodifiableSet(ls));
178        }
179    
180        private static String langResourceName(NameType nameType) {
181            return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
182        }
183    
184        private final Set<String> languages;
185    
186        /**
187         * No languages at all.
188         */
189        public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
190            @Override
191            public boolean contains(String language) {
192                return false;
193            }
194    
195            @Override
196            public String getAny() {
197                throw new NoSuchElementException("Can't fetch any language from the empty language set.");
198            }
199    
200            @Override
201            public boolean isEmpty() {
202                return true;
203            }
204    
205            @Override
206            public boolean isSingleton() {
207                return false;
208            }
209    
210            @Override
211            public LanguageSet restrictTo(LanguageSet other) {
212                return this;
213            }
214    
215            @Override
216            public String toString() {
217                return "NO_LANGUAGES";
218            }
219        };
220    
221        /**
222         * Any/all languages.
223         */
224        public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
225            @Override
226            public boolean contains(String language) {
227                return true;
228            }
229    
230            @Override
231            public String getAny() {
232                throw new NoSuchElementException("Can't fetch any language from the any language set.");
233            }
234    
235            @Override
236            public boolean isEmpty() {
237                return false;
238            }
239    
240            @Override
241            public boolean isSingleton() {
242                return false;
243            }
244    
245            @Override
246            public LanguageSet restrictTo(LanguageSet other) {
247                return other;
248            }
249    
250            @Override
251            public String toString() {
252                return "ANY_LANGUAGE";
253            }
254        };
255    
256        private Languages(Set<String> languages) {
257            this.languages = languages;
258        }
259    
260        public Set<String> getLanguages() {
261            return this.languages;
262        }
263    }