How does JavaBeans Property Adapter work? - java

What I'm trying to do works fine if I follow the JavaFX property definition described here.
Now, instead, I want to define properties from Java Beans objects using Java Beans Property Adapter. Since there is no documentation I can't figure out how it works.
Suppose I have a simple POJO class:
public class Person {
private String name;
public String getName() {
return name;
}
public void setName( String name ) {
this.name = name;
}
}
and a PersonProperty:
public class PersonProperty {
private Person person = new Person();
private JavaBeanStringProperty name;
public PersonProperty() throws NoSuchMethodException {
name = JavaBeanStringPropertyBuilder.create().bean( person ).name( "name" ).build();
}
public Person getPerson() {
return person;
}
public void setPerson( Person person ) {
this.person = person;
}
public JavaBeanStringProperty nameProperty() {
return name;
}
}
and finally a test:
public void personTest() throws NoSuchMethodException {
PersonProperty pp = new PersonProperty();
pp.getPerson().setName( "A" );
pp.getPerson().setName( "B" );
pp.nameProperty().addListener( new ChangeListener<String>() {
#Override
public void changed( ObservableValue<? extends String> ov, String t, String t1 ) {
System.out.println( "from " + t + " to " + t1 );
}
} );
pp.getPerson().setName( "C" );
pp.getPerson().setName( "D" );
}
I'm expecting to see:
from B to C
from C to D
Instead nothing appears.
If I add pp.nameProperty().set("E") at the end of personTest I get from B to E

I think the issue here is that Person is indeed a POJO, but not a JavaBean: It is missing the hooks for PropertyChangeListeners. Java will not magically know when Person#name changes. Instead, the JavaFX adapter will look for a way to add a PropertyChangeListener and listen to events for a property called 'name'. If you add a PropertyChangeSupport instance to your Person class it will work as expected:
public class Person {
private String name;
private PropertyChangeSupport _changeSupport;
public Person() {
_changeSupport = new PropertyChangeSupport(this);
}
public String getName() {
return name;
}
public void setName( String name ) {
final String prev = this.name;
this.name = name;
_changeSupport.firePropertyChange("name", prev, name);
}
public void addPropertyChangeListener(final PropertyChangeListener listener) {
_changeSupport.addPropertyChangeListener(listener);
}
}

Related

Javafx observable list of values that are themselves observable?

