Javafx observable list of values that are themselves observable? - java

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]]]

Related

Testing Array element using Junit in Eclipse

I want to test if it adds all of the elements into the group array using Junit.
Here's the code:
public class Group{
private String groupName;
private ArrayList<Item> group = new ArrayList<Item>();
public Group(String groupName) {
super();
this.groupName = groupName;
}
public String getGroupName() {
return groupName;
}
public void setGroupName(String groupName) {
this.groupName = groupName;
}
public void addItem(Item item) {
group.add(item);
}
public Item getItem(int index) {
return group.get(index);
}
public String toString(boolean includePrice) {
String string = "Group \"" + groupName + "\" contains...\n";
for (int i = 0; i < group.size(); i++) {
Item item = group.get(i);
String itemString = includePrice? item.toString() : item.getDisplayName();
string = string + (i + 1) + ") " + itemString + "\n";
}
return string;
}
}
There are 2 ways to test if addItem method adds all of the elements into the group array-
1st way
Create a method getItems like this
public List<Item> getItems() {
return group;
}
and in the junit test call the getItems method to check if all the elements are added
#Test
void shouldAddAllItems1() {
Group group = new Group("group1");
List<Item> expectedItems = Arrays.asList(new Item("item1"), new Item("item2"));
group.addItem(new Item("item1"));
group.addItem(new Item("item2"));
assertEquals(expectedItems, group.getItems());
}
2nd way
Use the getItem(int index) method to check if all the elements are added.
#Test
void shouldAddAllItems2() {
Group group = new Group("group2");
group.addItem(new Item("item1"));
group.addItem(new Item("item2"));
assertEquals(new Item("item1"), group.getItem(0));
assertEquals(new Item("item2"), group.getItem(1));
}
Note: For both solutions, you will need to override the equals and hashcode methods in Item class for the equality check to work.

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 + '\'' +
'}';
}
}

Ignore existing item in the LInkedList

