1 /* 2 * Copyright (c) 2015, 2018, 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.access.JavaUtilResourceBundleAccess; 29 import jdk.internal.access.SharedSecrets; 30 31 import java.io.IOException; 32 import java.io.InputStream; 33 import java.io.UncheckedIOException; 34 import java.security.AccessController; 35 import java.security.PrivilegedAction; 36 import java.util.Locale; 37 import java.util.PropertyResourceBundle; 38 import java.util.ResourceBundle; 39 import static sun.security.util.SecurityConstants.GET_CLASSLOADER_PERMISSION; 40 41 /** 42 * {@code AbstractResourceBundleProvider} is an abstract class that provides 43 * the basic support for a provider implementation class for 44 * {@link ResourceBundleProvider}. 45 * 46 * <p> 47 * Resource bundles can be packaged in one or more 48 * named modules, <em>service provider modules</em>. The <em>consumer</em> of the 49 * resource bundle is the one calling {@link ResourceBundle#getBundle(String)}. 50 * In order for the consumer module to load a resource bundle 51 * "{@code com.example.app.MyResources}" provided by another module, 52 * it will use the {@linkplain java.util.ServiceLoader service loader} 53 * mechanism. A service interface named "{@code com.example.app.spi.MyResourcesProvider}" 54 * must be defined and a <em>service provider module</em> will provide an 55 * implementation class of "{@code com.example.app.spi.MyResourcesProvider}" 56 * as follows: 57 * 58 * <blockquote><pre> 59 * {@code import com.example.app.spi.MyResourcesProvider; 60 * class MyResourcesProviderImpl extends AbstractResourceBundleProvider 61 * implements MyResourcesProvider 62 * { 63 * public MyResourcesProviderImpl() { 64 * super("java.properties"); 65 * } 66 * // this provider maps the resource bundle to per-language package 67 * protected String toBundleName(String baseName, Locale locale) { 68 * return "p." + locale.getLanguage() + "." + baseName; 69 * } 70 * 71 * public ResourceBundle getBundle(String baseName, Locale locale) { 72 * // this module only provides bundles in French 73 * if (locale.equals(Locale.FRENCH)) { 74 * return super.getBundle(baseName, locale); 75 * } 76 * // otherwise return null 77 * return null; 78 * } 79 * }}</pre></blockquote> 80 * 81 * Refer to {@link ResourceBundleProvider} for details. 82 * 83 * @see <a href="../ResourceBundle.html#resource-bundle-modules"> 84 * Resource Bundles and Named Modules</a> 85 * @since 9 86 * @spec JPMS 87 88 */ 89 public abstract class AbstractResourceBundleProvider implements ResourceBundleProvider { 90 private static final JavaUtilResourceBundleAccess RB_ACCESS = 91 SharedSecrets.getJavaUtilResourceBundleAccess(); 92 93 private static final String FORMAT_CLASS = "java.class"; 94 private static final String FORMAT_PROPERTIES = "java.properties"; 95 96 private final String[] formats; 97 98 /** 99 * Constructs an {@code AbstractResourceBundleProvider} with the 100 * "java.properties" format. This constructor is equivalent to 101 * {@code AbstractResourceBundleProvider("java.properties")}. 102 */ 103 protected AbstractResourceBundleProvider() { 104 this(FORMAT_PROPERTIES); 105 } 106 107 /** 108 * Constructs an {@code AbstractResourceBundleProvider} with the specified 109 * {@code formats}. The {@link #getBundle(String, Locale)} method looks up 110 * resource bundles for the given {@code formats}. {@code formats} must 111 * be "java.class" or "java.properties". 112 * 113 * @param formats the formats to be used for loading resource bundles 114 * @throws NullPointerException if the given {@code formats} is null 115 * @throws IllegalArgumentException if the given {@code formats} is not 116 * "java.class" or "java.properties". 117 */ 118 protected AbstractResourceBundleProvider(String... formats) { 119 this.formats = formats.clone(); // defensive copy 120 if (this.formats.length == 0) { 121 throw new IllegalArgumentException("empty formats"); 122 } 123 for (String f : this.formats) { 124 if (!FORMAT_CLASS.equals(f) && !FORMAT_PROPERTIES.equals(f)) { 125 throw new IllegalArgumentException(f); 126 } 127 } 128 } 129 130 /** 131 * Returns the bundle name for the given {@code baseName} and {@code 132 * locale} that this provider provides. 133 * 134 * @apiNote 135 * A resource bundle provider may package its resource bundles in the 136 * same package as the base name of the resource bundle if the package 137 * is not split among other named modules. If there are more than one 138 * bundle providers providing the resource bundle of a given base name, 139 * the resource bundles can be packaged with per-language grouping 140 * or per-region grouping to eliminate the split packages. 141 * 142 * <p>For example, if {@code baseName} is {@code "p.resources.Bundle"} then 143 * the resource bundle name of {@code "p.resources.Bundle"} of 144 * <code style="white-space:nowrap">Locale("ja", "", "XX")</code> 145 * and {@code Locale("en")} could be <code style="white-space:nowrap"> 146 * "p.resources.ja.Bundle_ja_ _XX"</code> and 147 * {@code "p.resources.Bundle_en"} respectively. 148 * 149 * <p> This method is called from the default implementation of the 150 * {@link #getBundle(String, Locale)} method. 151 * 152 * @implNote The default implementation of this method is the same as the 153 * implementation of 154 * {@link java.util.ResourceBundle.Control#toBundleName(String, Locale)}. 155 * 156 * @param baseName the base name of the resource bundle, a fully qualified 157 * class name 158 * @param locale the locale for which a resource bundle should be loaded 159 * @return the bundle name for the resource bundle 160 */ 161 protected String toBundleName(String baseName, Locale locale) { 162 return ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT) 163 .toBundleName(baseName, locale); 164 } 165 166 /** 167 * Returns a {@code ResourceBundle} for the given {@code baseName} and 168 * {@code locale}. 169 * 170 * @implNote 171 * The default implementation of this method calls the 172 * {@link #toBundleName(String, Locale) toBundleName} method to get the 173 * bundle name for the {@code baseName} and {@code locale} and finds the 174 * resource bundle of the bundle name local in the module of this provider. 175 * It will only search the formats specified when this provider was 176 * constructed. 177 * 178 * @param baseName the base bundle name of the resource bundle, a fully 179 * qualified class name. 180 * @param locale the locale for which the resource bundle should be instantiated 181 * @return {@code ResourceBundle} of the given {@code baseName} and 182 * {@code locale}, or {@code null} if no resource bundle is found 183 * @throws NullPointerException if {@code baseName} or {@code locale} is 184 * {@code null} 185 * @throws UncheckedIOException if any IO exception occurred during resource 186 * bundle loading 187 */ 188 @Override 189 public ResourceBundle getBundle(String baseName, Locale locale) { 190 Module module = this.getClass().getModule(); 191 String bundleName = toBundleName(baseName, locale); 192 ResourceBundle bundle = null; 193 194 for (String format : formats) { 195 try { 196 if (FORMAT_CLASS.equals(format)) { 197 bundle = loadResourceBundle(module, bundleName); 198 } else if (FORMAT_PROPERTIES.equals(format)) { 199 bundle = loadPropertyResourceBundle(module, bundleName); 200 } 201 if (bundle != null) { 202 break; 203 } 204 } catch (IOException e) { 205 throw new UncheckedIOException(e); 206 } 207 } 208 return bundle; 209 } 210 211 /* 212 * Returns the ResourceBundle of .class format if found in the module 213 * of this provider. 214 */ 215 private static ResourceBundle loadResourceBundle(Module module, String bundleName) 216 { 217 PrivilegedAction<Class<?>> pa = () -> Class.forName(module, bundleName); 218 Class<?> c = AccessController.doPrivileged(pa, null, GET_CLASSLOADER_PERMISSION); 219 if (c != null && ResourceBundle.class.isAssignableFrom(c)) { 220 @SuppressWarnings("unchecked") 221 Class<ResourceBundle> bundleClass = (Class<ResourceBundle>) c; 222 return RB_ACCESS.newResourceBundle(bundleClass); 223 } 224 return null; 225 } 226 227 /* 228 * Returns the ResourceBundle of .property format if found in the module 229 * of this provider. 230 */ 231 private static ResourceBundle loadPropertyResourceBundle(Module module, 232 String bundleName) 233 throws IOException 234 { 235 String resourceName = toResourceName(bundleName, "properties"); 236 if (resourceName == null) { 237 return null; 238 } 239 240 PrivilegedAction<InputStream> pa = () -> { 241 try { 242 return module.getResourceAsStream(resourceName); 243 } catch (IOException e) { 244 throw new UncheckedIOException(e); 245 } 246 }; 247 try (InputStream stream = AccessController.doPrivileged(pa)) { 248 if (stream != null) { 249 return new PropertyResourceBundle(stream); 250 } else { 251 return null; 252 } 253 } catch (UncheckedIOException e) { 254 throw e.getCause(); 255 } 256 } 257 258 private static String toResourceName(String bundleName, String suffix) { 259 if (bundleName.contains("://")) { 260 return null; 261 } 262 StringBuilder sb = new StringBuilder(bundleName.length() + 1 + suffix.length()); 263 sb.append(bundleName.replace('.', '/')).append('.').append(suffix); 264 return sb.toString(); 265 } 266 267 }