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