1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.common.util;
21  
22  import java.io.IOException;
23  import java.net.URL;
24  import java.util.HashMap;
25  import java.util.HashSet;
26  import java.util.Locale;
27  import java.util.Map;
28  import java.util.MissingResourceException;
29  import java.util.ResourceBundle;
30  import java.util.Set;
31  
32  import org.crosswire.jsword.book.Books;
33  import org.crosswire.jsword.internationalisation.LocaleProviderManager;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * A utility class that converts bcp-47 codes as supported by {@link Language} to their
39   * localized language name.
40   * 
41   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
42   * @author DM Smith
43   */
44  public final class Languages {
45  
46      /**
47       * Make the class a true utility class by having a private constructor.
48       */
49      private Languages() {
50      }
51  
52      /**
53       * Get the language name for the BCP-47 specification of the language.
54       * 
55       * @param code the BCP-47 specification for the language
56       * @return the name of the language
57       */
58      public static String getName(String code) {
59          // Returning the code is the fallback for lookup
60          String name = code;
61          try {
62              ResourceBundle langs = getLocalisedCommonLanguages();
63              if (langs != null) {
64                  name = langs.getString(code);
65              }
66          } catch (MissingResourceException e) {
67              // This is allowed
68          }
69          return name;
70      }
71  
72      /**
73       * Gets the localised common languages. Caching here, is done to prevent extra logging 
74       * happening every time we miss the iso639 ResourceBundle
75       * and end up having to lookup the iso639full
76       * 
77       * @return the localised common languages
78       */
79      private static ResourceBundle getLocalisedCommonLanguages() {
80          Locale locale = LocaleProviderManager.getLocale();
81          ResourceBundle langs = localisedCommonLanguages.get(locale);
82          if (langs == null) {
83              synchronized (Languages.class) {
84                  langs = localisedCommonLanguages.get(locale);
85                  if (langs == null) {
86                      langs = initLanguages(locale);
87                      if (langs != null) {
88                          localisedCommonLanguages.put(locale, langs);
89                      }
90                  }
91              }
92          }
93          return langs;
94      }
95  
96      private static ResourceBundle initLanguages(Locale locale) {
97          try {
98              return ResourceBundle.getBundle("iso639", locale, CWClassLoader.instance());
99          } catch (MissingResourceException e) {
100             log.info("Unable to find language in iso639 bundle", e);
101         }
102         return null;
103     }
104 
105     /**
106      * Provide a fallback lookup against a huge list of all languages.
107      * The basic list has a few hundred languages. The full list has
108      * over 7000. As a fallback, this file is not internationalized.
109      */
110     public static final class AllLanguages {
111         /**
112          * This is a singleton class. Do not allow construction.
113          */
114         private AllLanguages() { }
115 
116         /**
117          * Get the language name for the code. If the language name is not known
118          * then return the code.
119          * 
120          * @param languageCode the language code
121          * @return the name for the language.
122          */
123         public static String getName(String languageCode) {
124             if (instance != null) {
125                 String name = instance.get(languageCode);
126                 if (name != null) {
127                     return name;
128                 }
129             }
130             return languageCode;
131         }
132 
133         /**
134          * Do lazy loading of the huge file of languages.
135          * Note: It is OK for it not to be present.
136          */
137         private static PropertyMap instance;
138         static {
139             try {
140                 instance = ResourceUtil.getProperties("iso639full");
141                 log.debug("Loading iso639full.properties file");
142             } catch (IOException e) {
143                 log.info("Unable to load iso639full.properties", e);
144             }
145         }
146     }
147 
148     /**
149      * Provide a fallback lookup against a huge list of all languages.
150      * The basic list has a few hundred languages. The full list has
151      * over 7000. As a fallback, this file is not internationalized.
152      */
153     public static final class RtoL {
154         /**
155          * This is a singleton class. Do not allow construction.
156          */
157         private RtoL() { }
158 
159         /**
160          * Determine whether this language is a Left-to-Right or a Right-to-Left
161          * language. If the language has a script, it is used for the determination.
162          * Otherwise, check the language.
163          * <p>
164          * Note: This is problematic. Languages do not have direction.
165          * Scripts do. Further, there are over 7000 living languages, many of which
166          * are written in Right-to-Left scripts and are not listed here.
167          * </p>
168          * 
169          * @param script the iso15924 script code, must be in Title case
170          * @param lang the iso639 language code, must be lower case
171          * @return true if the language is Right-to-Left
172          */
173         public static boolean isRtoL(String script, String lang) {
174             if (script != null) {
175                 return rtol.contains(script);
176             }
177             if (lang != null) {
178                 return rtol.contains(lang);
179             }
180             return false;
181         }
182 
183         /**
184          * Do lazy loading of the huge file of languages.
185          * Note: It is OK for it not to be present.
186          */
187         private static Set rtol = new HashSet();
188         /**
189          * load RtoL data
190          */
191         static {
192             try {
193                 URL index = ResourceUtil.getResource(Translations.class, "rtol.txt");
194                 String[] list = NetUtil.listByIndexFile(NetUtil.toURI(index));
195                 log.debug("Loading iso639full.properties file");
196                 for (int i = 0; i < list.length; i++) {
197                     rtol.add(list[i]);
198                 }
199             } catch (IOException ex) {
200                 log.info("Unable to load rtol.txt", ex);
201             }
202         }
203     }
204 
205     private static Map<Locale, ResourceBundle> localisedCommonLanguages = new HashMap<Locale, ResourceBundle>();
206 
207     /**
208      * The log stream
209      */
210     protected static final Logger log = LoggerFactory.getLogger(Books.class);
211 }
212