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);
}
}