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.util.Collections; 021import java.util.EnumMap; 022import java.util.HashSet; 023import java.util.Map; 024import java.util.NoSuchElementException; 025import java.util.Scanner; 026import java.util.Set; 027 028import org.apache.commons.codec.Resources; 029 030/** 031 * Language codes. 032 * <p> 033 * Language codes are typically loaded from resource files. These are UTF-8 034 * encoded text files. They are systematically named following the pattern: 035 * <blockquote>org/apache/commons/codec/language/bm/${{@link NameType#getName()} 036 * languages.txt</blockquote> 037 * <p> 038 * The format of these resources is the following: 039 * <ul> 040 * <li><b>Language:</b> a single string containing no whitespace</li> 041 * <li><b>End-of-line comments:</b> Any occurrence of '//' will cause all text 042 * following on that line to be discarded as a comment.</li> 043 * <li><b>Multi-line comments:</b> Any line starting with '/*' will start 044 * multi-line commenting mode. This will skip all content until a line ending in 045 * '*' and '/' is found.</li> 046 * <li><b>Blank lines:</b> All blank lines will be skipped.</li> 047 * </ul> 048 * <p> 049 * Ported from language.php 050 * <p> 051 * This class is immutable and thread-safe. 052 * 053 * @since 1.6 054 */ 055public class Languages { 056 // Implementation note: This class is divided into two sections. The first part 057 // is a static factory interface that 058 // exposes org/apache/commons/codec/language/bm/%s_languages.txt for %s in 059 // NameType.* as a list of supported 060 // languages, and a second part that provides instance methods for accessing 061 // this set for supported languages. 062 063 /** 064 * A set of languages. 065 */ 066 public static abstract class LanguageSet { 067 068 public static LanguageSet from(final Set<String> langs) { 069 return langs.isEmpty() ? NO_LANGUAGES : new SomeLanguages(langs); 070 } 071 072 public abstract boolean contains(String language); 073 074 public abstract String getAny(); 075 076 public abstract boolean isEmpty(); 077 078 public abstract boolean isSingleton(); 079 080 public abstract LanguageSet restrictTo(LanguageSet other); 081 082 abstract LanguageSet merge(LanguageSet other); 083 } 084 085 /** 086 * Some languages, explicitly enumerated. 087 */ 088 public static final class SomeLanguages extends LanguageSet { 089 private final Set<String> languages; 090 091 private SomeLanguages(final Set<String> languages) { 092 this.languages = Collections.unmodifiableSet(languages); 093 } 094 095 @Override 096 public boolean contains(final String language) { 097 return this.languages.contains(language); 098 } 099 100 @Override 101 public String getAny() { 102 return this.languages.iterator().next(); 103 } 104 105 public Set<String> getLanguages() { 106 return this.languages; 107 } 108 109 @Override 110 public boolean isEmpty() { 111 return this.languages.isEmpty(); 112 } 113 114 @Override 115 public boolean isSingleton() { 116 return this.languages.size() == 1; 117 } 118 119 @Override 120 public LanguageSet restrictTo(final LanguageSet other) { 121 if (other == NO_LANGUAGES) { 122 return other; 123 } else if (other == ANY_LANGUAGE) { 124 return this; 125 } else { 126 final SomeLanguages sl = (SomeLanguages) other; 127 final Set<String> ls = new HashSet<>(Math.min(languages.size(), sl.languages.size())); 128 for (final String lang : languages) { 129 if (sl.languages.contains(lang)) { 130 ls.add(lang); 131 } 132 } 133 return from(ls); 134 } 135 } 136 137 @Override 138 public LanguageSet merge(final LanguageSet other) { 139 if (other == NO_LANGUAGES) { 140 return this; 141 } else if (other == ANY_LANGUAGE) { 142 return other; 143 } else { 144 final SomeLanguages sl = (SomeLanguages) other; 145 final Set<String> ls = new HashSet<>(languages); 146 for (final String lang : sl.languages) { 147 ls.add(lang); 148 } 149 return from(ls); 150 } 151 } 152 153 @Override 154 public String toString() { 155 return "Languages(" + languages.toString() + ")"; 156 } 157 158 } 159 160 public static final String ANY = "any"; 161 162 private static final Map<NameType, Languages> LANGUAGES = new EnumMap<>(NameType.class); 163 164 static { 165 for (final NameType s : NameType.values()) { 166 LANGUAGES.put(s, getInstance(langResourceName(s))); 167 } 168 } 169 170 public static Languages getInstance(final NameType nameType) { 171 return LANGUAGES.get(nameType); 172 } 173 174 public static Languages getInstance(final String languagesResourceName) { 175 // read languages list 176 final Set<String> ls = new HashSet<>(); 177 try (final Scanner lsScanner = new Scanner(Resources.getInputStream(languagesResourceName), 178 ResourceConstants.ENCODING)) { 179 boolean inExtendedComment = false; 180 while (lsScanner.hasNextLine()) { 181 final String line = lsScanner.nextLine().trim(); 182 if (inExtendedComment) { 183 if (line.endsWith(ResourceConstants.EXT_CMT_END)) { 184 inExtendedComment = false; 185 } 186 } else { 187 if (line.startsWith(ResourceConstants.EXT_CMT_START)) { 188 inExtendedComment = true; 189 } else if (line.length() > 0) { 190 ls.add(line); 191 } 192 } 193 } 194 return new Languages(Collections.unmodifiableSet(ls)); 195 } 196 } 197 198 private static String langResourceName(final NameType nameType) { 199 return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName()); 200 } 201 202 private final Set<String> languages; 203 204 /** 205 * No languages at all. 206 */ 207 public static final LanguageSet NO_LANGUAGES = new LanguageSet() { 208 @Override 209 public boolean contains(final String language) { 210 return false; 211 } 212 213 @Override 214 public String getAny() { 215 throw new NoSuchElementException("Can't fetch any language from the empty language set."); 216 } 217 218 @Override 219 public boolean isEmpty() { 220 return true; 221 } 222 223 @Override 224 public boolean isSingleton() { 225 return false; 226 } 227 228 @Override 229 public LanguageSet restrictTo(final LanguageSet other) { 230 return this; 231 } 232 233 @Override 234 public LanguageSet merge(final LanguageSet other) { 235 return other; 236 } 237 238 @Override 239 public String toString() { 240 return "NO_LANGUAGES"; 241 } 242 }; 243 244 /** 245 * Any/all languages. 246 */ 247 public static final LanguageSet ANY_LANGUAGE = new LanguageSet() { 248 @Override 249 public boolean contains(final String language) { 250 return true; 251 } 252 253 @Override 254 public String getAny() { 255 throw new NoSuchElementException("Can't fetch any language from the any language set."); 256 } 257 258 @Override 259 public boolean isEmpty() { 260 return false; 261 } 262 263 @Override 264 public boolean isSingleton() { 265 return false; 266 } 267 268 @Override 269 public LanguageSet restrictTo(final LanguageSet other) { 270 return other; 271 } 272 273 @Override 274 public LanguageSet merge(final LanguageSet other) { 275 return other; 276 } 277 278 @Override 279 public String toString() { 280 return "ANY_LANGUAGE"; 281 } 282 }; 283 284 private Languages(final Set<String> languages) { 285 this.languages = languages; 286 } 287 288 public Set<String> getLanguages() { 289 return this.languages; 290 } 291}