Radu Stefan Popescu
Radu Stefan Popescu

Reputation: 197

JavaFX group ObservableList by multiple parameters into a map and bind them

Assume we have an ObservableList<Person> where Person has properties:age, gender. The goal is to create an observable (at all levels) map similar to Map<Gender, Map<Age, List<Person>>> that I can bind to the ObservableList.

The reason for doing this is because I have to extract live aggregate data based on such a grouping.

Upvotes: 0

Views: 658

Answers (1)

fabian
fabian

Reputation: 82451

This requires you to add a listener to each property and specify the bean in for the properties to avoid creating 2 listeners for every Person:

private final ObjectProperty<Gender> gender = new SimpleObjectProperty<>(this, "gender");
private final IntegerProperty age = new SimpleIntegerProperty(this, "age");
public static void main(String[] args) {
    ObservableList<Person> data = FXCollections.observableArrayList();
    ObservableMap<Gender, ObservableMap<Number, ObservableList<Person>>> grouped = FXCollections.observableHashMap();
    ChangeListener<Gender> genderChangeListener = (observable, oldValue, newValue) -> {
        ObservableMap<Number, ObservableList<Person>> m = grouped.get(oldValue);
        Person person = (Person) ((Property) observable).getBean();

        // remove person from list and remove list, if it becomes empty
        m.compute(person.getAge(), (a, lp) -> {
            lp.remove(person);
            return lp.isEmpty() ? null : lp;
        });

        // remove age map, if it's empty
        if (m.isEmpty()) {
            grouped.remove(oldValue);
        }

        // add person at new position generating the Map/List, if necessary
        grouped.computeIfAbsent(newValue, g -> FXCollections.observableHashMap())
                .computeIfAbsent(person.getAge(), a -> FXCollections.observableArrayList())
                .add(person);
    };
    ChangeListener<Number> ageChangeListener = (observable, oldValue, newValue) -> {
        Person person = (Person) ((Property) observable).getBean();
        ObservableMap<Number, ObservableList<Person>> map = grouped.get(person.getGender());

        // remove person from list and remove list, if it becomes empty
        map.compute(oldValue, (a, lp) -> {
            lp.remove(person);
            return lp.isEmpty() ? null : lp;
        });

        // add person at new position generating the List, if necessary
        map.computeIfAbsent(newValue, a -> FXCollections.observableArrayList())
                .add(person);
    };
    data.addListener((ListChangeListener.Change<? extends Person> c) -> {
        while (c.next()) {
            for (Person p : c.getRemoved()) {
                // unregister the listeners of removed Persons
                p.genderProperty().removeListener(genderChangeListener);
                p.ageProperty().removeListener(ageChangeListener);

                // remove person from grouped
                ObservableMap<Number, ObservableList<Person>> m = grouped.get(p.getGender());
                m.compute(p.getAge(), (a, lp) -> {
                    lp.remove(p);
                    return lp.isEmpty() ? null : lp;
                });
                if (m.isEmpty()) {
                    grouped.remove(p.getGender());
                }
            }
            for (Person p : c.getAddedSubList()) {
                // add listeners to person
                p.genderProperty().addListener(genderChangeListener);
                p.ageProperty().addListener(ageChangeListener);

                // add person to grouped generating the Map/List, if necessary
                grouped.computeIfAbsent(p.getGender(), g -> FXCollections.observableHashMap())
                        .computeIfAbsent(p.getAge(), a -> FXCollections.observableArrayList())
                        .add(p);
            }
        }
    });

    // test
    Person p = new Person("Frank", Gender.MALE, 20);
    Person p2 = new Person("Lisa", Gender.FEMALE, 32);
    Person p3 = new Person("Nora", Gender.FEMALE, 52);
    Person p4 = new Person("Carl", Gender.MALE, 62);

    System.out.println(grouped);
    data.addAll(p, p2, p3, p4);
    System.out.println(grouped);
    p3.setAge(32);
    System.out.println(grouped);
    p.setGender(Gender.FEMALE);
    System.out.println(grouped);
    data.remove(p3);
    System.out.println(grouped);
    data.remove(p4);
    System.out.println(grouped);
}

Upvotes: 1

Related Questions