1 /* 2 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.spi; 27 28 import jdk.internal.misc.JavaUtilResourceBundleAccess; 29 import jdk.internal.misc.SharedSecrets; 30 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.UncheckedIOException; 34 import java.lang.reflect.Module; 35 import java.security.AccessController; 36 import java.security.PrivilegedAction; 37 import java.util.Locale; 38 import java.util.PropertyResourceBundle; 39 import java.util.ResourceBundle; 40 import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; 41 42 /** 43 * {@code AbstractResourceBundleProvider} is an abstract class that provides 44 * the basic support for a provider implementation class for 45 * {@link ResourceBundleProvider}. 46 * 47 * <p> 48 * Resource bundles can be packaged in one or more 49 * named modules, <em>bundle modules</em>. The <em>consumer</em> of the 50 * resource bundle is the one calling {@link ResourceBundle#getBundle(String)}. 51 * In order for the consumer module to load a resource bundle 52 * "{@code com.example.app.MyResources}" provided by another module, 53 * it will use the {@linkplain java.util.ServiceLoader service loader} 54 * mechanism. A service interface named "{@code com.example.app.MyResourcesProvider}" 55 * must be defined and a <em>bundle provider module</em> will provide an 56 * implementation class of "{@code com.example.app.MyResourcesProvider}" 57 * as follows: 58 * 59 * <pre><code> 60 * import com.example.app.MyResourcesProvider; 61 * class MyResourcesProviderImpl extends AbstractResourceBundleProvider 62 * implements MyResourcesProvider 63 * { 64 * protected String toBundleName(String baseName, Locale locale) { 65 * // return the bundle name per the naming of the resource bundle 66 * : 67 * } 68 * 69 * public ResourceBundle getBundle(String baseName, Locale locale) { 70 * // this module only provides bundles in french 71 * if (locale.equals(Locale.FRENCH)) { 72 * return super.getBundle(baseName, locale); 73 * } 74 * return null; 75 * } 76 * }</code></pre> 77 * 78 * @see <a href="../ResourceBundle.html#bundleprovider"> 79 * Resource Bundles in Named Modules</a> 80 * @see <a href="../ResourceBundle.html#RBP_support"> 81 * ResourceBundleProvider Service Providers</a> 82 * 83 * @since 9 84 * @spec JPMS 85 */ 86 public abstract class AbstractResourceBundleProvider implements ResourceBundleProvider { 87 private static final JavaUtilResourceBundleAccess RB_ACCESS = 88 SharedSecrets.getJavaUtilResourceBundleAccess(); 89 90 private static final String FORMAT_CLASS = "java.class"; 91 private static final String FORMAT_PROPERTIES = "java.properties"; 92 93 private final String[] formats; 94 95 /** 96 * Constructs an {@code AbstractResourceBundleProvider} with the 97 * "java.properties" format. This constructor is equivalent to 98 * {@code AbstractResourceBundleProvider("java.properties")}. 99 */ 100 protected AbstractResourceBundleProvider() { 101 this(FORMAT_PROPERTIES); 102 } 103 104 /** 105 * Constructs an {@code AbstractResourceBundleProvider} with the specified 106 * {@code formats}. The {@link #getBundle(String, Locale)} method looks up 107 * resource bundles for the given {@code formats}. {@code formats} must 108 * be "java.class" or "java.properties". 109 * 110 * @param formats the formats to be used for loading resource bundles 111 * @throws NullPointerException if the given {@code formats} is null 112 * @throws IllegalArgumentException if the given {@code formats} is not 113 * "java.class" or "java.properties". 114 */ 115 protected AbstractResourceBundleProvider(String... formats) { 116 this.formats = formats.clone(); // defensive copy 117 if (this.formats.length == 0) { 118 throw new IllegalArgumentException("empty formats"); 119 } 120 for (String f : this.formats) { 121 if (!FORMAT_CLASS.equals(f) && !FORMAT_PROPERTIES.equals(f)) { 122 throw new IllegalArgumentException(f); 123 } 124 } 125 } 126 127 /** 128 * Returns the bundle name for the given {@code baseName} and {@code 129 * locale} that this provider provides. 130 * 131 * @apiNote 132 * A resource bundle provider may package its resource bundles in the 133 * same package as the base name of the resource bundle if the package 134 * is not split among other named modules. If there are more than one 135 * bundle providers providing the resource bundle of a given base name, 136 * the resource bundles can be packaged with per-language grouping 137 * or per-region grouping to eliminate the split packages. 138 * 139 * <p>For example, if {@code baseName} is {@code "p.resources.Bundle"} then 140 * the resource bundle name of {@code "p.resources.Bundle"} of 141 * {@code Locale("ja", "", "XX")} and {@code Locale("en")} 142 * could be {@code "p.resources.ja.Bundle_ja_ _XX"} and 143 * {@code p.resources.Bundle_en"} respectively 144 * 145 * <p> This method is called from the default implementation of the 146 * {@link #getBundle(String, Locale)} method. 147 * 148 * @implNote The default implementation of this method is the same as the 149 * implementation of 150 * {@link java.util.ResourceBundle.Control#toBundleName(String, Locale)}. 151 * 152 * @param baseName the base name of the resource bundle, a fully qualified 153 * class name 154 * @param locale the locale for which a resource bundle should be loaded 155 * @return the bundle name for the resource bundle 156 */ 157 protected String toBundleName(String baseName, Locale locale) { 158 return ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT) 159 .toBundleName(baseName, locale); 160 } 161 162 /** 163 * Returns a {@code ResourceBundle} for the given {@code baseName} and 164 * {@code locale}. 165 * 166 * @implNote 167 * The default implementation of this method calls the 168 * {@link #toBundleName(String, Locale) toBundleName} method to get the 169 * bundle name for the {@code baseName} and {@code locale} and finds the 170 * resource bundle of the bundle name local in the module of this provider. 171 * It will only search the formats specified when this provider was 172 * constructed. 173 * 174 * @param baseName the base bundle name of the resource bundle, a fully 175 * qualified class name. 176 * @param locale the locale for which the resource bundle should be instantiated 177 * @return {@code ResourceBundle} of the given {@code baseName} and 178 * {@code locale}, or {@code null} if no resource bundle is found 179 * @throws NullPointerException if {@code baseName} or {@code locale} is 180 * {@code null} 181 * @throws UncheckedIOException if any IO exception occurred during resource 182 * bundle loading 183 */ 184 @Override 185 public ResourceBundle getBundle(String baseName, Locale locale) { 186 Module module = this.getClass().getModule(); 187 String bundleName = toBundleName(baseName, locale); 188 ResourceBundle bundle = null; 189 190 for (String format : formats) { 191 try { 192 if (FORMAT_CLASS.equals(format)) { 193 bundle = loadResourceBundle(module, bundleName); 194 } else if (FORMAT_PROPERTIES.equals(format)) { 195 bundle = loadPropertyResourceBundle(module, bundleName); 196 } 197 if (bundle != null) { 198 break; 199 } 200 } catch (IOException e) { 201 throw new UncheckedIOException(e); 202 } 203 } 204 return bundle; 205 } 206 207 /* 208 * Returns the ResourceBundle of .class format if found in the module 209 * of this provider. 210 */ 211 private static ResourceBundle loadResourceBundle(Module module, String bundleName) 212 { 213 PrivilegedAction<Class<?>> pa = () -> Class.forName(module, bundleName); 214 Class<?> c = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION); 215 if (c != null && ResourceBundle.class.isAssignableFrom(c)) { 216 @SuppressWarnings("unchecked") 217 Class<ResourceBundle> bundleClass = (Class<ResourceBundle>) c; 218 return RB_ACCESS.newResourceBundle(bundleClass); 219 } 220 return null; 221 } 222 223 /* 224 * Returns the ResourceBundle of .property format if found in the module 225 * of this provider. 226 */ 227 private static ResourceBundle loadPropertyResourceBundle(Module module, 228 String bundleName) 229 throws IOException 230 { 231 String resourceName = toResourceName(bundleName, "properties"); 232 if (resourceName == null) { 233 return null; 234 } 235 236 PrivilegedAction<InputStream> pa = () -> { 237 try { 238 return module.getResourceAsStream(resourceName); 239 } catch (IOException e) { 240 throw new UncheckedIOException(e); 241 } 242 }; 243 try (InputStream stream = AccessController.doPrivileged(pa)) { 244 if (stream != null) { 245 return new PropertyResourceBundle(stream); 246 } else { 247 return null; 248 } 249 } catch (UncheckedIOException e) { 250 throw e.getCause(); 251 } 252 } 253 254 private static String toResourceName(String bundleName, String suffix) { 255 if (bundleName.contains("://")) { 256 return null; 257 } 258 StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length()); 259 sb.append(bundleName.replace('.', '/')).append('.').append(suffix); 260 return sb.toString(); 261 } 262 263 }