Зеркально отразить один observableList другому

  1. В JavaFX у меня есть ObservableList объектов, и хочу другого ObservableList это будет зеркально отражать первый список, но будет содержать строковое представление каждого объекта. Есть ли что-нибудь проще, чем написать кастом ListChangeListener сделать преобразование? у меня есть StringConverter который может обеспечить зеркальное отображение.

  2. Точно так же, учитывая ObservableList<String>как мне создать вторую ObservableList<String> который имеет постоянную запись с индексом 0 и отражает первый список, начинающийся с индекса 1?

2 ответа

Решение

По первому вопросу, самый простой способ сделать это - использовать инфраструктуру EasyBind. Тогда это так же просто, как

ObservableList<String> stringList = EasyBind.map(myBaseList, myConverter::toString);

Вот SSCCE, использующий EasyBind:

import org.fxmisc.easybind.EasyBind;

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.util.StringConverter;


public class MappedAndTransformedListExample {

    public static void main(String[] ags) {
        ObservableList<Person> baseList = FXCollections.observableArrayList(
                new Person("Jacob", "Smith", "jacob.smith@example.com"),
                new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
                new Person("Ethan", "Williams", "ethan.williams@example.com"),
                new Person("Emma", "Jones", "emma.jones@example.com")
        );

        StringConverter<Person> converter = new StringConverter<Person>() {

            @Override
            public String toString(Person person) {
                return person.getFirstName() + " " + person.getLastName();
            }

            @Override
            public Person fromString(String string) {
                int indexOfDelimiter = string.indexOf(' ');
                return new Person(string.substring(0, indexOfDelimiter), 
                        string.substring(indexOfDelimiter+1), 
                        "");
            }

        };

        ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);

        namesList.forEach(System.out::println);

        namesList.addListener((Change<? extends String> c) -> {
            while (c.next()) {
                if (c.wasAdded()) {
                    System.out.println("Added "+c.getAddedSubList());
                }
            }
        });

        System.out.println("\nAdding Michael to base list...\n");
        baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));

        namesList.forEach(System.out::println);
    }



    public static class Person {
        private final StringProperty firstName = new SimpleStringProperty(this, "firstName");
        private final StringProperty lastName = new SimpleStringProperty(this, "lastName");
        private final StringProperty email = new SimpleStringProperty(this, "email");

        public Person(String firstName, String lastName, String email) {
            this.firstName.set(firstName);
            this.lastName.set(lastName);
            this.email.set(email);
        }

        public final StringProperty firstNameProperty() {
            return this.firstName;
        }

        public final String getFirstName() {
            return this.firstNameProperty().get();
        }

        public final void setFirstName(final String firstName) {
            this.firstNameProperty().set(firstName);
        }

        public final StringProperty lastNameProperty() {
            return this.lastName;
        }

        public final java.lang.String getLastName() {
            return this.lastNameProperty().get();
        }

        public final void setLastName(final java.lang.String lastName) {
            this.lastNameProperty().set(lastName);
        }

        public final StringProperty emailProperty() {
            return this.email;
        }

        public final java.lang.String getEmail() {
            return this.emailProperty().get();
        }

        public final void setEmail(final java.lang.String email) {
            this.emailProperty().set(email);
        }

    }
}

Если по какой-либо причине вы предпочитаете не использовать сторонние фреймворки, вы можете использовать TransformationList (что EasyBind делает изнутри: я скопировал приведенный ниже код из исходного кода и изменил его).

Выше вы бы заменили

ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);

с

    ObservableList<String> namesList = new TransformationList<String, Person>(baseList) {

        @Override
        public int getSourceIndex(int index) {
            return index ;
        }

        @Override
        public String get(int index) {
            return converter.toString(getSource().get(index));
        }

        @Override
        public int size() {
            return getSource().size();
        }

        @Override
        protected void sourceChanged(Change<? extends Person> c) {
            fireChange(new Change<String>(this) {
                @Override
                public boolean wasAdded() {
                    return c.wasAdded();
                }

                @Override
                public boolean wasRemoved() {
                    return c.wasRemoved();
                }

                @Override
                public boolean wasReplaced() {
                    return c.wasReplaced();
                }

                @Override
                public boolean wasUpdated() {
                    return c.wasUpdated();
                }

                @Override
                public boolean wasPermutated() {
                    return c.wasPermutated();
                }

                @Override
                public int getPermutation(int i) {
                    return c.getPermutation(i);
                }

                @Override
                protected int[] getPermutation() {
                    // This method is only called by the superclass methods
                    // wasPermutated() and getPermutation(int), which are
                    // both overriden by this class. There is no other way
                    // this method can be called.
                    throw new AssertionError("Unreachable code");
                }

                @Override
                public List<String> getRemoved() {
                    ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
                    for(Person removedPerson: c.getRemoved()) {
                        res.add(converter.toString(removedPerson));
                    }
                    return res;
                }

                @Override
                public int getFrom() {
                    return c.getFrom();
                }

                @Override
                public int getTo() {
                    return c.getTo();
                }

                @Override
                public boolean next() {
                    return c.next();
                }

                @Override
                public void reset() {
                    c.reset();
                }
            });
        }


    };

Для второго вопроса вы должны использовать список преобразований. Вот обновленный main(...) метод, который показывает, как это сделать. (Это работает так же хорошо со второй версией части 1.)

