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.io.InputStream;
021import java.util.Collections;
022import java.util.EnumMap;
023import java.util.HashSet;
024import java.util.Map;
025import java.util.NoSuchElementException;
026import java.util.Scanner;
027import 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.java 1694610 2015-08-07 03:47:38Z ggregory $
052 */
053public class Languages {
054    // Implementation 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 for supported languages.
057
058    /**
059     * A set of languages.
060     */
061    public static abstract class LanguageSet {
062
063        public static LanguageSet from(final 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        abstract LanguageSet merge(LanguageSet other);
078    }
079
080    /**
081     * Some languages, explicitly enumerated.
082     */
083    public static final class SomeLanguages extends LanguageSet {
084        private final Set<String> languages;
085
086        private SomeLanguages(final Set<String> languages) {
087            this.languages = Collections.unmodifiableSet(languages);
088        }
089
090        @Override
091        public boolean contains(final String language) {
092            return this.languages.contains(language);
093        }
094
095        @Override
096        public String getAny() {
097            return this.languages.iterator().next();
098        }
099
100        public Set<String> getLanguages() {
101            return this.languages;
102        }
103
104        @Override
105        public boolean isEmpty() {
106            return this.languages.isEmpty();
107        }
108
109        @Override
110        public boolean isSingleton() {
111            return this.languages.size() == 1;
112        }
113
114        @Override
115        public LanguageSet restrictTo(final LanguageSet other) {
116            if (other == NO_LANGUAGES) {
117                return other;
118            } else if (other == ANY_LANGUAGE) {
119                return this;
120            } else {
121                final SomeLanguages sl = (SomeLanguages) other;
122                final Set<String> ls = new HashSet<String>(Math.min(languages.size(), sl.languages.size()));
123                for (final String lang : languages) {
124                    if (sl.languages.contains(lang)) {
125                        ls.add(lang);
126                    }
127                }
128                return from(ls);
129            }
130        }
131
132        @Override
133        public LanguageSet merge(final LanguageSet other) {
134            if (other == NO_LANGUAGES) {
135                return this;
136            } else if (other == ANY_LANGUAGE) {
137                return other;
138            } else {
139                final SomeLanguages sl = (SomeLanguages) other;
140                final Set<String> ls = new HashSet<String>(languages);
141                for (final String lang : sl.languages) {
142                  ls.add(lang);
143                }
144                return from(ls);
145            }
146        }
147
148        @Override
149        public String toString() {
150            return "Languages(" + languages.toString() + ")";
151        }
152
153    }
154
155    public static final String ANY = "any";
156
157    private static final Map<NameType, Languages> LANGUAGES = new EnumMap<NameType, Languages>(NameType.class);
158
159    static {
160        for (final NameType s : NameType.values()) {
161            LANGUAGES.put(s, getInstance(langResourceName(s)));
162        }
163    }
164
165    public static Languages getInstance(final NameType nameType) {
166        return LANGUAGES.get(nameType);
167    }
168
169    public static Languages getInstance(final String languagesResourceName) {
170        // read languages list
171        final Set<String> ls = new HashSet<String>();
172        final InputStream langIS = Languages.class.getClassLoader().getResourceAsStream(languagesResourceName);
173
174        if (langIS == null) {
175            throw new IllegalArgumentException("Unable to resolve required resource: " + languagesResourceName);
176        }
177
178        final Scanner lsScanner = new Scanner(langIS, ResourceConstants.ENCODING);
179        try {
180            boolean inExtendedComment = false;
181            while (lsScanner.hasNextLine()) {
182                final String line = lsScanner.nextLine().trim();
183                if (inExtendedComment) {
184                    if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
185                        inExtendedComment = false;
186                    }
187                } else {
188                    if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
189                        inExtendedComment = true;
190                    } else if (line.length() > 0) {
191                        ls.add(line);
192                    }
193                }
194            }
195        } finally {
196            lsScanner.close();
197        }
198
199        return new Languages(Collections.unmodifiableSet(ls));
200    }
201
202    private static String langResourceName(final NameType nameType) {
203        return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
204    }
205
206    private final Set<String> languages;
207
208    /**
209     * No languages at all.
210     */
211    public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
212        @Override
213        public boolean contains(final String language) {
214            return false;
215        }
216
217        @Override
218        public String getAny() {
219            throw new NoSuchElementException("Can't fetch any language from the empty language set.");
220        }
221
222        @Override
223        public boolean isEmpty() {
224            return true;
225        }
226
227        @Override
228        public boolean isSingleton() {
229            return false;
230        }
231
232        @Override
233        public LanguageSet restrictTo(final LanguageSet other) {
234            return this;
235        }
236
237        @Override
238        public LanguageSet merge(final LanguageSet other) {
239            return other;
240        }
241
242        @Override
243        public String toString() {
244            return "NO_LANGUAGES";
245        }
246    };
247
248    /**
249     * Any/all languages.
250     */
251    public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
252        @Override
253        public boolean contains(final String language) {
254            return true;
255        }
256
257        @Override
258        public String getAny() {
259            throw new NoSuchElementException("Can't fetch any language from the any language set.");
260        }
261
262        @Override
263        public boolean isEmpty() {
264            return false;
265        }
266
267        @Override
268        public boolean isSingleton() {
269            return false;
270        }
271
272        @Override
273        public LanguageSet restrictTo(final LanguageSet other) {
274            return other;
275        }
276
277        @Override
278        public LanguageSet merge(final LanguageSet other) {
279            return other;
280        }
281
282        @Override
283        public String toString() {
284            return "ANY_LANGUAGE";
285        }
286    };
287
288    private Languages(final Set<String> languages) {
289        this.languages = languages;
290    }
291
292    public Set<String> getLanguages() {
293        return this.languages;
294    }
295}