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    *      http://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.io.InputStream;
21  import java.util.Collections;
22  import java.util.EnumMap;
23  import java.util.HashSet;
24  import java.util.Map;
25  import java.util.NoSuchElementException;
26  import java.util.Scanner;
27  import java.util.Set;
28  
29  /**
30   * Language codes.
31   * <p>
32   * Language codes are typically loaded from resource files. These are UTF-8 encoded text files. They are
33   * systematically named following the pattern:
34   * <blockquote>org/apache/commons/codec/language/bm/${{@link NameType#getName()} languages.txt</blockquote>
35   * <p>
36   * The format of these resources is the following:
37   * <ul>
38   * <li><b>Language:</b> a single string containing no whitespace</li>
39   * <li><b>End-of-line comments:</b> Any occurrence of '//' will cause all text following on that line to be
40   * discarded as a comment.</li>
41   * <li><b>Multi-line comments:</b> Any line starting with '/*' will start multi-line commenting mode.
42   * This will skip all content until a line ending in '*' and '/' is found.</li>
43   * <li><b>Blank lines:</b> All blank lines will be skipped.</li>
44   * </ul>
45   * <p>
46   * Ported from language.php
47   * <p>
48   * This class is immutable and thread-safe.
49   *
50   * @since 1.6
51   * @version $Id: Languages.java 1636435 2014-11-03 20:37:24Z tn $
52   */
53  public class Languages {
54      // Implementation note: This class is divided into two sections. The first part is a static factory interface that
55      // exposes org/apache/commons/codec/language/bm/%s_languages.txt for %s in NameType.* as a list of supported
56      // languages, and a second part that provides instance methods for accessing this set for supported languages.
57  
58      /**
59       * A set of languages.
60       */
61      public static abstract class LanguageSet {
62  
63          public static LanguageSet from(final Set<String> langs) {
64              return langs.isEmpty() ? NO_LANGUAGES : new SomeLanguages(langs);
65          }
66  
67          public abstract boolean contains(String language);
68  
69          public abstract String getAny();
70  
71          public abstract boolean isEmpty();
72  
73          public abstract boolean isSingleton();
74  
75          public abstract LanguageSet restrictTo(LanguageSet other);
76  
77          abstract LanguageSet merge(LanguageSet other);
78      }
79  
80      /**
81       * Some languages, explicitly enumerated.
82       */
83      public static final class SomeLanguages extends LanguageSet {
84          private final Set<String> languages;
85  
86          private SomeLanguages(final Set<String> languages) {
87              this.languages = Collections.unmodifiableSet(languages);
88          }
89  
90          @Override
91          public boolean contains(final String language) {
92              return this.languages.contains(language);
93          }
94  
95          @Override
96          public String getAny() {
97              return this.languages.iterator().next();
98          }
99  
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 (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 (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 }