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
018package org.apache.commons.codec.language.bm;
019
020import java.util.Collections;
021import java.util.EnumMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.NoSuchElementException;
025import java.util.Scanner;
026import java.util.Set;
027
028import org.apache.commons.codec.Resources;
029
030/**
031 * Language codes.
032 * <p>
033 * Language codes are typically loaded from resource files. These are UTF-8
034 * encoded text files. They are systematically named following the pattern:
035 * <blockquote>org/apache/commons/codec/language/bm/${{@link NameType#getName()}
036 * languages.txt</blockquote>
037 * <p>
038 * The format of these resources is the following:
039 * <ul>
040 * <li><b>Language:</b> a single string containing no whitespace</li>
041 * <li><b>End-of-line comments:</b> Any occurrence of '//' will cause all text
042 * following on that line to be discarded as a comment.</li>
043 * <li><b>Multi-line comments:</b> Any line starting with '/*' will start
044 * multi-line commenting mode. This will skip all content until a line ending in
045 * '*' and '/' is found.</li>
046 * <li><b>Blank lines:</b> All blank lines will be skipped.</li>
047 * </ul>
048 * <p>
049 * Ported from language.php
050 * <p>
051 * This class is immutable and thread-safe.
052 *
053 * @since 1.6
054 */
055public class Languages {
056    // Implementation note: This class is divided into two sections. The first part
057    // is a static factory interface that
058    // exposes org/apache/commons/codec/language/bm/%s_languages.txt for %s in
059    // NameType.* as a list of supported
060    // languages, and a second part that provides instance methods for accessing
061    // this set for supported languages.
062
063    /**
064     * A set of languages.
065     */
066    public static abstract class LanguageSet {
067
068        public static LanguageSet from(final Set<String> langs) {
069            return langs.isEmpty() ? NO_LANGUAGES : new SomeLanguages(langs);
070        }
071
072        public abstract boolean contains(String language);
073
074        public abstract String getAny();
075
076        public abstract boolean isEmpty();
077
078        public abstract boolean isSingleton();
079
080        public abstract LanguageSet restrictTo(LanguageSet other);
081
082        abstract LanguageSet merge(LanguageSet other);
083    }
084
085    /**
086     * Some languages, explicitly enumerated.
087     */
088    public static final class SomeLanguages extends LanguageSet {
089        private final Set<String> languages;
090
091        private SomeLanguages(final Set<String> languages) {
092            this.languages = Collections.unmodifiableSet(languages);
093        }
094
095        @Override
096        public boolean contains(final String language) {
097            return this.languages.contains(language);
098        }
099
100        @Override
101        public String getAny() {
102            return this.languages.iterator().next();
103        }
104
105        public Set<String> getLanguages() {
106            return this.languages;
107        }
108
109        @Override
110        public boolean isEmpty() {
111            return this.languages.isEmpty();
112        }
113
114        @Override
115        public boolean isSingleton() {
116            return this.languages.size() == 1;
117        }
118
119        @Override
120        public LanguageSet restrictTo(final LanguageSet other) {
121            if (other == NO_LANGUAGES) {
122                return other;
123            } else if (other == ANY_LANGUAGE) {
124                return this;
125            } else {
126                final SomeLanguages sl = (SomeLanguages) other;
127                final Set<String> ls = new HashSet<>(Math.min(languages.size(), sl.languages.size()));
128                for (final String lang : languages) {
129                    if (sl.languages.contains(lang)) {
130                        ls.add(lang);
131                    }
132                }
133                return from(ls);
134            }
135        }
136
137        @Override
138        public LanguageSet merge(final LanguageSet other) {
139            if (other == NO_LANGUAGES) {
140                return this;
141            } else if (other == ANY_LANGUAGE) {
142                return other;
143            } else {
144                final SomeLanguages sl = (SomeLanguages) other;
145                final Set<String> ls = new HashSet<>(languages);
146                for (final String lang : sl.languages) {
147                    ls.add(lang);
148                }
149                return from(ls);
150            }
151        }
152
153        @Override
154        public String toString() {
155            return "Languages(" + languages.toString() + ")";
156        }
157
158    }
159
160    public static final String ANY = "any";
161
162    private static final Map<NameType, Languages> LANGUAGES = new EnumMap<>(NameType.class);
163
164    static {
165        for (final NameType s : NameType.values()) {
166            LANGUAGES.put(s, getInstance(langResourceName(s)));
167        }
168    }
169
170    public static Languages getInstance(final NameType nameType) {
171        return LANGUAGES.get(nameType);
172    }
173
174    public static Languages getInstance(final String languagesResourceName) {
175        // read languages list
176        final Set<String> ls = new HashSet<>();
177        try (final Scanner lsScanner = new Scanner(Resources.getInputStream(languagesResourceName),
178                ResourceConstants.ENCODING)) {
179            boolean inExtendedComment = false;
180            while (lsScanner.hasNextLine()) {
181                final String line = lsScanner.nextLine().trim();
182                if (inExtendedComment) {
183                    if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
184                        inExtendedComment = false;
185                    }
186                } else {
187                    if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
188                        inExtendedComment = true;
189                    } else if (line.length() > 0) {
190                        ls.add(line);
191                    }
192                }
193            }
194            return new Languages(Collections.unmodifiableSet(ls));
195        }
196    }
197
198    private static String langResourceName(final NameType nameType) {
199        return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
200    }
201
202    private final Set<String> languages;
203
204    /**
205     * No languages at all.
206     */
207    public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
208        @Override
209        public boolean contains(final String language) {
210            return false;
211        }
212
213        @Override
214        public String getAny() {
215            throw new NoSuchElementException("Can't fetch any language from the empty language set.");
216        }
217
218        @Override
219        public boolean isEmpty() {
220            return true;
221        }
222
223        @Override
224        public boolean isSingleton() {
225            return false;
226        }
227
228        @Override
229        public LanguageSet restrictTo(final LanguageSet other) {
230            return this;
231        }
232
233        @Override
234        public LanguageSet merge(final LanguageSet other) {
235            return other;
236        }
237
238        @Override
239        public String toString() {
240            return "NO_LANGUAGES";
241        }
242    };
243
244    /**
245     * Any/all languages.
246     */
247    public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
248        @Override
249        public boolean contains(final String language) {
250            return true;
251        }
252
253        @Override
254        public String getAny() {
255            throw new NoSuchElementException("Can't fetch any language from the any language set.");
256        }
257
258        @Override
259        public boolean isEmpty() {
260            return false;
261        }
262
263        @Override
264        public boolean isSingleton() {
265            return false;
266        }
267
268        @Override
269        public LanguageSet restrictTo(final LanguageSet other) {
270            return other;
271        }
272
273        @Override
274        public LanguageSet merge(final LanguageSet other) {
275            return other;
276        }
277
278        @Override
279        public String toString() {
280            return "ANY_LANGUAGE";
281        }
282    };
283
284    private Languages(final Set<String> languages) {
285        this.languages = languages;
286    }
287
288    public Set<String> getLanguages() {
289        return this.languages;
290    }
291}