Duplicate Items while converting an object to a node inside two observablelists JavaFX

I am creating a Report Management Software that have a list of clients as it’s property.

I have two observablelists(data object, node) that I’ve mapped which creates a node based on the data model, using sdorof and C.Kadura’s solution found here: Link to the solution. (no changes)

it works when there are no extracted properties inside the list but breaks when a data model’s property has been extracted.

what happens is that, if I add a data inside the source, several duplicate nodes will be added towards the mapped observablelist. Any help or explanations as to why this is happening will help tremendously towards my situation.

ObservableListMapper.java
Source

Extractor

_clients = new ExpandedObservableList<>(clients, c -> new Observable[] { c.dirtyProperty() });
        if (_clients.isEmpty())
            _clients.add(new Client());
public class ObservableListMapper {

    public static <E, F> void mapContent(ObservableList<F> mapped, ObservableList<? extends E> source,
            Function<? super E, ? extends F> mapper) {
        map(mapped, source, mapper);
    }

    private static <E, F> Object map(ObservableList<F> mapped, ObservableList<? extends E> source,
            Function<? super E, ? extends F> mapper) {
        final ListContentMapping<E, F> contentMapping = new ListContentMapping<E, F>(mapped, mapper);
        mapped.setAll(source.stream().map(mapper).distinct().collect(Collectors.toList()));
        source.removeListener(contentMapping);
        source.addListener(contentMapping);
        return contentMapping;
    }

    private static class ListContentMapping<E, F> implements ListChangeListener<E>, WeakListener {
        private final WeakReference<List<F>> mappedRef;
        private final Function<? super E, ? extends F> mapper;
        private IdentityHashMap<E, F> cache = null;

        public ListContentMapping(List<F> mapped, Function<? super E, ? extends F> mapper) {
            this.mappedRef = new WeakReference<List<F>>(mapped);
            this.mapper = mapper;
        }

        @Override
        public void onChanged(Change<? extends E> change) {
            final List<F> mapped = mappedRef.get();
            if (mapped == null) {
                change.getList().removeListener(this);
            } else {
                while (change.next()) {
                    if (change.wasPermutated()) {
                        List<? extends E> orig = change.getList().subList(change.getFrom(), change.getTo());
                        List<F> sub = mapped.subList(change.getFrom(), change.getTo());
                        cache(orig, sub);
                        sub.clear();
                        mapped.addAll(change.getFrom(),
                                orig.stream().distinct().map(e -> computeIfAbsent(e)).collect(Collectors.toList()));
                    } else {
                        if (change.wasRemoved()) {
                            List<F> sub = mapped.subList(change.getFrom(), change.getFrom() + change.getRemovedSize());
                            if (change.wasAdded()) {
                                List<? extends E> orig = change.getRemoved();
                                cache(orig, sub);
                            }
                            sub.clear();
                        }

                        if (change.wasAdded()) {
                            mapped.addAll(change.getFrom(), change.getAddedSubList().stream().distinct()
                                    .map(e -> computeIfAbsent(e)).collect(Collectors.toList()));
                        }
                    }
                }
                cache = null;
            }
        }

        private void cache(List<? extends E> orig, List<F> mapped) {
            if (cache == null)
                cache = new IdentityHashMap<>();
            for (int i = 0; i < orig.size(); i++)
                cache.put(orig.get(i), mapped.get(i));
        }

        private F computeIfAbsent(E e) {
            F f = null;
            if (cache != null)
                f = cache.get(e);
            if (f == null)
                f = mapper.apply(e);
            return f;
        }

        @Override
        public boolean wasGarbageCollected() {
            return mappedRef.get() == null;
        }

        @Override
        public int hashCode() {
            final List<F> list = mappedRef.get();
            return (list == null) ? 0 : list.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }

            final List<F> mapped1 = mappedRef.get();
            if (mapped1 == null) {
                return false;
            }

            if (obj instanceof ListContentMapping) {
                final ListContentMapping<?, ?> other = (ListContentMapping<?, ?>) obj;
                final List<?> mapped2 = other.mappedRef.get();
                return mapped1 == mapped2;
            }
            return false;
        }
    }
}

