Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8199724

Unidirectional content binding between ObservableMaps treats a change as a removal

XMLWordPrintable

    • x86_64
    • generic

      FULL PRODUCT VERSION :
      java version "1.8.0_162"
      Java(TM) SE Runtime Environment (build 1.8.0_162-b12)
      Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Windows 10, Linux 3.0.101-63-default

      A DESCRIPTION OF THE PROBLEM :
      Bindings.bindContent for an Observable map will treat the case where the value for a key is being replaced by a new value as a removal.

      The following was debugged into com.sun.javafx.binding.ContentBinding:

      @Override
      public void onChanged(Change<? extends K, ? extends V> change) {
         final Map<K, V> map = mapRef.get();
         if (map == null) {
            change.getMap().removeListener(this);
         } else {
            if (change.wasRemoved()) {
               map.remove(change.getKey());
            } else {
               map.put(change.getKey(), change.getValueAdded());
            }
         }
      }

      The changed.wasRemoved() check does not also see if wasAdded() returns true to see if the key was changed and will assume that it was removed.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Make a source and destination map
      2. Use Bindings.bindContent to bind them together
      3. Add content to the source map, then replace a value for a key
      4. See that the destination map does not match the source map

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Adding things
      Added: 1 -> Thing 1
      Added: 2 -> Thing 2
      Added: 3 -> Thing 3
      Added: 4 -> Thing 4
      Added: 5 -> Thing 5
      Destination: {1=Thing 1, 2=Thing 2, 3=Thing 3, 4=Thing 4, 5=Thing 5}

      Removing things
      Removed: 3 ( was Thing 3 )
      Removed: 5 ( was Thing 5 )
      Destination: {1=Thing 1, 2=Thing 2, 4=Thing 4}

      Adding/modifying things
      Modified: 1 from Thing 1 to Thing 1-1
      Modified: 2 from Thing 2 to Thing 2-2
      Added: 3 -> Thing 3-3
      Modified: 4 from Thing 4 to Thing 4-4
      Added: 5 -> Thing 5-5
      Destination: {1=Thing 1-1, 2=Thing 2-2, 3=Thing 3-3, 4=Thing 4-4, 5=Thing 5-5}

      Batch remove
      Removed: 1 ( was Thing 1-1 )
      Removed: 5 ( was Thing 5-5 )
      Destination: {2=Thing 2-2, 3=Thing 3-3, 4=Thing 4-4}

      Batch add/modify
      Added: 1 -> Thing 1-1-1
      Modified: 3 from Thing 3-3 to Thing 3-3-3
      Added: 5 -> Thing 5-5-5
      Added: 6 -> Thing 6-6-6
      Destination: {1=Thing 1-1-1, 2=Thing 2-2, 3=Thing 3-3-3, 4=Thing 4-4, 5=Thing 5-5-5, 6=Thing 6-6-6}
      ACTUAL -
      Adding things
      Added: 1 -> Thing 1
      Added: 2 -> Thing 2
      Added: 3 -> Thing 3
      Added: 4 -> Thing 4
      Added: 5 -> Thing 5
      Destination: {1=Thing 1, 2=Thing 2, 3=Thing 3, 4=Thing 4, 5=Thing 5}

      Removing things
      Removed: 3 ( was Thing 3 )
      Removed: 5 ( was Thing 5 )
      Destination: {1=Thing 1, 2=Thing 2, 4=Thing 4}

      Adding/modifying things
      Removed: 1 ( was Thing 1 )
      Removed: 2 ( was Thing 2 )
      Added: 3 -> Thing 3-3
      Removed: 4 ( was Thing 4 )
      Added: 5 -> Thing 5-5
      Destination: {3=Thing 3-3, 5=Thing 5-5}

      Batch remove
      Removed: 5 ( was Thing 5-5 )
      Destination: {3=Thing 3-3}

      Batch add/modify
      Added: 1 -> Thing 1-1-1
      Removed: 3 ( was Thing 3-3 )
      Added: 5 -> Thing 5-5-5
      Added: 6 -> Thing 6-6-6
      Destination: {1=Thing 1-1-1, 5=Thing 5-5-5, 6=Thing 6-6-6}

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
         public static void main(String[] arguments)
         {
            // Set up the content binding and listeners.
            ObservableMap<Integer, String> source = FXCollections.observableHashMap();
            ObservableMap<Integer, String> destination = FXCollections.observableHashMap();
            Bindings.bindContent(destination, source);
            destination.addListener(s_destinationMapListener);

            // First set, add some things.
            System.out.println("Adding things");
            source.put(1, "Thing 1");
            source.put(2, "Thing 2");
            source.put(3, "Thing 3");
            source.put(4, "Thing 4");
            source.put(5, "Thing 5");
            System.out.println("Destination: " + destination);
            System.out.println();

            // Next, remove some things.
            System.out.println("Removing things");
            source.remove(3);
            source.remove(5);
            System.out.println("Destination: " + destination);
            System.out.println();

            // Next, add and modify.
            System.out.println("Adding/modifying things");
            source.put(1, "Thing 1-1");
            source.put(2, "Thing 2-2");
            source.put(3, "Thing 3-3");
            source.put(4, "Thing 4-4");
            source.put(5, "Thing 5-5");
            System.out.println("Destination: " + destination);
            System.out.println();

            // Next, remove a bunch.
            System.out.println("Batch remove");
            source.keySet().removeAll(Arrays.asList(new Integer[]{0, 1, 5, 6, 7}));
            System.out.println("Destination: " + destination);
            System.out.println();

            // Next, add a bunch at once.
            System.out.println("Batch add/modify");
            Map<Integer, String> next = new HashMap<>();
            next.put(1, "Thing 1-1-1");
            next.put(3, "Thing 3-3-3");
            next.put(5, "Thing 5-5-5");
            next.put(6, "Thing 6-6-6");
            source.putAll(next);
            System.out.println("Destination: " + destination);
            System.out.println();
         }

         private static MapChangeListener<Integer, String> s_destinationMapListener =
            new MapChangeListener<Integer, String>()
            {
               @Override
               public void onChanged(Change<? extends Integer, ? extends String> change)
               {
                  if (change.wasAdded())
                  {
                     if (change.wasRemoved())
                     {
                        System.out.println("Modified: " + change.getKey() + " from " + change.getValueRemoved()
                           + " to " + change.getValueAdded());
                     }
                     else
                     {
                        System.out.println("Added: " + change.getKey() + " -> " + change.getValueAdded());
                     }
                  }
                  else if (change.wasRemoved())
                  {
                     System.out.println(
                        "Removed: " + change.getKey() + " ( was " + change.getValueRemoved() + " )");
                  }
               }
            };
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Set up a listener on the source map and manually update the destination map.

            pmangal Priyanka Mangal (Inactive)
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: