I have a properties file where the order of the values is important. I want to be able to iterate through the properties file and output the values based on the order of the original file.
However, since the Properties file is backed by, correct me if I'm wrong, a Map that does not maintain insertion order, the iterator returns the values in the wrong order.
Here is the code I'm using
Enumeration names = propfile.propertyNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
//do stuff
}
Is there anyway to get the Properties back in order short of writting my own custom file parser?
Extend java.util.Properties, override both put() and keys():
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Properties;
import java.util.HashMap;
public class LinkedProperties extends Properties {
private final HashSet<Object> keys = new LinkedHashSet<Object>();
public LinkedProperties() {
}
public Iterable<Object> orderedKeys() {
return Collections.list(keys());
}
public Enumeration<Object> keys() {
return Collections.<Object>enumeration(keys);
}
public Object put(Object key, Object value) {
keys.add(key);
return super.put(key, value);
}
}
Nope - maps are inherently "unordered".
You could possibly create your own subclass of Properties which overrode setProperty and possibly put, but it would probably get very implementation-specific... Properties is a prime example of bad encapsulation. When I last wrote an extended version (about 10 years ago!) it ended up being hideous and definitely sensitive to the implementation details of Properties.
If you can alter the property names your could prefix them with a numeral or other sortable prefix and then sort the Properties KeySet.
Working example :
Map<String,String> properties = getOrderedProperties(new FileInputStream(new File("./a.properties")));
properties.entrySet().forEach(System.out::println);
Code for it
public Map<String, String> getOrderedProperties(InputStream in) throws IOException{
Map<String, String> mp = new LinkedHashMap<>();
(new Properties(){
public synchronized Object put(Object key, Object value) {
return mp.put((String) key, (String) value);
}
}).load(in);
return mp;
}
Dominique Laurent's solution above works great for me. I also added the following method override:
public Set<String> stringPropertyNames() {
Set<String> set = new LinkedHashSet<String>();
for (Object key : this.keys) {
set.add((String)key);
}
return set;
}
Probably not the most efficient, but it's only executed once in my servlet lifecycle.
Thanks Dominique!
Apache Commons Configuration might do the trick for you. I haven't tested this myself, but I checked their sources and looks like property keys are backed by LinkedList in AbstractFileConfiguration class:
public Iterator getKeys()
{
reload();
List keyList = new LinkedList();
enterNoReload();
try
{
for (Iterator it = super.getKeys(); it.hasNext();)
{
keyList.add(it.next());
}
return keyList.iterator();
}
finally
{
exitNoReload();
}
}
I'll add one more famous YAEOOJP (Yet Another Example Of Ordered Java Properties) to this thread because it seems nobody could ever care less about default properties which you can feed to your properties.
#see http://docs.oracle.com/javase/tutorial/essential/environment/properties.html
That's my class: surely not 1016% compliant with any possible situation, but that is fine for my limited dumb purposes right now. Any further comment for correction is appreciated so the Greater Good can benefit.
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
/**
* Remember javadocs >:o
*/
public class LinkedProperties extends Properties {
protected LinkedProperties linkedDefaults;
protected Set<Object> linkedKeys = new LinkedHashSet<>();
public LinkedProperties() { super(); }
public LinkedProperties(LinkedProperties defaultProps) {
super(defaultProps); // super.defaults = defaultProps;
this.linkedDefaults = defaultProps;
}
#Override
public synchronized Enumeration<?> propertyNames() {
return keys();
}
#Override
public Enumeration<Object> keys() {
Set<Object> allKeys = new LinkedHashSet<>();
if (null != defaults) {
allKeys.addAll(linkedDefaults.linkedKeys);
}
allKeys.addAll(this.linkedKeys);
return Collections.enumeration(allKeys);
}
#Override
public synchronized Object put(Object key, Object value) {
linkedKeys.add(key);
return super.put(key, value);
}
#Override
public synchronized Object remove(Object key) {
linkedKeys.remove(key);
return super.remove(key);
}
#Override
public synchronized void putAll(Map<?, ?> values) {
for (Object key : values.keySet()) {
linkedKeys.add(key);
}
super.putAll(values);
}
#Override
public synchronized void clear() {
super.clear();
linkedKeys.clear();
}
private static final long serialVersionUID = 0xC00L;
}
In the interest of completeness ...
public class LinkedProperties extends Properties {
private final LinkedHashSet<Object> keys = new LinkedHashSet<Object>();
#Override
public Enumeration<?> propertyNames() {
return Collections.enumeration(keys);
}
#Override
public synchronized Enumeration<Object> elements() {
return Collections.enumeration(keys);
}
public Enumeration<Object> keys() {
return Collections.enumeration(keys);
}
public Object put(Object key, Object value) {
keys.add(key);
return super.put(key, value);
}
#Override
public synchronized Object remove(Object key) {
keys.remove(key);
return super.remove(key);
}
#Override
public synchronized void clear() {
keys.clear();
super.clear();
}
}
I dont think the methods returning set should be overridden as a set by definition does not maintain insertion order
Map<String, String> mapFile = new LinkedHashMap<String, String>();
ResourceBundle bundle = ResourceBundle.getBundle(fileName);
TreeSet<String> keySet = new TreeSet<String>(bundle.keySet());
for(String key : keySet){
System.out.println(key+" "+bundle.getString(key));
mapFile.put(key, bundle.getString(key));
}
This persist the order of property file
You must override also keySet() if you want to export Properties as XML:
public Set<Object> keySet() {
return keys;
}
See https://github.com/etiennestuder/java-ordered-properties for a complete implementation that allows to read/write properties files in a well-defined order.
OrderedProperties properties = new OrderedProperties();
properties.load(new FileInputStream(new File("~/some.properties")));
In some answers it is assumed that properties read from file are put to instance of Properties (by calls to put) in order they appear they in file. While this is in general how it behaves I don't see any guarantee for such order.
IMHO: it is better to read the file line by line (so that the order is guaranteed), than use the Properties class just as a parser of single property
line and finally store it in some ordered Collection like LinkedHashMap.
This can be achieved like this:
private LinkedHashMap<String, String> readPropertiesInOrderFrom(InputStream propertiesFileInputStream)
throws IOException {
if (propertiesFileInputStream == null) {
return new LinkedHashMap(0);
}
LinkedHashMap<String, String> orderedProperties = new LinkedHashMap<String, String>();
final Properties properties = new Properties(); // use only as a parser
final BufferedReader reader = new BufferedReader(new InputStreamReader(propertiesFileInputStream));
String rawLine = reader.readLine();
while (rawLine != null) {
final ByteArrayInputStream lineStream = new ByteArrayInputStream(rawLine.getBytes("ISO-8859-1"));
properties.load(lineStream); // load only one line, so there is no problem with mixing the order in which "put" method is called
final Enumeration<?> propertyNames = properties.<String>propertyNames();
if (propertyNames.hasMoreElements()) { // need to check because there can be empty or not parsable line for example
final String parsedKey = (String) propertyNames.nextElement();
final String parsedValue = properties.getProperty(parsedKey);
orderedProperties.put(parsedKey, parsedValue);
properties.clear(); // make sure next iteration of while loop does not access current property
}
rawLine = reader.readLine();
}
return orderedProperties;
}
Just note that the method posted above takes an InputStream which should be closed afterwards (of course there is no problem to rewrite it to take just a file as an argument).
As I see it, Properties is to much bound to Hashtable. I suggest reading it in order to a LinkedHashMap. For that you'll only need to override a single method, Object put(Object key, Object value), disregarding the Properties as a key/value container:
public class InOrderPropertiesLoader<T extends Map<String, String>> {
private final T map;
private final Properties properties = new Properties() {
public Object put(Object key, Object value) {
map.put((String) key, (String) value);
return null;
}
};
public InOrderPropertiesLoader(T map) {
this.map = map;
}
public synchronized T load(InputStream inStream) throws IOException {
properties.load(inStream);
return map;
}
}
Usage:
LinkedHashMap<String, String> props = new LinkedHashMap<>();
try (InputStream inputStream = new FileInputStream(file)) {
new InOrderPropertiesLoader<>(props).load(inputStream);
}
For those who read this topic recently:
just use class PropertiesConfiguration from org.apache.commons:commons-configuration2.
I've tested that it keeps properties ordering (because it uses LinkedHashMap internally).
Doing:
`
PropertiesConfiguration properties = new PropertiesConfiguration();
properties.read(new FileReader("/some/path));
properties.write(new FileWriter("/some/other/path"));
`
only removes trailing whitespace and unnecessary escapes.
For Kotlin users, here's a basic example that's functional for write operations. The order is simply determined by the order of your calls to setProperty(k,v).
Per Kotlin's documentation for MutableMap and MutableSet, they both:
preserve the entry iteration order.
Not all use cases are covered.
class OrderedProperties: Properties() {
private val orderedMap = mutableMapOf<Any, Any>()
override val entries: MutableSet<MutableMap.MutableEntry<Any, Any>>
get() = Collections.synchronizedSet(orderedMap.entries)
#Synchronized
override fun put(key: Any?, value: Any?): Any? {
key ?: return null
value ?: return null
orderedMap[key] = value
return orderedMap
}
override fun setProperty(key: String?, value: String?): Any? {
return this.put(key, value)
}
}
An alternative is just to write your own properties file using LinkedHashMap, here is what I use :
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
public class OrderedProperties {
private static Map<String, String> properties = new LinkedHashMap<String, String>();
private static OrderedProperties instance = null;
private OrderedProperties() {
}
//The propertyFileName is read from the classpath and should be of format : key=value
public static synchronized OrderedProperties getInstance(String propertyFileName) {
if (instance == null) {
instance = new OrderedProperties();
readPropertiesFile(propertyFileName);
}
return instance;
}
private static void readPropertiesFile(String propertyFileName){
LineIterator lineIterator = null;
try {
//read file from classpath
URL url = instance.getClass().getResource(propertyFileName);
lineIterator = FileUtils.lineIterator(new File(url.getFile()), "UTF-8");
while (lineIterator.hasNext()) {
String line = lineIterator.nextLine();
//Continue to parse if there are blank lines (prevents IndesOutOfBoundsException)
if (!line.trim().isEmpty()) {
List<String> keyValuesPairs = Arrays.asList(line.split("="));
properties.put(keyValuesPairs.get(0) , keyValuesPairs.get(1));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
lineIterator.close();
}
}
public Map<String, String> getProperties() {
return OrderedProperties.properties;
}
public String getProperty(String key) {
return OrderedProperties.properties.get(key);
}
}
To use :
OrderedProperties o = OrderedProperties.getInstance("/project.properties");
System.out.println(o.getProperty("test"));
Sample properties file (in this case project.properties) :
test=test2
Related
I have a BiDiMap class. How can I make it generic, by accepting not only String but also Object type of objects as input parameters, with keeping all the original functions working. For example I'd like to be able to use function put() with Object, Object as input parameters instead of String, String. I'd like to change all the input parameters and returning values of String type to Object types.
package MyBiDiMap;
import java.util.HashMap;
import java.util.Map;
public class BiDiMap {
private Map<String, String> keyValue;
private Map<String, String> valueKey;
public BiDiMap() {
this.keyValue = new HashMap<>();
this.valueKey = new HashMap<>();
}
private BiDiMap(Map<String, String> keyValue,
Map<String, String> valueKey) {
this.keyValue = keyValue;
this.valueKey = valueKey;
}
public void put(String key, String value) {
if (this.keyValue.containsKey(key)
|| this.valueKey.containsKey(value)) {
this.remove(key);
this.removeInverse(value);
}
this.keyValue.put(key, value);
this.valueKey.put(value, key);
}
public String get(String key) {
return this.keyValue.get(key);
}
public String getInverse(String value) {
return this.valueKey.get(value);
}
public void remove(String key) {
String value = this.keyValue.remove(key);
this.valueKey.remove(value);
}
public void removeInverse(String value) {
String key = this.valueKey.remove(value);
this.keyValue.remove(key);
}
public int size() {
return this.keyValue.size();
}
public BiDiMap getInverse() {
return new BiDiMap(this.valueKey, this.keyValue);
}
}
The answer is pretty simple: by introducing two generic types, named K and V on your class and by then vigorously replacing all occurance of String with K (where your key type should be used), and similarly with V where values are required.
In other words: don't use specific types when declaring the two maps, but in all places, use the new generic types you added on class level.
Is there a way to sort the Properties object in java?
I have the string which groups the Properties and checks whether the data is available in the map format.
You can find an example without sub-classing Properties and working with Java 8/9/10
By this way, keySet, keys and entrySet methods from Properties return sorted keys. Then method store save the properties file sorted too.
This code is
here
Properties properties = new Properties() {
private static final long serialVersionUID = 1L;
#Override
public Set<Object> keySet() {
return Collections.unmodifiableSet(new TreeSet<Object>(super.keySet()));
}
#Override
public Set<Map.Entry<Object, Object>> entrySet() {
Set<Map.Entry<Object, Object>> set1 = super.entrySet();
Set<Map.Entry<Object, Object>> set2 = new LinkedHashSet<Map.Entry<Object, Object>>(set1.size());
Iterator<Map.Entry<Object, Object>> iterator = set1.stream().sorted(new Comparator<Map.Entry<Object, Object>>() {
#Override
public int compare(java.util.Map.Entry<Object, Object> o1, java.util.Map.Entry<Object, Object> o2) {
return o1.getKey().toString().compareTo(o2.getKey().toString());
}
}).iterator();
while (iterator.hasNext())
set2.add(iterator.next());
return set2;
}
#Override
public synchronized Enumeration<Object> keys() {
return Collections.enumeration(new TreeSet<Object>(super.keySet()));
}
};
Please use this example :
from the link : http://www.java2s.com/Tutorial/Java/0140__Collections/SortPropertieswhensaving.htm
import java.io.FileOutputStream;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
public class Main{
public static void main(String[] args) throws Exception {
SortedProperties sp = new SortedProperties();
sp.put("B", "value B");
sp.put("C", "value C");
sp.put("A", "value A");
sp.put("D", "value D");
FileOutputStream fos = new FileOutputStream("sp.props");
sp.store(fos, "sorted props");
}
}
class SortedProperties extends Properties {
public Enumeration keys() {
Enumeration keysEnum = super.keys();
Vector<String> keyList = new Vector<String>();
while(keysEnum.hasMoreElements()){
keyList.add((String)keysEnum.nextElement());
}
Collections.sort(keyList);
return keyList.elements();
}
}
Consider that TreeMap is a sorted Map, then you can do:
//properties as they are:
System.out.println(System.getProperties());
//now sorted:
TreeMap<Object, Object> sorted = new TreeMap<>(System.getProperties());
System.out.println(sorted);
I'm wondering, if there is a generic way to fill a map with properties you just know the prefix.
Assuming there are a bunch of properties like
namespace.prop1=value1
namespace.prop2=value2
namespace.iDontKnowThisNameAtCompileTime=anothervalue
I'd like to have a generic way to fill this property inside a map, something like
#Component
#ConfigurationProperties("namespace")
public class MyGenericProps {
private Map<String, String> propmap = new HashMap<String, String>();
// setter and getter for propmap omitted
public Set<String> returnAllKeys() {
return propmap.keySet();
}
}
Or is there another convenient way to collect all properties with a certain prefix, instead of iterating over all PropertySources in the environment?
Thanks
Hansjoerg
As long as you're happy having every property added into the map, rather than just those that you don't know in advance, you can do this with #ConfigurationProperties. If you want to grab everything that's beneath namespace then you need to use an empty prefix and provide a getter for a map named namespace:
#ConfigurationProperties("")
public class CustomProperties {
private final Map<String, String> namespace = new HashMap<>();
public Map<String, String> getNamespace() {
return namespace;
}
}
Spring Boot uses the getNamespace method to retrieve the map so that it can add the properties to it. With these properties:
namespace.a=alpha
namespace.b=bravo
namespace.c=charlie
The namespace map will contain three entries:
{a=alpha, b=bravo, c=charlie}
If the properties were nested more deeply, for example:
namespace.foo.bar.a=alpha
namespace.foo.bar.b=bravo
namespace.foo.bar.c=charlie
Then you'd use namespace.foo as the prefix and rename namespace and getNamespace on CustomProperties to bar and getBar respectively.
Note that you should apply #EnableConfigurationProperties to your configuration to enable support for #ConfigurationProperties. You can then reference any beans that you want to be processed using that annotation, rather than providing an #Bean method for them, or using #Component to have them discovered by component scanning:
#SpringBootApplication
#EnableConfigurationProperties(CustomProperties.class)
public class YourApplication {
// …
}
In addition to this, my problem was that I didn't had multiple simple key/value properties but whole objects:
zuul:
routes:
query1:
path: /api/apps/test1/query/**
stripPrefix: false
url: "https://test.url.com/query1"
query2:
path: /api/apps/test2/query/**
stripPrefix: false
url: "https://test.url.com/query2"
index1:
path: /api/apps/*/index/**
stripPrefix: false
url: "https://test.url.com/index"
Following Jake's advice I tried to use a Map with a Pojo like this:
#ConfigurationProperties("zuul")
public class RouteConfig {
private Map<String, Route> routes = new HashMap<>();
public Map<String, Route> getRoutes() {
return routes;
}
public static class Route {
private String path;
private boolean stripPrefix;
String url;
// [getters + setters]
}
}
Works like a charm,
Thanks!
I was going nuts trying to understand why #Andy's answer wasn't working for me (as in, the Map was remaining empty) just to realize that I had Lombok's #Builder annotation getting in the way, which added a non-empty constructor. I'm adding this answer to emphasize that in order for #ConfigurationProperties to work on Map, the value type must have a No-Arguments constructor. This is also mentioned in Spring's documentation:
Such arrangement relies on a default empty constructor and getters and setters are usually mandatory ...
I hope this will save someone else some time.
I wrote myself a MapFilter class to handle this efficiently. Essentially, you create a Map and then filter it by specifying a prefix for the key. There is also a constructor that takes a Properties for convenience.
Be aware that this just filters the main map. Any changes applied to the filtered map are also applied to the base map, including deletions etc but obviously changes to the main map will not be reflected in the filtered map until something causes a rebuild.
It is also very easy (and efficient) to filter already filtered maps.
public class MapFilter<T> implements Map<String, T> {
// The enclosed map -- could also be a MapFilter.
final private Map<String, T> map;
// Use a TreeMap for predictable iteration order.
// Store Map.Entry to reflect changes down into the underlying map.
// The Key is the shortened string. The entry.key is the full string.
final private Map<String, Map.Entry<String, T>> entries = new TreeMap<>();
// The prefix they are looking for in this map.
final private String prefix;
public MapFilter(Map<String, T> map, String prefix) {
// Store my backing map.
this.map = map;
// Record my prefix.
this.prefix = prefix;
// Build my entries.
rebuildEntries();
}
public MapFilter(Map<String, T> map) {
this(map, "");
}
private synchronized void rebuildEntries() {
// Start empty.
entries.clear();
// Build my entry set.
for (Map.Entry<String, T> e : map.entrySet()) {
String key = e.getKey();
// Retain each one that starts with the specified prefix.
if (key.startsWith(prefix)) {
// Key it on the remainder.
String k = key.substring(prefix.length());
// Entries k always contains the LAST occurrence if there are multiples.
entries.put(k, e);
}
}
}
#Override
public String toString() {
return "MapFilter (" + prefix + ") of " + map + " containing " + entrySet();
}
// Constructor from a properties file.
public MapFilter(Properties p, String prefix) {
// Properties extends HashTable<Object,Object> so it implements Map.
// I need Map<String,T> so I wrap it in a HashMap for simplicity.
// Java-8 breaks if we use diamond inference.
this(new HashMap<String, T>((Map) p), prefix);
}
// Helper to fast filter the map.
public MapFilter<T> filter(String prefix) {
// Wrap me in a new filter.
return new MapFilter<>(this, prefix);
}
// Count my entries.
#Override
public int size() {
return entries.size();
}
// Are we empty.
#Override
public boolean isEmpty() {
return entries.isEmpty();
}
// Is this key in me?
#Override
public boolean containsKey(Object key) {
return entries.containsKey(key);
}
// Is this value in me.
#Override
public boolean containsValue(Object value) {
// Walk the values.
for (Map.Entry<String, T> e : entries.values()) {
if (value.equals(e.getValue())) {
// Its there!
return true;
}
}
return false;
}
// Get the referenced value - if present.
#Override
public T get(Object key) {
return get(key, null);
}
// Get the referenced value - if present.
public T get(Object key, T dflt) {
Map.Entry<String, T> e = entries.get((String) key);
return e != null ? e.getValue() : dflt;
}
// Add to the underlying map.
#Override
public T put(String key, T value) {
T old = null;
// Do I have an entry for it already?
Map.Entry<String, T> entry = entries.get(key);
// Was it already there?
if (entry != null) {
// Yes. Just update it.
old = entry.setValue(value);
} else {
// Add it to the map.
map.put(prefix + key, value);
// Rebuild.
rebuildEntries();
}
return old;
}
// Get rid of that one.
#Override
public T remove(Object key) {
// Do I have an entry for it?
Map.Entry<String, T> entry = entries.get((String) key);
if (entry != null) {
entries.remove(key);
// Change the underlying map.
return map.remove(prefix + key);
}
return null;
}
// Add all of them.
#Override
public void putAll(Map<? extends String, ? extends T> m) {
for (Map.Entry<? extends String, ? extends T> e : m.entrySet()) {
put(e.getKey(), e.getValue());
}
}
// Clear everything out.
#Override
public void clear() {
// Just remove mine.
// This does not clear the underlying map - perhaps it should remove the filtered entries.
for (String key : entries.keySet()) {
map.remove(prefix + key);
}
entries.clear();
}
#Override
public Set<String> keySet() {
return entries.keySet();
}
#Override
public Collection<T> values() {
// Roll them all out into a new ArrayList.
List<T> values = new ArrayList<>();
for (Map.Entry<String, T> v : entries.values()) {
values.add(v.getValue());
}
return values;
}
#Override
public Set<Map.Entry<String, T>> entrySet() {
// Roll them all out into a new TreeSet.
Set<Map.Entry<String, T>> entrySet = new TreeSet<>();
for (Map.Entry<String, Map.Entry<String, T>> v : entries.entrySet()) {
entrySet.add(new Entry<>(v));
}
return entrySet;
}
/**
* An entry.
*
* #param <T>
*
* The type of the value.
*/
private static class Entry<T> implements Map.Entry<String, T>, Comparable<Entry<T>> {
// Note that entry in the entry is an entry in the underlying map.
private final Map.Entry<String, Map.Entry<String, T>> entry;
Entry(Map.Entry<String, Map.Entry<String, T>> entry) {
this.entry = entry;
}
#Override
public String getKey() {
return entry.getKey();
}
#Override
public T getValue() {
// Remember that the value is the entry in the underlying map.
return entry.getValue().getValue();
}
#Override
public T setValue(T newValue) {
// Remember that the value is the entry in the underlying map.
return entry.getValue().setValue(newValue);
}
#Override
public boolean equals(Object o) {
if (!(o instanceof Entry)) {
return false;
}
Entry e = (Entry) o;
return getKey().equals(e.getKey()) && getValue().equals(e.getValue());
}
#Override
public int hashCode() {
return getKey().hashCode() ^ getValue().hashCode();
}
#Override
public String toString() {
return getKey() + "=" + getValue();
}
#Override
public int compareTo(Entry<T> o) {
return getKey().compareTo(o.getKey());
}
}
// Simple tests.
public static void main(String[] args) {
String[] samples = {
"Some.For.Me",
"Some.For.You",
"Some.More",
"Yet.More"};
Map map = new HashMap();
for (String s : samples) {
map.put(s, s);
}
Map all = new MapFilter(map);
Map some = new MapFilter(map, "Some.");
Map someFor = new MapFilter(some, "For.");
System.out.println("All: " + all);
System.out.println("Some: " + some);
System.out.println("Some.For: " + someFor);
}
}
Most of the places I pass through are not fully descriptive or are a bit outdated, so here are the full steps that I have taken to do that. Note: The Spring boot version I used is 2.4.0:
Add in pom.xml - spring-boot-configuration-processor
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
Create a properties file and set data in it - shared-messages.properties Note: "LIBRARY_10001" is the map key, "Unable to find the book" is the map value
shared-messages.messages.LIBRARY_10001=Unable to find the book
shared-messages.messages.LIBRARY_10002=Book already exists
Create a Properties class that uses the properties file
#ConfigurationProperties("shared-messages")
#Getter
public class LibraryProperties {
private final Map<String, String> messages = new HashMap<>();
}
On the Application level, define the Property source and Enable Configuration Property
#EnableConfigurationProperties(LibraryProperties.class)
#PropertySource("shared-messages.properties")
public class LibraryApplication {
....
}
On the Service level, inject "LibraryProperties" class and access the property you need from
#Autowired
private LibraryProperties libraryProperties;
libraryProperties.getMessages().get("LIBRARY_10001")
It might not be the perfect solution but I shared the way I managed to do that because I tried different combinations that didn't work for me
I am using org.apache.commons.lang3.text.StrSubstitutor to parse a String. I have it setup similar to this:
StrSubstitutor sub = new StrSubstitutor(messageValues, "&(", ")");
String format = sub.replace("Information: &(killer) killed &(target)!");
This no longer works if I write the keys in different cases:
"Information: &(KILLER) killed &(TARGET)!"
Is there a way of making the keys for the String Substitutor case-insensitive?
I cannot use toLowerCase() because I only want the keys to be case-insensitive.
StrSubstitutor has a constructor that takes an instance of StrLookup. You can create an implementation of StrLookup that lowercases the keys its looking for before actually looking for them.
Here's how it looks like:
public class CaseInsensitiveStrLookup<V> extends StrLookup<V> {
private final Map<String, V> map;
CaseInsensitiveStrLookup(final Map<String, V> map) {
this.map = map;
}
#Override
public String lookup(final String key) {
String lowercaseKey = key.toLowerCase(); //lowercase the key you're looking for
if (map == null) {
return null;
}
final Object obj = map.get(lowercaseKey);
if (obj == null) {
return null;
}
return obj.toString();
}
}
Using this StrLookup implementation you don't need to worry about what kind of Map you're passing to the constructor.
The following test case returns succesfully, using the above implementation:
import org.apache.commons.lang3.text.StrSubstitutor;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.HashMap;
import java.util.Map;
#Test
public class TestClass {
#Test
public void test() {
Map<String, String> messageValues = new HashMap<String, String>();
messageValues.put("killer", "Johnson");
messageValues.put("target", "Quagmire");
StrSubstitutor sub = new StrSubstitutor(new CaseInsensitiveStrLookup<String>(messageValues), "&(", ")", '\\');
String format2 = sub.replace("Information: &(killer) killed &(target)!");
String format = sub.replace("Information: &(KILLER) killed &(TARGET)!");
Assert.assertEquals(format, "Information: Johnson killed Quagmire!");
Assert.assertEquals(format2, "Information: Johnson killed Quagmire!");
}
}
You don't need to write a custom class. Assuming you can live with the log(n) access times, just use a case-insensitive TreeMap.
public static void main(String[] args) {
Map<String, String> m = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
m.put("foo", "bar");
StrSubstitutor sub = new StrSubstitutor(m);
String s = sub.replace("${FOO}");
System.out.println(s);
} // prints "bar"
I think this case-insensitive map would work:
import java.util.HashMap;
import java.util.Map;
public class CaseMap<V> extends HashMap<String, V> {
public CaseMap() {
}
public CaseMap(int capacity) {
super(capacity);
}
public CaseMap(int capacity, float loadFactor) {
super(capacity, loadFactor);
}
public CaseMap(Map<String, ? extends V> map) {
putAll(map);
}
public V put(String key, V value) {
return super.put(key.toUpperCase(), value);
}
public V get(Object key) {
if (!(key instanceof String)) return null;
return super.get(((String)key).toUpperCase());
}
}
If you don't control the creation of the messageValues map, you could build a CaseMap from it like this:
CaseMap<String> caseFreeMessageValues = new CaseMap<String>(messageValues);
And then build your StrSubstitutor like this:
StrSubstitutor sub = new StrSubstitutor(messageValues, "&(", ")");
String format = sub.replace("Information: &(KILLER) killed &(TARGET)!");
You might want to think about other methods of Map that should be overridden as well, such as containsKey.
In case you need flexibility with both the Map and the Tokens being case insensitive AND you are not in control of the map being built you can use something like this.
String replaceTokens(String str, Map<String, String> messageValues) {
if(tokenToValue == null || tokenToValue.size() < 1) return str;
StrSubstitutor caseInsensitiveTokenReplacer = new StrSubstitutor(new CaseInsensitiveStrLookup<>(messageValues),
"&(", ")", '\\');
return caseInsensitiveTokenReplacer.replace(str);
}
StrLookup Implementation
public class CaseInsensitiveStrLookup<V> extends StrLookup<V> {
private final Map<String, V> map = new TreeMap<String, V>(String.CASE_INSENSITIVE_ORDER);
public CaseInsensitiveStrLookup(final Map<String, V> map) throws NullValueKeyNotSupported {
if(map.containsKey(null)) throw new Exception(); // Dont want to support null
this.map.putAll(map);
}
#Override
public String lookup(final String key) {
V value = map.get(key);
if(value == null) return null;
return value.toString();
}}
In Python, the defaultdict class provides a convenient way to create a mapping from key -> [list of values], in the following example,
from collections import defaultdict
d = defaultdict(list)
d[1].append(2)
d[1].append(3)
# d is now {1: [2, 3]}
Is there an equivalent to this in Java?
There is nothing that gives the behaviour of default dict out of the box. However creating your own default dict in Java would not be that difficult.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public class DefaultDict<K, V> extends HashMap<K, V> {
Class<V> klass;
public DefaultDict(Class klass) {
this.klass = klass;
}
#Override
public V get(Object key) {
V returnValue = super.get(key);
if (returnValue == null) {
try {
returnValue = klass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
this.put((K) key, returnValue);
}
return returnValue;
}
}
This class could be used like below:
public static void main(String[] args) {
DefaultDict<Integer, List<Integer>> dict =
new DefaultDict<Integer, List<Integer>>(ArrayList.class);
dict.get(1).add(2);
dict.get(1).add(3);
System.out.println(dict);
}
This code would print: {1=[2, 3]}
In most common cases where you want a defaultdict, you'll be even happier with a properly designed Multimap or Multiset, which is what you're really looking for. A Multimap is a key -> collection mapping (default is an empty collection) and a Multiset is a key -> int mapping (default is zero).
Guava provides very nice implementations of both Multimaps and Multisets which will cover almost all use cases.
But (and this is why I posted a new answer) with Java 8 you can now replicate the remaining use cases of defaultdict with any existing Map.
getOrDefault(), as the name suggests, returns the value if present, or returns a default value. This does not store the default value in the map.
computeIfAbsent() computes a value from the provided function (which could always return the same default value) and does store the computed value in the map before returning.
If you want to encapsulate these calls you can use Guava's ForwardingMap:
public class DefaultMap<K, V> extends ForwardingMap<K, V> {
private final Map<K, V> delegate;
private final Supplier<V> defaultSupplier;
/**
* Creates a map which uses the given value as the default for <i>all</i>
* keys. You should only use immutable values as a shared default key.
* Prefer {#link #create(Supplier)} to construct a new instance for each key.
*/
public static DefaultMap<K, V> create(V defaultValue) {
return create(() -> defaultValue);
}
public static DefaultMap<K, V> create(Supplier<V> defaultSupplier) {
return new DefaultMap<>(new HashMap<>(), defaultSupplier);
}
public DefaultMap<K, V>(Map<K, V> delegate, Supplier<V> defaultSupplier) {
this.delegate = Objects.requireNonNull(delegate);
this.defaultSupplier = Objects.requireNonNull(defaultSupplier);
}
#Override
public V get(K key) {
return delegate().computeIfAbsent(key, k -> defaultSupplier.get());
}
}
Then construct your default map like so:
Map<String, List<String>> defaultMap = DefaultMap.create(ArrayList::new);
in addition to apache collections, check also google collections:
A collection similar to a Map, but which may associate multiple values with a single key. If you call put(K, V) twice, with the same key but different values, the multimap contains mappings from the key to both values.
In Java 8+ you can use:
map.computeIfAbsent(1, k -> new ArrayList<Integer>()).add(2);
You can use MultiMap from Apache Commons.
Using just the Java runtime library you could use a HashMap and add an ArrayList to hold your values when the key does not exist yet or add the value to the list when the key does exist.
The solution from #tendayi-mawushe did not work for me with Primitive types (e.g. InstantiationException Integer), here is one implementation that works with Integer, Double, Float. I often use Maps with these and added static constructors for conveninence
import java.util.HashMap;
import java.util.Map;
/** Simulate the behaviour of Python's defaultdict */
public class DefaultHashMap<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = 1L;
private final Class<V> cls;
private final Number defaultValue;
#SuppressWarnings({ "rawtypes", "unchecked" })
public DefaultHashMap(Class factory) {
this.cls = factory;
this.defaultValue = null;
}
public DefaultHashMap(Number defaultValue) {
this.cls = null;
this.defaultValue = defaultValue;
}
#SuppressWarnings("unchecked")
#Override
public V get(Object key) {
V value = super.get(key);
if (value == null) {
if (defaultValue == null) {
try {
value = cls.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
} else {
value = (V) defaultValue;
}
this.put((K) key, value);
}
return value;
}
public static <T> Map<T, Integer> intDefaultMap() {
return new DefaultHashMap<T, Integer>(0);
}
public static <T> Map<T, Double> doubleDefaultMap() {
return new DefaultHashMap<T, Double>(0d);
}
public static <T> Map<T, Float> floatDefaultMap() {
return new DefaultHashMap<T, Float>(0f);
}
public static <T> Map<T, String> stringDefaultMap() {
return new DefaultHashMap<T, String>(String.class);
}
}
And a test, for good manners:
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Test;
public class DefaultHashMapTest {
#Test
public void test() {
Map<String, List<String>> dm = new DefaultHashMap<String, List<String>>(
ArrayList.class);
dm.get("nokey").add("one");
dm.get("nokey").add("two");
assertEquals(2, dm.get("nokey").size());
assertEquals(0, dm.get("nokey2").size());
}
#Test
public void testInt() {
Map<String, Integer> dm = DefaultHashMap.intDefaultMap();
assertEquals(new Integer(0), dm.get("nokey"));
assertEquals(new Integer(0), dm.get("nokey2"));
dm.put("nokey", 3);
assertEquals(new Integer(0), dm.get("nokey2"));
dm.put("nokey3", 3);
assertEquals(new Integer(3), dm.get("nokey3"));
}
#Test
public void testString() {
Map<String, String> dm = DefaultHashMap.stringDefaultMap();
assertEquals("", dm.get("nokey"));
dm.put("nokey1", "mykey");
assertEquals("mykey", dm.get("nokey1"));
}
}
I wrote the library Guavaberry containing such data structure: DefaultHashMap.
It is highly tested and documented. You can find it and integrate it pretty easily via Maven Central.
The main advatage is that it uses lambda to define the factory method. So, you can add an arbitrarly defined instance of a class (instead of relying on the existence of the default constructor):
DefaultHashMap<Integer, List<String>> map = new DefaultHashMap(() -> new ArrayList<>());
map.get(11).add("first");
I hope that can be of help.