/* * Copyright 2008-2017 the original author or authors. * * 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 griffon.util; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.LinkedHashSet; import java.util.Map; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import static griffon.util.GriffonNameUtils.requireNonBlank; import static griffon.util.TypeUtils.castToBoolean; import static griffon.util.TypeUtils.castToDouble; import static griffon.util.TypeUtils.castToFloat; import static griffon.util.TypeUtils.castToInt; import static griffon.util.TypeUtils.castToLong; import static griffon.util.TypeUtils.castToNumber; import static java.util.Collections.unmodifiableSet; import static java.util.Objects.requireNonNull; /** * Utility class for reading configuration properties. * * @author Andres Almiray */ public final class ConfigUtils { private static final String ERROR_CONFIG_NULL = "Argument 'config' must not be null"; private static final String ERROR_KEY_BLANK = "Argument 'key' must not be blank"; private ConfigUtils() { // prevent instantiation } /** * Converts a {@code ResourceBundle} instance into a {@code Properties} instance. * * @param resourceBundle the {@code ResourceBundle} to be converted. Must not be null. * * @return a newly created {@code Properties} with all key/value pairs from the given {@code ResourceBundle}. * * @since 2.10.0 */ @Nonnull public static Properties toProperties(@Nonnull ResourceBundle resourceBundle) { requireNonNull(resourceBundle, "Argument 'resourceBundle' must not be null"); Properties properties = new Properties(); for (String key : resourceBundle.keySet()) { properties.put(key, resourceBundle.getObject(key)); } return properties; } /** * Returns true if there's a non-null value for the specified key. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return true if there's a value for the specified key, false otherwise * * @since 2.2.0 */ @SuppressWarnings("unchecked") public static boolean containsKey(@Nonnull Map<String, Object> config, @Nonnull String key) { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); if (config.containsKey(key)) { return true; } String[] keys = key.split("\\."); for (int i = 0; i < keys.length - 1; i++) { if (config != null) { Object node = config.get(keys[i]); if (node instanceof Map) { config = (Map<String, Object>) node; } else { return false; } } else { return false; } } return config != null && config.containsKey(keys[keys.length - 1]); } /** * Returns true if there's a non-null value for the specified key. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return true if there's a value for the specified key, false otherwise * * @since 2.2.0 */ @SuppressWarnings("unchecked") public static boolean containsKey(@Nonnull ResourceBundle config, @Nonnull String key) { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); String[] keys = key.split("\\."); try { if (config.containsKey(key)) { return true; } } catch (MissingResourceException mre) { // OK } if (keys.length == 1) { return config.containsKey(keys[0]); } Object node = config.getObject(keys[0]); if (!(node instanceof Map)) { return false; } Map<String, Object> map = (Map) node; for (int i = 1; i < keys.length - 1; i++) { if (map != null) { node = map.get(keys[i]); if (node instanceof Map) { map = (Map) node; } else { return false; } } else { return false; } } return map != null && map.containsKey(keys[keys.length - 1]); } /** * Returns true if there's a non-null value for the specified key. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return true if there's a value for the specified key, false otherwise */ @SuppressWarnings("unchecked") public static boolean isValueDefined(@Nonnull Map<String, Object> config, @Nonnull String key) { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); if (config.containsKey(key)) { return true; } String[] keys = key.split("\\."); for (int i = 0; i < keys.length - 1; i++) { if (config != null) { Object node = config.get(keys[i]); if (node instanceof Map) { config = (Map<String, Object>) node; } else { return false; } } else { return false; } } if (config == null) { return false; } Object value = config.get(keys[keys.length - 1]); return value != null; } /** * Returns true if there's a non-null value for the specified key. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return true if there's a value for the specified key, false otherwise */ @SuppressWarnings("unchecked") public static boolean isValueDefined(@Nonnull ResourceBundle config, @Nonnull String key) { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); String[] keys = key.split("\\."); try { Object value = config.getObject(key); if (value != null) { return true; } } catch (MissingResourceException mre) { // OK } if (keys.length == 1) { try { Object node = config.getObject(keys[0]); return node != null; } catch (MissingResourceException mre) { return false; } } Object node = config.getObject(keys[0]); if (!(node instanceof Map)) { return false; } Map<String, Object> map = (Map) node; for (int i = 1; i < keys.length - 1; i++) { if (map != null) { node = map.get(keys[i]); if (node instanceof Map) { map = (Map) node; } else { return false; } } else { return false; } } if (map == null) { return false; } Object value = map.get(keys[keys.length - 1]); return value != null; } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ @Nullable @SuppressWarnings({"unchecked", "ConstantConditions"}) public static <T> T getConfigValue(@Nonnull Map<String, Object> config, @Nonnull String key, @Nullable T defaultValue) { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); if (config.containsKey(key)) { return (T) config.get(key); } String[] keys = key.split("\\."); for (int i = 0; i < keys.length - 1; i++) { if (config != null) { Object node = config.get(keys[i]); if (node instanceof Map) { config = (Map) node; } else { return defaultValue; } } else { return defaultValue; } } if (config == null) { return defaultValue; } Object value = config.get(keys[keys.length - 1]); return value != null ? (T) value : defaultValue; } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ @Nullable @SuppressWarnings({"unchecked", "ConstantConditions"}) public static <T> T getConfigValue(@Nonnull ResourceBundle config, @Nonnull String key, @Nullable T defaultValue) { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); String[] keys = key.split("\\."); try { Object value = config.getObject(key); if (value != null) { return (T) value; } } catch (MissingResourceException mre) { // OK } if (keys.length == 1) { Object node = config.getObject(keys[0]); return node != null ? (T) node : defaultValue; } Object node = config.getObject(keys[0]); if (!(node instanceof Map)) { return defaultValue; } Map<String, Object> map = (Map) node; for (int i = 1; i < keys.length - 1; i++) { if (map != null) { node = map.get(keys[i]); if (node instanceof Map) { map = (Map) node; } else { return defaultValue; } } else { return defaultValue; } } if (map == null) { return defaultValue; } Object value = map.get(keys[keys.length - 1]); return value != null ? (T) value : defaultValue; } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key or the default value if no match is found */ @Nullable @SuppressWarnings({"unchecked", "ConstantConditions"}) public static <T> T getConfigValue(@Nonnull Map<String, Object> config, @Nonnull String key) throws MissingResourceException { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); String type = config.getClass().getName(); if (config.containsKey(key)) { return (T) config.get(key); } String[] keys = key.split("\\."); for (int i = 0; i < keys.length - 1; i++) { if (config != null) { Object node = config.get(keys[i]); if (node instanceof Map) { config = (Map) node; } else { throw missingResource(type, key); } } else { throw missingResource(type, key); } } if (config == null) { throw missingResource(type, key); } Object value = config.get(keys[keys.length - 1]); if (value != null) { return (T) value; } throw missingResource(type, key); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key or the default value if no match is found */ @Nullable @SuppressWarnings({"unchecked", "ConstantConditions"}) public static <T> T getConfigValue(@Nonnull ResourceBundle config, @Nonnull String key) throws MissingResourceException { requireNonNull(config, ERROR_CONFIG_NULL); requireNonBlank(key, ERROR_KEY_BLANK); String type = config.getClass().getName(); String[] keys = key.split("\\."); try { Object value = config.getObject(key); if (value != null) { return (T) value; } } catch (MissingResourceException mre) { // OK } if (keys.length == 1) { Object node = config.getObject(keys[0]); if (node != null) { return (T) node; } throw missingResource(type, key); } Object node = config.getObject(keys[0]); if (!(node instanceof Map)) { throw missingResource(type, key); } Map<String, Object> map = (Map<String, Object>) node; for (int i = 1; i < keys.length - 1; i++) { if (map != null) { node = map.get(keys[i]); if (node instanceof Map) { map = (Map<String, Object>) node; } else { throw missingResource(type, key); } } else { throw missingResource(type, key); } } if (map == null) { throw missingResource(type, key); } Object value = map.get(keys[keys.length - 1]); if (value != null) { return (T) value; } throw missingResource(type, key); } private static MissingResourceException missingResource(String classname, String key) throws MissingResourceException { return new MissingResourceException("Can't find resource for bundle " + classname + ", key " + key, classname, key); } /** * Returns the value for the specified key coerced to a boolean. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key. Returns {@code false} if no match. */ public static boolean getConfigValueAsBoolean(@Nonnull Map<String, Object> config, @Nonnull String key) { return getConfigValueAsBoolean(config, key, false); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ public static boolean getConfigValueAsBoolean(@Nonnull Map<String, Object> config, @Nonnull String key, boolean defaultValue) { Object value = getConfigValue(config, key, defaultValue); return castToBoolean(value); } /** * Returns the value for the specified key coerced to an int. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key. Returns {@code 0} if no match. */ public static int getConfigValueAsInt(@Nonnull Map<String, Object> config, @Nonnull String key) { return getConfigValueAsInt(config, key, 0); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ public static int getConfigValueAsInt(@Nonnull Map<String, Object> config, @Nonnull String key, int defaultValue) { Object value = getConfigValue(config, key, defaultValue); return castToInt(value); } /** * Returns the value for the specified key coerced to a long. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key. Returns {@code 0L} if no match. */ public static long getConfigValueAsLong(@Nonnull Map<String, Object> config, @Nonnull String key) { return getConfigValueAsLong(config, key, 0L); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ public static long getConfigValueAsLong(@Nonnull Map<String, Object> config, @Nonnull String key, long defaultValue) { Object value = getConfigValue(config, key, defaultValue); return castToLong(value); } /** * Returns the value for the specified key coerced to a double. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key. Returns {@code 0d} if no match. */ public static double getConfigValueAsDouble(@Nonnull Map<String, Object> config, @Nonnull String key) { return getConfigValueAsDouble(config, key, 0d); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ public static double getConfigValueAsDouble(@Nonnull Map<String, Object> config, @Nonnull String key, double defaultValue) { Object value = getConfigValue(config, key, defaultValue); return castToDouble(value); } /** * Returns the value for the specified key coerced to a float. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key. Returns {@code 0f} if no match. */ public static float getConfigValueAsFloat(@Nonnull Map<String, Object> config, @Nonnull String key) { return getConfigValueAsFloat(config, key, 0f); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ public static float getConfigValueAsFloat(@Nonnull Map<String, Object> config, @Nonnull String key, float defaultValue) { Object value = getConfigValue(config, key, defaultValue); return castToFloat(value); } /** * Returns the value for the specified key coerced to a Number. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key. Returns {@code null} if no match. */ @Nullable public static Number getConfigValueAsNumber(@Nonnull Map<String, Object> config, @Nonnull String key) { return getConfigValueAsNumber(config, key, null); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ @Nullable public static Number getConfigValueAsNumber(@Nonnull Map<String, Object> config, @Nonnull String key, @Nullable Number defaultValue) { Object value = getConfigValue(config, key, defaultValue); return castToNumber(value); } /** * Returns the value for the specified key converted to a String. * * @param config the configuration object to be searched upon * @param key the key to be searched * * @return the value of the key. Returns {@code ""} if no match. */ @Nullable public static String getConfigValueAsString(@Nonnull Map<String, Object> config, @Nonnull String key) { return getConfigValueAsString(config, key, ""); } /** * Returns the value for the specified key with an optional default value if no match is found. * * @param config the configuration object to be searched upon * @param key the key to be searched * @param defaultValue the value to send back if no match is found * * @return the value of the key or the default value if no match is found */ @Nullable public static String getConfigValueAsString(@Nonnull Map<String, Object> config, @Nonnull String key, @Nullable String defaultValue) { Object value = getConfigValue(config, key, defaultValue); return value != null ? String.valueOf(value) : null; } // the following taken from SpringFramework::org.springframework.util.StringUtils /** * Extract the filename extension from the given path, * e.g. "mypath/myfile.txt" -> "txt". * * @param path the file path (may be <code>null</code>) * * @return the extracted filename extension, or <code>null</code> if none */ public static String getFilenameExtension(String path) { if (path == null) { return null; } int extIndex = path.lastIndexOf("."); if (extIndex == -1) { return null; } int folderIndex = path.lastIndexOf("/"); if (folderIndex > extIndex) { return null; } return path.substring(extIndex + 1); } /** * Strip the filename extension from the given path, * e.g. "mypath/myfile.txt" -> "mypath/myfile". * * @param path the file path (may be <code>null</code>) * * @return the path with stripped filename extension, * or <code>null</code> if none */ public static String stripFilenameExtension(String path) { if (path == null) { return null; } int extIndex = path.lastIndexOf("."); if (extIndex == -1) { return path; } int folderIndex = path.lastIndexOf("/"); if (folderIndex > extIndex) { return path; } return path.substring(0, extIndex); } @Nonnull public static Set<String> collectKeys(@Nonnull Map<String, Object> map) { requireNonNull(map, "Argument 'map' must not be null"); Set<String> keys = new LinkedHashSet<>(); for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); doCollectKeys(key, value, keys); } return unmodifiableSet(keys); } @SuppressWarnings("unchecked") private static void doCollectKeys(String key, Object value, Set<String> keys) { if (value instanceof Map) { Map<String, Object> map = (Map<String, Object>) value; for (Map.Entry<String, Object> entry : map.entrySet()) { doCollectKeys(key + "." + entry.getKey(), entry.getValue(), keys); } } else { keys.add(key); } } }