Client.java

public class Client extends Person {
    public static List<String> CLIENT_TYPES() {
        File clienttype_file = new File(Directory.DATA_REPORT.getPath() + "client.type.json");
        return JsonService.deserialize_list(FileUtil.read(clienttype_file), String.class, JsonModule.MAPPER);
    }
private final ExpandedStringProperty _type;
    private final ExpandedBooleanProperty _orderer;
    private final ExpandedBooleanProperty _recepient;
    private final ExpandedObjectProperty<Contact> _email;
    private final ExpandedStringProperty _companyname;
    private final ExpandedStringProperty _note;

    public Client() {
        this(new Person(),
                "",
                false,
                false,
                new Contact(Contact.Medium.EMAIL,
                        Contact.Type.PERSONAL,
                        ""),
                "",
                "");
    }

    public Client(Person person,
            String type,
            boolean orderer,
            boolean recepient,
            Contact email,
            String companyname,
            String note) {
        super(person);
        _type = new ExpandedStringProperty(type);
        _orderer = new ExpandedBooleanProperty(orderer);
        _recepient = new ExpandedBooleanProperty(recepient);
        _email = new ExpandedObjectProperty<Contact>(email);
        _companyname = new ExpandedStringProperty(companyname);
        _note = new ExpandedStringProperty(note);

        _load_bindings_client();
    }

    protected void _load_bindings_client() {
        _dirty.unbind();
        _dirty.bind(_id.dirtyProperty()
                .or(_givenname.dirtyProperty())
                .or(_middlename.dirtyProperty())
                .or(_surname.dirtyProperty())
                .or(_suffix.dirtyProperty())
                .or(getAddress().dirtyProperty())
                .or(Bindings.createBooleanBinding(() -> _contacts.stream().anyMatch(c -> c.isDirty()), _contacts))
                .or(_type.dirtyProperty())
                .or(_orderer.dirtyProperty())
                .or(_recepient.dirtyProperty())
                .or(getEmail().dirtyProperty())
                .or(_companyname.dirtyProperty())
                .or(_note.dirtyProperty()));
    }

    @Override
    public void rebaseline() {
        super.rebaseline();
        _type.rebaseline();
        _orderer.rebaseline();
        _recepient.rebaseline();
        getEmail().rebaseline();
        _companyname.rebaseline();
        _note.rebaseline();
    }

    @Override
    public void reset() {
        super.reset();
        _type.reset();
        _orderer.reset();
        _recepient.reset();
        getEmail().reset();
        _companyname.reset();
        _note.reset();
    }

    public ExpandedStringProperty typeProperty() {
        return _type;
    }

    public String getType() {
        return typeProperty().get();
    }

    public void setType(String type) {
        typeProperty().set(type);
    }

    public ExpandedBooleanProperty ordererProperty() {
        return _orderer;
    }

    public Boolean isOrderer() {
        return ordererProperty().getValue();
    }

    public void setOrderer(Boolean orderer) {
        ordererProperty().set(orderer);
    }

    public ExpandedBooleanProperty recepientProperty() {
        return _recepient;
    }

    public Boolean isRecepient() {
        return recepientProperty().getValue();
    }

    public void setRecepient(Boolean recepient) {
        recepientProperty().set(recepient);
    }

    public ExpandedObjectProperty<Contact> emailProperty() {
        return _email;
    }

    public Contact getEmail() {
        return emailProperty().get();
    }

    public void setEmail(Contact email) {
        emailProperty().set(email);
    }

    public ExpandedStringProperty companynameProperty() {
        return _companyname;
    }

    public String getCompanyname() {
        return companynameProperty().get();
    }

    public void setCompanyname(String companyname) {
        companynameProperty().set(companyname);
    }

    public ExpandedStringProperty notesProperty() {
        return _note;
    }

    public String getNotes() {
        return notesProperty().get();
    }

    public void setNotes(String notes) {
        notesProperty().set(notes);
    }
}

