blob: 3bc8d8d8a1c2ca46f97c1fd375a63452617e1bf4 [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
*
* http://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 limitations
* under the License.
*******************************************************************************/
package org.apache.ofbiz.base.util;
import java.io.BufferedInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.InvalidPropertiesFormatException;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.Set;
import org.apache.ofbiz.base.location.FlexibleLocation;
import org.apache.ofbiz.base.util.cache.UtilCache;
import org.apache.ofbiz.base.util.collections.ResourceBundleMapWrapper;
import org.apache.ofbiz.base.util.string.FlexibleStringExpander;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/** Generic Property Accessor with Cache - Utilities for working with properties files.
* <p>UtilProperties divides properties files into two classes: non-locale-specific -
* which are used for application parameters, configuration settings, etc; and
* locale-specific - which are used for UI labels, system messages, etc. Each class
* of properties files is kept in its own cache.</p>
* <p>The locale-specific class of properties files can be in any one of three
* formats: the standard text-based key=value format (*.properties file), the Java
* XML properties format, and the OFBiz-specific XML file format
* (see the <a href="#xmlToProperties(java.io.InputStream,%20java.util.Locale,%20java.util.Properties)">xmlToProperties</a>
* method).</p>
*/
@SuppressWarnings("serial")
public final class UtilProperties implements Serializable {
public static final String module = UtilProperties.class.getName();
private UtilProperties() {}
/**
* A cache for storing Properties instances. Each Properties instance is keyed by its URL.
*/
private static final UtilCache<String, Properties> urlCache = UtilCache.createUtilCache("properties.UtilPropertiesUrlCache");
private static final Set<String> propertiesNotFound = new HashSet<String>();
/** Compares the specified property to the compareString, returns true if they are the same, false otherwise
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param compareString The String to compare the property value to
* @return True if the strings are the same, false otherwise
*/
public static boolean propertyValueEquals(String resource, String name, String compareString) {
String value = getPropertyValue(resource, name);
if (value == null) return false;
return value.trim().equals(compareString);
}
/** Compares Ignoring Case the specified property to the compareString, returns true if they are the same, false otherwise
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param compareString The String to compare the property value to
* @return True if the strings are the same, false otherwise
*/
public static boolean propertyValueEqualsIgnoreCase(String resource, String name, String compareString) {
String value = getPropertyValue(resource, name);
if (value == null) return false;
return value.trim().equalsIgnoreCase(compareString);
}
/** Returns the value of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultValue is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultValue The value to return if the property is not found
* @return The value of the property in the properties file, or if not found then the defaultValue
*/
public static String getPropertyValue(String resource, String name, String defaultValue) {
String value = getPropertyValue(resource, name);
if (UtilValidate.isEmpty(value))
return defaultValue;
else
return value;
}
public static double getPropertyNumber(String resource, String name, double defaultValue) {
String str = getPropertyValue(resource, name);
if (str == null) {
return defaultValue;
}
try {
return Double.parseDouble(str);
} catch (NumberFormatException nfe) {
return defaultValue;
}
}
public static double getPropertyNumber(String resource, String name) {
return getPropertyNumber(resource, name, 0.00000);
}
/**
* Returns the Number as a Number-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultObject is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultNumber Optional: The Number to return if the property is not found.
* @param type A String of the the Object the Number is converted to (like "Integer").
* @return A Number-Object of the property as the defined type; or if not found the defaultObject
*/
private static Number getPropertyNumber(String resource, String name, Number defaultNumber, String type) {
String str = getPropertyValue(resource, name);
if (UtilValidate.isEmpty(str)) {
Debug.logWarning("Error converting String \"" + str + "\" to " + type + "; using defaultNumber " + defaultNumber + ".", module);
return defaultNumber;
} else
try {
return (Number)(ObjectType.simpleTypeConvert(str, type, null, null));
} catch (GeneralException e) {
Debug.logWarning("Error converting String \"" + str + "\" to " + type + "; using defaultNumber " + defaultNumber + ".", module);
}
return defaultNumber;
}
/**
* Returns a Boolean-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultValue is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultValue Optional: The Value to return if the property is not found or not the correct format.
* @return A Boolean-Object of the property; or if not found the defaultValue
*/
public static Boolean getPropertyAsBoolean(String resource, String name, boolean defaultValue) {
String str = getPropertyValue(resource, name);
if ("true".equalsIgnoreCase(str)) return Boolean.TRUE;
else if ("false".equalsIgnoreCase(str)) return Boolean.FALSE;
else return defaultValue;
}
/**
* Returns an Integer-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultNumber is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultNumber Optional: The Value to return if the property is not found.
* @return An Integer-Object of the property; or if not found the defaultNumber
*/
public static Integer getPropertyAsInteger(String resource, String name, int defaultNumber) {
return (Integer)getPropertyNumber(resource, name, defaultNumber, "Integer");
}
/**
* Returns a Long-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultNumber is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultNumber Optional: The Value to return if the property is not found.
* @return A Long-Object of the property; or if not found the defaultNumber
*/
public static Long getPropertyAsLong(String resource, String name, long defaultNumber) {
return (Long)getPropertyNumber(resource, name, defaultNumber, "Long");
}
/**
* Returns a Float-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultNumber is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultNumber Optional: The Value to return if the property is not found.
* @return A Long-Object of the property; or if not found the defaultNumber
*/
public static Float getPropertyAsFloat(String resource, String name, float defaultNumber) {
return (Float)getPropertyNumber(resource, name, defaultNumber, "Float");
}
/**
* Returns a Double-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultNumber is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultNumber Optional: The Value to return if the property is not found.
* @return A Double-Object of the property; or if not found the defaultNumber
*/
public static Double getPropertyAsDouble(String resource, String name, double defaultNumber) {
return (Double)getPropertyNumber(resource, name, defaultNumber, "Double");
}
/**
* Returns a BigInteger-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultNumber is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultNumber Optional: The Value to return if the property is not found.
* @return A BigInteger-Object of the property; or if not found the defaultNumber
*/
public static BigInteger getPropertyAsBigInteger(String resource, String name, BigInteger defaultNumber) {
String strValue = getPropertyValue(resource, name);
BigInteger result = defaultNumber;
try {
result = new BigInteger(strValue);
} catch (NumberFormatException nfe) {
Debug.logWarning("Couldnt convert String \"" + strValue + "\" to BigInteger; using defaultNumber " + defaultNumber.toString() + ".", module);
}
return result;
}
/**
* Returns a BigDecimal-Object of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultNumber is returned.
* @param resource The name of the resource - if the properties file is 'webevent.properties', the resource name is 'webevent'
* @param name The name of the property in the properties file
* @param defaultNumber Optional: The Value to return if the property is not found.
* @return A BigDecimal-Object of the property; or if not found the defaultNumber
*/
public static BigDecimal getPropertyAsBigDecimal(String resource, String name, BigDecimal defaultNumber) {
String strValue = getPropertyValue(resource, name);
BigDecimal result = defaultNumber;
try {
result = new BigDecimal(strValue);
} catch (NumberFormatException nfe) {
Debug.logWarning("Couldnt convert String \"" + strValue + "\" to BigDecimal; using defaultNumber " + defaultNumber.toString() + ".", module);
}
return result;
}
/** Returns the value of the specified property name from the specified resource/properties file
* @param resource The name of the resource - can be a file, class, or URL
* @param name The name of the property in the properties file
* @return The value of the property in the properties file
*/
public static String getPropertyValue(String resource, String name) {
if (UtilValidate.isEmpty(resource)) return "";
if (UtilValidate.isEmpty(name)) return "";
Properties properties = getProperties(resource);
if (properties == null) {
return "";
}
String value = null;
try {
value = properties.getProperty(name);
} catch (Exception e) {
Debug.logInfo(e, module);
}
return value == null ? "" : value.trim();
}
/**
* Returns a new <code>Properties</code> instance created from <code>fileName</code>.
* <p>This method is intended for low-level framework classes that need to read
* properties files before OFBiz has been fully initialized.</p>
*
* @param fileName The full name of the properties file ("foo.properties")
* @return A new <code>Properties</code> instance created from <code>fileName</code>, or
* <code>null</code> if the file was not found
* @throws IllegalArgumentException if <code>fileName</code> is empty
* @throws IllegalStateException if there was a problem reading the file
*/
public static Properties createProperties(String fileName) {
Assert.notEmpty("fileName", fileName);
InputStream inStream = null;
try {
URL url = Thread.currentThread().getContextClassLoader().getResource(fileName);
if (url == null) {
return null;
}
inStream = url.openStream();
Properties properties = new Properties();
properties.load(inStream);
return properties;
} catch (Exception e) {
throw new IllegalStateException("Exception thrown while reading " + fileName + ": " + e);
} finally {
if (inStream != null) {
try {
inStream.close();
} catch (IOException e) {
System.out.println("Exception thrown while closing InputStream: " + e);
}
}
}
}
/** Returns the specified resource/properties file
* @param resource The name of the resource - can be a file, class, or URL
* @return The properties file
*/
public static Properties getProperties(String resource) {
if (UtilValidate.isEmpty(resource)) {
return null;
}
URL url = resolvePropertiesUrl(resource, null);
return getProperties(url);
}
/** Returns the specified resource/properties file
* @param url The URL to the resource
* @return The properties file
*/
public static Properties getProperties(URL url) {
if (url == null) {
return null;
}
String cacheKey = url.toString();
Properties properties = urlCache.get(cacheKey);
if (properties == null) {
try {
properties = new ExtendedProperties(url, null);
urlCache.put(cacheKey, properties);
} catch (Exception e) {
Debug.logInfo(e, module);
}
}
if (properties == null) {
Debug.logInfo("[UtilProperties.getProperties] could not find resource: " + url, module);
return null;
}
return properties;
}
// ========= URL Based Methods ==========
/** Compares the specified property to the compareString, returns true if they are the same, false otherwise
* @param url URL object specifying the location of the resource
* @param name The name of the property in the properties file
* @param compareString The String to compare the property value to
* @return True if the strings are the same, false otherwise
*/
public static boolean propertyValueEquals(URL url, String name, String compareString) {
String value = getPropertyValue(url, name);
if (value == null) return false;
return value.trim().equals(compareString);
}
/** Compares Ignoring Case the specified property to the compareString, returns true if they are the same, false otherwise
* @param url URL object specifying the location of the resource
* @param name The name of the property in the properties file
* @param compareString The String to compare the property value to
* @return True if the strings are the same, false otherwise
*/
public static boolean propertyValueEqualsIgnoreCase(URL url, String name, String compareString) {
String value = getPropertyValue(url, name);
if (value == null) return false;
return value.trim().equalsIgnoreCase(compareString);
}
/** Returns the value of the specified property name from the specified resource/properties file.
* If the specified property name or properties file is not found, the defaultValue is returned.
* @param url URL object specifying the location of the resource
* @param name The name of the property in the properties file
* @param defaultValue The value to return if the property is not found
* @return The value of the property in the properties file, or if not found then the defaultValue
*/
public static String getPropertyValue(URL url, String name, String defaultValue) {
String value = getPropertyValue(url, name);
if (UtilValidate.isEmpty(value))
return defaultValue;
else
return value;
}
public static double getPropertyNumber(URL url, String name, double defaultValue) {
String str = getPropertyValue(url, name);
if (str == null) {
return defaultValue;
}
try {
return Double.parseDouble(str);
} catch (NumberFormatException nfe) {
return defaultValue;
}
}
public static double getPropertyNumber(URL url, String name) {
return getPropertyNumber(url, name, 0.00000);
}
/** Returns the value of the specified property name from the specified resource/properties file
* @param url URL object specifying the location of the resource
* @param name The name of the property in the properties file
* @return The value of the property in the properties file
*/
public static String getPropertyValue(URL url, String name) {
if (url == null) return "";
if (UtilValidate.isEmpty(name)) return "";
Properties properties = getProperties(url);
if (properties == null) {
return null;
}
String value = null;
try {
value = properties.getProperty(name);
} catch (Exception e) {
Debug.logInfo(e, module);
}
return value == null ? "" : value.trim();
}
/** Returns the value of a split property name from the specified resource/properties file
* Rather than specifying the property name the value of a name.X property is specified which
* will correspond to a value.X property whose value will be returned. X is a number from 1 to
* whatever and all values are checked until a name.X for a certain X is not found.
* @param url URL object specifying the location of the resource
* @param name The name of the split property in the properties file
* @return The value of the split property from the properties file
*/
public static String getSplitPropertyValue(URL url, String name) {
if (url == null) return "";
if (UtilValidate.isEmpty(name)) return "";
Properties properties = getProperties(url);
if (properties == null) {
return null;
}
String value = null;
try {
int curIdx = 1;
String curName = null;
while ((curName = properties.getProperty("name." + curIdx)) != null) {
if (name.equals(curName)) {
value = properties.getProperty("value." + curIdx);
break;
}
curIdx++;
}
} catch (Exception e) {
Debug.logInfo(e, module);
}
return value == null ? "" : value.trim();
}
/** Sets the specified value of the specified property name to the specified resource/properties file
* @param resource The name of the resource - must be a file
* @param name The name of the property in the properties file
* @param value The value of the property in the properties file */
public static void setPropertyValue(String resource, String name, String value) {
if (UtilValidate.isEmpty(resource)) return;
if (UtilValidate.isEmpty(name)) return;
Properties properties = getProperties(resource);
if (properties == null) {
return;
}
try {
properties.setProperty(name, value);
FileOutputStream propFile = new FileOutputStream(resource);
if ("XuiLabels".equals(name)) {
properties.store(propFile,
"##############################################################################\n"
+"# Licensed to the Apache Software Foundation (ASF) under one \n"
+"# or more contributor license agreements. See the NOTICE file \n"
+"# distributed with this work for additional information \n"
+"# regarding copyright ownership. The ASF licenses this file \n"
+"# to you under the Apache License, Version 2.0 (the \n"
+"# \"License\"); you may not use this file except in compliance \n"
+"# with the License. You may obtain a copy of the License at \n"
+"# \n"
+"# http://www.apache.org/licenses/LICENSE-2.0 \n"
+"# \n"
+"# Unless required by applicable law or agreed to in writing, \n"
+"# software distributed under the License is distributed on an \n"
+"# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY \n"
+"# KIND, either express or implied. See the License for the \n"
+"# specific language governing permissions and limitations \n"
+"# under the License. \n"
+"###############################################################################\n"
+"# \n"
+"# Dynamically modified by OFBiz Framework (org.apache.ofbiz.base.util : UtilProperties.setPropertyValue)\n"
+"# \n"
+"# By default the screen is 1024x768 wide. If you want to use another screen size,\n"
+"# you must create a new directory under plugins/pos/screens, like the 800x600.\n"
+"# You must also set the 3 related parameters (StartClass, ClientWidth, ClientHeight) accordingly.\n"
+"#");
} else {
properties.store(propFile,
"##############################################################################\n"
+"# Licensed to the Apache Software Foundation (ASF) under one \n"
+"# or more contributor license agreements. See the NOTICE file \n"
+"# distributed with this work for additional information \n"
+"# regarding copyright ownership. The ASF licenses this file \n"
+"# to you under the Apache License, Version 2.0 (the \n"
+"# \"License\"); you may not use this file except in compliance \n"
+"# with the License. You may obtain a copy of the License at \n"
+"# \n"
+"# http://www.apache.org/licenses/LICENSE-2.0 \n"
+"# \n"
+"# Unless required by applicable law or agreed to in writing, \n"
+"# software distributed under the License is distributed on an \n"
+"# \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY \n"
+"# KIND, either express or implied. See the License for the \n"
+"# specific language governing permissions and limitations \n"
+"# under the License. \n"
+"###############################################################################\n"
+"# \n"
+"# Dynamically modified by OFBiz Framework (org.apache.ofbiz.base.util : UtilProperties.setPropertyValue)\n"
+"# The comments have been removed, you may still find them on the OFBiz repository... \n"
+"#");
}
propFile.close();
} catch (FileNotFoundException e) {
Debug.logInfo(e, "Unable to located the resource file.", module);
} catch (IOException e) {
Debug.logError(e, module);
}
}
/** Sets the specified value of the specified property name to the specified resource/properties in memory, does not persist it
* @param resource The name of the resource
* @param name The name of the property in the resource
* @param value The value of the property to set in memory */
public static void setPropertyValueInMemory(String resource, String name, String value) {
if (UtilValidate.isEmpty(resource)) return;
if (UtilValidate.isEmpty(name)) return;
Properties properties = getProperties(resource);
if (properties == null) {
return;
}
properties.setProperty(name, value);
}
// ========= Locale & Resource Based Methods ==========
/** Returns the value of the specified property name from the specified
* resource/properties file corresponding to the given locale.
* @param resource The name of the resource - can be a file, class, or URL
* @param name The name of the property in the properties file
* @param locale The locale that the given resource will correspond to
* @return The value of the property in the properties file
*/
public static String getMessage(String resource, String name, Locale locale) {
if (UtilValidate.isEmpty(resource)) return "";
if (UtilValidate.isEmpty(name)) return "";
ResourceBundle bundle = getResourceBundle(resource, locale);
if (bundle == null) return name;
String value = null;
if (bundle.containsKey(name)) {
value = bundle.getString(name);
} else {
Debug.logInfo(name + " misses in " + resource + " for locale " + locale, module);
return name;
}
return value == null ? name : value.trim();
}
/** Returns the value of the specified property name from the specified resource/properties file corresponding
* to the given locale and replacing argument place holders with the given arguments using the MessageFormat class
* @param resource The name of the resource - can be a file, class, or URL
* @param name The name of the property in the properties file
* @param arguments An array of Objects to insert into the message argument place holders
* @param locale The locale that the given resource will correspond to
* @return The value of the property in the properties file
*/
public static String getMessage(String resource, String name, Object[] arguments, Locale locale) {
String value = getMessage(resource, name, locale);
if (UtilValidate.isEmpty(value)) {
return "";
} else {
if (arguments != null && arguments.length > 0) {
value = MessageFormat.format(value, arguments);
}
return value;
}
}
/** Returns the value of the specified property name from the specified resource/properties file corresponding
* to the given locale and replacing argument place holders with the given arguments using the MessageFormat class
* @param resource The name of the resource - can be a file, class, or URL
* @param name The name of the property in the properties file
* @param arguments A List of Objects to insert into the message argument place holders
* @param locale The locale that the given resource will correspond to
* @return The value of the property in the properties file
*/
public static <E> String getMessage(String resource, String name, List<E> arguments, Locale locale) {
String value = getMessage(resource, name, locale);
if (UtilValidate.isEmpty(value)) {
return "";
} else {
if (UtilValidate.isNotEmpty(arguments)) {
value = MessageFormat.format(value, arguments.toArray());
}
return value;
}
}
public static String getMessageList(String resource, String name, Locale locale, Object... arguments) {
return getMessage(resource, name, arguments, locale);
}
/** Returns the value of the specified property name from the specified resource/properties file corresponding
* to the given locale and replacing argument place holders with the given arguments using the FlexibleStringExpander class
* @param resource The name of the resource - can be a file, class, or URL
* @param name The name of the property in the properties file
* @param context A Map of Objects to insert into the message place holders using the ${} syntax of the FlexibleStringExpander
* @param locale The locale that the given resource will correspond to
* @return The value of the property in the properties file
*/
public static String getMessage(String resource, String name, Map<String, ? extends Object> context, Locale locale) {
String value = getMessage(resource, name, locale);
if (UtilValidate.isEmpty(value)) {
return "";
} else {
if (UtilValidate.isNotEmpty(context)) {
value = FlexibleStringExpander.expandString(value, context, locale);
}
return value;
}
}
public static String getMessageMap(String resource, String name, Locale locale, Object... context) {
return getMessage(resource, name, UtilGenerics.toMap(String.class, context), locale);
}
private static Set<String> resourceNotFoundMessagesShown = new HashSet<String>();
/** Returns the specified resource/properties file as a ResourceBundle
* @param resource The name of the resource - can be a file, class, or URL
* @param locale The locale that the given resource will correspond to
* @return The ResourceBundle
*/
public static ResourceBundle getResourceBundle(String resource, Locale locale) {
if (UtilValidate.isEmpty(resource)) {
throw new IllegalArgumentException("resource cannot be null or empty");
}
if (locale == null) {
throw new IllegalArgumentException("locale cannot be null");
}
ResourceBundle bundle = null;
try {
bundle = UtilResourceBundle.getBundle(resource, locale, (ClassLoader) null);
} catch (MissingResourceException e) {
String resourceCacheKey = createResourceName(resource, locale, false);
if (!resourceNotFoundMessagesShown.contains(resourceCacheKey)) {
resourceNotFoundMessagesShown.add(resourceCacheKey);
Debug.logInfo("[UtilProperties.getPropertyValue] could not find resource: " + resource + " for locale " + locale, module);
}
throw new IllegalArgumentException("Could not find resource bundle [" + resource + "] in the locale [" + locale + "]");
}
return bundle;
}
/** Returns the specified resource/properties file as a Map with the original
* ResourceBundle in the Map under the key _RESOURCE_BUNDLE_
* @param resource The name of the resource - can be a file, class, or URL
* @param locale The locale that the given resource will correspond to
* @return Map containing all entries in The ResourceBundle
*/
public static ResourceBundleMapWrapper getResourceBundleMap(String resource, Locale locale) {
return new ResourceBundleMapWrapper(getResourceBundle(resource, locale));
}
/** Returns the specified resource/properties file as a Map with the original
* ResourceBundle in the Map under the key _RESOURCE_BUNDLE_
* @param resource The name of the resource - can be a file, class, or URL
* @param locale The locale that the given resource will correspond to
* @param context The screen rendering context
* @return Map containing all entries in The ResourceBundle
*/
public static ResourceBundleMapWrapper getResourceBundleMap(String resource, Locale locale, Map<String, Object> context) {
return new ResourceBundleMapWrapper(getResourceBundle(resource, locale), context);
}
/** Returns the specified resource/properties file.<p>Note that this method
* will return a Properties instance for the specified locale <em>only</em> -
* if you need <a href="http://www.w3.org/International/">I18n</a> properties, then use
* <a href="#getResourceBundle(java.lang.String,%20java.util.Locale)">
* getResourceBundle(String resource, Locale locale)</a>. This method is
* intended to be used primarily by the UtilProperties class.</p>
* @param resource The name of the resource - can be a file, class, or URL
* @param locale The desired locale
* @return The Properties instance, or null if no matching properties are found
*/
public static Properties getProperties(String resource, Locale locale) {
if (UtilValidate.isEmpty(resource)) {
throw new IllegalArgumentException("resource cannot be null or empty");
}
if (locale == null) {
throw new IllegalArgumentException("locale cannot be null");
}
Properties properties = null;
URL url = resolvePropertiesUrl(resource, locale);
if (url != null) {
try {
properties = new ExtendedProperties(url, locale);
} catch (Exception e) {
if (UtilValidate.isNotEmpty(e.getMessage())) {
Debug.logInfo(e.getMessage(), module);
} else {
Debug.logInfo("Exception thrown: " + e.getClass().getName(), module);
}
properties = null;
}
}
if (UtilValidate.isNotEmpty(properties)) {
if (Debug.verboseOn()) Debug.logVerbose("Loaded " + properties.size() + " properties for: " + resource + " (" + locale + ")", module);
}
return properties;
}
// ========= Classes and Methods for expanded Properties file support ========== //
// Private lazy-initializer class
private static class FallbackLocaleHolder {
private static final Locale fallbackLocale = getFallbackLocale();
private static Locale getFallbackLocale() {
Locale fallbackLocale = null;
String locale = getPropertyValue("general", "locale.properties.fallback");
if (UtilValidate.isNotEmpty(locale)) {
fallbackLocale = UtilMisc.parseLocale(locale);
}
if (fallbackLocale == null) {
fallbackLocale = Locale.ENGLISH;
}
return fallbackLocale;
}
}
/** Returns the configured fallback locale. UtilProperties uses this locale
* to resolve locale-specific XML properties.<p>The fallback locale can be
* configured using the <code>locale.properties.fallback</code> property in
* <code>general.properties</code>.
* @return The configured fallback locale
*/
public static Locale getFallbackLocale() {
return FallbackLocaleHolder.fallbackLocale;
}
/** Converts a Locale instance to a candidate Locale list. The list
* is ordered most-specific to least-specific. Example:
* <code>localeToCandidateList(Locale.US)</code> would return
* a list containing <code>en_US</code> and <code>en</code>.
* @return A list of candidate locales.
*/
public static List<Locale> localeToCandidateList(Locale locale) {
List<Locale> localeList = new LinkedList<Locale>();
localeList.add(locale);
String localeString = locale.toString();
int pos = localeString.lastIndexOf("_", localeString.length());
while (pos != -1) {
localeString = localeString.substring(0, pos);
localeList.add(new Locale(localeString));
pos = localeString.lastIndexOf("_", localeString.length());
}
return localeList;
}
// Private lazy-initializer class
private static class CandidateLocalesHolder {
private static Set<Locale> defaultCandidateLocales = getDefaultCandidateLocales();
private static Set<Locale> getDefaultCandidateLocales() {
Set<Locale> defaultCandidateLocales = new LinkedHashSet<Locale>();
defaultCandidateLocales.addAll(localeToCandidateList(Locale.getDefault()));
defaultCandidateLocales.addAll(localeToCandidateList(getFallbackLocale()));
defaultCandidateLocales.add(Locale.ROOT);
return Collections.unmodifiableSet(defaultCandidateLocales);
}
}
/** Returns the default candidate Locale list. The list is populated
* with the JVM's default locale, the OFBiz fallback locale, and
* the <code>LOCALE_ROOT</code> (empty) locale - in that order.
* @return A list of default candidate locales.
*/
public static Set<Locale> getDefaultCandidateLocales() {
return CandidateLocalesHolder.defaultCandidateLocales;
}
/** Returns a list of candidate locales based on a supplied locale.
* The returned list consists of the supplied locale and the
* <a href="#getDefaultCandidateLocales()">default candidate locales</a>
* - in that order.
* @param locale The desired locale
* @return A list of candidate locales
*/
public static List<Locale> getCandidateLocales(Locale locale) {
// Java 6 conformance
if (Locale.ROOT.equals(locale)) {
return UtilMisc.toList(locale);
}
Set<Locale> localeSet = new LinkedHashSet<Locale>();
localeSet.addAll(localeToCandidateList(locale));
localeSet.addAll(getDefaultCandidateLocales());
List<Locale> localeList = new ArrayList<Locale>(localeSet);
return localeList;
}
/** Create a localized resource name based on a resource name and
* a locale.
* @param resource The desired resource
* @param locale The desired locale
* @param removeExtension Remove file extension from resource String
* @return Localized resource name
*/
public static String createResourceName(String resource, Locale locale, boolean removeExtension) {
String resourceName = resource;
if (removeExtension) {
if (resourceName.endsWith(".xml")) {
resourceName = resourceName.replace(".xml", "");
} else if (resourceName.endsWith(".properties")) {
resourceName = resourceName.replace(".properties", "");
}
}
if (locale != null) {
if (UtilValidate.isNotEmpty(locale.toString())) {
resourceName = resourceName + "_" + locale;
}
}
return resourceName;
}
public static boolean isPropertiesResourceNotFound(String resource, Locale locale, boolean removeExtension) {
return propertiesNotFound.contains(createResourceName(resource, locale, removeExtension));
}
/**
* Resolve a properties file URL.
* <p>This method uses the following strategy:</p>
* <ul>
* <li>Locate the XML file specified in <code>resource (MyProps.xml)</code></li>
* <li>Locate the file that starts with the name specified in
* <code>resource</code> and ends with the locale's string and
* <code>.xml (MyProps_en.xml)</code>
* </li>
* <li>Locate the file that starts with the name specified in
* <code>resource</code> and ends with the locale's string and
* <code>.properties (MyProps_en.properties)</code>
* </li>
* <li>Locate the file that starts with the name specified in
* <code>resource and ends with the locale's string (MyProps_en)</code>
* </li>
* </ul>
*
* The <code>component://</code> protocol is supported in the
* <code>resource</code> parameter.
*
* @param resource The resource to resolve
* @param locale The desired locale
* @return A URL instance or null if not found.
*/
public static URL resolvePropertiesUrl(String resource, Locale locale) {
if (UtilValidate.isEmpty(resource)) {
throw new IllegalArgumentException("resource cannot be null or empty");
}
String resourceName = createResourceName(resource, locale, false);
if (propertiesNotFound.contains(resourceName)) {
return null;
}
boolean containsProtocol = resource.contains(":");
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = null;
try {
// Check for complete URL first
if (resource.endsWith(".xml") || resource.endsWith(".properties")) {
if (containsProtocol) {
url = FlexibleLocation.resolveLocation(resource, loader);
} else {
url = UtilURL.fromResource(resource, loader);
}
if (url != null) {
return url;
}
}
// Check for *.properties file
if (containsProtocol) {
url = FlexibleLocation.resolveLocation(resourceName + ".properties", loader);
} else {
url = UtilURL.fromResource(resourceName + ".properties", loader);
}
if (url != null) {
return url;
}
// Check for Java XML properties file
if (containsProtocol) {
url = FlexibleLocation.resolveLocation(resourceName + ".xml", loader);
} else {
url = UtilURL.fromResource(resourceName + ".xml", loader);
}
if (url != null) {
return url;
}
// Check for Custom XML properties file
if (containsProtocol) {
url = FlexibleLocation.resolveLocation(resource + ".xml", loader);
} else {
url = UtilURL.fromResource(resource + ".xml", loader);
}
if (url != null) {
return url;
}
if (containsProtocol) {
url = FlexibleLocation.resolveLocation(resource, loader);
} else {
url = UtilURL.fromResource(resource, loader);
}
if (url != null) {
return url;
}
} catch (Exception e) {
Debug.logInfo("Properties resolver: invalid URL - " + e.getMessage(), module);
}
if (propertiesNotFound.size() <= 300) {
// Sanity check - list could get quite large
propertiesNotFound.add(resourceName);
}
return null;
}
/**
* Convert XML property file to Properties instance. This method will convert
* both the Java XML properties file format and the OFBiz custom XML
* properties file format.
*
* <p>The format of the custom XML properties file is:</p>
* <pre>
* {@code
* <resource>
* <property key="key">
* <value xml:lang="locale 1">Some value</value>
* <value xml:lang="locale 2">Some value</value>
* ...
* </property>
* ...
* </resource>
* }
* </pre>
* where <em>"locale 1", "locale 2"</em> are valid xml:lang values..
*
* @param in XML file InputStream
* @param locale The desired locale
* @param properties Optional Properties object to populate
* @return Properties instance or null if not found
*/
public static Properties xmlToProperties(InputStream in, Locale locale, Properties properties) throws IOException, InvalidPropertiesFormatException {
if (in == null) {
throw new IllegalArgumentException("InputStream cannot be null");
}
Document doc = null;
try {
doc = UtilXml.readXmlDocument(in, true, "XML Properties file");
in.close();
} catch (Exception e) {
Debug.logWarning(e, "XML file for locale " + locale + " could not be loaded.", module);
in.close();
return null;
}
Element resourceElement = doc.getDocumentElement();
List<? extends Element> propertyList = UtilXml.childElementList(resourceElement, "property");
if (UtilValidate.isNotEmpty(propertyList)) {
// Custom XML properties file format
if (locale == null) {
throw new IllegalArgumentException("locale cannot be null");
}
String localeString = locale.toString();
String correctedLocaleString = localeString.replace('_','-');
for (Element property : propertyList) {
// Support old way of specifying xml:lang value.
// Old way: en_AU, new way: en-AU
Element value = UtilXml.firstChildElement(property, "value", "xml:lang", correctedLocaleString);
if( value == null ) {
value = UtilXml.firstChildElement(property, "value", "xml:lang", localeString);
}
if (value != null) {
if (properties == null) {
properties = new Properties();
}
String valueString = UtilXml.elementValue(value);
if (valueString != null) {
properties.put(property.getAttribute("key"), valueString);
}
}
}
return properties;
}
propertyList = UtilXml.childElementList(resourceElement, "entry");
if (UtilValidate.isEmpty(propertyList)) {
throw new InvalidPropertiesFormatException("XML properties file invalid or empty");
}
// Java XML properties file format
for (Element property : propertyList) {
String value = UtilXml.elementValue(property);
if (value != null) {
if (properties == null) {
properties = new Properties();
}
properties.put(property.getAttribute("key"), value);
}
}
return properties;
}
/** Custom ResourceBundle class. This class extends ResourceBundle
* to add custom bundle caching code and support for the OFBiz custom XML
* properties file format.
*/
public static class UtilResourceBundle extends ResourceBundle {
private static final UtilCache<String, UtilResourceBundle> bundleCache = UtilCache.createUtilCache("properties.UtilPropertiesBundleCache");
protected Properties properties = null;
protected Locale locale = null;
protected int hashCode = hashCode();
protected UtilResourceBundle() {}
public UtilResourceBundle(Properties properties, Locale locale, UtilResourceBundle parent) {
this.properties = properties;
this.locale = locale;
setParent(parent);
String hashString = properties.toString();
if (parent != null) {
hashString += parent.properties;
}
this.hashCode = hashString.hashCode();
}
public static ResourceBundle getBundle(String resource, Locale locale, ClassLoader loader) throws MissingResourceException {
String resourceName = createResourceName(resource, locale, true);
UtilResourceBundle bundle = bundleCache.get(resourceName);
if (bundle == null) {
double startTime = System.currentTimeMillis();
List<Locale> candidateLocales = (List<Locale>) getCandidateLocales(locale);
UtilResourceBundle parentBundle = null;
int numProperties = 0;
while (candidateLocales.size() > 0) {
Locale candidateLocale = candidateLocales.remove(candidateLocales.size() - 1);
// ResourceBundles are connected together as a singly-linked list
String lookupName = createResourceName(resource, candidateLocale, true);
UtilResourceBundle lookupBundle = bundleCache.get(lookupName);
if (lookupBundle == null) {
Properties newProps = getProperties(resource, candidateLocale);
if (UtilValidate.isNotEmpty(newProps)) {
// The last bundle we found becomes the parent of the new bundle
parentBundle = bundle;
bundle = new UtilResourceBundle(newProps, candidateLocale, parentBundle);
bundleCache.putIfAbsent(lookupName, bundle);
numProperties = newProps.size();
}
} else {
parentBundle = bundle;
bundle = lookupBundle;
}
}
if (bundle == null) {
throw new MissingResourceException("Resource " + resource + ", locale " + locale + " not found", null, null);
} else if (!bundle.getLocale().equals(locale)) {
// Create a "dummy" bundle for the requested locale
bundle = new UtilResourceBundle(bundle.properties, locale, parentBundle);
}
double totalTime = System.currentTimeMillis() - startTime;
if (Debug.infoOn()) {
Debug.logInfo("ResourceBundle " + resource + " (" + locale + ") created in " + totalTime / 1000.0 + "s with "
+ numProperties + " properties", module);
}
bundleCache.putIfAbsent(resourceName, bundle);
}
return bundle;
}
@Override
public int hashCode() {
return this.hashCode;
}
@Override
public boolean equals(Object obj) {
return obj == null ? false : obj.hashCode() == this.hashCode;
}
@Override
public Locale getLocale() {
return this.locale;
}
@Override
protected Object handleGetObject(String key) {
return properties.get(key);
}
@Override
public Enumeration<String> getKeys() {
return new Enumeration<String>() {
Iterator<String> i = UtilGenerics.cast(properties.keySet().iterator());
public boolean hasMoreElements() {
return (i.hasNext());
}
public String nextElement() {
return i.next();
}
};
}
}
/** Custom Properties class. Extended from Properties to add support
* for the OFBiz custom XML file format.
*/
public static class ExtendedProperties extends Properties {
public ExtendedProperties() {
super();
}
public ExtendedProperties(Properties defaults) {
super(defaults);
}
public ExtendedProperties(URL url, Locale locale) throws IOException, InvalidPropertiesFormatException {
InputStream in = null;
try {
in = new BufferedInputStream(url.openStream());
if (url.getFile().endsWith(".xml")) {
xmlToProperties(in, locale, this);
} else {
load(in);
}
} finally {
if (in != null) {
in.close();
}
}
}
@Override
public void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException {
try {
xmlToProperties(in, null, this);
} finally {
in.close();
}
}
}
}