All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.norconex.commons.lang.map.Properties Maven / Gradle / Ivy

/* Copyright 2010-2016 Norconex Inc.
 *
 * Licensed 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 com.norconex.commons.lang.map;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.LocaleUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;

/**
 * 

This class is a enhanced version of {@link java.util.Properties} * that enforces the use of String keys and values internally, but offers many * convenience methods for storing and retrieving multiple values of different * types (e.g. Integer, Locale, File, etc). You can also see it as a * string-based multi-value map with helpful methods. While it does not extend * {@link java.util.Properties}, it offers similar load and store * methods and can be used as a replacement for it in many cases.

* *

As of 1.4, this class no longer extends {@code TreeMap}. * It now extends {@link ObservableMap} which means you can listen * for map changes.

* *

To insert values, there are set methods and add methods. * The set methods will replace any value(s) already present under the * given key. It is essentially the same behavior as * {@link Map#put(Object, Object)}. The add method will add the * new value(s) to the list of already existing ones (if any). *

* *

Upon encountering a problem in parsing the * data to its target format, a {@link PropertiesException} is thrown.

* @author Pascal Essiembre */ @SuppressWarnings("nls") public class Properties extends ObservableMap> implements Serializable { //TODO remove support for case sensitivity and provide a utility //class that does it instead on any string-key maps? // OR, store it in a case sensitive way instead of keeping // multiple keys of different cases around. Could provide // options to put everyting lower, upper, or rely on first key // entered (to know which case-version to use). private static final long serialVersionUID = -7215126924574341L; private static final Logger LOG = LogManager.getLogger(Properties.class); /** * Default delimiter when storing/loading multi-values to/from * .properties files. */ public static final String DEFAULT_MULTIVALUE_DELIMITER = "^|~"; private final boolean caseInsensitiveKeys; private String multiValueDelimiter = DEFAULT_MULTIVALUE_DELIMITER; /** * Create a new instance with case-sensitive keys. * Internally wraps a {@link HashMap} to store keys and values. */ public Properties() { this(false); } /** * Creates a new instance. Internally wraps a {@link HashMap} to * store keys and values. * @param caseInsensitiveKeys when true methods taking a * key argument will consider the key being passed without * consideration for character case. */ public Properties(boolean caseInsensitiveKeys) { this(null, caseInsensitiveKeys); } /** * Decorates {@code Map} as a {@code Properties}. * As of version 1.4 the {@code Map} argument is decorated so that * modifications to this instance will also modify the supplied {@code Map}. * To use a {@code Map} to initialize values only, use the * {@link #load(Map)} method. * @param map the Map to decorate */ public Properties(Map> map) { this(map, false); } /** * Decorates a {@code Map} argument as a {@code Properties}. * As of version 1.4 the {@code Map} argument is decorated so that * modifications to this instance will also modify the supplied {@code Map}. * To use a {@code Map} to initialize values only, use the * {@link #load(Map)} method. * @param map the Map to decorate * @param caseInsensitiveKeys when true methods taking a * key argument will consider the key being passed without * consideration for character case. */ public Properties( Map> map, boolean caseInsensitiveKeys) { super(map); this.caseInsensitiveKeys = caseInsensitiveKeys; } /** * Gets whether keys are case sensitive or not. * @return true if case sensitive * @since 1.4 * @deprecated Since 1.8.0, use {@link #isCaseInsensitiveKeys()} */ @Deprecated public boolean isCaseSensitiveKeys() { return !caseInsensitiveKeys; } /** * Gets whether keys are case sensitive or not. * @return true if case insensitive * @since 1.8 */ public boolean isCaseInsensitiveKeys() { return caseInsensitiveKeys; } /** * Gets multiple value string delimiter. * @return multiple value string delimiter * @since 1.4 */ public String getMultiValueDelimiter() { return multiValueDelimiter; } /** * Sets multiple value string delimiter. * @param multiValueDelimiter multiple value string delimiter * @since 1.4 */ public void setMultiValueDelimiter(String multiValueDelimiter) { this.multiValueDelimiter = multiValueDelimiter; } //--- Store ---------------------------------------------------------------- /** * Writes this property list (key and element pairs) in this * Properties table to the output stream as UTF-8 in a format * suitable for loading into a Properties table using the * {@link #load(InputStream) load} method. * Otherwise, the same considerations as * {@link #store(OutputStream, String)} apply. * @param comments a description of the property list. * @return the properties as string * @throws IOException problem storing to string */ public String storeToString(String comments) throws IOException { StringWriter writer = new StringWriter(); store(writer, comments); String str = writer.toString(); writer.close(); return str; } /** * Writes this {@link Map} (key and element pairs) to the output character * stream in a format suitable for using the * {@link #load(Reader)} method. * If a key only has one value, then this method behavior is the * exact same as the {@link Properties#store(Writer, String)} method. * Keys with multi-values are joined into a single string, using * the default delimiter: * {@link Properties#DEFAULT_MULTIVALUE_DELIMITER} * @param writer an output character stream writer. * @param comments a description of the property list. * @throws IOException i/o problem * @see Properties#store(Writer, String) */ public void store(Writer writer, String comments) throws IOException { store(writer, comments, DEFAULT_MULTIVALUE_DELIMITER); } /** * Writes this {@link Map} (key and element pairs) to the output character * stream in a format suitable for using the * {@link #load(Reader, String)} method. * If a key only has one value, then this method behavior is the * exact same as the {@link Properties#store(Writer, String)} method. * Keys with multi-values are joined into a single string, using * the delimiter provided. * @param writer an output character stream writer. * @param comments a description of the property list. * @param delimiter string to used as a separator when joining * multiple values for the same key. * @throws IOException i/o problem * @see Properties#store(Writer, String) */ public void store(Writer writer, String comments, String delimiter) throws IOException { java.util.Properties p = new java.util.Properties(); for (String key : keySet()) { List values = getStrings(key); p.put(key, StringUtils.join(values, delimiter)); } p.store(writer, comments); p = null; } /** * Writes this {@link Map} (key and element pairs) to the output character * stream in a format suitable for using the * {@link #load(InputStream)} method. * If a key only has one value, then this method behavior is the * exact same as the {@link Properties#store(OutputStream, String)} method. * Keys with multi-values are joined into a single string, using * the default delimiter: * {@link Properties#DEFAULT_MULTIVALUE_DELIMITER} * @param out an output stream. * @param comments a description of the property list. * @throws IOException i/o problem * @see Properties#store(OutputStream, String) */ public void store(OutputStream out, String comments) throws IOException { store(out, comments, DEFAULT_MULTIVALUE_DELIMITER); } /** * Writes this {@link Map} (key and element pairs) to the output character * stream as UTF-8 in a format suitable for using the * {@link #load(InputStream, String)} method. * If a key only has one value, then this method behavior is the * exact same as the {@link Properties#store(OutputStream, String)} method. * Keys with multi-values are joined into a single string, using * the delimiter provided. * @param out an output stream. * @param comments a description of the property list. * @param delimiter delimiter string to used as a separator when joining * multiple values for the same key. * @throws IOException i/o problem * @see Properties#store(OutputStream, String) */ public void store(OutputStream out, String comments, String delimiter) throws IOException { store(new OutputStreamWriter( out, CharEncoding.UTF_8), comments, delimiter); } /** * Emits an XML document representing all of the properties contained * in this {@link Map}, using the specified encoding. * If a key only has one value, then this method behavior is the * exact same as the * {@link Properties#storeToXML(OutputStream, String, String)} method, * where the character encoding is "UTF-8". * Keys with multi-values are joined into a single string, using * the default delimiter: * {@link Properties#DEFAULT_MULTIVALUE_DELIMITER} * @param os the output stream on which to emit the XML document. * @param comment a description of the property list, or null * if no comment is desired. * @throws IOException i/o problem * @see Properties#storeToXML(OutputStream, String, String) */ public synchronized void storeToXML( OutputStream os, String comment) throws IOException { storeToXML(os, comment, CharEncoding.UTF_8); } /** * Emits an XML document representing all of the properties contained * in this {@link Map}, using the specified encoding. * If a key only has one value, then this method behavior is the * exact same as the * {@link Properties#storeToXML(OutputStream, String, String)} method. * Keys with multi-values are joined into a single string, using * the default delimiter: * {@link Properties#DEFAULT_MULTIVALUE_DELIMITER} * @param os the output stream on which to emit the XML document. * @param comment a description of the property list, or null * if no comment is desired. * @param encoding character encoding * @throws IOException i/o problem * @see Properties#storeToXML(OutputStream, String, String) */ public synchronized void storeToXML(OutputStream os, String comment, String encoding) throws IOException { storeToXML(os, comment, encoding, DEFAULT_MULTIVALUE_DELIMITER); } /** * Emits an XML document representing all of the properties contained * in this {@link Map}, using the specified encoding. * If a key only has one value, then this method behavior is the * exact same as the * {@link Properties#storeToXML(OutputStream, String, String)} method. * Keys with multi-values are joined into a single string, using * the delimiter provided. * @param os the output stream on which to emit the XML document. * @param comment a description of the property list, or null * if no comment is desired. * @param encoding character encoding * @param delimiter delimiter string to used as a separator when joining * multiple values for the same key. * @throws IOException i/o problem * @see Properties#storeToXML(OutputStream, String, String) */ public synchronized void storeToXML(OutputStream os, String comment, String encoding, String delimiter) throws IOException { java.util.Properties p = new java.util.Properties(); for (String key : keySet()) { List values = getStrings(key); p.put(key, StringUtils.join(values, delimiter)); } p.storeToXML(os, comment, encoding); p = null; } //--- Load ----------------------------------------------------------------- /** * Reads a property list (key and element pairs) from the input * character stream in a simple line-oriented format. * If a key was stored with multiple values using a delimiter, this, * method will split these values appropriately assuming the delimiter is * {@link Properties#DEFAULT_MULTIVALUE_DELIMITER} * If the key value was stored as a * single value, then this method behavior is the * exact same as the * {@link Properties#load(Reader)} method. * @param reader the input character stream. * @throws IOException i/o problem * @see Properties#load(Reader) */ public synchronized void load(Reader reader) throws IOException { load(reader, DEFAULT_MULTIVALUE_DELIMITER); } /** * Reads a property list (key and element pairs) from the input * character stream in a simple line-oriented format. * If a key was stored with multiple values using a delimiter, * this method will split these values appropriately provided the * supplied delimiter is the same. If the key value was stored as a * single value, then this method behavior is the * exact same as the * {@link Properties#load(Reader)} method. * @param reader the input character stream. * @param delimiter delimiter string to used to parse a multi value * key. * @throws IOException i/o problem * @see Properties#load(Reader) */ public synchronized void load(Reader reader, String delimiter) throws IOException { java.util.Properties p = new java.util.Properties(); p.load(reader); for (String key : p.stringPropertyNames()) { List values = new ArrayList<>(); String value = p.getProperty(key); if (value != null) { values.addAll(Arrays.asList( StringUtils.splitByWholeSeparator(value, delimiter))); } put(key, values); } p = null; } /** *

Reads all key/value pairs in the given map, and * add them to this Map. Keys are converted to strings * using their toString() method, with exception * of values being arrays or collections. In such case, the entry * is considered a multi-value one and each value will be converted * to individual strings. null keys are ignored. * null values are converted to an empty string.

*

Changes to this instance * won't be reflected in the given Map. If you want otherwise, * use invoke the constructor with a Map argument.

* * @param map the map containing values to load */ public synchronized void load(Map map) { if (map != null) { for (Entry entry : map.entrySet()) { Object keyObj = entry.getKey(); if (keyObj == null) { continue; } String key = Objects.toString(keyObj, null); Object valObj = entry.getValue(); if (valObj == null) { valObj = StringUtils.EMPTY; } Iterable it = null; if (valObj.getClass().isArray()) { it = Arrays.asList((Object[]) valObj); } else if (valObj instanceof Iterable) { it = (Iterable) valObj; } if (it == null) { addString(key, Objects.toString(valObj, null)); } else { for (Object val : it) { addString(key, Objects.toString(val, null)); } } } } } /** * Reads a property list (key and element pairs) from the input * character stream (UTF-8) in a simple line-oriented format. * If a key was stored with multiple values using a delimiter, this, * method will split these values appropriately assuming the delimiter is * {@link Properties#DEFAULT_MULTIVALUE_DELIMITER} * If the key value was stored as a * single value, then this method behavior is the * exact same as the * {@link Properties#load(InputStream)} method. * @param inStream the input stream. * @throws IOException i/o problem * @see Properties#load(InputStream) */ public synchronized void load(InputStream inStream) throws IOException { load(new InputStreamReader(inStream, CharEncoding.UTF_8), DEFAULT_MULTIVALUE_DELIMITER); } /** * Reads a property list (key and element pairs) from the input * character stream (UTF8) in a simple line-oriented format. * If a key was stored with multiple values using a delimiter, * this method will split these values appropriately provided the * supplied delimiter is the same. If the key value was stored as a * single value, then this method behavior is the * exact same as the * {@link Properties#load(InputStream)} method. * @param inStream the input stream. * @param delimiter delimiter string to used to parse a multi value * key. * @throws IOException i/o problem * @see Properties#load(InputStream) */ public synchronized void load(InputStream inStream, String delimiter) throws IOException { load(new InputStreamReader(inStream, CharEncoding.UTF_8), delimiter); } /** * Loads all of the properties represented by the XML document on the * specified input stream into this instance. * If a key was stored with multiple values using a delimiter, * method will split these values appropriately assuming the delimiter is * {@link Properties#DEFAULT_MULTIVALUE_DELIMITER} * If the key value was stored as a * single value, then this method behavior is the * exact same as the * {@link Properties#loadFromXML(InputStream)} method. * @param in in the input stream from which to read the XML document. * @throws IOException i/o problem */ public synchronized void loadFromXML(InputStream in) throws IOException { loadFromXML(in, DEFAULT_MULTIVALUE_DELIMITER); } /** * Loads all of the properties represented by the XML document on the * specified input stream into this instance. * If a key was stored with multiple values using a delimiter, * this method will split these values appropriately provided the * supplied delimiter is the same. If the key value was stored as a * single value, then this method behavior is the * exact same as the * {@link Properties#loadFromXML(InputStream)} method. * @param in in the input stream from which to read the XML document. * @param delimiter delimiter string to used to parse a multi value * key. * @throws IOException i/o problem */ public synchronized void loadFromXML(InputStream in, String delimiter) throws IOException { java.util.Properties p = new java.util.Properties(); p.loadFromXML(in); List values = new ArrayList<>(); for (String key : p.stringPropertyNames()) { String value = p.getProperty(key); if (value != null) { values.addAll(Arrays.asList( StringUtils.splitByWholeSeparator(value, delimiter))); } put(key, values); } p = null; } /** * Reads a property list (key and element pairs) from the UTF-8 input * string. Otherwise, the same considerations as * {@link #load(InputStream)} apply. * @param str the string to load * @throws IOException problem loading string */ public void loadFromString(String str) throws IOException { InputStream is = new ByteArrayInputStream( str.getBytes(CharEncoding.UTF_8)); load(is); is.close(); } //--- String --------------------------------------------------------------- /** * Gets value as string. * @param key property key * @return the value */ public final String getString(String key) { List list = get(key); if (list != null && !list.isEmpty()) { return list.get(0); } return null; } /** * Gets value as string. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final String getString(String key, String defaultValue) { String s = getString(key); if (s == null) { return defaultValue; } return s; } /** * Gets values as a list of strings. This method is null-safe. No matches * returns an empty list. * @param key property key * @return the values */ public final List getStrings(String key) { List values = get(key); if (values == null) { return new ArrayList<>(); } return new ArrayList<>(values); } /** * Sets one or multiple string values replacing existing ones. * Setting a single null value or an empty string array is * the same as calling {@link #remove(Object)} with the same key. * When setting multiple values, null values are converted * to blank strings. * @param key the key of the value to set * @param values the values to set */ public final void setString(String key, String... values) { if (ArrayUtils.isEmpty(values)) { remove(key); } put(key, new ArrayList<>(Arrays.asList(values))); } /** * Adds one or multiple string values. * Adding a single null value has no effect. * When adding multiple values, null values are converted * to blank strings. * @param key the key of the value to set * @param values the values to set */ public final void addString(String key, String... values) { if (ArrayUtils.isEmpty(values)) { return; } List list = get(key); if (list == null) { list = new ArrayList<>(); } list.addAll(Arrays.asList(values)); put(key, list); } //--- Integer -------------------------------------------------------------- /** * Gets value as an integer. * @param key property key * @return the value */ public final int getInt(String key) { try { return Integer.parseInt(getString(key)); } catch (NumberFormatException e) { throw createTypedException( "Could not parse integer value.", key, getString(key), e); } } /** * Gets value as an integer. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final int getInt(String key, int defaultValue) { String value = getString(key, Integer.toString(defaultValue)); try { return Integer.parseInt(value); } catch (NumberFormatException e) { throw createTypedException( "Could not parse integer value.", key, value, e); } } /** * Gets values as a list of integers. * @param key property key * @return the values */ public final List getInts(String key) { List values = getStrings(key); String errVal = null; try { List ints = new ArrayList<>(values.size()); for (String value : values) { errVal = value; ints.add(Integer.parseInt(value)); } return ints; } catch (NumberFormatException e) { throw createTypedException( "Could not parse integer value.", key, errVal, e); } } /** * Sets one or multiple integer values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setInt(String key, int... values) { setString(key, toStringArray(ArrayUtils.toObject(values))); } /** * Adds one or multiple integer values values. * @param key the key of the values to set * @param values the values to set */ public final void addInt(String key, int... values) { addString(key, toStringArray(ArrayUtils.toObject(values))); } //--- Double --------------------------------------------------------------- /** * Gets value as a double. * @param key property key * @return the value */ public final double getDouble(String key) { try { return Double.parseDouble(getString(key)); } catch (NumberFormatException e) { throw createTypedException( "Could not parse double value.", key, getString(key), e); } } /** * Gets value as a double. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final double getDouble(String key, double defaultValue) { String value = getString(key, Double.toString(defaultValue)); try { return Double.parseDouble(value); } catch (NumberFormatException e) { throw createTypedException( "Could not parse double value.", key, value, e); } } /** * Gets values as a list of doubles. * @param key property key * @return the values */ public final List getDoubles(String key) { List values = getStrings(key); String errVal = null; try { List list = new ArrayList<>(values.size()); for (String value : values) { errVal = value; list.add(Double.parseDouble(value)); } return list; } catch (NumberFormatException e) { throw createTypedException( "Could not parse double value.", key, errVal, e); } } /** * Sets one or multiple double values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setDouble(String key, double... values) { setString(key, toStringArray(ArrayUtils.toObject(values))); } /** * Adds one or multiple double values. * @param key the key of the values to set * @param values the values to set */ public final void addDouble(String key, double... values) { addString(key, toStringArray(ArrayUtils.toObject(values))); } //--- Long ----------------------------------------------------------------- /** * Gets value as a long. * @param key property key * @return the value */ public final long getLong(String key) { try { return Long.parseLong(getString(key)); } catch (NumberFormatException e) { throw createTypedException( "Could not parse long value.", key, getString(key), e); } } /** * Gets value as a long. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final long getLong(String key, long defaultValue) { String value = getString(key, Long.toString(defaultValue)); try { return Long.parseLong(value); } catch (NumberFormatException e) { throw createTypedException( "Could not parse long value.", key, value, e); } } /** * Gets values as a list of longs. * @param key property key * @return the values */ public final List getLongs(String key) { List values = getStrings(key); String errVal = null; try { List list = new ArrayList<>(values.size()); for (String value : values) { errVal = value; list.add(Long.parseLong(value)); } return list; } catch (NumberFormatException e) { throw createTypedException( "Could not parse long value.", key, errVal, e); } } /** * Sets one or multiple long values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setLong(String key, long... values) { setString(key, toStringArray(ArrayUtils.toObject(values))); } /** * Add one or multiple long values. * @param key the key of the values to set * @param values the values to set */ public final void addLong(String key, long... values) { addString(key, toStringArray(ArrayUtils.toObject(values))); } //--- Float ---------------------------------------------------------------- /** * Gets value as a float. * @param key property key * @return the value */ public final float getFloat(String key) { try { return Float.parseFloat(getString(key)); } catch (NumberFormatException e) { throw createTypedException( "Could not parse float value.", key, getString(key), e); } } /** * Gets value as a float. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final float getFloat(String key, float defaultValue) { String value = getString(key, Float.toString(defaultValue)); try { return Float.parseFloat(value); } catch (NumberFormatException e) { throw createTypedException( "Could not parse float value.", key, value, e); } } /** * Gets values as a list of floats. * @param key property key * @return the values */ public final List getFloats(String key) { List values = getStrings(key); String errVal = null; try { List list = new ArrayList<>(values.size()); for (String value : values) { errVal = value; list.add(Float.parseFloat(value)); } return list; } catch (NumberFormatException e) { throw createTypedException( "Could not parse float value.", key, errVal, e); } } /** * Sets one or multiple float values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setFloat(String key, float... values) { setString(key, toStringArray(ArrayUtils.toObject(values))); } /** * Adds one or multiple long values. * @param key the key of the values to set * @param values the values to set */ public final void addFloat(String key, float... values) { addString(key, toStringArray(ArrayUtils.toObject(values))); } //--- BigDecimal ----------------------------------------------------------- /** * Gets value as a BigDecimal. * @param key property key * @return the value */ public final BigDecimal getBigDecimal(String key) { String value = getString(key); if (value == null || value.trim().length() == 0) { return null; } try { return new BigDecimal(value); } catch (NumberFormatException e) { throw createTypedException( "Could not parse BigDecimal value.", key, value, e); } } /** * Gets value as a BigDecimal. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final BigDecimal getBigDecimal(String key, BigDecimal defaultValue) { BigDecimal value = getBigDecimal(key); if (value == null) { return defaultValue; } return value; } /** * Gets values as a list of BigDecimals. * @param key property key * @return the values */ public final List getBigDecimals(String key) { List values = getStrings(key); String errVal = null; try { List list = new ArrayList<>(values.size()); for (String value : values) { errVal = value; list.add(new BigDecimal(value)); } return list; } catch (NumberFormatException e) { throw createTypedException( "Could not parse BigDecimal value.", key, errVal, e); } } /** * Sets one or multiple BigDecimal values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setBigDecimal(String key, BigDecimal... values) { setString(key, toStringArray(values)); } /** * Add one or multiple BigDecimal values. * @param key the key of the values to set * @param values the values to set */ public final void addBigDecimal(String key, BigDecimal... values) { addString(key, toStringArray(values)); } //--- Date ----------------------------------------------------------------- /** * Gets value as a date. * @param key property key * @return the value */ public final Date getDate(String key) { String value = getString(key); if (StringUtils.isBlank(value)) { return null; } try { return new Date(Long.parseLong(value)); } catch (NumberFormatException e) { throw createTypedException( "Could not parse Date value.", key, value, e); } } /** * Gets value as a date. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final Date getDate(String key, Date defaultValue) { Date value = getDate(key); if (value == null) { return defaultValue; } return value; } /** * Gets values as a list of dates. * @param key property key * @return the values */ public final List getDates(String key) { List values = getStrings(key); String errVal = null; try { List list = new ArrayList<>(values.size()); for (String value : values) { errVal = value; list.add(new Date(Long.parseLong(value))); } return list; } catch (NumberFormatException e) { throw createTypedException( "Could not parse Date value.", key, errVal, e); } } /** * Sets one or multiple date values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setDate(String key, Date... values) { setString(key, datesToStringArray(values)); } /** * Add one or multiple date values. * @param key the key of the values to set * @param values the values to set */ public final void addDate(String key, Date... values) { addString(key, datesToStringArray(values)); } private String[] datesToStringArray(Date... values) { if (values == null) { return null; } String[] array = new String[values.length]; for (int i = 0; i < array.length; i++) { Date value = values[i]; if (value == null) { array[i] = null; } else { array[i] = Long.toString(value.getTime()); } } return array; } //--- Boolean -------------------------------------------------------------- /** * Gets value as a boolean. The underlying string value matching * the key must exist and equal "true" (ignoring case) to return * true. Any other value (including null) * will return false. * @param key property key * @return the value */ public final boolean getBoolean(String key) { return Boolean.parseBoolean(getString(key)); } /** * Gets value as a boolean. The underlying string value matching * the key must exist and equal "true" (ignoring case) to return * true. Any other value (including null) * will return false. If there are no entries for the given * key, the default value is returned instead. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final boolean getBoolean(String key, boolean defaultValue) { return Boolean.parseBoolean(getString(key, Boolean.toString(defaultValue))); } /** * Gets values as a list of booleans. * @param key property key * @return the values */ public final List getBooleans(String key) { List values = getStrings(key); List list = new ArrayList<>(values.size()); for (String value : values) { list.add(Boolean.parseBoolean(value)); } return list; } /** * Sets one or multiple boolean values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setBoolean(String key, boolean... values) { setString(key, toStringArray(ArrayUtils.toObject(values))); } /** * Adds one or multiple boolean values. * @param key the key of the values to set * @param values the values to set */ public final void addBoolean(String key, boolean... values) { addString(key, toStringArray(ArrayUtils.toObject(values))); } //--- Locale --------------------------------------------------------------- /** * Gets value as a locale. * @param key property key * @return the value */ public final Locale getLocale(String key) { try { return LocaleUtils.toLocale(getString(key)); } catch (IllegalArgumentException e) { throw createTypedException( "Could not parse Locale value.", key, getString(key), e); } } /** * Gets value as a locale. * @param key property key * @param defaultValue default value to return when original value is null. * @return the value */ public final Locale getLocale(String key, Locale defaultValue) { try { return LocaleUtils.toLocale(getString(key)); } catch (IllegalArgumentException e) { return defaultValue; } } /** * Gets values as a list of locales. * @param key property key * @return the values */ public final List getLocales(String key) { List values = getStrings(key); String errVal = null; try { List list = new ArrayList<>(values.size()); for (String value : values) { errVal = value; list.add(LocaleUtils.toLocale(value)); } return list; } catch (IllegalArgumentException e) { throw createTypedException( "Could not parse locale value.", key, errVal, e); } } /** * Sets one or multiple locale values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setLocale(String key, Locale... values) { setString(key, toStringArray(values)); } /** * Adds one or multiple locale values. * @param key the key of the values to set * @param values the values to set */ public final void addLocale(String key, Locale... values) { addString(key, toStringArray(values)); } //--- File ----------------------------------------------------------------- /** * Gets a file, assuming key value is a file system path. * @param key properties key * @return a File */ public final File getFile(String key) { String filePath = getString(key); if (filePath == null) { return null; } return new File(filePath); } /** * Gets a file, assuming key value is a file system path. * @param key properties key * @param defaultValue default file being returned if no file has been * defined for the given key in the properties. * @return a File */ public final File getFile(String key, File defaultValue) { File value = getFile(key); if (value == null) { return defaultValue; } return value; } /** * Gets values as a list of files. * @param key property key * @return the values */ public final List getFiles(String key) { List values = getStrings(key); List list = new ArrayList<>(values.size()); for (String value : values) { list.add(new File(value)); } return list; } /** * Sets one or multiple file values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setFile(String key, File... values) { setString(key, filesToStringArray(values)); } /** * Adds one or multiple file values. * @param key the key of the values to set * @param values the values to set */ public final void addFile(String key, File... values) { addString(key, filesToStringArray(values)); } private String[] filesToStringArray(File... values) { if (values == null) { return null; } String[] array = new String[values.length]; for (int i = 0; i < array.length; i++) { array[i] = values[i].getPath(); } return array; } //--- Class ---------------------------------------------------------------- /** * Gets a class, assuming key value is a fully qualified class name * available in the classloader. * @param key properties key * @return initialized class */ public final Class getClass(String key) { String value = getString(key); try { return Class.forName(value); } catch (ClassNotFoundException e) { throw createTypedException( "Could not parse class value.", key, value, e); } } /** * Gets a class, assuming key value is a fully qualified class name * available in the classloader. * @param key properties key * @param defaultValue default file being returned if no class has been * defined for the given key in the properties. * @return initialized class */ public final Class getClass(String key, Class defaultValue) { Class value = getClass(key); if (value == null) { return defaultValue; } return value; } /** * Gets values as a list of initialized classes. * @param key property key * @return the values */ public final List> getClasses(String key) { List values = getStrings(key); List> list = new ArrayList<>(values.size()); for (String value : values) { list.add(getClass(value)); } return list; } /** * Sets one or multiple class values, replacing existing ones. * @param key the key of the values to set * @param values the values to set */ public final void setClass(String key, Class... values) { setString(key, classesToStringArray(values)); } /** * Adds one or multiple class values. * @param key the key of the values to set * @param values the values to set */ public final void addClass(String key, Class... values) { addString(key, classesToStringArray(values)); } //--- Other ---------------------------------------------------------------- @Override public final List get(Object key) { return super.get(caseResolvedKey(key)); } @Override public final List remove(Object key) { return super.remove(caseResolvedKey(key)); } /* * Puts the list of values to the given key. Supplying a null * list has the same effect as calling {@link #remove(Object)} with * the same key. */ @Override public List put(String key, List values) { if (values == null) { return remove(key); } List nullSafeValues = new ArrayList<>(values.size()); for (String value : nullSafeValues) { if (value == null) { nullSafeValues.add(StringUtils.EMPTY); } else { nullSafeValues.add(value); } } return super.put(caseResolvedKey(key), values); } /* * When case insensitive, values of equal keys are merged into the first * key encountered. */ @Override public void putAll(Map> m) { if (!caseInsensitiveKeys) { super.putAll(m); return; } if (m == null) { return; } for (Entry> entry : m.entrySet()) { String key = entry.getKey(); List values = entry.getValue(); if (values != null) { addString(key, values.toArray(ArrayUtils.EMPTY_STRING_ARRAY)); } } } //--- Privates ------------------------------------------------------------- // If a key already exists under a different case, return it, else // return the one passed as argument private String caseResolvedKey(Object key) { String resolvedKey = Objects.toString(key, null); if (!isCaseInsensitiveKeys()) { return resolvedKey; } for (String existingKey : super.keySet()) { if (StringUtils.equalsIgnoreCase(existingKey, resolvedKey)) { resolvedKey = existingKey; break; } } return resolvedKey; } private String[] classesToStringArray(Class... values) { if (values == null) { return null; } String[] array = new String[values.length]; for (int i = 0; i < array.length; i++) { array[i] = values[i].getName(); } return array; } private PropertiesException createTypedException( String msg, String key, String value, Exception cause) { String message = msg + " [key=" + key + "; value=" + value + "]."; LOG.error(message, cause); return new PropertiesException(message, cause); } private String[] toStringArray(Object[] array) { if (array == null) { return null; } String[] strArray = new String[array.length]; for (int i = 0; i < array.length; i++) { strArray[i] = Objects.toString(array[i], StringUtils.EMPTY); } return strArray; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy