Using Javafx.Beans Properties in Model Classes

Using javafx.beans properties in model classes

I'm going to offer something of a dissenting opinion here.

JavaFX Properties and JPA

As I commented on jewelsea's answer, using a JavaFX property-based bean with JPA is possible as long as you use "property access" rather than "field access". The blog post I linked there goes into more detail on this, but the basic idea is that any annotations should be on the get...() methods and not on the fields. As far as I can see, this does prevent use of any of the read-only JavaFX property patterns in conjunction with JPA, but I've never really felt JPA played well with read only properties (i.e. get methods and no set method) anyway.

Serialization

Contrary to my comment on jewelsea's answer, and with the benefit of a few weeks to work with this (and having been placed in a position where I was facing replicating several entity classes on a JavaFX client side using JavaFX properties), I think the lack of serialization of JavaFX properties can be worked around. The key observation is that you really only need to serialize the wrapped state of the property (not, for example, any listeners). You can do this by implementing java.io.Externalizable. Externalizable is a sub-interface of Serializable that requires you to fill in the readExternal(...) and writeExternal(...) methods. These methods can be implemented to externalize just the state wrapped by the property, rather than the property itself. This means that if your entity is serialized and then deserialized, you will end up with a new property instance, and any listeners will not be preserved (i.e. the listeners effectively become transient), but as far as I can see this would be what was wanted in any reasonable use case.

I experimented with beans defined this way and it all seems to work nicely. Additionally, I ran a small experiment in transferring them between the client and a restful web service, using the Jackson mapper to convert to and from a JSON representation. Since the mapper just relies on using the get and set methods, this works just fine.

Some caveats

A couple of points need to be observed. As with any Serialization, it's important to have a no-argument constructor. And of course, all values wrapped by the JavaFX properties must themselves be serializable - again this is the same rule as for any serializable bean.

The point about JavaFX properties working via side-effects is well taken, and care needs to be exercised when moving these properties (which are, to some extent, designed with a single-threaded model in mind) to a potentially multi-threaded server. A good rule of thumb is probably that if you use this strategy, listeners should only be registered on the client side (and remember, those listeners are transient with respect to transfer back to the server, whether by serialization or by JSON representation). Of course, that suggests that using these on the server side then might be a bad design; it becomes a trade-off between the convenience of having a single entity that is "all things to all people" (observable properties for the JavaFX client, serializable for persistence and/or remote access, and with persistent mappings for JPA) versus exposing functionality (e.g. observability) where it might not be fully appropriate (on the server).

Finally, if you do use the JPA annotations, those have runtime retention which implies (I think) that your JavaFX client will need the a javax.persistence specification on the classpath).

Here's an example of such a "man for all seasons" entity:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.time.MonthDay;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
* Entity implementation class for Entity: Person
*
*/
@Entity

public class Person implements Externalizable {

private static final long serialVersionUID = 1L;

public Person() {

}

public Person(String name, MonthDay birthday) {
setName(name);
setBirthday(birthday);
}

private final IntegerProperty id = new SimpleIntegerProperty(this, "id");

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public int getId() {
return id.get();
}

public void setId(int id) {
this.id.set(id);
}

public IntegerProperty idProperty() {
return id ;
}

private final StringProperty name = new SimpleStringProperty(this, "name");

// redundant, but here to indicate that annotations must be on the property accessors:
@Column(name="name")
public final String getName() {
return name.get();
}

public final void setName(String name) {
this.name.set(name);
}

public StringProperty nameProperty() {
return name ;
}

private final ObjectProperty<MonthDay> birthday = new SimpleObjectProperty<>();

public final MonthDay getBirthday() {
return birthday.get();
}

public final void setBirthday(MonthDay birthday) {
this.birthday.set(birthday);
}

public ObjectProperty<MonthDay> birthdayProperty() {
return birthday ;
}

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeInt(getId());
out.writeObject(getName());
out.writeObject(getBirthday());
}

@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
setId(in.readInt());
setName((String) in.readObject());
setBirthday((MonthDay) in.readObject());
}

}

How to use collection types in model class as javafx.beans.property

You should use SetProperty like this:

// set of user's certificates
private SetProperty<Certificate> certificates;

Then, you can set the values using an ObservableSet. You can construct an ObservableSet with the help of FXCollections.observableSet(E... elements).

JavaBean wrapping with JavaFX Properties

The Simple*Property classes are full, standalone implementations of their corresponding Property abstract classes, and do not rely on any other object. So, for example, SimpleStringProperty contains a (private) String field itself which holds the current value of the property.

The parameters to the constructor you showed:

new SimpleStringProperty(bean, "name")

are:

  • bean: the bean to which the property belongs, if any
  • name: the name of the property

The bean can be useful in a ChangeListener's changed(...) method as you can retrieve the "owning bean" of the property that changed from the property itself. The name can be used similarly (if you have the same listener registered with multiple properties, you can figure out which property changed: though I never use this pattern).

So a typical use of a SimpleStringProperty as an observable property of an object looks like:

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

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

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

public StringProperty firstNameProperty() {
return firstName ;
}

// ... other properties, etc
}

The functionality you are looking for: to wrap an existing Java Bean style property in a JavaFX observable property is implemented by classes in the javafx.beans.property.adapter package. So, for example, you could do

StringProperty nameProperty = new JavaBeanStringPropertyBuilder()
.bean(bean)
.name("name")
.build();

Calling

nameProperty.set("James");

with this setup will effectively cause a call to

bean.setName("James");

If the bean supports PropertyChangeListeners, the JavaBeanStringProperty will register a PropertyChangeListener with the bean. Any changes to the name property of the Java Bean will be translated by the JavaBeanStringProperty into JavaFX property changes. Consequently, if the underlying JavaBean supports PropertyChangeListeners, then changes to the bean via

bean.setName(...);

will result in any ChangeListeners (or InvalidationListeners) registered with the JavaBeanStringProperty being notified of the change.

So, for example, if the Bean class is

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

public class Bean {

private String name ;
private final PropertyChangeSupport propertySupport ;

public Bean(String name) {
this.name = name ;
this.propertySupport = new PropertyChangeSupport(this);
}

public Bean() {
this("");
}

public String getName() {
return name ;
}

public String setName(String name) {
String oldName = this.name ;
this.name = name ;
propertySupport.firePropertyChange("name", oldName, name);
}

public void addPropertyChangeListener(PropertyChangeListener listener) {
propertySupport.addPropertyChangeListener(listener);
}
}

Then the following code:

Bean bean = new Bean();
StringProperty nameProperty() = new JavaBeanStringPropertyBuilder()
.bean(bean)
.name("name")
.build();
nameProperty().addListener((obs, oldName, newName) -> System.out.println("name changed from "+oldName+" to "+newName));
bean.setName("James");
System.out.println(nameProperty().get());

will produce the output:

name changed from to James 
James

If the JavaBean does not support PropertyChangeListeners, then changes to the bean via bean.setName(...) will not propagate to ChangeListeners or InvalidationListeners registered with the JavaBeanStringProperty.

So if the bean is simply

public class Bean {

public Bean() {
this("");
}

public Bean(String name) {
this.name = name ;
}

private String name ;

public String getName() {
return name ;
}

public void setName(String name) {
this.name = name ;
}
}

The JavaBeanStringProperty would have no way to observe the change, so the change listener would never be invoked by a call to bean.setName(). So the test code above would simply output

James

JavaFX 8: Separation of model/domain from view

Why do you want to avoid using any JavaFX class at all?

JavaFX Properties (found in the javafx.beans.property package) are just an extension to the regular JavaBeans properties.

They are part of the JavaFX Properties and Bindings framework, which doesn't depend on the JavaFX Toolkit to have its functionality implemented, nor does it require your application to leverage the JavaFX UI classes in order to build its graphical user interface.
It can therefore be used as a standalone facility anywhere in your code, without having to worry about the model/domain being coupled to this particular implementation of the view.

You should consider the JavaFX Properties and Bindings framework a general utility that is inherently indipendent of the implementation of the view due to its nature, just like is any other general-purpose library (e.g. Guava). You could, for example, switch to a Swing application at any time, while still keep using it.

But if you still prefer to not leverage its functionality when possible, then there's a case when you can actually do so:
if what's being presented isn't going to change (e.g. the state corresponding to the table model in your domain class is immutable), and standard property getters are present, then, as per the PropertyValueFactory documentation:

There is fall-through support for attempting to call get<property>() or is<property>(). If a method matching this pattern exists, the value returned from this method is wrapped in a ReadOnlyObjectWrapper and returned to the TableCell. However, in this situation, this means that the TableCell will not be able to observe the ObservableValue for changes.

Avoid adapters; they are meant to be used with legacy code only that cannot be altered in any way.

Related Posts

  • JavaBean wrapping with JavaFX Properties
  • JavaFX Properties Design
  • Does the use of ObservableList in JavaFX go against Model-View-Controller separation?

set() method doesn't work in JavaFX properties

The tutorial you're watching is using the "lazy pattern" for JavaFX properties. That is, the property object itself is not created until either written to or queried directly. But there's a mistake in the implementation. I don't know if the tutorial made a mistake or if you simply made a mistake when copying the code, but the property getter should look like:

public final DoubleProperty numberProperty() {
if (number == null) {
// this constructor sets the initial value to 0.0
number = new SimpleDoubleProperty(this, "number");
}
return number;
}

Note two differences between the above code and your code:

  1. The above sets the number field, then returns the value of said field.

    You are currently returning a new SimpleDoubleProperty every time and never storing it. That means you can't observe it for changes because you change a different instance whenever you set the property.

  2. The name of the method is numberProperty.

    That follows naming conventions for JavaFX properties, which is: If you have a property named foo then the getter should be named getFoo(), the setter (if writable) should be named setFoo(...), and the property getter should be named fooProperty(). Note the setter should have only one parameter whose type matches the value type stored in the property.

  3. Bonus: I set the "bean" and "name" of the SimpleDoubleProperty.

    This is not strictly necessary to make your code work, but is the "proper" thing to do.

JavaBeanProperties in JavaFX without pulling in java.desktop (Swing, AWT)

You could use a generic solution that doesn’t use Reflection at all:

public class DelegatingProperty<B,T> extends ObjectPropertyBase<T>
implements JavaBeanProperty<T> {
/**
* Create a property without PropertyChangeEvent based notification.
*/
public static <O, V> JavaBeanProperty<V> get(O bean, String name,
Function<? super O, ? extends V> getter,
BiConsumer<? super O, ? super V> setter) {
return new DelegatingProperty<>(bean, name, getter, setter, null, null);
}
/**
* Create a property with PropertyChangeEvent based notification.
*/
public static <O, V> JavaBeanProperty<V> get(O bean, String name,
Function<? super O, ? extends V> getter, BiConsumer<? super O, ? super V> setter,
BiConsumer<? super O, ? super PropertyChangeListener> register,
BiConsumer<? super O, ? super PropertyChangeListener> unregister) {
return new DelegatingProperty<>(bean, name, getter, setter, register, unregister);
}
B bean;
String name;
Function<? super B, ? extends T> getter;
BiConsumer<? super B, ? super T> setter;
BiConsumer<? super B, ? super PropertyChangeListener> unregister;
PropertyChangeListener listener;

private DelegatingProperty(B bean, String name,
Function<? super B, ? extends T> getter,
BiConsumer<? super B, ? super T> setter,
BiConsumer<? super B, ? super PropertyChangeListener> register,
BiConsumer<? super B, ? super PropertyChangeListener> unregister) {
this.bean = Objects.requireNonNull(bean);
this.name = name;
this.getter = Objects.requireNonNull(getter);
this.setter = Objects.requireNonNull(setter);
if(register != null || unregister != null) {
Objects.requireNonNull(register);
this.unregister = Objects.requireNonNull(unregister);
register.accept(bean, listener = event -> fireValueChangedEvent());
}
}

@Override
public Object getBean() {
return bean;
}

@Override
public String getName() {
return name;
}

@Override
public T get() {
return getter.apply(bean);
}

@Override
public void set(T value) {
if(isBound()) throw new IllegalStateException("bound property");
T old = getter.apply(bean);
setter.accept(bean, value);
T now = getter.apply(bean);
if(!Objects.equals(old, now)) fireValueChangedEvent();
}

@Override
protected void invalidated() {
if(isBound()) {
setter.accept(bean, super.get());
}
}

@Override
public void fireValueChangedEvent() {
super.fireValueChangedEvent();
}

@Override
public void dispose() {
if(unregister != null && listener != null) {
unregister.accept(bean, listener);
listener = null;
}
}
}

Then, to stay at your example, you could get the name property of Entity as

JavaBeanProperty<String> prop = DelegatingProperty.get(bean, "name",
Entity::getName, Entity::setName,
Entity::addPropertyChangeListener, Entity::removePropertyChangeListener);

It’s more verbose, but on the other hand, provides more compile time safety, as the presence of all methods required for the property is checked at compile-time, and will likely have a higher runtime performance.

When you have a lot of properties in one bean class with event support, you may benefit from a dedicated factory method, e.g.

public static <V> JavaBeanProperty<V> property(Entity theBean, String name,
Function<? super Entity, ? extends V> getter,
BiConsumer<? super Entity, ? super V> setter) {
return DelegatingProperty.get(theBean, name, getter, setter,
Entity::addPropertyChangeListener, Entity::removePropertyChangeListener);
}

which you then can use as

JavaBeanProperty<String> nameProp
= property(bean, "name", Entity::getName, Entity::setName);
JavaBeanProperty<String> otherProp
= property(bean, "other", Entity::getOther, Entity::setOther);

Of course, it would also be possible to provide them via instance methods of the bean itself instead of a static factory method, too, perhaps with a lazily populated field holding the property, etc.

There are several roads you can go from this starting point.

JavaFX saving properties to DB

Add @Access(AccessType.PROPERTY) annotation to your class. Now Hibernate will look for columns in getter methods. Then add @Column(name = "id") annotations to your getters.

@Entity
@Table(name = "person")
@Access(AccessType.PROPERTY)
public class Person implements Serializable {

private IntegerProperty id;
private StringProperty firstName;
private StringProperty lastName;
private IntegerProperty age;
private StringProperty address;

public Person() {
}

public Person(int id, String firstName, String lastName, int age, String address) {
this.id = new SimpleIntegerProperty(id);
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
this.age = new SimpleIntegerProperty(age);
this.address = new SimpleStringProperty(address);
}

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
public int getId() {
return id.get();
}

public void setId(int id) {
this.id = new SimpleIntegerProperty(id);
}

public IntegerProperty idProperty() {
return id;
}

@Column(name = "firstname")
public String getFirstName() {
return firstName.get();
}

public void setFirstName(String firstName) {
this.firstName = new SimpleStringProperty(firstName);
}

public StringProperty firstNameProperty() {
return firstName;
}

@Column(name = "lastname")
public String getLastName() {
return lastName.get();
}

public void setLastName(String lastName) {
this.lastName = new SimpleStringProperty(lastName);
}

public StringProperty lastNameProperty() {
return lastName;
}

@Column(name = "age")
public int getAge() {
return age.get();
}

public void setAge(int age) {
this.age = new SimpleIntegerProperty(age);
}

public IntegerProperty ageProperty() {
return age;
}

@Column(name = "address")
public String getAddress() {
return address.get();
}

public void setAddress(String address) {
this.address = new SimpleStringProperty(address);
}

public StringProperty addressProperty() {
return address;
}
}


Related Topics



Leave a reply



Submit