Converting a (YAML) file to any MAP implementation - java

I was working on a spare time project where I needed to read values from a YAML file and store them in a HashMap, another YAML file had to be stored in a LinkedHashMap. I used an API to do the reading, some explanation was added in the code below (though I believe it's quite redundant). Only the method that returns a LinkedHashMap was included because the other one is practically identical.
Currently I'm using seperate methods for getting a HashMap and LinkedHashMap but noticed that the code was quite similar. So I wondered, would it be possible to write a general method that puts the paths and values from the YAML file into any Collections implementation (that are implementing Hash Table)? And if so, how could one accomplish that?
public LinkedHashMap<String, Object> fileToLinkedHashMap(File yamlFile)
{
LinkedHashMap<String, Object> fileContents = new LinkedHashMap<String, Object>();
//Part of the API I'm using, reads from YAML File and stores the contents
YamlConfiguration config = YamlConfiguration.loadConfiguration(yamlFile);
//Configuration#getKeys(true) Gets all paths within the read File
for (String path : config.getKeys(true))
{
//Gets the value of a path
if (config.get(path) != null)
fileContents.put(path, config.get(path));
}
return fileContents;
}
Note: I know I'm currently not checking if the given file is a YAML file, this is redundant within this question.

You can make use of functional interfaces (introduced in java 8) for this:
public void consumeFile(File yamlFile, BiConsumer<? super String, ? super Object> consumer){
YamlConfiguration config = YamlConfiguration.loadConfiguration(yamlFile);
for (String path : config.getKeys(true)){
if (config.get(path) != null){
consumer.accept(path, config.get(path));
}
}
}
Which can then be called with literally anything, you just have to provide a lambda which accepts 2 parameters:
// collect into a map
Map<String, Object> map = /* hash map, linked hash map, tree map, you decide */;
consumeFile(yamlFile, map::put);
// just print them, why not?
consumeFile(yamlFile, (key, value) -> System.out.println(key + " = " + value));
You see, the uses are possibly endless. Only limited by your use case and imagination.
If you can't use java 8 (you probably should though) there is still hope. As you both times return a Map you can decide when calling the method what map implementation you'd like to collect into:
public Map<String, Object> consumeFile(File yamlFile, Map<String, Object> map){
YamlConfiguration config = YamlConfiguration.loadConfiguration(yamlFile);
for (String path : config.getKeys(true)){
if (config.get(path) != null){
map.put(path, config.get(path));
}
}
return map;
}
Which may be called like this:
Map<String, Object> map = consumeFile(yamlFile, new /*Linked*/HashMap<>());
Again what map implementation you want to use, you can decide for your needs.

Related

How to create a map with enum values in java?

I need to validate the value of a parameters passed as part of REST API. These parameters are a fixed set of values. I thought of using a map having parameter name as key and enum as value. So I can check if the value sent in REST API is one of the enum keys.
But I am not able to create a Map with String key and enum value in java, tried creating Map and then putting and enum as value, but it fails.
class Validation {
enum Type {
INTERNAL,
EXTERNAL
};
}
Map<String, Object> validationMap = new HashMap<String, Object>();
validationMap.put("type", Validation.Type);
This is throwing an error that type is not defined.
This is probably what you're looking for, Map<String, Object> is changed to Map<String, Validation.type>:
Map<String, Validation.type> validationMap = new HashMap<String, Validation.type>();
validationMap.put("type", Validation.type.INTERNAL);
validationMap.put("type2", Validation.type.EXTERNAL);
Your original code would have worked, if you had changed Validation.type to Validation.type.INTERNAL for example, however your validationMap map allows the storage of any Object, so validationMap.put("type2", 123.123); would also have worked, which is unlikely to be something you want.
Is this what you need?
Map<String, List<Validation.type>> validationMap = new HashMap<>();
validationMap.put("type",
Arrays.asList(Validation.type.EXTERNAL,Validation.type.INTERNAL));
I believe Your code does not work because Validation.Type is not an instance (of an enum). When you put a value in your map, I believe you would want to put in an actual instance as a value. I suppose that is why the above answers suggest adding an actual instance to the Map such as Type.INTERNAL or using a class instance.
This code will run
Map<String, Object> validationMap = new HashMap<String, Object>();
// this works
validationMap.put("type", Validation.Type.EXTERNAL);
// as shown by:
System.out.println(validationMap.toString());
// here is an analogy:
// make an instance of a class
Validation validation = new Validation();
// this works
validationMap.put("classType",validation);
Validation validationGet = (Validation) validationMap.get("classType");
System.out.println(validationMap.toString());
// by analogy this will not work because Validation is not an instance:
// validationMap.put("classType",Validation);
And the output on my machine is:
{type=EXTERNAL}
{type=EXTERNAL, classType=Validation#3feba861}

Best approach to delete a set of entries in a Map

Is there any fast and reliable way/approach to remove a set of entries based on an attribute on the entry's value. Below approach loops every entry which in undesirable.
e.g: ConcurrentHashMap - Entries will be in millions
Iterator<Map.Entry<String, POJO>> iterator = map.entrySet().iterator();
for (Iterator<Map.Entry<String, POJO>> it = iterator; it.hasNext(); ) {
Map.Entry<String, POJO> entry = it.next();
POJO value = entry.getValue();
if (value != null && *attribute*.equalsIgnoreCase(value.getAttribute())) {
it.remove();
}
}
There is no better way of mutating map in place without utilizing additional data structures. What you are basically asking for is secondary index like employed in databases, where you have pointers to entries based on some non-primary key properties. If you don't want to store extra index, there is no easier way then to iterate through all entries.
What I would suggest you to look into is composing map view over your original map. For example something like (using guava)
Map<String,POJO> smallerMap = Maps.filterValues(map,
v -> !attribute.equalsIgnoreCase(v.getAttribute())
);
You will need to be careful with such view (don't call size() on it for example), but for access etc it should be fine (depending on your exact needs, memory constraints etc).
And side note - please note I have removed null check for value. You cannot store null values in ConcurrentHashMap - and it is also not that great idea in normal map as well, better to remove entire key.
There are two solutions that I can think of
First solution:
Create another map for storing the object hashcode as key and corresponding key as value. The structure could be like as below
Map<Integer, String> map2 = new HashMap<>();
Here is working solution. Only drawback of this is getting a unique hashcode might be tough if there are large number of objects.
import java.util.HashMap;
import java.util.Map;
public class DualHashMap {
public static void main(String a[]){
Map<String, Pojo> map = new HashMap<>();
Map<Integer, String> map2 = new HashMap<>();
Pojo object1 = new Pojo();
Pojo object2 = new Pojo();
map.put( "key1", object1);
map.put( "key2", object2);
map2.put(object1.hashCode(), "key1");
map2.put(object2.hashCode(), "key2");
// Now let say you have to delete object1 you can do as follow
map.remove(map2.get(object1.hashCode()));
}
}
class Pojo{
#Override
public int hashCode(){
return super.hashCode(); //You must work on this yourself, and make sure hashcode is unique for each object
}
}
2nd solution:-
Use the solution provided for dual hashmap by Guava or Apache
The Apache Commons class you need is BidiMap.
Here is another for you by Guava

Unable to retrieve multi line value using map.get()

I have a multi-line value that needs to be processed. I'm using the map.get() method to retrieve that value but it seems to be getting only the last line value.
Here's my code:
map = new LinkedHashMap();
updateMap("BUG", parser, map, bugRec);
map.put(nextBuildIdTagName, nextBuildId); // putting the new value in
String value = (String)map.get(nextBuildIdTagName); // This is where it is not working
nextBuildIdTagName already has a value, and the new value gets inserted as a new line. I need to be able to retrieve the existing value as well as the new value.
So from what I understand you want to store multiple values in the map under single key. Easiest way to do it (without using any external lib with multimap impl) is to create a map of lists like this:
Map<String, List<String>> map = new HashMap<>();
then when you add to map you can do sth like this:
if(!map.containsKey(nextBuildIdTagName)) {
map.put(nextBuildIdTagName, new ArrayList<>());
}
map.get(nextBuildIdTagName).add(nextBuildId);
then to get all the items and iterate over them
for(String value : map.get(nextBuildIdTagName)) {
// do sth to each line
}
In the javadoc for HashMap (of which LinkedHashMap is a subclass), it is clearly stated that: "If the map previously contained a mapping for the key, the old value is replaced". To store multiple values (the lines) for the same key, you'd need to do something like:
Map<String, List<String>> map = new Linked HashMap<>();
And the add to the list (which is the map value) the needes lines. Note: here I assumed your key to be a String, change its type as needed.
Source: http://docs.oracle.com/javase/7/docs/api/java/util/HashMap.html#put(K,%20V)

How to pass in initialized HashMap as param?

How can I pass in a new HashMap in the most canonical (simplest, shortest hand) form?
// 1. ? (of course this doesn't work)
passMyHashMap(new HashMap<String, String>().put("key", "val"));
// 2. ? (of course this doesn't work)
passMyHashMap(new HashMap<String, String>(){"key", "val"});
void passMyHashMap(HashMap<?, ?> hm) {
// do stuff witih my hashMap
}
Create it, initialize it, then pass it:
Map<String,String> myMap = new HashMap<String,String>();
myMap.put("key", "val");
passMyHashMap(myMap);
You could use the "double curly" style that David Wallace mentions in a comment, I suppose:
passMyHashMap(new HashMap<String,String>(){{
put("x", "y");
put("a", "b");
}});
This essentially derives a new class from HashMap and sets up values in the initializer block. I don't particularly care for it (hence originally not mentioning it), but it doesn't really cause any problems per se, it's more of a style preference (it does spit out an extra .class file, although in most cases that's not a big deal). You could compress it all to one line if you'd like, but readability will suffer.
You can't call put and pass the HashMap into the method at the same time, because the put method doesn't return the HashMap. It returns the old value from the old mapping, if it existed.
You must create the map, populate it separately, then pass it in. It's more readable that way anyway.
HashMap<String, String> map = new HashMap<>();
map.put("key", "val");
passMyHashMap(map);
HashMap< K,V>.put
public **V** put(K key,V value)
Associates the specified value with the specified key in this map. If
the map previously contained a mapping for the key, the old value is
replaced.
Returns the previous value associated with key, or null if there was
no mapping for key. (A null return can also indicate that the map
previously associated null with key.)
As you can see, it does not return the type HashMap<?, ?>
You can't do that. What you can do is create a factory that allow you to do so.
public class MapFactory{
public static Map<String, String> put(final Map<String, String> map, final String key, final String valeu){
map.put(key, value);
return map;
}
}
passMyHashMap(MapFactory.put(new HashMap<String, String>(),"key", "value"));
Although I can't image a approach that would need such implementation, also I kinda don't like it. I would recommend you to create your map, pass the values and just then send to your method.
Map<String, String> map = new HashMap<String, String>();
map.put("key","value");
passMyHashMap(map);

Efficiently "modifying" an ImmutableMap

We're currently using Guava for its immutable collections but I was surprised to find that their maps don't have methods to easily create new maps with minor modifications. And on top of that, their builder doesn't allow assigning new values to keys, or removing keys.
So if I wanted to modify just one value, here's what I would like to be able to do:
ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
ImmutableMap<Guid, ImmutableMap<String, Integer>> modifiedMap =
originalMap.cloneAndPut(key, value);
Here's what it looks like Guava are expecting me to do:
ImmutableMap<Guid, ImmutableMap<String, Integer>> originalMap = /* get the map */;
Map<Guid, ImmutableMap<String, Integer>> mutableCopy = new LinkedHashMap<>(originalMap);
mutableCopy.put(key, value);
originalMap = ImmutableMap.copyOf(mutableCopy);
/* put the map back */
By doing this I get a new copy of the map with the modification I want. The original copy is untouched and I will be using an atomic reference to put the thing back so the whole setup is thread-safe.
It's just slow.
There is a lot of wasted copying going on under the covers here. Suppose there's 1,024 buckets in the map. That's 1,023 buckets which you're unnecessarily creating all over again (twice each, too), when you could have used those immutable buckets as-is and cloned only one of them.
So I guess:
Is there a Guava utility method buried somewhere for this sort of thing? (It isn't in Maps or on the ImmutableMap.Builder.)
Is there any other Java library which gets this sort of thing right? I am under the impression that Clojure has this sort of thing under the hood but we're not ready to switch languages just yet...
A bit unexpected the map of Functional Java is mutable like Guava's. The list is immutable though as I would expect.
Googling for "persistent collection java" brought up: pcollections. There are's a Map implementation.
Before actually using any other implementation I would benchmark the memory and performance characteristics against Guava. I would not be surprised if it's still better.
One may go with the following, to just duplicate the map once instead of twice:
ImmutableMap<String, Object> originalMap = /* get the map */;
Map<String, Object> modifiedMap = ImmutableMap.<String, Object>builder().putAll( originalMap )
.put( "new key", new Object() )
.build();
However, removing a value looks quite less beautiful with an iteration over the existing map, e.g. like this:
ImmutableMap<String, Object> originalMap = /* get the map */;
if( !originalMap.containsKey( "key to remove" ) ) {
return;
}
ImmutableMap.Builder<String, Object> mapBuilder = ImmutableMap.builder();
for( Map.Entry<String, Object> originalEntry : originalMap.entrySet() ) {
if( originalEntry.getKey().equals( "key to remove" ) ) {
continue;
}
mapBuilder.put( originalEntry );
}
Map<String, Object> modifiedMap = mapBuilder.build();

Categories

Resources