ClientItem.java

package com.avertapest.layout.app.reports.page.people;

import org.kordamp.ikonli.javafx.FontIcon;

import com.avertapest.App;
import com.avertapest.core.AppLoader;
import com.avertapest.core.model.info.obj.Contact;
import com.avertapest.core.model.info.obj.person.Client;
import com.avertapest.core.model.report.obj.Report;

import dev.sol.tools.observable.ObservableListMapper;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;

public class ClientItem extends StackPane {
    @FXML
    private TextField fname_field;
    @FXML
    private FontIcon fname_change;

    @FXML
    private TextField mname_field;
    @FXML
    private FontIcon mname_change;

    @FXML
    private TextField lname_field;
    @FXML
    private FontIcon lname_change;

    @FXML
    private TextField suffix_field;
    @FXML
    private FontIcon suffix_change;

    @FXML
    private TextField street_field;
    @FXML
    private FontIcon street_change;

    @FXML
    private TextField city_field;
    @FXML
    private FontIcon city_change;

    @FXML
    private TextField state_field;
    @FXML
    private FontIcon state_change;

    @FXML
    private TextField postal_field;
    @FXML
    private FontIcon postal_change;

    @FXML
    private TextField email_field;
    @FXML
    private FontIcon email_change;

    @FXML
    private TextField company_field;
    @FXML
    private FontIcon company_change;

    @FXML
    private ComboBox<String> settings_type_field;
    @FXML
    private CheckBox settings_orderer_field;
    @FXML
    private CheckBox settings_recepient_field;
    @FXML
    private FontIcon settings_change;

    @FXML
    private TextArea notes_field;
    @FXML
    private FontIcon notes_change;

    @FXML
    private FontIcon contact_change;
    @FXML
    private VBox contact_container;

    @FXML
    private Button button_remove;

    @FXML
    private void handleCopyServiceAddress() {
        _client.setAddress(_report.getServiceAddress());
    }

    @FXML
    private void handleAddContact() {
        _client.addContact(new Contact());
    }

    @FXML
    private void handleRemoveClient() {
        _report.getClients().remove(_client);
    }

    private App _app;
    private Client _client;
    private Report _report;

    private ObservableList<ContactItem> _client_contacts;

    public ClientItem(Client client, Report report, App app) {
        _client = client;
        _report = report;
        _app = app;

        getChildren().setAll(AppLoader.report_form_people_clientitem(this, _app));
        _load_fields();
        _load_bindings();
        _load_listeners();
    }

    private void _load_fields() {
        settings_type_field.setItems(FXCollections.observableArrayList(Client.CLIENT_TYPES()));
        if (_client.getType().isEmpty() || !Client.CLIENT_TYPES().contains(_client.getType())) {
            _client.setType(Client.CLIENT_TYPES().get(0));
            _client.typeProperty().rebaseline();
        }

        _client_contacts = FXCollections.observableArrayList();
    }

