View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.codec.language.bm;
19  
20  import java.util.Collections;
21  import java.util.EnumMap;
22  import java.util.HashSet;
23  import java.util.Map;
24  import java.util.NoSuchElementException;
25  import java.util.Scanner;
26  import java.util.Set;
27  import java.util.stream.Collectors;
28  
29  import org.apache.commons.codec.Resources;
30  
31  /**
32   * Language codes.
33   * <p>
34   * Language codes are typically loaded from resource files. These are UTF-8
35   * encoded text files. They are systematically named following the pattern:
36   * </p>
37   * <blockquote>org/apache/commons/codec/language/bm/${{@link NameType#getName()}
38   * languages.txt</blockquote>
39   * <p>
40   * The format of these resources is the following:
41   * </p>
42   * <ul>
43   * <li><strong>Language:</strong> a single string containing no whitespace</li>
44   * <li><strong>End-of-line comments:</strong> Any occurrence of '//' will cause all text
45   * following on that line to be discarded as a comment.</li>
46   * <li><strong>Multi-line comments:</strong> Any line starting with '/*' will start
47   * multi-line commenting mode. This will skip all content until a line ending in
48   * '*' and '/' is found.</li>
49   * <li><strong>Blank lines:</strong> All blank lines will be skipped.</li>
50   * </ul>
51   * <p>
52   * Ported from language.php
53   * </p>
54   * <p>
55   * This class is immutable and thread-safe.
56   * </p>
57   *
58   * @since 1.6
59   */
60  public class Languages {
61      // Implementation note: This class is divided into two sections. The first part
62      // is a static factory interface that
63      // exposes org/apache/commons/codec/language/bm/%s_languages.txt for %s in
64      // NameType.* as a list of supported
65      // languages, and a second part that provides instance methods for accessing
66      // this set for supported languages.
67  
68      /**
69       * A set of languages.
70       */
71      public abstract static class LanguageSet {
72  
73          /**
74           * Gets a language set for the given languages.
75           *
76           * @param languages a language set.
77           * @return a LanguageSet.
78           */
79          public static LanguageSet from(final Set<String> languages) {
80              return languages.isEmpty() ? NO_LANGUAGES : new SomeLanguages(languages);
81          }
82  
83          /**
84           * Constructs a new instance for subclasses.
85           */
86          public LanguageSet() {
87              // empty
88          }
89  
90          /**
91           * Tests whether this instance contains the given value.
92           *
93           * @param language the value to test.
94           * @return whether this instance contains the given value.
95           */
96          public abstract boolean contains(String language);
97  
98          /**
99           * Gets any of this instance's element.
100          *
101          * @return any of this instance's element.
102          */
103         public abstract String getAny();
104 
105         /**
106          * Tests whether this instance is empty.
107          *
108          * @return whether this instance is empty.
109          */
110         public abstract boolean isEmpty();
111 
112         /**
113          * Tests whether this instance contains a single element.
114          *
115          * @return whether this instance contains a single element.
116          */
117         public abstract boolean isSingleton();
118 
119         abstract LanguageSet merge(LanguageSet other);
120 
121         /**
122          * Returns an instance restricted to this instances and the given values'.
123          *
124          * @param other The other instance.
125          * @return an instance restricted to this instances and the given values'.
126          */
127         public abstract LanguageSet restrictTo(LanguageSet other);
128     }
129 
130     /**
131      * Some languages, explicitly enumerated.
132      */
133     public static final class SomeLanguages extends LanguageSet {
134         private final Set<String> languages;
135 
136         private SomeLanguages(final Set<String> languages) {
137             this.languages = Collections.unmodifiableSet(languages);
138         }
139 
140         @Override
141         public boolean contains(final String language) {
142             return this.languages.contains(language);
143         }
144 
145         @Override
146         public String getAny() {
147             return this.languages.iterator().next();
148         }
149 
150         /**
151          * Gets the language strings
152          *
153          * @return the languages strings.
154          */
155         public Set<String> getLanguages() {
156             return this.languages;
157         }
158 
159         @Override
160         public boolean isEmpty() {
161             return this.languages.isEmpty();
162         }
163 
164         @Override
165         public boolean isSingleton() {
166             return this.languages.size() == 1;
167         }
168 
169         @Override
170         public LanguageSet merge(final LanguageSet other) {
171             if (other == NO_LANGUAGES) {
172                 return this;
173             }
174             if (other == ANY_LANGUAGE) {
175                 return other;
176             }
177             final SomeLanguages someLanguages = (SomeLanguages) other;
178             final Set<String> set = new HashSet<>(languages);
179             set.addAll(someLanguages.languages);
180             return from(set);
181         }
182 
183         @Override
184         public LanguageSet restrictTo(final LanguageSet other) {
185             if (other == NO_LANGUAGES) {
186                 return other;
187             }
188             if (other == ANY_LANGUAGE) {
189                 return this;
190             }
191             final SomeLanguages someLanguages = (SomeLanguages) other;
192             return from(languages.stream().filter(lang -> someLanguages.languages.contains(lang)).collect(Collectors.toSet()));
193         }
194 
195         @Override
196         public String toString() {
197             return "Languages(" + languages.toString() + ")";
198         }
199 
200     }
201 
202     /**
203      * Marker for any language.
204      */
205     public static final String ANY = "any";
206 
207     private static final Map<NameType, Languages> LANGUAGES = new EnumMap<>(NameType.class);
208 
209     /**
210      * No languages at all.
211      */
212     public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
213 
214         @Override
215         public boolean contains(final String language) {
216             return false;
217         }
218 
219         @Override
220         public String getAny() {
221             throw new NoSuchElementException("Can't fetch any language from the empty language set.");
222         }
223 
224         @Override
225         public boolean isEmpty() {
226             return true;
227         }
228 
229         @Override
230         public boolean isSingleton() {
231             return false;
232         }
233 
234         @Override
235         public LanguageSet merge(final LanguageSet other) {
236             return other;
237         }
238 
239         @Override
240         public LanguageSet restrictTo(final LanguageSet other) {
241             return this;
242         }
243 
244         @Override
245         public String toString() {
246             return "NO_LANGUAGES";
247         }
248     };
249 
250     /**
251      * Any/all languages.
252      */
253     public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
254 
255         @Override
256         public boolean contains(final String language) {
257             return true;
258         }
259 
260         @Override
261         public String getAny() {
262             throw new NoSuchElementException("Can't fetch any language from the any language set.");
263         }
264 
265         @Override
266         public boolean isEmpty() {
267             return false;
268         }
269 
270         @Override
271         public boolean isSingleton() {
272             return false;
273         }
274 
275         @Override
276         public LanguageSet merge(final LanguageSet other) {
277             return other;
278         }
279 
280         @Override
281         public LanguageSet restrictTo(final LanguageSet other) {
282             return other;
283         }
284 
285         @Override
286         public String toString() {
287             return "ANY_LANGUAGE";
288         }
289     };
290 
291     static {
292         for (final NameType s : NameType.values()) {
293             LANGUAGES.put(s, getInstance(langResourceName(s)));
294         }
295     }
296 
297     /**
298      * Gets an instance for the given name type.
299      *
300      * @param nameType The name type to lookup.
301      * @return an instance for the given name type.
302      */
303     public static Languages getInstance(final NameType nameType) {
304         return LANGUAGES.get(nameType);
305     }
306 
307     /**
308      * Gets a new instance for the given resource name.
309      *
310      * @param languagesResourceName the resource name to lookup.
311      * @return a new instance.
312      */
313     public static Languages getInstance(final String languagesResourceName) {
314         // read languages list
315         final Set<String> ls = new HashSet<>();
316         try (Scanner lsScanner = new Scanner(Resources.getInputStream(languagesResourceName),
317                 ResourceConstants.ENCODING)) {
318             boolean inExtendedComment = false;
319             while (lsScanner.hasNextLine()) {
320                 final String line = lsScanner.nextLine().trim();
321                 if (inExtendedComment) {
322                     if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
323                         inExtendedComment = false;
324                     }
325                 } else if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
326                     inExtendedComment = true;
327                 } else if (!line.isEmpty()) {
328                     ls.add(line);
329                 }
330             }
331             return new Languages(Collections.unmodifiableSet(ls));
332         }
333     }
334 
335     private static String langResourceName(final NameType nameType) {
336         return String.format("/org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
337     }
338 
339     private final Set<String> languages;
340 
341     private Languages(final Set<String> languages) {
342         this.languages = languages;
343     }
344 
345     /**
346      * Gets the language set.
347      *
348      * @return the language set.
349      */
350     public Set<String> getLanguages() {
351         return this.languages;
352     }
353 }