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

com.brambolt.util.Maps Maven / Gradle / Ivy

There is a newer version: 2022.05.01-7057
Show newest version
package com.brambolt.util;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.stream.Collectors;

import static java.util.Arrays.asList;

/**
 * Convenience functions for working with maps.
 */
public class Maps {

    /**
     * A convenience method for creating maps.
     *
     * 

The parameters are a sequence of key-value pairs, such that odd-numbered * parameters are the keys, and the following even-numbered parameters are * the values to assign to the respective keys.

* *

For example, asMap<Integer, Integer>(1, 2, 3, 4) * produces the map [ 1: 2, 3: 4 ].

* * @param The key type for the map to create * @param The value type for the map to create * @param pairs The pairs for the map to create * @return The newly created map holding the pairs * @throws ArrayIndexOutOfBoundsException if the number of parameters is odd * @throws ClassCastException if an odd-numbered parameter is not of the key * type K, or an even-numbered parameter is not of the * value type V */ @SuppressWarnings({"unchecked", "unused"}) public static Map asMap(Object... pairs) { Map map = new HashMap<>(); for (int i = 0; i < pairs.length; ++i) map.put((K) pairs[i++], (V) pairs[i]); return map; } /** * Formats the parameter map as a string, with one line per key. * *

For example, the map [a: 1, b: 2] converts to:

* *
     *     a=1
     *     b=2
     * 
* * @param map The map to format. * @param The key type of the map. * @param The value type of the map. * @return A string representation of the parameter map. */ public static String format(Map map) { return format(map, "\n"); } /** * Formats the parameter map as a string, using the parameter delimiter. * *

For example, if the map [a: 1, b: 2] is formatted with * the delimiter "," the result is a=1,b=2.

* * @param map The map to format as a string. * @param delimiter The delimiter to separate the key-value pairs with. * @param The key type of the map. * @param The value type of the map. * @return The formatted map. */ public static String format(Map map, String delimiter) { return map.keySet().stream().sorted() .map(key -> String.format("%s=%s", key, map.get(key))) .collect(Collectors.joining(delimiter)); } /** * Merges the two parameter maps by overlaying the second onto the first. * *

The intersection of the two maps must have consistent structure. * This means that for a given key in the intersection, either both values * are maps or neither value is a map. If one value is a map but the other * is a scalar then an IllegalStateException is thrown.

* * @param existing The original map * @param overwrites The overlay map to override the original map with. * @return The merged map. * @throws IllegalStateException If the two maps have inconsistent structure. */ public static Map merge(Map existing, Map overwrites) { Map results = new HashMap<>(existing); overwrites.forEach((k, v) -> results.merge(k, v, Maps::overwrite)); return results; } @SuppressWarnings("unchecked") private static Object overwrite(Object v1, Object v2) { if (!(v1 instanceof Map)) if (v2 instanceof Map) throw invalidOverwrite(v1, v2); else return v2; else { if (!(v2 instanceof Map)) throw invalidOverwrite(v1, v2); else return merge((Map) v1, (Map) v2); } } private static IllegalStateException invalidOverwrite(Object v1, Object v2) { return new IllegalStateException( "Invalid replacement of " + v1.toString() + " with " + v2.toString()); } /** * Locates the last segment of the parameter key. For example, if the * parameter key is a.b.c then c is returned. * @param segmentedKey The segmented key to locate the last segment of. * @return The last segment of the key. */ @SuppressWarnings("unused") public static String getLastSegment(String segmentedKey) { return null == segmentedKey ? null : (segmentedKey.contains(".") ? segmentedKey.substring(segmentedKey.lastIndexOf(".") + 1) : segmentedKey); } /** * Segments the parameter key and descents the parameter map to locate a * value, or throws NoSuchElementException if no value exists. * *

For example, if the map is [a: [b: [c: 1]] and the * parameter key is a.b then the return value will be * [c: 1].

* * @param nested The map to look for a value in. * @param segmentedKey The segmented key to split and look up. * @return The value produced by splitting the key and descending the map. * @throws NoSuchElementException If the key does not map to a value. */ @SuppressWarnings("unused") public static Object segmentedGet(Map nested, String segmentedKey) { return segmentedGet(nested, convert(segmentedKey)); } /** * Descents the parameter map to locate a value for the parameter key * segments, or throws NoSuchElementException if no value * exists. * *

For example, if the map is [a: [b: [c: 1]] and the * parameter key is [a, b] then the return value will be * [c: 1].

* * @param nested The map to look for a value in. * @param segmentedKey The segmented key to split and look up. * @return The value produced by splitting the key and descending the map. * @throws NoSuchElementException If the key does not map to a value. */ @SuppressWarnings("unchecked") public static Object segmentedGet(Map nested, List segmentedKey) { if (null == segmentedKey) return nested; switch (segmentedKey.size()) { case 0: return nested; case 1: String key = segmentedKey.get(0); throwIfNotFound(nested, key); return nested.get(key); default: String segment = segmentedKey.get(0); throwIfNotFound(nested, segment); return segmentedGet( (Map) nested.get(segment), segmentedKey.subList(1, segmentedKey.size())); } } private static void throwIfNotFound(Map map, String key) { if (!map.containsKey(key)) throw new NoSuchElementException( String.format("Not found: %s [%s]", key, format(map))); } /** * Converts the segmented map key to a list of segments. For example, * a.b.c converts to [a, b, c]. * @param segmentedKey The segmented key to convert. * @return A list holding the segments of the parameter key. */ public static List convert(String segmentedKey) { return asList(segmentedKey.trim().split("\\.")); } /** * Converts a properties object into a nested map. The nesting is derived * by splitting segmented property names. For example, the property * a.b.c=1 results in [a: [b: [c: 1]]]. * @param properties The properties to convert to a nested map. * @return A nested map derived by segmenting the property names. */ @SuppressWarnings("unused") public static Map convert(Properties properties) { return convert(properties, getKeys(properties)); } /** * Creates a list of the property names in the parameter properties. * @param properties The properties to extract property names from . * @return A list holding the property names in the parameter properties. */ @SuppressWarnings("unchecked") public static List getKeys(Properties properties) { return (List) java.util.Collections.list(properties.propertyNames()); } /** * Extracts a nested map of property values from the parameter properties. * The nesting is defined by segmenting the parameter keys. The values are * retrieved by looking each key up in the parameter properties. For * example, [a.b=1, c.d=2], a.b converts to * [a: [b: 1]]. * @param properties The properties to look up values in. * @param keys The property names to create the map from. * @return A nested map holding the requested values. */ public static Map convert(Properties properties, Collection keys) { return keys.stream().collect( HashMap::new, (Map map, String name) -> insert(properties, name, map), (Map m, Map u) -> {}); } private static void insert(Properties properties, String name, Map map) { insert(properties, name, map, asList(name.split("\\."))); } /** * Modifies the parameter map in-place, by reading the property value * for the parameter name and creating a nested structure with the value * "at the bottom" of the nesting. * * For example, for the parameters a.b.c.d, {} and * c.d, and property value X for the * parameter name, the empty map will be modified to {c:{d:X}}. * * If no value is found the nesting is still created. If the previous example * is modified by removing the value X for a.b.c.d * then the resulting map will be {c:{}}. This is so the caller * can look up a.b.c without a null pointer exception. * * @param properties The underlying properties collection * @param name The property name to look up and use for nesting * @param map The out-parameter map to be modified * @param segments The segments to create the nesting from * @return The modified out-parameter map */ private static Map insert(Properties properties, String name, Map map, List segments) { if (segments.size() < 1) throw new IllegalArgumentException("Empty segments list [$name] [$map]"); String key = segments.get(0); if (1 < segments.size()) // We're not yet at the end of the segments list... insert the tail: insertTail(properties, name, map, key, segments.subList(1, segments.size())); else // We've reached the end of the segments list; time to put in the value: insertValue(name, map, key, properties.getProperty(name)); // Return the map, modified to hold the parameter segments with the // value for the parameter name at the botton of the nesting: return map; } @SuppressWarnings("unchecked") private static void insertTail(Properties properties, String name, Map map, String key, List tail) { if (!map.containsKey(key)) { // The parameter map does not have a value for the key; then // we create a new map that just contains the tail segments // with the last segment holding an empty map: map.put(key, insert(properties, name, new HashMap<>(), tail)); return; } try { Object value = map.get(key); // The parameter map does have a value for the key; we attempt // adding the tail segments and throw an exception if the key // value is not a map, or there is a similar conflict further // down: insert(properties, name, (Map) value, tail); } catch (Throwable t) { // We can get a ClassCastException here, for example if the value // of the key `version` is 1, and we attempt to add `version.short`. // // Oops, the parameter segments are incompatible with the // existing properties; there is no way to proceed (because // overwriting is not allowed): throw new RuntimeException(String.format( "Property insertion for %s failed: [%s]", name, map.get(key)), t); } } private static void insertValue(String name, Map map, String key, String value) { if (null == value) return; // Nothing to do // Don't trim the value or check for the empty string if (map.containsKey(key)) // There is a conflict (and overwriting is not allowed): throw new IllegalStateException("Second value found for property " + name + ": [" + map.get(key) + "] [" + value + "]"); else // There is a value, and no conflict - insert it: map.put(key, value); // If there is no value then we do nothing } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy