Java Hashmap to JavaFX Treeview and Back? - java

I have a nested HashMap which looks like this:
Map<String,Object> myMap = new HashMap<String,Object>();
This myMap is nested, Like:
String key => String val
String key => String val
String key => Map<String,Object> val
And then that value may contain another similar Map<String,Object>.
I do not expect it to be nested more than 3 levels.
The leafs or last values are always String.
Now I'm trying to make a way to edit this HashMap in a JavaFX GUI.
From what I have learnt so far,
the best way seems like making a editable JavaFX TreeView
and somehow translating the Map to a TreeView and back.
So far I'm thinking
TreeMap<String, Object> treeMap = new TreeMap<String, Object>();
treeMap.putAll(myMap);
And then somehow translating that TreeMap to a JavaFX TreeView.
But I can not figure out how to proceed.
Another headache is that after user edits I need to translate it all back to a HashMap such as the original myMap. Although sorting / sequence is not required.

Create a suitable class to represent the map entry. Since you need to modify the Map, you need to store key and Map in that class.
If you use TextFieldTreeCell as cell type, the StringConverter can be used to modify the source data structure on a edit:
private static TreeItem<MapItem> createTree(Map<String, Object> map) {
TreeItem<MapItem> result = new TreeItem<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
result.getChildren().add(createTree(map, entry));
}
return result;
}
private static TreeItem<MapItem> createTree(Map<String, Object> map, Map.Entry<String, Object> entry) {
MapItem mi = new MapItem(map, entry.getKey());
TreeItem<MapItem> result = new TreeItem<>(mi);
Object value = entry.getValue();
if (value instanceof Map) {
Map<String, Object> vMap = (Map<String, Object>)value;
// recursive creation of subtrees for map entries
for (Map.Entry<String, Object> e : vMap.entrySet()) {
result.getChildren().add(createTree(vMap, e));
}
} else {
result.getChildren().add(new TreeItem<>(new MapItem(null, value.toString())));
}
return result;
}
private static class MapItem {
private final Map<String, Object> map;
private final String value;
public MapItem(Map<String, Object> map, String value) {
this.map = map;
this.value = value;
}
}
private static class Converter extends StringConverter<MapItem> {
private final TreeCell<MapItem> cell;
public Converter(TreeCell<MapItem> cell) {
this.cell = cell;
}
#Override
public String toString(MapItem object) {
return object == null ? null : object.value;
}
#Override
public MapItem fromString(String string) {
MapItem mi = cell.getItem();
if (mi != null) {
TreeItem<MapItem> item = cell.getTreeItem();
if (item.isLeaf()) {
MapItem parentItem = item.getParent().getValue();
// modify value in parent map
parentItem.map.put(parentItem.value, string);
mi = new MapItem(mi.map, string);
} else if (!mi.map.containsKey(string)) {
// change key of mapping, if there is no mapping for the new key
mi.map.put(string, mi.map.remove(mi.value));
mi = new MapItem(mi.map, string);
}
}
return mi;
}
}
#Override
public void start(Stage primaryStage) {
Map<String, Object> map = new HashMap<>();
map.put("a", "b");
Map<String, Object> inner = new HashMap<>();
map.put("c", inner);
inner.put("d", "e");
Map<String, Object> inner2 = new HashMap<>();
inner.put("f", inner2);
inner2.put("g", "h");
inner2.put("i", "j");
TreeView<MapItem> treeView = new TreeView<>(createTree(map));
treeView.setEditable(true);
treeView.setShowRoot(false);
treeView.setCellFactory(t -> {
TextFieldTreeCell<MapItem> cell = new TextFieldTreeCell<>();
cell.setConverter(new Converter(cell));
return cell;
});
Button btn = new Button("Print Map");
btn.setOnAction((ActionEvent event) -> {
System.out.println(map);
});
VBox root = new VBox(10, btn, treeView);
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

There is not build-in method that would convert:
either Map directly to TreeView
or TreeMap to TreeView
Moreover, If you see the structure of Map<?,?>, we can see that it is a combination of key, value pair whereas TreeView<?> consists of only value, like that of Collection interface. So It's not possible to insert key, value pair in TreeView. However you can insert only values from Map.
The best you can do is define a new data structure like this:
public class YourCustomDataStructure extends TreeItem<String> {
...
/*
Now you can define methods that will convert `List`s directly to `YourCustomDataStructure`.
You can even define method to convert `Map` values to `YourCustomDataStructure`.
*/
public boolean addAll(Map map) {
//your implementation to convert map to TreeItem
}
public boolean addAll(List list) {
//your implementation to convert list to TreeItem
}
}
Now convert your list or map to YourCustomDataStructure using addAll() method from previous step.
List<Object> list = getListFromSomewhere();
YourCustomDataStructure<String> customList = new YourCustomDataStructure<String>("customList Node");
customList.getChildren().addAll(list);
Now since YourCustomDataStructure extends TreeItem so it's object's can be directly passed to TreeView's constructor and they will be automatically converted to TreeView.
TreeView<String> treeView = new TreeView<String>(customList);
P.S.: I know defining new data structure and all the methods will require lots of efforts at initial level, but once those methods are defined, then it will become too easy to convert TreeView to Map and vice versa.

Related

Java streams: Add to map but avoid mutation

I often find myself in a situation where I need to create a Map of objects from a Set or List.
The key is usually some String or Enum or the like, and the value is some new object with data lumped together.
The usual way of doing this, for my part, is by first creating the Map<String, SomeKeyValueObject> and then iterating over the Set or List I get in and mutate my newly created map.
Like the following example:
class Example {
Map<String, GroupedDataObject> groupData(final List<SomeData> list){
final Map<String, GroupedDataObject> map = new HashMap<>();
for(final SomeData data : list){
final String key = data.valueToGroupBy();
map.put(key, GroupedDataObject.of(map.get(key), data.displayName(), data.data()));
}
return map;
}
}
class SomeData {
private final String valueToGroupBy;
private final Object data;
private final String displayName;
public SomeData(final String valueToGroupBy, final String displayName, final Object data) {
this.valueToGroupBy = valueToGroupBy;
this.data = data;
this.displayName = displayName;
}
public String valueToGroupBy() {
return valueToGroupBy;
}
public Object data() {
return data;
}
public String displayName() {
return displayName;
}
}
class GroupedDataObject{
private final String key;
private final List<Object> datas;
private GroupedDataObject(final String key, final List<Object> list) {
this.key = key;
this.datas = list;
}
public static GroupedDataObject of(final GroupedDataObject groupedDataObject, final String key, final Object data) {
final List<Object> list = new ArrayList<>();
if(groupedDataObject != null){
list.addAll(groupedDataObject.datas());
}
list.add(data);
return new GroupedDataObject(key, list);
}
public String key() {
return key;
}
public List<Object> datas() {
return datas;
}
}
This feels very unclean. We create a map, and then mutate it over and over.
I've taken a liking to java 8s use of Streams and creating non-mutating data structures (or rather, you don't see the mutation). So is there a way to turn this grouping of data into something that uses a declarative approach rather than the imperative way?
I tried to implement the suggestion in https://stackoverflow.com/a/34453814/3478016 but I seem to be stumbling. Using the approach in the answer (the suggestion of using Collectors.groupingBy and Collectors.mapping) I'm able to get the data sorted into a map. But I can't group the "datas" into one and the same object.
Is there some way to do it in a declarative way, or am I stuck with the imperative?
You can use Collectors.toMap with a merge function instead of Collectors.groupingBy.
Map<String, GroupedDataObject> map =
list.stream()
.collect(Collectors.toMap(SomeData::valueToGroupBy,
d -> {
List<Object> l = new ArrayList<>();
l.add(d.data());
return new GroupedDataObject(d.valueToGroupBy(), l);
},
(g1,g2) -> {
g1.datas().addAll(g2.datas());
return g1;
}));
The GroupedDataObject constructor must be made accessible in order for this to work.
If you avoid the GroupedDataObject and simply want a map with a key and a list you can use Collectors.groupingBy that you have been looking into.
Collectors.groupingBy will allow you to do this:
List<SomeObject> list = getSomeList();
Map<SomeKey, List<SomeObject>> = list.stream().collect(Collectors.groupingBy(SomeObject::getKeyMethod));
This will require SomeKey to have proper implementations of equals and hashValue
Sometimes streams are not the way to go. I believe this is one of those times.
A little refactoring using merge() gives you:
Map<String, MyTuple> groupData(final List<SomeData> list) {
Map<String, MyTuple> map = new HashMap<>();
list.forEach(d -> map.merge(d.valueToGroupBy(), new MyTuple(data.displayName(), data.data()),
(a, b) -> {a.addAll(b.getDatas()); return a;});
Assuming a reasonable class to hold your stuff:
class MyTuple {
String displayName;
List<Object> datas = new ArrayList<>();
// getters plus constructor that takes 1 data and adds it to list
}

Refactoring Java Map of Map of Map

I'm reviewing an old code of an project and got a datastructure as bellow using Map of Map of Map(3-Layered Map):
// data structure
Map<String, Map<String, Map<String, List<String>>>> tagTree
= new HashMap<String, Map<String,Map<String,List<String>>>>();
And fetch the values from Map (I think this is the nice part)
// fetch at tag values
List<String> tagList1 = tagTree.get("Java").get("Active").get("Tags");
List<String> tagList2 = tagTree.get("Java").get("Latest").get("SubTags");
Put the values in Map (little bit complex and error-prone)
// put values
Map<String, Map<String, List<String>>> javaLangMap = new HashMap<String, Map<String, List<String>>>();
Map<String, List<String>> javaStatusMap = new HashMap<String, List<String>>();
List<String> javaTagList = new ArrayList<String>();
javaTagList.add("Java-OOP");
javaTagList.add("Java-Variables");
// put tag list
javaStatusMap.put("Tags", javaTagList);
// put status-wise tag
javaLangMap.put("Active", javaStatusMap);
// put language-wise tag
tagTree.put("Java", javaLangMap);
Currently this is serving to maintain following structure
TagLanguage -> TagStatus -> TagType -> TagList
I'm planning to refactor this Map because it's hard to read for other developers.
Please share your Idea How to do it by considering following cases:
All four layer may be changed during runtime.
All Level should
accessible
In-memory solution required i.e. dont use Database table hierarchy .
If you only ever wanted to access the last level of your data structure, you could use a Multimap<Triple<String,String,String>,String>. Multimap<K,V> is a data structure from Guava which basically is a nicer Map<K,Collection<V>>. Triple<L,M,R> is a 3-elements tuple data structure from Apache Commons Lang3 which is Comparable and implements equals.
You could declare your tag tree like this:
Multimap<Triple<String, String, String>, String> tagTree = HashMultimap.create();
And then fill it like this:
tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-OOP");
tagTree.put(Triple.of("Java", "Active", "Tags"), "Java-Variables");
Or:
tagTree.putAll(Triple.of("Java", "Active", "Tags"), Arrays.asList("Java-OOP", "Java-Variables"));
And then get your values from it like this:
Set<String> values = tagTree.get(Triple.of("Java", "Active", "Tags"));
Here is another rough solution that may suit you which enables to get with 1, 2 or 3 keys:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
public class ThreeLevelMap<K1, K2, K3, V> {
private Map<K1, Map<K2, Multimap<K3, V>>> firstLevelMap = new HashMap<>();
private Map<Pair<K1, K2>, Multimap<K3, V>> secondLevelMap = new HashMap<>();
private Multimap<Triple<K1, K2, K3>, V> thirdLevelMap = HashMultimap.create();
public void put(K1 key1, K2 key2, K3 key3, V value) {
thirdLevelMap.put(Triple.of(key1, key2, key3), value);
final Pair<K1, K2> secondLevelKey = Pair.of(key1, key2);
Multimap<K3, V> secondLevelContainer = secondLevelMap.get(secondLevelKey);
if (secondLevelContainer == null) {
secondLevelContainer = HashMultimap.create();
secondLevelMap.put(secondLevelKey, secondLevelContainer);
}
secondLevelContainer.put(key3, value);
Map<K2, Multimap<K3, V>> firstLevelContainer = firstLevelMap.get(key1);
if (firstLevelContainer == null) {
firstLevelContainer = new HashMap<>();
firstLevelMap.put(key1, firstLevelContainer);
}
firstLevelContainer.put(key2, secondLevelContainer);
}
public Collection<V> get(K1 key1, K2 key2, K3 key3) {
return thirdLevelMap.get(Triple.of(key1, key2, key3));
}
public Multimap<K3, V> get(K1 key1, K2 key2) {
return secondLevelMap.get(Pair.of(key1, key2));
}
public Map<K2, Multimap<K3, V>> get(K1 key1) {
return firstLevelMap.get(key1);
}
}
You can use it this way:
ThreeLevelMap<String, String, String, String> tlm = new ThreeLevelMap<>();
tlm.put("Java", "Active", "Tags", "Java-OOP");
tlm.put("Java", "Active", "Tags", "Java-Variables");
Map<String, Multimap<String, String>> firstLevelMap = tlm.get("Java");
Multimap<String, String> secondLevelMap = tlm.get("Java", "Active");
Collection<String> tags = tlm.get("Java", "Active", "Tags");
I say it is rough because:
the maps that the get methods return are modifiable
I didn't implement remove methods
I didn't test it a lot
I don't think that this is such a bad solution.
It's just a tree representation, for a tree where every leaf is at level 3.
If this was not the case (different leaf levels, etc.) you would have to build up a tree class structure.
But what I would change is to put everything in a class, with a get and set method, including null-checks.
In the following code, the add method takes care of the error-prone handling of the intermediate level maps, while get checks for null values in the intermediate levels:
public class TreeStructure {
Map<String, Map<String, Map<String, List<String>>>> tagTree
= new HashMap<String, Map<String,Map<String,List<String>>>>();
// ... Constructor ...
// This method adds all intermediate levels if not existing
public void add(String level1, String level2, String level3) {
String l1 = tagTree.get(level1);
if(l1 == null)
tagTree.put(level1, new HashMap<String, Map<String, List<String>>>());
l1 = tagTree.get(level1);
String l2 = l1.get(level2),
if(l2 == null)
tagTree.put(level2, new Map<String, List<String>>(););
l2 = l1.get(level2);
String l3 = l2.get(level3);
if(l3 == null) l2.add(level3, new ArrayList<>());
}
// This method checks, if every intermediate level existed
// Otherwise, get() returns null, and the next get() would fail
public String get(String level1, String level2, String level3) {
String l1 = tagTree.get(level1);
if(l1 == null)
return null;
String l2 = l1.get(level2),
if(l2 == null)
return null;
l2 = l1.get(level2);
String l3 = l2.get(level3);
return l3;
}
}
(Code untested)
You can create classes witch will hold the data structure
public class A {
Map<String, List<String>> map;
}
public class B {
Map<String, A> map;
}
Map<String, B> tagTree;
Use a Map of 3-Tuples:
class Tuple3<A,B,C> {
private A a;
private B b;
private C c;
// getters, setters, constructor
// make sure equals() and hashCode() are okay
}
Note, however, that the Map of Map of Maps can tell you in O(1) whether there are entries for some element by just looking in the outer map. Whereas, with the tuple solution, you can only work with full keys.
I think there is no reason for refactor this Map structure. But if possible, it is probably a good idea to encapsulate this Map in another class and give other developers a clean interface.
...
public void addTag(String language, String status, String tag)
public void removeTag(String language, String status, String tag)
public List<String> getTags(String language, String status)
...
I like most of the solutions suggested above. I think the simpler and efficient the design is - more supportable it will be.
Hence, I used basics - plain object composition to refactor your code.
package design;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class JavaTag{
private String tags;
JavaTag(String tags){
this.tags = tags;
}
}
class JavaTagStatusList{
private ArrayList<JavaTag> tagList = new ArrayList<JavaTag>();
JavaTagStatusList(){
}
public void addJavaTag (JavaTag tagObj){
if (tagObj != null){
tagList.add(tagObj);
}
}
}
class JavaTagStatusMap {
private HashMap<String, JavaTagStatusList> tagStatusMap = new HashMap<String, JavaTagStatusList>();
JavaTagStatusMap(){
}
public void addTagStatusEntry(String status, JavaTag obj){
if (tagStatusMap.containsKey(status)){
tagStatusMap.get(status).addJavaTag(obj);
}
else {
JavaTagStatusList statusList = new JavaTagStatusList();
statusList.addJavaTag(obj);
tagStatusMap.put(status, statusList);
}
}
}
Main:
public class MapofMapRefactor {
public static void main(String[] args) {
JavaTag tag1 = new JavaTag("Java-OOP");
JavaTag tag2 = new JavaTag("Java-Variables");
JavaTagStatusMap statusMap = new JavaTagStatusMap();
statusMap.addTagStatusEntry("Active", tag1);
statusMap.addTagStatusEntry("Active", tag2);
// HashMap of Java Lang Map
HashMap<String, JavaTagStatusMap> javaLanguageMap = new HashMap<String, JavaTagStatusMap>();
javaLanguageMap.put("Java", statusMap);
}
}

Generics and concrete implementations of strategy components

I ran into a little snag with the concrete implementation of strategy components using generic types.
Wondering if anyone can point me in the right direction with an example?
Here is what I am working towards, but I get caught up when I declare the decode method as it expcects a List when I create the ArrayList... Not a surprise.
public class CsvFormat<T,T1> implements FormatStrategy<T,T1> {
public CsvFormat(boolean header) {
setHeader(header);
#Override
public final T decode(T1 csvData) {
csvData = new ArrayList(); //ERROR****
List<Map<String, String>> decodedData = new ArrayList<Map<String, String>>(); //turn collection into an array of maps
if (this.hasHeader()) {
decodeDataWithHeader(csvData, decodedData);
} else {
decodeDataNoHeader(csvData, decodedData);
}
return decodedData;
}
private void decodeDataNoHeader(List<String> csvData, List<Map<String, String>> records) {
int recordCount = FIRST_IDX;
List<String> fields = null; //= Arrays.asList(csvData.get(recordCount).split(DELIM)); //turn line into a list, first record
for (String data : csvData) { //for each unformatted string
int delimIndex = FIRST_IDX; //reset delim
fields = Arrays.asList(data.split(DELIM));//after header, start mapping
records.add(new LinkedHashMap<String, String>()); //make a new map
recordCount++;
for (String field : fields) {
final String KEY_ID = "Column-" + (delimIndex + RECORD_BUFFER);
records.get(records.size() - RECORD_BUFFER).put(KEY_ID, field);
delimIndex++;
}
}
}
Here is what I had to start with The only way I can think of so far to achieve the above without error is to overload the the decode methods based on what object they are passed..
public class CsvFormat implements FormatStrategy<
List<Map<String, String>>, List<String>> {
public CsvFormat(boolean header) {
setHeader(header);
}
#Override
public final List<Map<String, String>> decode(List<String> csvData) {
List<Map<String, String>> decodedData = new ArrayList<Map<String, String>>(); //turn collection into an array of maps
if (this.hasHeader()) {
decodeDataWithHeader(csvData, decodedData);
} else {
decodeDataNoHeader(csvData, decodedData);
}
return decodedData;
}
private void decodeDataNoHeader(List<String> csvData, List<Map<String, String>> records) {
int recordCount = FIRST_IDX;
List<String> fields = null; //= Arrays.asList(csvData.get(recordCount).split(DELIM)); //turn line into a list, first record
for (String data : csvData) { //for each unformatted string
int delimIndex = FIRST_IDX; //reset delim
fields = Arrays.asList(data.split(DELIM));//after header, start mapping
records.add(new LinkedHashMap<String, String>()); //make a new map
recordCount++;
for (String field : fields) {
final String KEY_ID = "Column-" + (delimIndex + RECORD_BUFFER);
records.get(records.size() - RECORD_BUFFER).put(KEY_ID, field);
delimIndex++;
}
}
}
Actually the example you "started with" seems exactly right. You have written a decode method that requires a List<String> as input, so it stands to reason that you would be implementing the FormatStrategy interface with that specific type as T1 and the same goes for the output type T.
Why would you do icky runtime inspection of the input and loads of unsafe casting when you can actually follow the pattern and create a new class for each specific concrete type you care about?
The code you have written for decoding the data will always return a List<Map<String, String>> and can only work with a List<String> as input, so there is no reason for the CsvFormat class to have type parameters. So what you started with seems correct, why aren't you satisfied with it?

Looking for a "chained map" implementation in Java

I need a mapping from a list of keys to a value. I know I could write my own code like this:
Map<Person, Map<Daytime, Map<Food, Integer>>> eaten = ...;
Now I want to have some get and put methods like these:
Integer numberOfEggsIAteInTheMorning = eaten.get(me, morning, scrambledEggs);
eaten.put(me, evening, scrambledEggs, 1);
Do you know of an existing class that has this kind of API? I'm too lazy of writing it myself. ;)
If you look for a more generic approach, and you might have more than 2 or 3 'chain steps', I would suggest in applying some different structural approach, rather than sticking to using only basic collection classes. I have feeling that Composite Pattern could be the right choice if it's correctly applied.
EDIT: due to example requested
The full example would be somewhat time consuming, so let me just explain my idea with dirty Java/pseudocode mix (I'm not even sure if I've missed something!!!). Let's consider we have class BaseMap:
abstract class BaseMap {
public abstract Object getValue(Object.. keys);
public abstract void putValue(Object value, Object.. keys);
}
Then we could have ObjectMap that would be the 'leaf' of our composite structure:
class ObjectsMap extends BaseMap {
private Map<Object, Object> map = new [...]
public Object getValue(Object.. keys) {
// assert that keys.length == 1
return map.get(keys[0]);
}
public void putValue(Object value, Object.. keys) {
// assert that keys.length = 1
map.put(keys[0], value);
}
}
And the actual composite would be as such:
class CompositeMap extends BaseMap {
private Map<Object, BaseMap> compositeMaps = new [...]
public Object getValue(Object.. keys) {
// assert that keys.length > 1
return compositeMap.get(keys[0]).getValue(/* System.arrayCopy => subset of elements {keys_1, .. ,keys_max} */);
}
public void putValue(Object value, Object.. keys) {
// assert keys.length > 1
BaseMap newMap = null;
if (keys.length = 2) -> newMap = new ObjectsMap()
else newMap = new CompositeMap();
newMap.putValue(value, /*subset of keys {keys_1, .. , keys_max}*/);
}
}
You can use org.apache.commons.collections.keyvalue.MultiKey for that: Map<Multikey, Object>
It would be hard to implement a general chained map.
How would the declaration of the class look like? (You can't have a variable number of type parameters.
class ChainedMap<K1..., V>
Another option would be to have a ChainedMapUtil class that performs put / get recursively.
Here is an example of a recursive get. (Quite ugly solution though I must say.)
import java.util.*;
public class Test {
public static Object chainedGet(Map<?, ?> map, Object... keys) {
Object k = keys[0];
if (!map.containsKey(k)) return null;
if (keys.length == 1) return map.get(k);
Object[] tailKeys = Arrays.copyOfRange(keys, 1, keys.length);
return chainedGet((Map<?,?>) map.get(k), tailKeys);
}
public static void main(String[] arg) {
Map<String, String> m1 = new HashMap<String, String>();
m1.put("ipsum", "dolor");
Map<Integer, Map<String, String>> m2 =
new HashMap<Integer, Map<String, String>>();
m2.put(17, m1);
Map<String, Map<Integer, Map<String, String>>> chained =
new HashMap<String, Map<Integer, Map<String, String>>>();
chained.put("lorem", m2);
System.out.println(chainedGet(chained, "lorem", 17, "ipsum")); // dolor
System.out.println(chainedGet(chained, "lorem", 19, "ipsum")); // null
}
}
If you are going to write your own, I would suggest
eaten.increment(me, evening, scrambledEggs);
You could use a composite key
eaten.increment(Key.of(me, evening, scrambledEggs));
(TObjectIntHashMap supports increment and adjust)
You may not even need a custom key.
eaten.increment(me + "," + evening + "," + scrambledEggs);
It is fairly easy to decompose the key with split()
I once made a map using 3 keys just for fun.May be you can use it instead of using chained maps:
public class ThreeKeyMap<K1,K2,K3,V>{
class wrap{
K1 k1;
K2 k2;
K3 k3;
public wrap(K1 k1,K2 k2,K3 k3) {
this.k1=k1;this.k2=k2;this.k3=k3;
}
#Override
public boolean equals(Object arg0) {
// TODO Auto-generated method stub
wrap o=(wrap)arg0;
if(!this.k1.equals(o.k1))
return false;
if(!this.k2.equals(o.k2))
return false;
if(!this.k2.equals(o.k2))
return false;
return true;
}
#Override
public int hashCode() {
int result=17;
result=37*result+k1.hashCode();
result=37*result+k2.hashCode();
result=37*result+k3.hashCode();
return result;
}
}
HashMap<wrap,V> map=new HashMap<wrap, V>();
public V put(K1 k1,K2 k2,K3 k3,V arg1) {
return map.put(new wrap(k1,k2,k3), arg1);
}
public V get(Object k1,Object k2,Object k3) {
return map.get(new wrap((K1)k1,(K2)k2,(K3)k3));
}
public static void main(String[] args) {
ThreeKeyMap<Integer,Integer,Integer,String> birthDay=new ThreeKeyMap<Integer, Integer, Integer, String>();
birthDay.put(1, 1,1986,"Emil");
birthDay.put(2,4,2009, "Ansih");
birthDay.put(1, 1,1986,"Praveen");
System.out.println(birthDay.get(1,1,1986));
}
}
UPDATE:
As #Arturs Licis suggested.I looked up in net for composite pattern and I wrote a sample using it.I guess this is composite..Please comment if it is not so.
Person class:
public class Person {
private final String name;
private Map<Time, Food> map = new HashMap<Time, Food>();
public Person(String name) {
this.name = name;
}
void addTimeFood(Time time, Food food) {
map.put(time, food);
}
public String getName() {
return name;
}
Food getFood(Time time) {
Food tmp = null;
return (tmp = map.get(time)) == null ? Food.NoFood : tmp;
}
// main to test the person class
public static void main(String[] args) {
Person p1 = new Person("Jack");
p1.addTimeFood(Time.morning, Food.Bread);
p1.addTimeFood(Time.evening, Food.Chicken);
Person p2 = new Person("Jill");
p2.addTimeFood(Time.morning, Food.Egg);
p2.addTimeFood(Time.evening, Food.Rice);
Map<String, Person> map = new HashMap<String, Person>();
map.put(p1.getName(), p1);
map.put(p2.getName(), p2);
System.out.println(map.get("Jack").getFood(Time.evening));
}
#Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(name).append("\n");
b.append(map);
return b.toString();
}
}
Food class:
public enum Food {
Rice,
Egg,
Chicken,
Bread,
NoFood;
}
Time class:
public enum Time {
morning,
evening,
night
}

How do I access nested HashMaps in Java?

I have a HashMap in Java, the contents of which (as you all probably know) can be accessed by
HashMap.get("keyname");
If a have a HashMap inside another HashMap i.e. a nested HashMap, how would i access the contents? Can i do this like this, inline:
HashMap.get("keyname").get("nestedkeyname");
Thank you.
You can do it like you assumed. But your HashMap has to be templated:
Map<String, Map<String, String>> map =
new HashMap<String, Map<String, String>>();
Otherwise you have to do a cast to Map after you retrieve the second map from the first.
Map map = new HashMap();
((Map)map.get( "keyname" )).get( "nestedkeyname" );
You can get the nested value by repeating .get(), but with deeply nested maps you have to do a lot of casting into Map. An easier way is to use a generic method for getting a nested value.
Implementation
public static <T> T getNestedValue(Map map, String... keys) {
Object value = map;
for (String key : keys) {
value = ((Map) value).get(key);
}
return (T) value;
}
Usage
// Map contents with string and even a list:
{
"data": {
"vehicles": {
"list": [
{
"registration": {
"owner": {
"id": "3643619"
}
}
}
]
}
}
}
List<Map> list = getNestedValue(mapContents, "data", "vehicles", "list");
Map first = list.get(0);
String id = getNestedValue(first, "registration", "owner", "id");
Yes.
See:
public static void main(String args[]) {
HashMap<String, HashMap<String, Object>> map = new HashMap<String, HashMap<String,Object>>();
map.put("key", new HashMap<String, Object>());
map.get("key").put("key2", "val2");
System.out.println(map.get("key").get("key2"));
}
If you plan on constructing HashMaps with variable depth, use a recursive data structure.
Below is an implementation providing a sample interface:
class NestedMap<K, V> {
private final HashMap<K, NestedMap> child;
private V value;
public NestedMap() {
child = new HashMap<>();
value = null;
}
public boolean hasChild(K k) {
return this.child.containsKey(k);
}
public NestedMap<K, V> getChild(K k) {
return this.child.get(k);
}
public void makeChild(K k) {
this.child.put(k, new NestedMap());
}
public V getValue() {
return value;
}
public void setValue(V v) {
value = v;
}
}
and example usage:
class NestedMapIllustration {
public static void main(String[] args) {
NestedMap<Character, String> m = new NestedMap<>();
m.makeChild('f');
m.getChild('f').makeChild('o');
m.getChild('f').getChild('o').makeChild('o');
m.getChild('f').getChild('o').getChild('o').setValue("bar");
System.out.println(
"nested element at 'f' -> 'o' -> 'o' is " +
m.getChild('f').getChild('o').getChild('o').getValue());
}
}
As others have said you can do this but you should define the map with generics like so:
Map<String, Map<String, String>> map = new HashMap<String, Map<String,String>>();
However, if you just blindly run the following:
map.get("keyname").get("nestedkeyname");
you will get a null pointer exception whenever keyname is not in the map and your program will crash. You really should add the following check:
String valueFromMap = null;
if(map.containsKey("keyname")){
valueFromMap = map.get("keyname").get("nestedkeyname");
}
Yes, if you use the proper generic type signature for the outer hashmap.
HashMap<String, HashMap<String, Foo>> hm = new HashMap<String, HashMap<String, Foobar>>();
// populate the map
hm.get("keyname").get("nestedkeyname");
If you're not using generics, you'd have to do a cast to convert the object retrieved from the outer hash map to a HashMap (or at least a Map) before you could call its get() method. But you should be using generics ;-)
I prefer creating a custom map that extends HashMap. Then just override get() to add extra logic so that if the map doesnt contain your key. It will a create a new instance of the nested map, add it, then return it.
public class KMap<K, V> extends HashMap<K, V> {
public KMap() {
super();
}
#Override
public V get(Object key) {
if (this.containsKey(key)) {
return super.get(key);
} else {
Map<K, V> value = new KMap<K, V>();
super.put((K)key, (V)value);
return (V)value;
}
}
}
Now you can use it like so:
Map<Integer, Map<Integer, Map<String, Object>>> nestedMap = new KMap<Integer, Map<Integer, Map<String, Object>>>();
Map<String, Object> map = (Map<String, Object>) nestedMap.get(1).get(2);
Object obj= new Object();
map.put(someKey, obj);
I came to this StackOverflow page looking for a something ala valueForKeyPath known from objc. I also came by another post - "Key-Value Coding" for Java, but ended up writing my own.
I'm still looking for at better solution than PropertyUtils.getProperty in apache's beanutils library.
Usage
Map<String, Object> json = ...
public String getOptionalFirstName() {
return MyCode.getString(json, "contact", "firstName");
}
Implementation
public static String getString(Object object, String key0, String key1) {
if (key0 == null) {
return null;
}
if (key1 == null) {
return null;
}
if (object instanceof Map == false) {
return null;
}
#SuppressWarnings("unchecked")
Map<Object, Object> map = (Map<Object, Object>)object;
Object object1 = map.get(key0);
if (object1 instanceof Map == false) {
return null;
}
#SuppressWarnings("unchecked")
Map<Object, Object> map1 = (Map<Object, Object>)object1;
Object valueObject = map1.get(key1);
if (valueObject instanceof String == false) {
return null;
}
return (String)valueObject;
}
import java.util.*;
public class MyFirstJava {
public static void main(String[] args)
{
Animal dog = new Animal();
dog.Info("Dog","Breezi","Lab","Chicken liver");
dog.Getname();
Animal dog2= new Animal();
dog2.Info("Dog", "pumpkin", "POM", "Pedigree");
dog2.Getname();
HashMap<String, HashMap<String, Object>> dogs = new HashMap<>();
dogs.put("dog1", new HashMap<>() {{put("Name",dog.name);
put("Food",dog.food);put("Age",3);}});
dogs.put("dog2", new HashMap<>() {{put("Name",dog2.name);
put("Food",dog2.food);put("Age",6);}});
//dogs.get("dog1");
System.out.print(dogs + "\n");
System.out.print(dogs.get("dog1").get("Age"));
}
}
Example Map:
{
"data": {
"userData": {
"location": {
"city": "Banja Luka"
}
}
}
}
Implementation:
public static Object getValueFromMap(final Map<String, Object> map, final String key) {
try {
final String[] tmpKeys = key.split("\\.");
Map<String, Object> currentMap = map;
for (int i = 0; i < tmpKeys.length - 1; i++) {
currentMap = (Map<String, Object>) currentMap.get(tmpKeys[i]);
}
return currentMap.get(tmpKeys[tmpKeys.length - 1]);
} catch (Exception exception) {
return null;
}
}
Usage:
final Map<String, Object> data = new HashMap<>();
final Map<String, Object> userData = new HashMap<>();
final Map<String, Object> location = new HashMap<>();
location.put("city", "Banja Luka");
userData.put("location", location);
data.put("userData", userData);
System.out.println(getValueFromMap(data, "userData.location.city"));
Result:
Banja Luka
Process finished with exit code 0
I hit this discussion while trying to figure out how to get a value from a nested map of unknown depth and it helped me come up with the following solution to my problem. It is overkill for the original question but maybe it will be helpful to someone that finds themselves in a situation where you have less knowledge about the map being searched.
private static Object pullNestedVal(
Map<Object, Object> vmap,
Object ... keys) {
if ((keys.length == 0) || (vmap.size() == 0)) {
return null;
} else if (keys.length == 1) {
return vmap.get(keys[0]);
}
Object stageObj = vmap.get(keys[0]);
if (stageObj instanceof Map) {
Map<Object, Object> smap = (Map<Object, Object>) stageObj;
Object[] skeys = Arrays.copyOfRange(keys, 1, keys.length);
return pullNestedVal(smap, skeys);
} else {
return null;
}
}

Categories

Resources