    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$
    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<>(Math.min(languages.size(), sl.languages.size()));
    123                for (final 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<>(languages);
    141                for (final 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.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<>();
    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        try (final Scanner lsScanner = new Scanner(langIS, 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        }
    195
    196        return new Languages(Collections.unmodifiableSet(ls));
    197    }
    198
    199    private static String langResourceName(final NameType nameType) {
    200        return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
    201    }
    202
    203    private final Set<String> languages;
    204
    205    /**
    206     * No languages at all.
    207     */
    208    public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
    209        @Override
    210        public boolean contains(final String language) {
    211            return false;
    212        }
    213
    214        @Override
    215        public String getAny() {
    216            throw new NoSuchElementException("Can't fetch any language from the empty language set.");
    217        }
    218
    219        @Override
    220        public boolean isEmpty() {
    221            return true;
    222        }
    223
    224        @Override
    225        public boolean isSingleton() {
    226            return false;
    227        }
    228
    229        @Override
    230        public LanguageSet restrictTo(final LanguageSet other) {
    231            return this;
    232        }
    233
    234        @Override
    235        public LanguageSet merge(final LanguageSet other) {
    236            return other;
    237        }
    238
    239        @Override
    240        public String toString() {
    241            return "NO_LANGUAGES";
    242        }
    243    };
    244
    245    /**
    246     * Any/all languages.
    247     */
    248    public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
    249        @Override
    250        public boolean contains(final String language) {
    251            return true;
    252        }
    253
    254        @Override
    255        public String getAny() {
    256            throw new NoSuchElementException("Can't fetch any language from the any language set.");
    257        }
    258
    259        @Override
    260        public boolean isEmpty() {
    261            return false;
    262        }
    263
    264        @Override
    265        public boolean isSingleton() {
    266            return false;
    267        }
    268
    269        @Override
    270        public LanguageSet restrictTo(final LanguageSet other) {
    271            return other;
    272        }
    273
    274        @Override
    275        public LanguageSet merge(final LanguageSet other) {
    276            return other;
    277        }
    278
    279        @Override
    280        public String toString() {
    281            return "ANY_LANGUAGE";
    282        }
    283    };
    284
    285    private Languages(final Set<String> languages) {
    286        this.languages = languages;
    287    }
    288
    289    public Set<String> getLanguages() {
    290        return this.languages;
    291    }
    292}