Search code examples
javajavafxdata-bindingstack-overflow

JavaFX: Bind List<A> bidirectional to List<B> throws StackOverflowError


I am trying to bind two ListProperties bidirectional. The problem is, they have different types (A and B).

Test4#test01() illustrates what I would like to do.

Test4#test02() is my naive approach to do so, but this results in a StackOverflowError

How can I bind List<A> bidirectional to List<B> using given AB transformer and BA transformer? Here is a MWE:

public class Test4 {

    public Test4() {

    }

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
    }

    @AfterClass
    public static void tearDownAfterClass() throws Exception {
    }

    @Before
    public void setUp() throws Exception {
    }

    @After
    public void tearDown() throws Exception {
    }

    class A {
    @Override
    public boolean equals(final Object obj) {
        return obj instanceof A;
    }
    }

    class B {
    @Override
    public boolean equals(final Object obj) {
        return obj instanceof B;
    }
    }

    ListProperty<A> listA = new SimpleListProperty<>(FXCollections.observableArrayList());

    ListProperty<B> listB = new SimpleListProperty<>(FXCollections.observableArrayList());

    Function<A, B> trivialTransformerAB = a -> new B();

    Function<B, A> trivialTransformerBA = b -> new A();

    ListChangeListener<A> listAListener;

    ListChangeListener<B> listBListener;

    @Test
    public void test01() {
    // Bindings.bindContentBidirectional(listA, listB,
    // trivialTransformerAB, trivialTransformerBA);
    }

    @Test
    public void test02() {
    listAListener = c -> {
        while (c.next()) {
        if (c.wasRemoved() || c.wasUpdated()) {
            c.getList().subList(c.getFrom(), c.getTo())
                .forEach(a -> listB.remove(trivialTransformerAB.apply(a)));
        }
        if (c.wasAdded() || c.wasUpdated()) {
            c.getList().subList(c.getFrom(), c.getTo()).forEach(a -> {
            final B b = trivialTransformerAB.apply(a);
            if (!listB.contains(b)) {
                listB.add(trivialTransformerAB.apply(a));
            }
            });
        }
        }
    };
    listBListener = c -> {
        while (c.next()) {
        if (c.wasRemoved() || c.wasUpdated()) {
            c.getList().subList(c.getFrom(), c.getTo())
                .forEach(b -> listA.remove(trivialTransformerBA.apply(b)));
        }
        if (c.wasAdded() || c.wasUpdated()) {
            c.getList().subList(c.getFrom(), c.getTo()).forEach(b -> {
            final A a = trivialTransformerBA.apply(b);
            if (!listA.contains(a)) {
                listA.add(trivialTransformerBA.apply(b));
            }
            });
        }
        }
    };
    listA.addListener(listAListener);
    listB.addListener(listBListener);

    listA.add(new A());
    assertThat(listB.size(), is(1));
    }

}

Error:

java.lang.StackOverflowError
    at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031)
    at java.util.Collections$UnmodifiableCollection.isEmpty(Collections.java:1031)
[..]

Solution

  • The way you set up the listeners created an endless loop.

    I would suggest setting a breakpoint each in these two lines and step through with a debugger:

    listB.add(trivialTransformerAB.apply(a));
    
    listA.add(trivialTransformerBA.apply(b));