right now I have the following:
ObservableList<Person> personsList;
and my UI for displaying the person list is tied to personsList.
Person is something like below:
class Person {
Name name
// other details
List<SomeItem> list;
}
// Item is immutable, but SomeItem can mutate by setting and getting the Items
class SomeItem {
Item item
Item item2
}
The issue is SomeItem is mutable, so I would want any changes to SomeItem be propagated to the original personsList.
How would I achieve something like that?? Based on googling I kind of have the following modifications, but I am not sure if they work!
class Person {
Name name
// other details
List<SomeItem> list; // <-- change to ObservableListValue<SomeItem>
}
// Item is immutable, but SomeItem can mutate by setting and getting the Items
class SomeItem {
Item item // <-- change to ObservablePropertyBase<Item>
Item item2 // <-- change to ObservablePropertyBase<Item>
}
From what I understand, this changes would make SomeItem report when any ObservablePropertyBase<Item> changes, and then ObservableListValue<SomeItem> would propagate this changes up, which would be caught by personsList?
Edit: Question 2:
Is it possible to force refresh personsList? Lets say I make an overall update to a specific SomeItem, then I can refresh the entire personsList?
You can fire Change events by creating an ObservableList with an extractor.
Here is an example:
Name.java:
public class Name {
private final String name;
public Name(String name) {
this.name = name;
}
public final String getName() {
return name;
}
}
Item.java:
public class Item {
private final String name;
public Item(String name) {
this.name = name;
}
public final String getName() {
return name;
}
#Override
public String toString() {
return name;
}
}
SomeItem.java:
public class SomeItem {
private final ObjectProperty<Item> item1 = new SimpleObjectProperty<>(this, "item1");
private final ObjectProperty<Item> item2 = new SimpleObjectProperty<>(this, "item2");
public SomeItem(Item item1, Item item2) {
this.item1.set(item1);
this.item2.set(item2);
}
public final ObjectProperty<Item> item1Property() {
return item1;
}
public final Item getItem1() {
return item1.get();
}
public final void setItem1(Item item) {
item1.set(item);
}
public final ObjectProperty<Item> item2Property() {
return item2;
}
public final Item getItem2() {
return item2.get();
}
public final void setItem2(Item item) {
item2.set(item);
}
#Override
public String toString() {
return "[" + item1.get() + ", " + item2.get() + "]";
}
}
Person.java:
public class Person {
private final Name name;
private final ObservableList<SomeItem> someItems = FXCollections.observableArrayList(someItem ->
new Observable[]{someItem.item1Property(), someItem.item2Property()});
public Person(Name name, SomeItem... someItems) {
this.name = name;
this.someItems.addAll(someItems);
}
public final Name getName() {
return name;
}
public final ObservableList<SomeItem> getSomeItems() {
return someItems;
}
#Override
public String toString() {
return "[name=" + name.getName() + ", someItems=" + someItems + "]";
}
}
App.java:
public class App extends Application {
#Override
public void start(Stage stage) {
SomeItem someItem1 = new SomeItem(new Item("item1"), new Item("item2"));
SomeItem someItem2 = new SomeItem(new Item("item3"), new Item("item4"));
SomeItem someItem3 = new SomeItem(new Item("item5"), new Item("item6"));
SomeItem someItem4 = new SomeItem(new Item("item7"), new Item("item8"));
Person person1 = new Person(new Name("person1"), someItem1, someItem2);
Person person2 = new Person(new Name("person2"), someItem3, someItem4);
ObservableList<Person> persons = FXCollections.observableArrayList(person ->
new Observable[]{person.someItemsProperty()});
persons.addAll(person1, person2);
persons.addListener((ListChangeListener<Person>) c -> {
while (c.next()) {
if (c.wasUpdated()) {
System.out.println("Updated persons:");
IntStream.range(c.getFrom(), c.getTo())
.mapToObj(index -> "Person at index " + index + " was updated to: " + c.getList().get(index))
.forEach(System.out::println);
}
}
});
// Update items to trigger change event for testing
someItem1.setItem1(new Item("item1Updated"));
someItem4.setItem2(new Item("item8Updated"));
}
public static void main(String[] args) {
launch();
}
}
Output:
Updated persons:
Person at index 0 was updated to: [name=person1, someItems=[[item1Updated, item2], [item3, item4]]]
Updated persons:
Person at index 1 was updated to: [name=person2, someItems=[[item5, item6], [item7, item8Updated]]]

setName and received always null

