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.LanguageSet.html 928559 2014-11-10 02:53:54Z 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 (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}