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