I'm new in Java :] and I have a little problem with my app:
Why when I run it, it keeps saying me "null" even when person.setname("John")
I tried to fix it but without good result, what is wrong here and why?
I tried to debug it - same result - setName set name to John but anyway it keeps printing "null"
Really strange for new user like me.
If someone can help, or even try to say me what's wrong i'd be glad, thanks.
class App {
public static void main(String[] args) throws InterruptedException {
List<String> blacklist = Arrays.asList("Bill, Adam, Jessie");
;
Person person = new PersonWithBlacklistedCheck(
new PersonWithNullCheck(new Person()),
blacklist);
person.setName("John");
System.out.println("Person: " + person);
}
}
class PersonWithBlacklistedCheck extends Person {
private final List<String> blacklist;
private final Person target;
PersonWithBlacklistedCheck(Person target, List<String> blacklist) {
this.target = target;
this.blacklist = blacklist;
}
#Override
public String getName() {
return target.getName();
}
#Override
public void setName(String name) {
if (this.blacklist.contains(name)) {
throw new RuntimeException("[" + name + "] cannot be used as a name! it is blacklisted");
}
target.setName(name);
}
}
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class PersonWithNullCheck extends Person {
private final Person target;
PersonWithNullCheck(Person target) {
this.target = target;
}
#Override
public String getName() {
return target.getName();
}
#Override
public void setName(String name) {
if (name == null) {
throw new RuntimeException("[name] must not be null!!");
}
target.setName(name);
}
}
You have person object containing another person called "target" and another one "target" :
Blacklist should look like this:
List<String> blacklist = Arrays.asList("Bill", "Adam", "Jessie");
You were creating one entry: "Bill, Adam, Jessie".
You were doing some unnecessary metods override, adding unnecessary objects - when you extend class you have that object "person" already there, you don't need to put it as another object "target". If you want to have both null check and blacklist check executed before setting name you can extend classes in hierarchy: Person -> PersonWithNullCheck -> PersonWithBlacklistedCheck. Now setName() method will be executed in each of classes as ordered. Here's a fixed solution:
public class Test {
public static void main(String[] args) {
List<String> blacklist = Arrays.asList("Bill", "Adam", "Jessie");
Person person = new PersonWithBlacklistedCheck(blacklist);
person.setName("John");
System.out.println("Person: " + person);
}
}
class PersonWithBlacklistedCheck extends PersonWithNullCheck {
private final List<String> blacklist;
PersonWithBlacklistedCheck(List<String> blacklist) {
this.blacklist = blacklist;
}
#Override
public void setName(String name) {
if (this.blacklist.contains(name)) {
throw new RuntimeException("[" + name + "] cannot be used as a name! it is blacklisted");
}
super.setName(name);
}
}
class PersonWithNullCheck extends Person {
#Override
public void setName(String name) {
if (name == null) {
throw new RuntimeException("[name] must not be null!!");
}
super.setName(name);
}
}
class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}

return a subclass-type in java

I have a subclass "OnlineCourse". It´s a subclass of "Course". I want to return "OnlineCourse" in my class "Student". But instead of "EIST" I get back null.
Here´s what I have:
public class Student {
public String matriculationNumber;
public String name;
public int age;
public Course study() {
TODO 4: Comment the code below back in
Change the Course type to OnlineCourse and set its
title to "EIST"
return the new course
// Course course = new Course();
// course.join();
// return course;
Course EIST = new OnlineCourse();
EIST.join();
return EIST;
}
}
Subclass that extends course and should be initiated as the return type for "EIST" in the class Student.
public class OnlineCourse extends Course{
public URL livestreamUrl;
public Course join() {
System.out.println("joined the course " + title);
return this;
}
public Course drop() {
System.out.println("dropped out of the course" + title);
return this;
}
}
public abstract class Course {
public String title;
public String description;
public LocalDate examDate;
public List<Lecture> lectures;
public abstract Course join();
public abstract Course drop();
}
Main- Method:
public class Main {
public static void main(String[] args) {
var student = new Student();
student.matriculationNumber = "01234567";
student.name = "Joe Doe";
student.age = 42;
student.study();
}
}
I think you're saying the course title is showing as null. In which case you have to set it for it to print. I'd also note that where you have EIST - that's just a variable name, it can be anything and doesn't have any affect on any values.
If I were to guess, I think you'd want something like this -
public static void main(String[] args) {
var student = new Student();
student.matriculationNumber = "01234567";
student.name = "Joe Doe";
student.age = 42;
student.study("EIST");
}
And in Course, you'd want a setter method for the title like, -
public setCourseTitle(String title) {
this.title = title;
}
And in Student
public Course study(String courseTitle) {
Course EISTCourse = new OnlineCourse();
EISTCourse.setCourseTitle(courseTitle);
EISTCourse.join();
return EISTCourse;
}

Iterating over genric enum instance

