1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4 ******************************************************************************
5 * Copyright (C) 2004-2016, International Business Machines Corporation and
6 * others. All Rights Reserved.
7 ******************************************************************************
8 */
9 
10 package com.ibm.icu.impl;
11 
12 import java.io.InputStream;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.Enumeration;
16 import java.util.List;
17 import java.util.MissingResourceException;
18 import java.util.PropertyResourceBundle;
19 import java.util.ResourceBundle;
20 
21 import com.ibm.icu.util.ULocale;
22 import com.ibm.icu.util.UResourceBundle;
23 
24 /**
25  * just a wrapper for Java ListResourceBundles and
26  * @author ram
27  *
28  */
29 public final class ResourceBundleWrapper extends UResourceBundle {
30     private ResourceBundle bundle = null;
31     private String localeID = null;
32     private String baseName = null;
33     private List<String> keys = null;
34 
35     /** Loader for bundle instances, for caching. */
36     private static abstract class Loader {
load()37         abstract ResourceBundleWrapper load();
38     }
39 
40     private static CacheBase<String, ResourceBundleWrapper, Loader> BUNDLE_CACHE =
41             new SoftCache<String, ResourceBundleWrapper, Loader>() {
42         @Override
43         protected ResourceBundleWrapper createInstance(String unusedKey, Loader loader) {
44             return loader.load();
45         }
46     };
47 
ResourceBundleWrapper(ResourceBundle bundle)48     private ResourceBundleWrapper(ResourceBundle bundle){
49         this.bundle=bundle;
50     }
51 
52     @Override
handleGetObject(String aKey)53     protected Object handleGetObject(String aKey){
54         ResourceBundleWrapper current = this;
55         Object obj = null;
56         while(current!=null){
57             try{
58                 obj = current.bundle.getObject(aKey);
59                 break;
60             }catch(MissingResourceException ex){
61                 current = (ResourceBundleWrapper)current.getParent();
62             }
63         }
64         if (obj == null){
65             throw new MissingResourceException("Can't find resource for bundle "
66                                                +baseName
67                                                +", key "+aKey,
68                                                this.getClass().getName(),
69                                                aKey);
70         }
71         return obj;
72     }
73 
74     @Override
getKeys()75     public Enumeration<String> getKeys(){
76         return Collections.enumeration(keys);
77     }
78 
initKeysVector()79     private void initKeysVector(){
80         ResourceBundleWrapper current = this;
81         keys = new ArrayList<String>();
82         while(current!=null){
83             Enumeration<String> e = current.bundle.getKeys();
84             while(e.hasMoreElements()){
85                 String elem = e.nextElement();
86                 if(!keys.contains(elem)){
87                     keys.add(elem);
88                 }
89             }
90             current = (ResourceBundleWrapper)current.getParent();
91         }
92     }
93     @Override
getLocaleID()94     protected String getLocaleID(){
95         return localeID;
96     }
97 
98     @Override
getBaseName()99     protected String getBaseName(){
100         return bundle.getClass().getName().replace('.','/');
101     }
102 
103     @Override
getULocale()104     public ULocale getULocale(){
105         return new ULocale(localeID);
106     }
107 
108     @Override
getParent()109     public UResourceBundle getParent(){
110         return (UResourceBundle)parent;
111     }
112 
113     // Flag for enabling/disabling debugging code
114     private static final boolean DEBUG = ICUDebug.enabled("resourceBundleWrapper");
115 
116     // This method is for super class's instantiateBundle method
getBundleInstance(String baseName, String localeID, ClassLoader root, boolean disableFallback)117     public static ResourceBundleWrapper getBundleInstance(String baseName, String localeID,
118             ClassLoader root, boolean disableFallback) {
119         if (root == null) {
120             root = ClassLoaderUtil.getClassLoader();
121         }
122         ResourceBundleWrapper b;
123         if (disableFallback) {
124             b = instantiateBundle(baseName, localeID, null, root, disableFallback);
125         } else {
126             b = instantiateBundle(baseName, localeID, ULocale.getDefault().getBaseName(),
127                     root, disableFallback);
128         }
129         if(b==null){
130             String separator ="_";
131             if(baseName.indexOf('/')>=0){
132                 separator = "/";
133             }
134             throw new MissingResourceException("Could not find the bundle "+ baseName+separator+ localeID,"","");
135         }
136         return b;
137     }
138 
localeIDStartsWithLangSubtag(String localeID, String lang)139     private static boolean localeIDStartsWithLangSubtag(String localeID, String lang) {
140         return localeID.startsWith(lang) &&
141                 (localeID.length() == lang.length() || localeID.charAt(lang.length()) == '_');
142     }
143 
instantiateBundle( final String baseName, final String localeID, final String defaultID, final ClassLoader root, final boolean disableFallback)144     private static ResourceBundleWrapper instantiateBundle(
145              final String baseName, final String localeID, final String defaultID,
146              final ClassLoader root, final boolean disableFallback) {
147         final String name = localeID.isEmpty() ? baseName : baseName + '_' + localeID;
148         String cacheKey = disableFallback ? name : name + '#' + defaultID;
149         return BUNDLE_CACHE.getInstance(cacheKey, new Loader() {
150                 @Override
151                 public ResourceBundleWrapper load() {
152             ResourceBundleWrapper parent = null;
153             int i = localeID.lastIndexOf('_');
154 
155             boolean loadFromProperties = false;
156             boolean parentIsRoot = false;
157             if (i != -1) {
158                 String locName = localeID.substring(0, i);
159                 parent = instantiateBundle(baseName, locName, defaultID, root, disableFallback);
160             }else if(!localeID.isEmpty()){
161                 parent = instantiateBundle(baseName, "", defaultID, root, disableFallback);
162                 parentIsRoot = true;
163             }
164             ResourceBundleWrapper b = null;
165             try {
166                 Class<? extends ResourceBundle> cls =
167                         root.loadClass(name).asSubclass(ResourceBundle.class);
168                 ResourceBundle bx = cls.newInstance();
169                 b = new ResourceBundleWrapper(bx);
170                 if (parent != null) {
171                     b.setParent(parent);
172                 }
173                 b.baseName=baseName;
174                 b.localeID = localeID;
175             } catch (ClassNotFoundException e) {
176                 loadFromProperties = true;
177             } catch (NoClassDefFoundError e) {
178                 loadFromProperties = true;
179             } catch (Exception e) {
180                 if (DEBUG)
181                     System.out.println("failure");
182                 if (DEBUG)
183                     System.out.println(e);
184             }
185 
186             if (loadFromProperties) {
187                 try {
188                     final String resName = name.replace('.', '/') + ".properties";
189                     InputStream stream = java.security.AccessController.doPrivileged(
190                         new java.security.PrivilegedAction<InputStream>() {
191                             @Override
192                             public InputStream run() {
193                                 return root.getResourceAsStream(resName);
194                             }
195                         }
196                     );
197                     if (stream != null) {
198                         // make sure it is buffered
199                         stream = new java.io.BufferedInputStream(stream);
200                         try {
201                             b = new ResourceBundleWrapper(new PropertyResourceBundle(stream));
202                             if (parent != null) {
203                                 b.setParent(parent);
204                             }
205                             b.baseName=baseName;
206                             b.localeID=localeID;
207                         } catch (Exception ex) {
208                             // throw away exception
209                         } finally {
210                             try {
211                                 stream.close();
212                             } catch (Exception ex) {
213                                 // throw away exception
214                             }
215                         }
216                     }
217 
218                     // if a bogus locale is passed then the parent should be
219                     // the default locale not the root locale!
220                     if (b == null && !disableFallback &&
221                             !localeID.isEmpty() && localeID.indexOf('_') < 0 &&
222                             !localeIDStartsWithLangSubtag(defaultID, localeID)) {
223                         // localeID is only a language subtag, different from the default language.
224                         b = instantiateBundle(baseName, defaultID, defaultID, root, disableFallback);
225                     }
226                     // if still could not find the bundle then return the parent
227                     if(b==null && (!parentIsRoot || !disableFallback)){
228                         b=parent;
229                     }
230                 } catch (Exception e) {
231                     if (DEBUG)
232                         System.out.println("failure");
233                     if (DEBUG)
234                         System.out.println(e);
235                 }
236             }
237             if(b!=null){
238                 b.initKeysVector();
239             }else{
240                 if(DEBUG)System.out.println("Returning null for "+baseName+"_"+localeID);
241             }
242             return b;
243         }});
244     }
245 }
246