I am trying to ignore the existing item that I added in the duplicate.
Normally, if the item does not exist it will eventually added the item to the
LinkedList
When I try to added item again, I just wanted to ignore the adding process and the increment the value by 1.
But the problem is it keep adding the items to the LinkedList.
Can someone explain to me?
class Item{
Store store;
String name;
String code;
String number;
public Item(String name, String code){
this.name = name;
this.code = code;
number = 0;
}
public boolean itemExists(String name, String code){
return this.name.equals(name) && this.code.equals(code);
}
public void increment(){ number++; }
#Override
public String toString(){ return store.getName()+ " " + name + " " + code + " " +number; }
}
Items will be added to the factory.
class Factory{
private LinkedList<Item> items = new LinkedList<Item>():
private String name;
private String number;
public Factory(String name, String number){
this.name = name;
this.number = number;
}
public void getName(){
return name;
}
public void addItem(String name, String code){
items.add(new Item(this, name, code));
}
#Override
public String toString(){ return name + " " + number; }
public List<Item> getItems{
return items;
}
}
The factory then delivery to the store.
class Store{
private LinkedList<Factory> factories = new LinkedList<>();
public Store(){
factories.add(new Factory("MayFlower", "01");
factories.add(new Factory("SunFlower", "02");
factories.get(0).addItem("GTA", "001A");
factories.get(0).addItem("GTA", "002A");
factories.get(0).addItem("GTA", "003A");
factories.get(1).addItem("Sonic", "022A");
factories.get(1).addItem("Sonic", "023B");
factories.get(1).addItem("Sonic", "024C");
}
public List<Item> getItemFromFact(){
List<Item> temp = new ArrayList<>();
for(Factory factory: factories)
for(Item item: factory.getItems())
temp.add(item);
return temp;
}
}
The customer buy items at the store.
class Customer{
private LinkedList<Item> items = new LinkedList<>();
public static void main(String args[]){
new Customer.view();
}
private void view(){
for(Item item: items)
System.out.println(item);
}
private void adding(){
String name = "GTA";
String code = "001A":
List<Item> item = item(name, code);
if(!item.isEmpty()){
items.add(item);
item.increment(); // will increment the value;
}
else{
System.out.println("Item does not exists");
}
}
private List<Item> item(String name, String code){
List<item> temp = new ArrayList<>();
List<item> fromStore = new Store().getItemFromFact();
for(Item item: fromStore)
if(item.itemExists(name, code))
temp.add(item)
return temp;
}
}
The main problem is in the item class under item(). If I try with the same item again, it will just add another it become like this.
MayFlower GTA 001A 1
MayFlower GTA 001A 1
The result should be
MayFlower GTA 001A 2
after I added another item.
I problem I have is that I don't know how to match the item from exisiting.
If someone know the solution.
That's would be very helpful thanks.
There are so many problems in your design and code. I've discussed some of them as given below:
I do not understand why you need a reference to Store in Item. An Item should not know which Store or Factory it is going to belong to.
I also didn't understand the purpose of the attribute, number in Store. An Item shouldn't know how many numbers of it is present in a Store or Factory. If you keep it for any reason, it should be of a numeric type (e.g. int, double etc.) so that you can perform arithmetic operations on it.
Instead of a LinkedList of Item objects in Factory, you should have a variable of type, Map<String, Integer> and you can call it stock. The key in this Map is the unique identifier of the item, which is the combination code and name as per your requirement and the value will be the available number/quantity of the Item. Given below is a minimal verifiable example of how you can maintain stock:
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
class Item {
String name;
String code;
public Item(String name, String code) {
this.name = name;
this.code = code;
}
public String getName() {
return name;
}
public String getCode() {
return code;
}
#Override
public String toString() {
return "Name: " + name + "Code :" + code;
}
}
class Factory {
private Map<String, Integer> stock = new HashMap<String, Integer>();
private String name;
public Factory(String name) {
this.name = name;
}
public void addItem(Item item) {
if (item != null) {
String key = item.getName() + ":" + item.getCode();
stock.put(key, stock.getOrDefault(key, 0) + 1);
}
}
public void showStock() {
for (Entry<String, Integer> entry : stock.entrySet()) {
System.out.println("Item = " + entry.getKey() + ", Available quantity = " + entry.getValue());
}
}
}
public class Main {
public static void main(String[] args) {
Factory factory = new Factory("MayFlower");
factory.addItem(new Item("GTA", "001A"));
factory.addItem(new Item("GTA", "001A"));
factory.addItem(new Item("GTA", "002A"));
factory.addItem(new Item("GTA", "003A"));
factory.addItem(new Item("GTA", "003A"));
factory.addItem(new Item("GTA", "003A"));
factory.showStock();
}
}
Output:
Item = GTA:002A, Available quantity = 1
Item = GTA:003A, Available quantity = 3
Item = GTA:001A, Available quantity = 2

How does JavaBeans Property Adapter work?

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

Outlineview with specific relation with nodes and sub-nodes check-boxes

I have a question about the handling of node properties in an outlineview.
I have three level of nodes, the rootNode, the node and each node may have sub-nodes. Apart from the rootNode, all nodes and subnodes shall have the same (Boolean => checkbox) property. In my outlineview I have two columns, the node(s) column, and a property column with checkboxes.
What I need now is the behaviour, that when I activate the checkbox of a node, all its sub-nodes checkboxes shall be activated as well, when I deactivate the checkbox of a node, all its sub-nodes checkboxes shall be de-activated as well. If I expand the tree to see the sub-nodes, each sub-node may be selected as well.
My current code looks like the following (some parts are found on the internet):
The main api
public class Category {
private String name;
private Boolean x;
public Category() {
this("empty");
}
public Category(String name) {
this.name = name;
}
public Category(String name, Boolean x) {
this.name = name;
this.x = x;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getx() {
return x;
}
public void setx(Boolean x) {
this.x = x;
}
}
My ChildFactory for the nodes (category) looks like
public class CategoryChildrenFactory extends ChildFactory.Detachable<Category> {
/* detachable has no real effect on the current code, used to control
the life cylce */
#Override
protected boolean createKeys(List<Category> toPopulate) {
toPopulate.add(new Category("Cat1", false));
toPopulate.add(new Category("Cat2", false));
toPopulate.add(new Category("Cat3", false));
return true;
}
#Override
protected Node createNodeForKey(Category key) {
AllNode cn = new AllNode(key);
return cn;
}
}
and for the sub-nodes
public class MovieChildrenFactory extends ChildFactory<String>{
Category category;
public MovieChildrenFactory(Category category) {
this.category = category;
}
#Override
protected boolean createKeys(List<String> toPopulate) {
toPopulate.add("m_1");
toPopulate.add("m_2");
toPopulate.add("m_3");
return true;
}
#Override
protected Node createNodeForKey(String key) {
return new AllNode(category, key);
}
}
The nodes creation is put into a single class for both types (nodes, subnodes)
public class AllNode extends AbstractNode {
Category category;
String title;
private Sheet.Set set;
public AllNode(Category category) {
this(category, null);
this.category = category;
set = Sheet.createPropertiesSet();
}
public AllNode(Category category, String title) {
super(Children.LEAF, Lookups.singleton(category));
if (title == null) {
this.setChildren(Children.create(new MovieChildrenFactory(category), true));
}
this.title = title;
set = Sheet.createPropertiesSet();
}
#Override
public String getHtmlDisplayName() {
String name = "";
if (title == null) {
name = category.getName();
} else {
name = title;
}
return name;
}
#Override
protected Sheet createSheet() {
Sheet sheet = Sheet.createDefault();
Category obj = getLookup().lookup(Category.class);
PlotMathProperties pmp = new PlotMathProperties(obj, title);
set.put(pmp.getMyProperty());
sheet.put(set);
return sheet;
}
}
The properties are handled by
public class PlotMathProperties {
private MyProperty myProperty;
private String categoryName;
private String title;
public PlotMathProperties(Category category, String title) {
this.categoryName = category.getName();
this.title= title;
this.myProperty= new MyProperty ();
}
public XProperty getMyProperty () {
return myProperty;
}
public class MyProperty extends PropertySupport.ReadWrite<Boolean> {
private Boolean isMyProp = false;
public MyProperty () {
super("x", Boolean.class, "XPROP", "Is this a coloured or black and white movie");
}
#Override
public Boolean getValue() throws IllegalAccessException, InvocationTargetException {
return isMyProp ;
}
#Override
public void setValue(Boolean val) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
isMyProp = val;
if (isMyProp) {
System.out.println("active: " + categoryName + ", " + title);
} else {
System.out.println("de-active: " + categoryName + ", " + title);
}
}
}
}
Together with a TopComponent the outlineview looks nice and works well.
Has anyone an idea how to setup the the behaviour for the check-boxes
regards

Categories