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