public static void main(String[] ags) {
    ObservableList<Person> baseList = FXCollections.observableArrayList(
            new Person("Jacob", "Smith", "jacob.smith@example.com"),
            new Person("Isabella", "Johnson", "isabella.johnson@example.com"),
            new Person("Ethan", "Williams", "ethan.williams@example.com"),
            new Person("Emma", "Jones", "emma.jones@example.com")
    );

    StringConverter<Person> converter = new StringConverter<Person>() {

        @Override
        public String toString(Person person) {
            return person.getFirstName() + " " + person.getLastName();
        }

        @Override
        public Person fromString(String string) {
            int indexOfDelimiter = string.indexOf(' ');
            return new Person(string.substring(0, indexOfDelimiter), 
                    string.substring(indexOfDelimiter+1), 
                    "");
        }

    };

    ObservableList<String> namesList = EasyBind.map(baseList, converter::toString);

    ObservableList<String> namesListWithHeader = new TransformationList<String, String>(namesList) {

        @Override
        public int getSourceIndex(int index) {
            return index - 1 ;
        }

        @Override
        public String get(int index) {
            if (index == 0) {
                return "Contacts";
            } else {
                return getSource().get(index - 1);
            }
        }

        @Override
        public int size() {
            return getSource().size() + 1 ;
        }

        @Override
        protected void sourceChanged(Change<? extends String> c) {
            fireChange(new Change<String>(this) {
                @Override
                public boolean wasAdded() {
                    return c.wasAdded();
                }

                @Override
                public boolean wasRemoved() {
                    return c.wasRemoved();
                }

                @Override
                public boolean wasReplaced() {
                    return c.wasReplaced();
                }

                @Override
                public boolean wasUpdated() {
                    return c.wasUpdated();
                }

                @Override
                public boolean wasPermutated() {
                    return c.wasPermutated();
                }

                @Override
                public int getPermutation(int i) {
                    return c.getPermutation(i - 1) + 1;
                }

                @Override
                protected int[] getPermutation() {
                    // This method is only called by the superclass methods
                    // wasPermutated() and getPermutation(int), which are
                    // both overriden by this class. There is no other way
                    // this method can be called.
                    throw new AssertionError("Unreachable code");
                }

                @Override
                public List<String> getRemoved() {
                    ArrayList<String> res = new ArrayList<>(c.getRemovedSize());
                    for(String removed: c.getRemoved()) {
                        res.add(removed);
                    }
                    return res;
                }

                @Override
                public int getFrom() {
                    return c.getFrom() + 1;
                }

                @Override
                public int getTo() {
                    return c.getTo() + 1;
                }

                @Override
                public boolean next() {
                    return c.next();
                }

                @Override
                public void reset() {
                    c.reset();
                }
            });
        }

    };

    namesListWithHeader.forEach(System.out::println);

    namesListWithHeader.addListener((Change<? extends String> c) -> {
        while (c.next()) {
            if (c.wasAdded()) {
                System.out.println("Added "+c.getAddedSubList());
                System.out.println("From: "+c.getFrom()+", To: "+c.getTo());
            }
        }
    });

    System.out.println("\nAdding Michael to base list...\n");
    baseList.add(new Person("Michael", "Brown", "michael.brown@example.com"));

    namesListWithHeader.forEach(System.out::println);
}

Вот немного более короткая версия ответа James_D с использованием Lombok @Delegate, чтобы не писать все методы делегирования.

      /**
 * A List that mirrors a base List by applying a converter on all items.
 * @param <E> item type of the target List
 * @param <F> item type of the base list
 */
public class MirroringList<E, F> extends TransformationList<E, F> implements ObservableList<E> {

    /** mapping function from base list item type to target list item type */
    private final Function<F, E> converter;

    public MirroringList(ObservableList<? extends F> list, Function<F, E> converter) {
        super(list);
        this.converter = converter;
    }

    @Override
    public int getSourceIndex(int index) {
        return index;
    }

    @Override
    public E get(int index) {
        return converter.apply(getSource().get(index));
    }

    @Override
    public int size() {
        return getSource().size();
    }

    @Override
    protected void sourceChanged(Change<? extends F> change) {
        fireChange(new DelegatingChange(change, this));
    }


    /**
     * An implementation of {@link Change} that delegates all methods to a specified change except {@link #getRemoved()} 
     */
    private class DelegatingChange extends Change<E> implements DelegatingChangeExcluded<E> {

        @Delegate(excludes = DelegatingChangeExcluded.class)
        private final Change<? extends F> change;

        public DelegatingChange(Change<? extends F> change, MirroringList<E, F> list) {
            super(list);
            this.change = change;
        }

        @Override
        protected int[] getPermutation() {
            return new int[0];
        }

        @Override
        public List<E> getRemoved() {
            return change.getRemoved().stream()
                    .map(converter)
                    .collect(Collectors.toList());
        }
    }

    /**
     * This interface is only used to exclude some methods from delegated methods via Lombok's @{@link Delegate}
     * so that the compiler doesn't complain.
     */
    @SuppressWarnings("unused")
    private interface DelegatingChangeExcluded<E> {
        List<E> getRemoved();
        ObservableList<? extends E> getList();
        List<E> getAddedSubList();
    }
}
Другие вопросы по тегам