blob: f18c46f6022ea5edf30493ef37f36e2fe2a921ae [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
<<<<<<< Updated upstream
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
=======
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
>>>>>>> Stashed changes
* limitations under the License.
*/
package javax.jdo.spi;
import java.lang.reflect.InvocationTargetException;
import java.security.PrivilegedAction;
import java.text.MessageFormat;
import java.util.Hashtable;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.jdo.JDOFatalInternalException;
import javax.jdo.LegacyJava;
/**
* Helper class for constructing messages from bundles. The intended usage of this class is to
* construct a new instance bound to a bundle, as in
*
* <p><code>I18NHelper msg = I18NHelper.getInstance("javax.jdo.Bundle");</code>
*
* <p>This call uses the class loader that loaded the I18NHelper class to find the specified Bundle.
* The class provides two overloaded getInstance methods allowing to specify a different class
* loader: {@link #getInstance(Class cls)} looks for a bundle called "Bundle.properties" located in
* the package of the specified class object and {@link #getInstance(String bundleName,ClassLoader
* loader)} uses the specified class loader to find the bundle.
*
* <p>Subsequently, instance methods can be used to format message strings using the text from the
* bundle, as in
*
* <p><code>throw new JDOFatalInternalException (msg.msg("ERR_NoMetadata",
* cls.getName()));</code>
*
* @since 1.0.1
* @version 1.1
*/
public class I18NHelper {
/** Bundles that have already been loaded */
private static final Hashtable<String, ResourceBundle> bundles = new Hashtable<>();
/** Helper instances that have already been created */
private static final Hashtable<String, I18NHelper> helpers = new Hashtable<>();
/** The default locale for this VM. */
private static final Locale locale = Locale.getDefault();
/** The bundle used by this instance of the helper. */
private ResourceBundle bundle = null;
/** Throwable if ResourceBundle couldn't be loaded */
private Throwable failure = null;
/** The unqualified standard name of a bundle. */
private static final String BUNDLE_SUFFIX = ".Bundle"; // NOI18N
/** Constructor */
private I18NHelper() {}
/**
* Constructor for an instance bound to a bundle.
*
* @param bundleName the name of the resource bundle
* @param loader the class loader from which to load the resource bundle
*/
private I18NHelper(String bundleName, ClassLoader loader) {
try {
bundle = loadBundle(bundleName, loader);
} catch (Throwable e) {
failure = e;
}
}
/**
* An instance bound to a bundle. This method uses the current class loader to find the bundle.
*
* @param bundleName the name of the bundle
* @return the helper instance bound to the bundle
*/
public static I18NHelper getInstance(String bundleName) {
return getInstance(bundleName, I18NHelper.class.getClassLoader());
}
/**
* An instance bound to a bundle. This method figures out the bundle name for the class object's
* package and uses the class' class loader to find the bundle. Note, the specified class object
* must not be <code>null</code>.
*
* @param cls the class object from which to load the resource bundle
* @return the helper instance bound to the bundle
*/
public static I18NHelper getInstance(final Class<?> cls) {
ClassLoader classLoader = doPrivileged(cls::getClassLoader);
String bundle = getPackageName(cls.getName()) + BUNDLE_SUFFIX;
return getInstance(bundle, classLoader);
}
/**
* An instance bound to a bundle. This method uses the specified class loader to find the bundle.
* Note, the specified class loader must not be <code>null</code>.
*
* @param bundleName the name of the bundle
* @param loader the class loader from which to load the resource bundle
* @return the helper instance bound to the bundle
*/
public static I18NHelper getInstance(String bundleName, ClassLoader loader) {
I18NHelper helper = helpers.get(bundleName);
if (helper != null) {
return helper;
}
helper = new I18NHelper(bundleName, loader);
helpers.put(bundleName, helper);
// if two threads simultaneously create the same helper, return the first
// one to be put into the Hashtable. The other will be garbage collected.
return helpers.get(bundleName);
}
/**
* Message formatter
*
* @param messageKey the message key
* @return the resolved message text
*/
public String msg(String messageKey) {
assertBundle(messageKey);
return getMessage(bundle, messageKey);
}
/**
* Message formatter
*
* @param messageKey the message key
* @param arg1 the first argument
* @return the resolved message text
*/
public String msg(String messageKey, Object arg1) {
assertBundle(messageKey);
return getMessage(bundle, messageKey, arg1);
}
/**
* Message formatter
*
* @param messageKey the message key
* @param arg1 the first argument
* @param arg2 the second argument
* @return the resolved message text
*/
public String msg(String messageKey, Object arg1, Object arg2) {
assertBundle(messageKey);
return getMessage(bundle, messageKey, arg1, arg2);
}
/**
* Message formatter
*
* @param messageKey the message key
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @return the resolved message text
*/
public String msg(String messageKey, Object arg1, Object arg2, Object arg3) {
assertBundle(messageKey);
return getMessage(bundle, messageKey, arg1, arg2, arg3);
}
/**
* Message formatter
*
* @param messageKey the message key
* @param args the array of arguments
* @return the resolved message text
*/
public String msg(String messageKey, Object[] args) {
assertBundle(messageKey);
return getMessage(bundle, messageKey, args);
}
/**
* Message formatter
*
* @param messageKey the message key
* @param arg the argument
* @return the resolved message text
*/
public String msg(String messageKey, int arg) {
assertBundle(messageKey);
return getMessage(bundle, messageKey, arg);
}
/**
* Message formatter
*
* @param messageKey the message key
* @param arg the argument
* @return the resolved message text
*/
public String msg(String messageKey, boolean arg) {
assertBundle(messageKey);
return getMessage(bundle, messageKey, arg);
}
/**
* Returns the resource bundle used by this I18NHelper.
*
* @return the associated resource bundle
* @since 1.1
*/
public ResourceBundle getResourceBundle() {
assertBundle();
return bundle;
}
// ========= Internal helper methods ==========
/**
* Load ResourceBundle by bundle name
*
* @param bundleName the name of the bundle
* @param loader the class loader from which to load the resource bundle
* @return the ResourceBundle
*/
private static ResourceBundle loadBundle(String bundleName, ClassLoader loader) {
ResourceBundle messages = bundles.get(bundleName);
if (messages == null) // not found as loaded - add
{
if (loader != null) {
messages = ResourceBundle.getBundle(bundleName, locale, loader);
} else {
// the JDO library is loaded by the boostrap class loader
messages = ResourceBundle.getBundle(bundleName, locale, getSystemClassLoaderPrivileged());
}
bundles.put(bundleName, messages);
}
return messages;
}
/**
* Assert resources available
*
* @since 1.1
* @throws JDOFatalInternalException if the resource bundle could not be loaded during
* construction.
*/
private void assertBundle() {
if (failure != null)
throw new JDOFatalInternalException(
"No resources could be found for bundle:\"" + bundle + "\" ", failure);
}
/**
* Assert resources available
*
* @param key the message key
* @since 1.0.2
* @throws JDOFatalInternalException if the resource bundle could not be loaded during
* construction.
*/
private void assertBundle(String key) {
if (failure != null)
throw new JDOFatalInternalException(
"No resources could be found to annotate error message key:\"" + key + "\"", failure);
}
/**
* Returns message as <code>String</code>
*
* @param messages the resource bundle
* @param messageKey the message key
* @return the resolved message text
*/
private static String getMessage(ResourceBundle messages, String messageKey) {
return messages.getString(messageKey);
}
/**
* Formats message by adding array of arguments
*
* @param messages the resource bundle
* @param messageKey the message key
* @param msgArgs an array of arguments to substitute into the message
* @return the resolved message text
*/
private static String getMessage(ResourceBundle messages, String messageKey, Object[] msgArgs) {
for (int i = 0; i < msgArgs.length; i++) {
if (msgArgs[i] == null) msgArgs[i] = ""; // NOI18N
}
MessageFormat formatter = new MessageFormat(messages.getString(messageKey));
return formatter.format(msgArgs);
}
/**
* Formats message by adding an <code>Object</code> argument.
*
* @param messages the resource bundle
* @param messageKey the message key
* @param arg the argument
* @return the resolved message text
*/
private static String getMessage(ResourceBundle messages, String messageKey, Object arg) {
Object[] args = {arg};
return getMessage(messages, messageKey, args);
}
/**
* Formats message by adding two <code>Object</code> arguments.
*
* @param messages the resource bundle
* @param messageKey the message key
* @param arg1 the first argument
* @param arg2 the second argument
* @return the resolved message text
*/
private static String getMessage(
ResourceBundle messages, String messageKey, Object arg1, Object arg2) {
Object[] args = {arg1, arg2};
return getMessage(messages, messageKey, args);
}
/**
* Formats message by adding three <code>Object</code> arguments.
*
* @param messages the resource bundle
* @param messageKey the message key
* @param arg1 the first argument
* @param arg2 the second argument
* @param arg3 the third argument
* @return the resolved message text
*/
private static String getMessage(
ResourceBundle messages, String messageKey, Object arg1, Object arg2, Object arg3) {
Object[] args = {arg1, arg2, arg3};
return getMessage(messages, messageKey, args);
}
/**
* Formats message by adding an <code>int</code> as an argument.
*
* @param messages the resource bundle
* @param messageKey the message key
* @param arg the argument
* @return the resolved message text
*/
private static String getMessage(ResourceBundle messages, String messageKey, int arg) {
Object[] args = {Integer.valueOf(arg)};
return getMessage(messages, messageKey, args);
}
/**
* Formats message by adding a <code>boolean</code> as an argument.
*
* @param messages the resource bundle
* @param messageKey the message key
* @param arg the argument
* @return the resolved message text
*/
private static String getMessage(ResourceBundle messages, String messageKey, boolean arg) {
Object[] args = {String.valueOf(arg)};
return getMessage(messages, messageKey, args);
}
/**
* Returns the package portion of the specified class.
*
* @param className the name of the class from which to extract the package
* @return package portion of the specified class
*/
private static String getPackageName(final String className) {
final int index = className.lastIndexOf('.');
return ((index != -1) ? className.substring(0, index) : ""); // NOI18N
}
/** Get the system class loader. This must be done in a doPrivileged block because of security. */
private static ClassLoader getSystemClassLoaderPrivileged() {
return doPrivileged(ClassLoader::getSystemClassLoader);
}
@SuppressWarnings("unchecked")
private static <T> T doPrivileged(PrivilegedAction<T> privilegedAction) {
try {
return (T) LegacyJava.doPrivilegedAction.invoke(null, privilegedAction);
} catch (IllegalAccessException | InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
}
throw new JDOFatalInternalException(e.getMessage());
}
}
}