Languages.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.codec.language.bm;

  18. import java.util.Collections;
  19. import java.util.EnumMap;
  20. import java.util.HashSet;
  21. import java.util.Map;
  22. import java.util.NoSuchElementException;
  23. import java.util.Scanner;
  24. import java.util.Set;
  25. import java.util.stream.Collectors;

  26. import org.apache.commons.codec.Resources;

  27. /**
  28.  * Language codes.
  29.  * <p>
  30.  * Language codes are typically loaded from resource files. These are UTF-8
  31.  * encoded text files. They are systematically named following the pattern:
  32.  * </p>
  33.  * <blockquote>org/apache/commons/codec/language/bm/${{@link NameType#getName()}
  34.  * languages.txt</blockquote>
  35.  * <p>
  36.  * The format of these resources is the following:
  37.  * </p>
  38.  * <ul>
  39.  * <li><strong>Language:</strong> a single string containing no whitespace</li>
  40.  * <li><strong>End-of-line comments:</strong> Any occurrence of '//' will cause all text
  41.  * following on that line to be discarded as a comment.</li>
  42.  * <li><strong>Multi-line comments:</strong> Any line starting with '/*' will start
  43.  * multi-line commenting mode. This will skip all content until a line ending in
  44.  * '*' and '/' is found.</li>
  45.  * <li><strong>Blank lines:</strong> All blank lines will be skipped.</li>
  46.  * </ul>
  47.  * <p>
  48.  * Ported from language.php
  49.  * </p>
  50.  * <p>
  51.  * This class is immutable and thread-safe.
  52.  * </p>
  53.  *
  54.  * @since 1.6
  55.  */
  56. public class Languages {
  57.     // Implementation note: This class is divided into two sections. The first part
  58.     // is a static factory interface that
  59.     // exposes org/apache/commons/codec/language/bm/%s_languages.txt for %s in
  60.     // NameType.* as a list of supported
  61.     // languages, and a second part that provides instance methods for accessing
  62.     // this set for supported languages.

  63.     /**
  64.      * A set of languages.
  65.      */
  66.     public abstract static class LanguageSet {

  67.         /**
  68.          * Gets a language set for the given languages.
  69.          *
  70.          * @param languages a language set.
  71.          * @return a LanguageSet.
  72.          */
  73.         public static LanguageSet from(final Set<String> languages) {
  74.             return languages.isEmpty() ? NO_LANGUAGES : new SomeLanguages(languages);
  75.         }

  76.         /**
  77.          * Constructs a new instance for subclasses.
  78.          */
  79.         public LanguageSet() {
  80.             // empty
  81.         }

  82.         /**
  83.          * Tests whether this instance contains the given value.
  84.          *
  85.          * @param language the value to test.
  86.          * @return whether this instance contains the given value.
  87.          */
  88.         public abstract boolean contains(String language);

  89.         /**
  90.          * Gets any of this instance's element.
  91.          *
  92.          * @return any of this instance's element.
  93.          */
  94.         public abstract String getAny();

  95.         /**
  96.          * Tests whether this instance is empty.
  97.          *
  98.          * @return whether this instance is empty.
  99.          */
  100.         public abstract boolean isEmpty();

  101.         /**
  102.          * Tests whether this instance contains a single element.
  103.          *
  104.          * @return whether this instance contains a single element.
  105.          */
  106.         public abstract boolean isSingleton();

  107.         abstract LanguageSet merge(LanguageSet other);

  108.         /**
  109.          * Returns an instance restricted to this instances and the given values'.
  110.          *
  111.          * @param other The other instance.
  112.          * @return an instance restricted to this instances and the given values'.
  113.          */
  114.         public abstract LanguageSet restrictTo(LanguageSet other);
  115.     }

  116.     /**
  117.      * Some languages, explicitly enumerated.
  118.      */
  119.     public static final class SomeLanguages extends LanguageSet {
  120.         private final Set<String> languages;

  121.         private SomeLanguages(final Set<String> languages) {
  122.             this.languages = Collections.unmodifiableSet(languages);
  123.         }

  124.         @Override
  125.         public boolean contains(final String language) {
  126.             return this.languages.contains(language);
  127.         }

  128.         @Override
  129.         public String getAny() {
  130.             return this.languages.iterator().next();
  131.         }

  132.         /**
  133.          * Gets the language strings
  134.          *
  135.          * @return the languages strings.
  136.          */
  137.         public Set<String> getLanguages() {
  138.             return this.languages;
  139.         }

  140.         @Override
  141.         public boolean isEmpty() {
  142.             return this.languages.isEmpty();
  143.         }

  144.         @Override
  145.         public boolean isSingleton() {
  146.             return this.languages.size() == 1;
  147.         }

  148.         @Override
  149.         public LanguageSet merge(final LanguageSet other) {
  150.             if (other == NO_LANGUAGES) {
  151.                 return this;
  152.             }
  153.             if (other == ANY_LANGUAGE) {
  154.                 return other;
  155.             }
  156.             final SomeLanguages someLanguages = (SomeLanguages) other;
  157.             final Set<String> set = new HashSet<>(languages);
  158.             set.addAll(someLanguages.languages);
  159.             return from(set);
  160.         }

  161.         @Override
  162.         public LanguageSet restrictTo(final LanguageSet other) {
  163.             if (other == NO_LANGUAGES) {
  164.                 return other;
  165.             }
  166.             if (other == ANY_LANGUAGE) {
  167.                 return this;
  168.             }
  169.             final SomeLanguages someLanguages = (SomeLanguages) other;
  170.             return from(languages.stream().filter(lang -> someLanguages.languages.contains(lang)).collect(Collectors.toSet()));
  171.         }

  172.         @Override
  173.         public String toString() {
  174.             return "Languages(" + languages.toString() + ")";
  175.         }

  176.     }

  177.     /**
  178.      * Marker for any language.
  179.      */
  180.     public static final String ANY = "any";

  181.     private static final Map<NameType, Languages> LANGUAGES = new EnumMap<>(NameType.class);

  182.     /**
  183.      * No languages at all.
  184.      */
  185.     public static final LanguageSet NO_LANGUAGES = new LanguageSet() {

  186.         @Override
  187.         public boolean contains(final String language) {
  188.             return false;
  189.         }

  190.         @Override
  191.         public String getAny() {
  192.             throw new NoSuchElementException("Can't fetch any language from the empty language set.");
  193.         }

  194.         @Override
  195.         public boolean isEmpty() {
  196.             return true;
  197.         }

  198.         @Override
  199.         public boolean isSingleton() {
  200.             return false;
  201.         }

  202.         @Override
  203.         public LanguageSet merge(final LanguageSet other) {
  204.             return other;
  205.         }

  206.         @Override
  207.         public LanguageSet restrictTo(final LanguageSet other) {
  208.             return this;
  209.         }

  210.         @Override
  211.         public String toString() {
  212.             return "NO_LANGUAGES";
  213.         }
  214.     };

  215.     /**
  216.      * Any/all languages.
  217.      */
  218.     public static final LanguageSet ANY_LANGUAGE = new LanguageSet() {

  219.         @Override
  220.         public boolean contains(final String language) {
  221.             return true;
  222.         }

  223.         @Override
  224.         public String getAny() {
  225.             throw new NoSuchElementException("Can't fetch any language from the any language set.");
  226.         }

  227.         @Override
  228.         public boolean isEmpty() {
  229.             return false;
  230.         }

  231.         @Override
  232.         public boolean isSingleton() {
  233.             return false;
  234.         }

  235.         @Override
  236.         public LanguageSet merge(final LanguageSet other) {
  237.             return other;
  238.         }

  239.         @Override
  240.         public LanguageSet restrictTo(final LanguageSet other) {
  241.             return other;
  242.         }

  243.         @Override
  244.         public String toString() {
  245.             return "ANY_LANGUAGE";
  246.         }
  247.     };

  248.     static {
  249.         for (final NameType s : NameType.values()) {
  250.             LANGUAGES.put(s, getInstance(langResourceName(s)));
  251.         }
  252.     }

  253.     /**
  254.      * Gets an instance for the given name type.
  255.      *
  256.      * @param nameType The name type to lookup.
  257.      * @return an instance for the given name type.
  258.      */
  259.     public static Languages getInstance(final NameType nameType) {
  260.         return LANGUAGES.get(nameType);
  261.     }

  262.     /**
  263.      * Gets a new instance for the given resource name.
  264.      *
  265.      * @param languagesResourceName the resource name to lookup.
  266.      * @return a new instance.
  267.      */
  268.     public static Languages getInstance(final String languagesResourceName) {
  269.         // read languages list
  270.         final Set<String> ls = new HashSet<>();
  271.         try (Scanner lsScanner = new Scanner(Resources.getInputStream(languagesResourceName),
  272.                 ResourceConstants.ENCODING)) {
  273.             boolean inExtendedComment = false;
  274.             while (lsScanner.hasNextLine()) {
  275.                 final String line = lsScanner.nextLine().trim();
  276.                 if (inExtendedComment) {
  277.                     if (line.endsWith(ResourceConstants.EXT_CMT_END)) {
  278.                         inExtendedComment = false;
  279.                     }
  280.                 } else if (line.startsWith(ResourceConstants.EXT_CMT_START)) {
  281.                     inExtendedComment = true;
  282.                 } else if (!line.isEmpty()) {
  283.                     ls.add(line);
  284.                 }
  285.             }
  286.             return new Languages(Collections.unmodifiableSet(ls));
  287.         }
  288.     }

  289.     private static String langResourceName(final NameType nameType) {
  290.         return String.format("/org/apache/commons/codec/language/bm/%s_languages.txt", nameType.getName());
  291.     }

  292.     private final Set<String> languages;

  293.     private Languages(final Set<String> languages) {
  294.         this.languages = languages;
  295.     }

  296.     /**
  297.      * Gets the language set.
  298.      *
  299.      * @return the language set.
  300.      */
  301.     public Set<String> getLanguages() {
  302.         return this.languages;
  303.     }
  304. }