2

Can anyone explain me if this is a bug or am I missing something in JavaFX MapProperty binding?

Scenario: Two MapProperty instances - master and child.

  1. At first we bind child to master
  2. Then we store some values in master
  3. Unbind child from master
  4. Clear child
  5. both instances are empty - why?
  6. Store some values in child
  7. both instances contain the same values - why?

Code:

public static void main(String[] args) {

    MapProperty<String, Object> master = new SimpleMapProperty<String, Object>(
            FXCollections.observableMap(new HashMap<String, Object>()));
    MapProperty<String, Object> child = new SimpleMapProperty<String, Object>(
            FXCollections.observableMap(new HashMap<String, Object>()));

    child.bind(master);

    master.put("k1", "v1");

    System.out.println("Java version: " + System.getProperty("java.version"));
    System.out.println("OS version  : " + System.getProperty("os.name") + " - " + System.getProperty("os.arch"));
    System.out.println("------------");
    System.out.println("master: " + master);
    System.out.println("child : " + child);

    // Isn't this supposed to stop change listener ?????
    child.unbind();
    child.clear();

    System.out.println("------------");
    System.out.println("master: " + master);
    System.out.println("child : " + child);

    child.put("k2", "v2");

    System.out.println("------------");
    System.out.println("master: " + master);
    System.out.println("child : " + child);

}

Output:

run:
Java version: 1.8.0_45
OS version  : Windows 7 - amd64
------------
master: MapProperty [value: {k1=v1}]
child : MapProperty [bound, invalid]
------------
master: MapProperty [value: {}]
child : MapProperty [value: {}]
------------
master: MapProperty [value: {k2=v2}]
child : MapProperty [value: {k2=v2}]
BUILD SUCCESSFUL (total time: 0 seconds)
raivis
  • 53
  • 1
  • 5

1 Answers1

1

The value of a MapProperty is an ObservableMap, not the content of the ObservableMap.

Executing this code

MapProperty<String, Object> master = new SimpleMapProperty<String, Object>(
        FXCollections.observableMap(new HashMap<String, Object>()));
MapProperty<String, Object> child = new SimpleMapProperty<String, Object>(
        FXCollections.observableMap(new HashMap<String, Object>()));

ObservableMap<String, Object> childMap = child.get();
ObservableMap<String, Object> masterMap = master.get();

System.out.println("before binding: " + ((childMap == masterMap) ? "childMap == masterMap" : "childMap != masterMap"));

child.bind(master);

childMap = child.get();
masterMap = master.get();

System.out.println("after binding: " + ((childMap == masterMap) ? "childMap == masterMap" : "childMap != masterMap"));

child.unbind();
System.out.println("after unbinding: " + ((childMap == masterMap) ? "childMap == masterMap" : "childMap != masterMap"));   

shows that after binding, the ObservableMap in both child and main is the same object, because the property wraps the map and not its content:

before binding: childMap != masterMap
after binding: childMap == masterMap
after unbinding: childMap == masterMap

To bind the content of the map, use bindContent instead. Executing

MapProperty<String, Object> master = new SimpleMapProperty<String, Object>(
        FXCollections.observableMap(new HashMap<String, Object>()));
MapProperty<String, Object> child = new SimpleMapProperty<String, Object>(
        FXCollections.observableMap(new HashMap<String, Object>()));

child.bindContent(master);

master.put("k1", "v1");

System.out.println("Java version: " + System.getProperty("java.version"));
System.out.println("OS version  : " + System.getProperty("os.name") + " - " + System.getProperty("os.arch"));
System.out.println("------------");
System.out.println("master: " + master);
System.out.println("child : " + child);

child.unbindContent(master);
child.clear();

System.out.println("------------");
System.out.println("master: " + master);
System.out.println("child : " + child);

child.put("k2", "v2");

System.out.println("------------");
System.out.println("master: " + master);
System.out.println("child : " + child);

gives the following result:

Java version: 1.8.0_45
OS version  : Windows 7 - amd64
------------
master: MapProperty [value: {k1=v1}]
child : MapProperty [value: {k1=v1}]
------------
master: MapProperty [value: {k1=v1}]
child : MapProperty [value: {}]
------------
master: MapProperty [value: {k1=v1}]
child : MapProperty [value: {k2=v2}]
Modus Tollens
  • 5,083
  • 3
  • 38
  • 46
  • Thank you for clarifying that out. I don't understand unbind() method though. What's the point in having one if it does not provide desired effect? For the sake of simplicity I was not quite accurate with my example. What I really need is replacing master rather it's content. Content binding still works but you will have NullPointerException if new master will contain the same keys as old master and nulls as some of the values. I will mark your answer as correct as it does answer my original question though. – raivis Jun 08 '15 at 06:09
  • The line that causes NullPointerException is this one: 'if (oldEntry == null ? newEntry != null : !newEntry.equals(oldEntry)) {' in MapExpressionHelper class. Shouldn't it be this way 'if (oldEntry == null ? newEntry != null : !oldEntry.equals(newEntry)) {' ? – raivis Jun 08 '15 at 06:23
  • The unbind() method provides the desired effect for the ObservableMap values of the MapProperty. After calling unbind(), setting an ObservableMap with `master.set(map)` has no effect on `child`, and `child.set(map)` can be called. I am not sure I understand where you get a NullPointerException. Maybe it is best to post another question with an example. – Modus Tollens Jun 09 '15 at 15:28