    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}