As you can see below, I have three declared enums, and each class has a method called getEnumByName() which revives a name and returns the enum which has that name.
I have noticed that I am duplicating the same functionality of this method on each enum.
Is there any way to change this method to a generic one, which receives the given enum's type and does the same logic?
public class Enums {
public enum A {
APPLY("Apply", "abcde");
private String id;
private String name;
A(String name, String id) {
this.name = name;
this.id = id;
}
public static A getEnumByName(String name) throws Exception {
for (A instance : A.values()) {
if (instance.getName().equals(name)) return instance;
}
throw new Exception("There is no operations matches :" + name);
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
public enum B {
APPLY("Apply", "1"),
SAVE("Save", "2"),
REVERT("Revert", "2"),
REVERT_CHILD("Revert->Revert", "4"),
REVERT_APPLY("Revert->Revert Apply", "5"),
SYNC("Sync", "6"),
OPERATIONS("Operations", "7"),
IMPORT("Import", "8"),
EXPORT("Export", "9"),
DIFF("Diff", "10");
private String id;
private String name;
B(String name, String id) {
this.name = name;
this.id = id;
}
public static B getEnumByName(String name) throws Exception {
for (B instance : B.values()) {
if (instance.getName().equals(name)) return instance;
}
throw new Exception("There is no operations matches :" + name);
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
public enum C {
UPDATE_POLICES("Update Policies", "A"),
OPERATIONS("Operations", "B"),
IMPORT_CONFIGURATION_FILE("Import Configuration File", "c"),
EXPORT_CONFIGURATION_FILE("Export Configuration File", "d"),
EXPORT_LOG_SUPPORT_FILE("Export Log Support File", "f"),
EXPORT_TECHNICAL_SUPPORT_FILE("Export Technical Support File", "g"),
UPDATE_SOFTWARE_VERSION("Update Software Version", "g"),
UPDATE_SECURITY_SINGAUTES("Update Security Signatures", "h"),
DIFF("Diff", "k");
private String id;
private String name;
C(String name, String id) {
this.name = name;
this.id = id;
}
public static C getEnumByName(String name) throws Exception {
for (C instance : C.values()) {
if (instance.getName().equals(name)) return instance;
}
throw new Exception("There is no operations matches :" + name);
}
public String getName() {
return name;
}
public String getId() {
return id;
}
}
}
One option is to have them all implement a common interface called, say, Named:
interface Named {
String getName();
}
Now you can create a generic method like this:
static <E extends Enum<E> & Named> E getEnumByName(Class<E> enumClass, String name) throws Exception {
return Arrays.stream(enumClass.getEnumConstants())
.filter(e -> e.getName().equals(name))
.findAny()
.orElseThrow(() -> new Exception("There is no operations matches :" + name));
}
And call it like this:
A a = getEnumByName(A.class, "Apply");
Consider using the static Enum valueOf() method. You can call it generically as follows or just call it directly. See this answer for details.
static <E extends Enum<E>> E getEnumByName(Class<E> enumClass, String name) {
return Enum.valueOf(enumClass, name);
}

Accessing 2 models with 1 controller in Java MVC?

Is accessing 2 models with 1 controller in Java MVC alright?
The code looks similar to this:
This is the 1st model
public class People {
private String id, name;
public People(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
}
I'm still not sure about PeopleList class necessity. This is the 2nd model.
public class PeopleList extends ArrayList<People>{
#Override
public boolean add(People e) {
return super.add(e);
}
#Override
public int size() {
return super.size();
}
}
Here is the controller:
public class PeopleListController {
PeopleList peopleList;
public People findPeopleById(String id) {
People person = new People("", "");
for (People p : peopleList) {
if (p.getId().equals(id)) {
return p;
}
}
return person;
}
}
In the peopleListController, I accessed people using p.getId().
Is it alright? If it is, then it means I don't need to create
peopleController.
Or should I access p.getId() via a new controller
called peopleController?
Or should I just remove the peopleList class
and make an arrayList in this controller? ArrayList peopleList
Thank you for your time.

Categories