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 1541239 2013-11-12 21:20:05Z ggregory $
052 */
053public 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(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
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(final Set<String> languages) {
085            this.languages = Collections.unmodifiableSet(languages);
086        }
087
088        @Override
089        public boolean contains(final 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(final LanguageSet other) {
114            if (other == NO_LANGUAGES) {
115                return other;
116            } else if (other == ANY_LANGUAGE) {
117                return this;
118            } else {
119                final SomeLanguages sl = (SomeLanguages) other;
120                final Set<String> ls = new HashSet<String>(Math.min(languages.size(), sl.languages.size()));
121                for (String lang : languages) {
122                    if (sl.languages.contains(lang)) {
123                        ls.add(lang);
124                    }
125                }
126                return from(ls);
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 (final NameType s : NameType.values()) {
143            LANGUAGES.put(s, getInstance(langResourceName(s)));
144        }
145    }
146
147    public static Languages getInstance(final NameType nameType) {
148        return LANGUAGES.get(nameType);
149    }
150
151    public static Languages getInstance(final String languagesResourceName) {
152        // read languages list
153        final Set<String> ls = new HashSet<String>();
154        final 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        final Scanner lsScanner = new Scanner(langIS, ResourceConstants.ENCODING);
161        try {
162            boolean inExtendedComment = false;
163            while (lsScanner.hasNextLine()) {
164                final String line = lsScanner.nextLine().trim();
165                if (inExtendedComment) {
166                    if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
167                        inExtendedComment = false;
168                    }
169                } else {
170                    if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
171                        inExtendedComment = true;
172                    } else if (line.length() > 0) {
173                        ls.add(line);
174                    }
175                }
176            }
177        } finally {
178            lsScanner.close();
179        }
180
181        return new Languages(Collections.unmodifiableSet(ls));
182    }
183
184    private static String langResourceName(final NameType nameType) {
185        return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
186    }
187
188    private final Set<String> languages;
189
190    /**
191     * No languages at all.
192     */
193    public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
194        @Override
195        public boolean contains(final String language) {
196            return false;
197        }
198
199        @Override
200        public String getAny() {
201            throw new NoSuchElementException("Can't fetch any language from the empty language set.");
202        }
203
204        @Override
205        public boolean isEmpty() {
206            return true;
207        }
208
209        @Override
210        public boolean isSingleton() {
211            return false;
212        }
213
214        @Override
215        public LanguageSet restrictTo(final LanguageSet other) {
216            return this;
217        }
218
219        @Override
220        public String toString() {
221            return "NO_LANGUAGES";
222        }
223    };
224
225    /**
226     * Any/all languages.
227     */
228    public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
229        @Override
230        public boolean contains(final String language) {
231            return true;
232        }
233
234        @Override
235        public String getAny() {
236            throw new NoSuchElementException("Can't fetch any language from the any language set.");
237        }
238
239        @Override
240        public boolean isEmpty() {
241            return false;
242        }
243
244        @Override
245        public boolean isSingleton() {
246            return false;
247        }
248
249        @Override
250        public LanguageSet restrictTo(final LanguageSet other) {
251            return other;
252        }
253
254        @Override
255        public String toString() {
256            return "ANY_LANGUAGE";
257        }
258    };
259
260    private Languages(final Set<String> languages) {
261        this.languages = languages;
262    }
263
264    public Set<String> getLanguages() {
265        return this.languages;
266    }
267}