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
018 package org.apache.commons.codec.language.bm;
019
020 import java.io.InputStream;
021 import java.util.Collections;
022 import java.util.EnumMap;
023 import java.util.HashSet;
024 import java.util.Map;
025 import java.util.NoSuchElementException;
026 import java.util.Scanner;
027 import 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.LanguageSet.html 889935 2013-12-11 05:05:13Z ggregory $
052 */
053 public 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 if (sl.languages.containsAll(languages)) {
121 return this;
122 } else {
123 final Set<String> ls = new HashSet<String>(this.languages);
124 ls.retainAll(sl.languages);
125 return from(ls);
126 }
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 boolean inExtendedComment = false;
162 while (lsScanner.hasNextLine()) {
163 final String line = lsScanner.nextLine().trim();
164 if (inExtendedComment) {
165 if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
166 inExtendedComment = false;
167 }
168 } else {
169 if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
170 inExtendedComment = true;
171 } else if (line.length() > 0) {
172 ls.add(line);
173 }
174 }
175 }
176
177 return new Languages(Collections.unmodifiableSet(ls));
178 }
179
180 private static String langResourceName(final NameType nameType) {
181 return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
182 }
183
184 private final Set<String> languages;
185
186 /**
187 * No languages at all.
188 */
189 public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
190 @Override
191 public boolean contains(final String language) {
192 return false;
193 }
194
195 @Override
196 public String getAny() {
197 throw new NoSuchElementException("Can't fetch any language from the empty language set.");
198 }
199
200 @Override
201 public boolean isEmpty() {
202 return true;
203 }
204
205 @Override
206 public boolean isSingleton() {
207 return false;
208 }
209
210 @Override
211 public LanguageSet restrictTo(final LanguageSet other) {
212 return this;
213 }
214
215 @Override
216 public String toString() {
217 return "NO_LANGUAGES";
218 }
219 };
220
221 /**
222 * Any/all languages.
223 */
224 public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
225 @Override
226 public boolean contains(final String language) {
227 return true;
228 }
229
230 @Override
231 public String getAny() {
232 throw new NoSuchElementException("Can't fetch any language from the any language set.");
233 }
234
235 @Override
236 public boolean isEmpty() {
237 return false;
238 }
239
240 @Override
241 public boolean isSingleton() {
242 return false;
243 }
244
245 @Override
246 public LanguageSet restrictTo(final LanguageSet other) {
247 return other;
248 }
249
250 @Override
251 public String toString() {
252 return "ANY_LANGUAGE";
253 }
254 };
255
256 private Languages(final Set<String> languages) {
257 this.languages = languages;
258 }
259
260 public Set<String> getLanguages() {
261 return this.languages;
262 }
263 }