3

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.

0

2 Answers 2

2

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();
}
Sign up to request clarification or add additional context in comments.

1 Comment

This is almost exactly how I solved it, Logged in to post it and here it is.
1

There is not build-in method that would convert:

  1. either Map directly to TreeView
  2. 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.

1 Comment

Thanks for answering, but the problem with this approach is that my original Hashmap is nested. So Its not going to be possible to convert a nested Hashmap to a TreeItem. Instead the key needs to be a root TreeItem. And the value under that key needs to be another TreeItem that is its child. When a nested value appears, the previous needs to happen recursively. I am currently trying to write a solution like that myself. If I succeed I will submit that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.