    private void _load_bindings() {
        fname_field.textProperty().bindBidirectional(_client.givennameProperty());
        fname_change.visibleProperty().bind(_client.givennameProperty().dirtyProperty());

        mname_field.textProperty().bindBidirectional(_client.middlenameProperty());
        mname_change.visibleProperty().bind(_client.middlenameProperty().dirtyProperty());

        lname_field.textProperty().bindBidirectional(_client.surnameProperty());
        lname_change.visibleProperty().bind(_client.surnameProperty().dirtyProperty());

        suffix_field.textProperty().bindBidirectional(_client.suffixProperty());
        suffix_change.visibleProperty().bind(_client.suffixProperty().dirtyProperty());

        street_field.textProperty().bindBidirectional(_client.getAddress().streetProperty());
        street_change.visibleProperty().bind(_client.getAddress().streetProperty().dirtyProperty());

        city_field.textProperty().bindBidirectional(_client.getAddress().cityProperty());
        city_change.visibleProperty().bind(_client.getAddress().cityProperty().dirtyProperty());

        state_field.textProperty().bindBidirectional(_client.getAddress().stateProperty());
        state_change.visibleProperty().bind(_client.getAddress().stateProperty().dirtyProperty());

        postal_field.textProperty().bindBidirectional(_client.getAddress().postalProperty());
        postal_change.visibleProperty().bind(_client.getAddress().postalProperty().dirtyProperty());

        email_field.textProperty().bindBidirectional(_client.getEmail().valueProperty());
        email_change.visibleProperty().bind(_client.getEmail().valueProperty().dirtyProperty());

        company_field.textProperty().bindBidirectional(_client.companynameProperty());
        company_change.visibleProperty().bind(_client.companynameProperty().dirtyProperty());

        settings_type_field.valueProperty().bindBidirectional(_client.typeProperty());
        settings_orderer_field.selectedProperty().bindBidirectional(_client.ordererProperty());
        settings_recepient_field.selectedProperty().bindBidirectional(_client.recepientProperty());
        settings_change.visibleProperty().bind(_client.typeProperty().dirtyProperty()
                .or(_client.ordererProperty().dirtyProperty())
                .or(_client.recepientProperty().dirtyProperty()));

        notes_field.textProperty().bindBidirectional(_client.notesProperty());
        notes_change.visibleProperty().bind(_client.notesProperty().dirtyProperty());

        ObservableListMapper.mapContent(_client_contacts, _client.getContacts(),
                contact -> new ContactItem(contact, _client));
        Bindings.bindContent(contact_container.getChildren(), _client_contacts);
        contact_change.visibleProperty().bind(Bindings.createBooleanBinding(() -> {
            if (_client.getContacts().isEmpty())
                return false;
            return _client.getContacts().stream().anyMatch(client -> client.isDirty());
        }, _client.getContacts()).or(_client.getContacts().dirtyProperty()));

        button_remove.visibleProperty().bind(
                Bindings.createBooleanBinding(() -> _report.getClients().indexOf(_client) > 0, _report.getClients()));
    }

    private void _load_listeners() {
    }
}

Controller

public class PeopleController {
    public static enum InspectorStatus {
        LOADING,
        EMPTY;

        private BooleanProperty _property;

        private InspectorStatus() {
            _property = new SimpleBooleanProperty();
        }

        public BooleanProperty property() {
            return _property;
        }

        public void set(boolean flag) {
            _property.set(flag);
        }
    }

    public static enum ClientStatus {
        LOADING;

        private BooleanProperty _property;

        private ClientStatus() {
            _property = new SimpleBooleanProperty();
        }

        public BooleanProperty property() {
            return _property;
        }

        public void set(boolean flag) {
            _property.set(flag);
        }
    }

    @FXML
    private ComboBox<Inspector> inspector_field;
    @FXML
    private StackPane inspector_container;

    @FXML
    private HBox client_container;
    @FXML
    private HBox client_loading;
    @FXML
    private Button client_add_field;
    @FXML
    private FontIcon client_change;

    @FXML
    private void handleAddClient() {
        _report.addClient(new Client());
    }

    private Report _report;
    private App _app;

    public void load(Report report, App app) {
        _app = app;
        _report = report;

        _init_clients();
    }

    private void _init_clients() {
        TaskService<ObservableList<ClientItem>> clientsTask = new TaskService<>() {
            @Override
            protected ObservableList<ClientItem> call() throws Exception {
                // Thread.sleep(1000);
                ObservableList<ClientItem> items = FXCollections.observableArrayList();
                ObservableListMapper.mapContent(items, _report.getClients(),
                        client -> new ClientItem(client, _report, _app));
                return items;
            }
        };

        clientsTask.setOnSucceeded(e -> {
            Bindings.bindContent(client_container.getChildren(), clientsTask.getValue());
            clientsTask.getValue().addListener((ListChangeListener<ClientItem>) change -> {
                if (change.next()) {
                    
                }
            });
        });

        ClientStatus.LOADING.property().unbind();
        ClientStatus.LOADING.property().bind(clientsTask.runningProperty());
        App.THREAD.registerTask(clientsTask);
    }
}

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật