    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.java 1636435 2014-11-03 20:37:24Z tn $
    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<String>(Math.min(languages.size(), sl.languages.size()));
    123                for (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<String>(languages);
    141                for (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, Languages>(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<String>();
    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        final Scanner lsScanner = new Scanner(langIS, ResourceConstants.ENCODING);
    179        try {
    180            boolean inExtendedComment = false;
    181            while (lsScanner.hasNextLine()) {
    182                final String line = lsScanner.nextLine().trim();
    183                if (inExtendedComment) {
    184                    if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
    185                        inExtendedComment = false;
    186                    }
    187                } else {
    188                    if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
    189                        inExtendedComment = true;
    190                    } else if (line.length() > 0) {
    191                        ls.add(line);
    192                    }
    193                }
    194            }
    195        } finally {
    196            lsScanner.close();
    197        }
    198
    199        return new Languages(Collections.unmodifiableSet(ls));
    200    }
    201
    202    private static String langResourceName(final NameType nameType) {
    203        return String.format("org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
    204    }
    205
    206    private final Set<String> languages;
    207
    208    /**
    209     * No languages at all.
    210     */
    211    public static final LanguageSet NO_LANGUAGES = new LanguageSet() {
    212        @Override
    213        public boolean contains(final String language) {
    214            return false;
    215        }
    216
    217        @Override
    218        public String getAny() {
    219            throw new NoSuchElementException("Can't fetch any language from the empty language set.");
    220        }
    221
    222        @Override
    223        public boolean isEmpty() {
    224            return true;
    225        }
    226
    227        @Override
    228        public boolean isSingleton() {
    229            return false;
    230        }
    231
    232        @Override
    233        public LanguageSet restrictTo(final LanguageSet other) {
    234            return this;
    235        }
    236
    237        @Override
    238        public LanguageSet merge(final LanguageSet other) {
    239            return other;
    240        }
    241
    242        @Override
    243        public String toString() {
    244            return "NO_LANGUAGES";
    245        }
    246    };
    247
    248    /**
    249     * Any/all languages.
    250     */
    251    public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {
    252        @Override
    253        public boolean contains(final String language) {
    254            return true;
    255        }
    256
    257        @Override
    258        public String getAny() {
    259            throw new NoSuchElementException("Can't fetch any language from the any language set.");
    260        }
    261
    262        @Override
    263        public boolean isEmpty() {
    264            return false;
    265        }
    266
    267        @Override
    268        public boolean isSingleton() {
    269            return false;
    270        }
    271
    272        @Override
    273        public LanguageSet restrictTo(final LanguageSet other) {
    274            return other;
    275        }
    276
    277        @Override
    278        public LanguageSet merge(final LanguageSet other) {
    279            return other;
    280        }
    281
    282        @Override
    283        public String toString() {
    284            return "ANY_LANGUAGE";
    285        }
    286    };
    287
    288    private Languages(final Set<String> languages) {
    289        this.languages = languages;
    290    }
    291
    292    public Set<String> getLanguages() {
    293        return this.languages;
    294    }
    295}