Bind java.util.Stack with JavaFX (ListView or TableView) - java

I'm recently using JavaFX and would like to implement the Observer pattern by binding my stack update, with a ListView or TableView from JavaFX. However, I don't know what changes to make to my ComplexNumberStack class.
public class ComplexNumberStack extends Stack<ComplexNumber> {
private static ComplexNumberStack instance = null;
/** This method provide the unique instance of ComplexNumberStack. */
public static ComplexNumberStack getInstance() {
if (instance == null)
instance = new ComplexNumberStack();
return instance;
}
/**
* This method provides a secure implementation of massive pop of operandNumber operands from the stack
*
* #param operandNumber specifies the number of operands to be taken from the stack
* #return an iterator of complexNumber taken
*/
public Iterator<ComplexNumber> getOperand(int operandNumber) {
List<ComplexNumber> operands = new ArrayList<>();
for (int i = 0; i < operandNumber; i++) {
try {
operands.add(pop());
} catch (EmptyStackException e) {
Collections.reverse(operands);
operands.forEach(this::push);
throw new InvalidParameterException("There aren't enough operands into the stack");
}
}
return operands.iterator();
}
}

This example adds a wrapper class around your stack implementation which provides an ObservableList that can be:
Placed in a ListView AND
Respond to bindings (see the pop button disable property binding in the example app).
For it to work, the mutation operations (e.g. push/pop) must be called on the wrapper class rather than the underlying class.
There are more efficient ways of implementing this (e.g. don't subclass stack, instead implement the Deque interface and use an ObservableList directly as storage, extending ObservableListBase).
However, this is what I came up with that still kept your underlying class and it might be fine or easily adaptable for your purposes.
public record ComplexNumber(double real, double imaginary) {}
The underlying stack implementation is unchanged from the class in your question.
import java.security.InvalidParameterException;
import java.util.*;
public class ComplexNumberStack extends Stack<ComplexNumber> {
private static ComplexNumberStack instance = null;
/**
* This method provide an instance of a ComplexNumberStack.
*/
public static ComplexNumberStack getInstance() {
if (instance == null)
instance = new ComplexNumberStack();
return instance;
}
/**
* This method provides a secure implementation of massive pop of operandNumber operands from the stack
*
* #param operandNumber specifies the number of operands to be taken from the stack
* #return an iterator of complexNumber taken
*/
public Iterator<ComplexNumber> getOperand(int operandNumber) {
List<ComplexNumber> operands = new ArrayList<>();
for (int i = 0; i < operandNumber; i++) {
try {
operands.add(pop());
} catch (EmptyStackException e) {
Collections.reverse(operands);
operands.forEach(this::push);
throw new InvalidParameterException("There aren't enough operands into the stack");
}
}
return operands.iterator();
}
}
Provides observability for the stack.
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.EmptyStackException;
public class ObservedComplexNumberStack {
private final ObservableList<ComplexNumber> observableList;
public ObservedComplexNumberStack(ComplexNumberStack complexNumberStack) {
observableList = FXCollections.observableList(complexNumberStack);
}
public ComplexNumber pop() {
if (observableList.size() == 0) {
throw new EmptyStackException();
}
return observableList.remove(observableList.size() - 1);
}
public ComplexNumber push(ComplexNumber number) {
observableList.add(number);
return number;
}
public ObservableList<ComplexNumber> getObservableList() {
return FXCollections.unmodifiableObservableList(observableList);
}
}
Test application.
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import java.util.concurrent.ThreadLocalRandom;
public class StackApplication extends Application {
#Override
public void start(Stage stage) {
ObservedComplexNumberStack stack = new ObservedComplexNumberStack(
ComplexNumberStack.getInstance()
);
ListView<ComplexNumber> listView = new ListView<>(stack.getObservableList());
listView.setPrefSize(80, 150);
listView.setCellFactory(param -> new ListCell<>() {
#Override
protected void updateItem(ComplexNumber item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText("");
return;
}
setText(String.format("%.2f + %.2fi", item.real(), item.imaginary()));
}
});
Button push = new Button("Push");
push.setOnAction(e -> {
stack.push(randomNum());
scrollToLastItem(listView);
});
Button pop = new Button("Pop");
pop.setOnAction(e -> {
stack.pop();
scrollToLastItem(listView);
});
pop.disableProperty().bind(Bindings.isEmpty(listView.getItems()));
HBox controls = new HBox(10, push, pop);
VBox layout = new VBox(10, controls, listView);
layout.setPadding(new Insets(10));
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private void scrollToLastItem(ListView<ComplexNumber> listView) {
if (listView.getItems().size() > 0) {
listView.scrollTo(listView.getItems().size() - 1);
}
}
private ComplexNumber randomNum() {
ThreadLocalRandom r = ThreadLocalRandom.current();
return new ComplexNumber(r.nextDouble(9), r.nextDouble(9));
}
public static void main(String[] args) {
launch();
}
}
Potential alteratives or improvements
hmm .. this looks a bit brittle - external code could change the stack without notifying the list (especially, since is a singleton and potential collaborators spread across the world
Yes, it's true, its buyer beware :-)
The alternate proposed solution of implementing Deque with operations directly on a backing observableList is probably preferred, but I'm not going to write that at this time (it would be quite a bit more work to do well).
The solution in this answer uses the FXCollections list wrapper, which, incidentally, on its own is another simple solution to this problem:
FXCollections.observableList(ComplexNumberStack.getInstance());
Though, it has some disadvantages:
Changes to the underlying stack will not be observed (also true of the solution in this answer).
You need to change the list to observe changes and the list won't have push/pop ops (unlike the solution in this answer, which does at least provide push/pop ops which will be observed).
If you are interested in how the JavaFX framework implementation wrapper works, you can see the code for ObservableListWrapper.
If you wished to, you could copy a version of ObservableListWrapper to your own package (you don't want to depend on com.sun code directly), then subclass it and adapt it to add your additional push/pop ops (as suggested by kleopatra in comments).

Related

Is there a way to change a Java property without firing a value changed event to it's listeners?

What I'm trying to do
I'm looking for a way to change a property, without a call to the listeners's changed method.
More specifically I'm trying to implement an undo/redo functionality. The way I've implemented it is as following, in an example with a BooleanProperty and a JavaFX CheckBox.
The selectedProperty of the CheckBox is changed by a mouse click.
A BooleanProperty (actually a JavaFX SimpleBooleanProperty) is changed because it is bound bidirectionally to the selectedProperty
The ChangeListener of the BooleanProperty registers this and adds a Command on the application's undoStack. The Command stores the property, the old and the new value.
The user clicks the undo button
Via the button the application takes that last Command from the stack and calls it's undo() method.
The undo() method changes the BooleanProperty back.
The ChangeListener registers this change again and creates a new Command
An endless cycle is created
My Hacky Solution
The way I did it is by passing the ChangeListener to the Command object. Then the undo() method first removes the ChangeListener, changes the BooleanProperty and then adds the ChangeListener again.
It feels wrong and hacky to pass the ChangeListener to the Command (in my actual implementation in the 3. step there are actually a few more classes between the ChangeListener and the Command which now all need to know about the ChangeListener)
My Question
Is this really the way to do it? Isn't there a way to change the property in step 6 and just tell it to not inform it's listeners? Or at least to get it's listeners?
There's no supported way of bypassing listeners, as you describe. You just need to build this logic into your undo/redo mechanism. The idea is basically to set a flag if you are performing an undo/redo, and not add the change to your stack if so.
Here's a very simple example: note this is not production quality - for example typing in a text control will add to the stack for every character change (keeping copies of the current text at each change). In real code, you should coalesce these changes together.
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
public class UndoManager {
private boolean performingUndoRedo = false ;
private Deque<Command<?>> undoStack = new LinkedList<>();
private Deque<Command<?>> redoStack = new LinkedList<>();
private Map<Property<?>, ChangeListener<?>> listeners = new HashMap<>();
public <T> void register(Property<T> property) {
// don't register properties multiple times:
if (listeners.containsKey(property)) {
return ;
}
// FIXME: should coalesce (some) changes on the same property, so, e.g. typing in a text
// control does not result in a separate command for each character
ChangeListener<? super T> listener = (obs, oldValue, newValue) -> {
if (! performingUndoRedo) {
Command<T> cmd = new Command<>(property, oldValue, newValue) ;
undoStack.addFirst(cmd);
}
};
property.addListener(listener);
listeners.put(property, listener);
}
public <T> void unregister(Property<T> property) {
listeners.remove(property);
}
public void undo() {
if (undoStack.isEmpty()) {
return ;
}
Command<?> command = undoStack.pop();
performingUndoRedo = true ;
command.undo();
redoStack.addFirst(command);
performingUndoRedo = false ;
}
public void redo() {
if (redoStack.isEmpty()) {
return ;
}
Command<?> command = redoStack.pop();
performingUndoRedo = true ;
command.redo();
undoStack.addFirst(command);
performingUndoRedo = false ;
}
private static class Command<T> {
private final Property<T> property ;
private final T oldValue ;
private final T newValue ;
public Command(Property<T> property, T oldValue, T newValue) {
super();
this.property = property;
this.oldValue = oldValue;
this.newValue = newValue;
}
private void undo() {
property.setValue(oldValue);
}
private void redo() {
property.setValue(newValue);
}
#Override
public String toString() {
return "property: "+property+", from: "+oldValue+", to: "+newValue ;
}
}
}
And here's a quick test harness:
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ListCell;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class UndoExample extends Application {
#Override
public void start(Stage stage) throws Exception {
ComboBox<Color> textColor = new ComboBox<Color>();
textColor.getItems().addAll(Color.BLACK, Color.RED, Color.DARKGREEN, Color.BLUE);
textColor.setValue(Color.BLACK);
textColor.setCellFactory(lv -> new ColorCell());
textColor.setButtonCell(new ColorCell());
CheckBox italic = new CheckBox("Italic");
TextArea text = new TextArea();
updateStyle(text, textColor.getValue(), italic.isSelected());
ChangeListener<Object> listener = (obs, oldValue, newValue) ->
updateStyle(text, textColor.getValue(), italic.isSelected());
textColor.valueProperty().addListener(listener);
italic.selectedProperty().addListener(listener);
UndoManager undoMgr = new UndoManager();
undoMgr.register(textColor.valueProperty());
undoMgr.register(italic.selectedProperty());
undoMgr.register(text.textProperty());
Button undo = new Button("Undo");
Button redo = new Button("Redo");
undo.setOnAction(e -> undoMgr.undo());
redo.setOnAction(e -> undoMgr.redo());
HBox controls = new HBox(textColor, italic, undo, redo);
controls.setSpacing(5);
BorderPane root = new BorderPane(text);
root.setTop(controls);
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
private void updateStyle(TextArea text, Color textColor, boolean italic) {
StringBuilder style = new StringBuilder()
.append("-fx-text-fill: ")
.append(hexString(textColor))
.append(";")
.append("-fx-font: ");
if (italic) {
style.append("italic ");
}
style.append("13pt sans-serif ;");
text.setStyle(style.toString());
}
private String hexString(Color color) {
int r = (int) (color.getRed() * 255) ;
int g = (int) (color.getGreen() * 255) ;
int b = (int) (color.getBlue() * 255) ;
return String.format("#%02x%02x%02x", r, g, b);
}
private static class ColorCell extends ListCell<Color> {
private Rectangle rect = new Rectangle(25, 25);
#Override
protected void updateItem(Color color, boolean empty) {
super.updateItem(color, empty);
if (empty || color==null) {
setGraphic(null);
} else {
rect.setFill(color);
setGraphic(rect);
}
}
}
public static void main(String[] args) {
Application.launch(args);
}
}
There is pretty much not a possibility to do this without "hacks"!
However, there is also a shorter solution, via using reflection:
/**
* Set the value of property without firing any change event.
* The value of property will be set via reflection.
* This property must be "Base" property such as {#link DoublePropertyBase}.
*
* #param property | Property to set!
* #param newValue | New value of property.
*/
public static <T> void setPropertyWithoutFiringEvent(Property<T> property, T newValue)
{
Class<?> cls = property.getClass();
while (cls != null) //While until helper variable is found
{
try
{
Field fieldH = cls.getDeclaredField("helper"), fieldV = cls.getDeclaredField("valid");
fieldH.setAccessible(true);
fieldV.setAccessible(true);
Object helper = fieldH.get(property), valid = fieldV.getBoolean(property); //Temporary values
fieldH.set(property, null); //Disabling ExpressionHelper by setting it on null;
property.setValue(newValue);
fieldH.set(property, helper); //Setting helper back!
fieldV.set(property, valid); //Important
return;
}
catch (Exception e)
{
cls = cls.getSuperclass(); //If not found go to super class of property next time!
}
}
System.err.println("Property " + property + " cant be set because variable \"helper\" was not found!");
}
This function temporarily disables ExpressionHelper what is an object responsible for firing change events, and then it will change the value of property and enable ExpressionHelper back! This will cause that one change will not be notified!
If the reflection is not friendly solution for you, then just use the solution above however this one is far shorter and simpler.

JavaFX: get a running total of values from an ObservableSet of DoubleProperty

I have an ObservableSet<DoubleProperty> itemSet which holds the DoubleProperty prop1 for any number of instances of Item.
I want to create another DoubleProperty total that will reflect an up to date total of all DoubleProperty's in itemSet.
The double value of each DoubleProperty in the set can change independently. The total value would need to reflect those changes.
This is the Item class:
class Item {
DoubleProperty prop1;
DoubleProperty prop2;
public Item() {
this.prop1 = new SimpleDoubleProperty(1.0);
this.prop2 = new SimpleDoubleProperty(2.0);
itemSet.add(this.prop1);
}
}
This is a sort of global variable class...
class ItemValue {
private ItemValue itemValue = null;
ObservableSet<DoubleProperty> itemSet = FXCollections.observableSet();
DoubleProperty total;
private ItemValue() {
this.total = new SimpleDoubleProperty(0.0);
// create several Item's here...
itemSet.addListener((InvalidationListener) observable -> {
/*
Something which binds the total
I figure it will need to go here so that if new items get added the total will reflect that?
*/
});
}
public ItemValue get() {
if (itemValue == null) itemValue = new ItemValue();
return itemValue;
}
As far as I know there is no built in way to do this simply. However, there's a couple of ways you could do this. The most(?) efficient, but more complicated way would be to listen to the ObservableSet for additions/removals, observe any current DoubleProperty elements, and modify the total property yourself.
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
public class SomeClass {
private final ReadOnlyDoubleWrapper total = new ReadOnlyDoubleWrapper(this, "total");
private void setTotal(double total) { this.total.set(total); }
public final double getTotal() { return total.get(); }
public final ReadOnlyDoubleProperty totalProperty() { return total.getReadOnlyProperty(); }
private final ObservableSet<DoubleProperty> propertySet = FXCollections.observableSet();
private final ChangeListener<Number> elementListener = this::elementValueChanged;
private final WeakChangeListener<Number> weakElementListener =
new WeakChangeListener<>(elementListener);
public SomeClass() {
propertySet.addListener(this::propertySetChanged);
}
private void propertySetChanged(SetChangeListener.Change<? extends DoubleProperty> change) {
if (change.wasRemoved()) {
change.getElementRemoved().removeListener(weakElementListener);
setTotal(getTotal() - change.getElementRemoved().get());
}
if (change.wasAdded()) {
change.getElementAdded().addListener(weakElementListener);
setTotal(getTotal() + change.getElementAdded().get());
}
}
private void elementValueChanged(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
setTotal(getTotal() - oldValue.doubleValue() + newValue.doubleValue());
}
}
Here the SetChangeListener, whose value is a method reference to propertySetChanged, watches for any changes to the ObservableSet. When a DoubleProperty is added it adds said property's value to the current total. When a DoubleProperty is removed it subtracts said property's value from the current total. This listener also adds or removes a ChangeListener to or from the DoubleProperty when it is added or removed from the ObservableSet, respectively.
The ChangeListener, whose value is a method reference to elementValueChanged, updates the total property when the value of any DoubleProperty changes. It does this by first subtracting the old value and then adding the new value to the current total. It is actually the WeakChangeListener, which wraps the original ChangeListener, that is added or removed. This helps avoid potential memory leaks. Remember to maintain a strong reference to the original ChangeListener when using WeakChangeListener otherwise the original ChangeListener may be garbage collected too soon.
A second option is to rebuild a binding every time the ObservableSet is invalidated and then bind the total property to said binding.
import javafx.beans.Observable;
import javafx.beans.binding.DoubleExpression;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
public class SomeClass {
private final ReadOnlyDoubleWrapper total = new ReadOnlyDoubleWrapper(this, "total");
private void setTotal(double total) { this.total.set(total); }
public final double getTotal() { return total.get(); }
public final ReadOnlyDoubleProperty totalProperty() { return total.getReadOnlyProperty(); }
private final ObservableSet<DoubleProperty> propertySet = FXCollections.observableSet();
public SomeClass() {
propertySet.addListener(this::propertySetInvalidated);
}
private void propertySetInvalidated(Observable observable) {
if (propertySet.isEmpty()) {
total.unbind();
setTotal(0.0);
} else if (propertySet.size() == 1) {
total.bind(propertySet.iterator().next());
} else {
DoubleExpression sum = null;
for (DoubleProperty property : propertySet) {
sum = (sum != null) ? sum.add(property) : property;
}
total.bind(sum);
}
}
}
In this case we add an InvalidationListener to the ObservableSet. This listener will be invoked whenever an element(s) is added to or removed from the ObservableSet. When this happens 1 of 3 things will happen:
If the ObservableSet is now empty unbind the total property and set it to zero.
This is a special case to deal with no elements
If the ObservableSet now only contains a single element simply bind the total property to said element.
Another special case dealing with a single element. It stops us from creating unnecessary objects and preforming unnecessary computations that would happen if we just skipped to the third branch.
Otherwise create one big binding that calculates the sum and then bind total to that binding.
This branch uses DoubleExpression.add(ObservableNumberValue). The resulting DoubleBinding from that method call will update whenever one of the two observables change. This is reduced into a single DoubleExpression which we then bind the total property to.
This second option will be less efficient because it requires iterating the entire ObservableSet every time. It also potentially leads to a lot of DoubleBinding objects being created. However, you may find it simpler to code/understand and the performance hit may not be significant enough for your application.
I would give the itemSet a change listener and just recalculate the total with some method every time its called.
See https://stackoverflow.com/a/44141262/8729420.

JavaFx: Populate ComboBox with different enums depending on another ComboBox

I have two ComboBoxes:
final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<>();
final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>();
Depending on the value chosen in comboBoxMainCategory, the comboBoxSubCategory should be populated with the corresponding enum.
public enum MainCategory { // extra enum class
EUROPE("Europe"),
USA("USA");
}
public enum SubCategoryEurope { // extra enum class
GERMANY("Germany"),
FRANCE("France");
}
public enum SubCategoryUSA {
COLORADO("Colorado"),
CALIFORNIA("California");
}
If "Europe" is chosen for comboBoxMainCategory, comboBoxSubCategory should be populated with SubCategoryEurope. If "USA", with SubCategoryUSA.
How do you achieve this?
Here's my code:
final ComboBox<MainCategory> comboBoxMainCategory = new ComboBox<();
final ComboBox<SubCategory> comboBoxSubCategory = new ComboBox<>();
comboBoxMainCategory.valueProperty().addListener((obs, oldValue,
newValue) ->
{
if (newValue == null) { // newValue: Europe || USA
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
} else if (newValue.equals(MainCategory.EUROPE)) {
comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryEurope.values()));
comboBoxSubCategory.setDisable(false);
} else {
comboBoxSubCategory.setItems(FXCollections.observableArrayList(SubCategoryUSA.values()));
comboBoxSubCategory.setDisable(false);}
});
Problem is, because comboBoxSubCategory is "SubCategory", there is a type error if it is populated with 'SubCategoryEurope' or 'SubCategoryUSA'.
What is the best way to solve this? Sorry if it's a silly question, I'm new to JavaFx.
Thanks a lot!
I wouldn't use enums at all, since this doesn't allow for data manipulation without recompiling. If you insist on using enums though, you need to use Object or a interface implemented with both subcategory enum types as parameter type for comboBoxSubCategory:
comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
if (newValue == null) { // newValue: Europe || USA
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
} else {
comboBoxSubCategory.setDisable(false);
List<? extends Object> list;
switch (newValue) {
case EUROPE:
list = Arrays.asList(SubCategoryEurope.values());
break;
default:
list = Arrays.asList(SubCategoryUSA.values());
break;
}
comboBoxSubCategory.getItems().setAll(list);
}
});
The better approach would be using a Map<String, List<String>> to store the data:
Map<String, List<String>> data = new HashMap<>();
data.put("EUROPE", Arrays.asList("GERMANY", "FRANCE"));
data.put("USA", Arrays.asList("COLORADO", "CALIFORNIA"));
comboBoxMainCategory.valueProperty().addListener((obs, oldValue, newValue) -> {
List<String> list = data.get(newValue);
if (list != null) {
comboBoxSubCategory.setDisable(false);
comboBoxSubCategory.getItems().setAll(list);
} else {
comboBoxSubCategory.getItems().clear();
comboBoxSubCategory.setDisable(true);
}
});
Just for fun (and to flesh out my comments): a more versatile approach than those in the other answers is to move away the focus of interest from the concrete nature of the backing data to a more general solution of the use-case at hand. The drawback of letting the UI implement special cases is always the same - you have to do it over and over again for each special UI and each special data type. The way out is always the same, too: implement a Model that takes over the general aspect and re-use that in concrete UI/data contexts.
The general aspects here are:
there's list of items with each having a list of dependent objects (same or different type)
this (let's call it root) list of items is shown in a control
from root list, a single item can be chosen (aka: selected)
another control should show the dependents of the root
The general approach is to have a Model that
manages list of items
has the notion of one of those items as selected (or current or active or ..)
manages a list of dependent items that always is the dependent list of the selected item
its state (root items, current item, dependent items) is exposed as properties
The advantages of such a Model are
can be formally and rigorouly tested, so using code can rely on its proper functioning
it's re-usable for any data context
it's re-usable for many controls
usage is pretty simple by binding
In the example below, the Model is named RelationModel which expects root items of type RelationProvider (which allows access to a list of dependents, it's one option, could just as well use f.i. a Function to build the dependents). It is used once with a plain Map of String/list and once with enums of Continents/Countries, each very simple to implement. Note that the resulting UI is blissfully unaware of the nature of the data, implemented solely against the model.
Naturally, not production grade, in particular, not formally tested and the model with just the barest functionality :)
public class CombosWithCategories extends Application {
public interface RelationProvider<T> {
default ObservableList<T> getRelations() {
return emptyObservableList();
};
}
/**
* A model that manages a list of RelationProviders and has the notion
* of a current relationProvider with relations (it's a kind-of selectionModel).
*
* <T> the type of elements in the list of relations
*/
public static class RelationModel<T> {
/**
* all relationProviders managed by this model
*/
private ListProperty<RelationProvider<T>> relationProviders;
/**
* The owner of the relations. Must be contained in the providers managed
* by this model.
*/
private ObjectProperty<RelationProvider<T>> relationProvider;
private ListProperty<T> relations;
public RelationModel() {
initProperties();
}
/**
* The RelationProviders managed by the model.
*/
public ListProperty<RelationProvider<T>> relationProvidersProperty() {
return relationProviders;
}
/**
* The RelationProvider that manages the current relations.
*/
public ObjectProperty<RelationProvider<T>> relationProviderProperty() {
return relationProvider;
}
public RelationProvider<T> getRelationProvider() {
return relationProviderProperty().get();
}
public ListProperty<T> relations() {
return relations;
}
/**
* Callback from invalidation of current relationProvider.
* Implemented to update relations.
*/
protected void relationProviderInvalidated() {
RelationProvider<T> value = getRelationProvider();
relations().set(value != null ? value.getRelations() : emptyObservableList());
}
/**
* Creates and wires all properties.
*/
private void initProperties() {
relationProviders = new SimpleListProperty<>(this, "relationProviders", observableArrayList());
relationProvider = new SimpleObjectProperty<>(this, "relationProvider") {
#Override
protected void invalidated() {
// todo: don't accept providers that are not in the list
relationProviderInvalidated();
}
};
relations = new SimpleListProperty<>(this, "relations");
relationProviderInvalidated();
}
}
/**
* Implement the ui against a RelationModel. Here we create
* the same UI with a model backed by enums or a Map, respectively
*/
private Parent createContent() {
TabPane tabPane = new TabPane(
new Tab("Enums", createRelationUI(createEnumRelationModel())),
new Tab("Manual map", createRelationUI(createMapRelationModel()))
);
return new BorderPane(tabPane);
}
/**
* Common factory for UI: creates and returns a Parent that
* contains two combo's configured to use the model.
*/
protected <T> Parent createRelationUI(RelationModel<T> model) {
ComboBox<RelationProvider<T>> providers = new ComboBox<>();
providers.itemsProperty().bind(model.relationProvidersProperty());
providers.valueProperty().bindBidirectional(model.relationProviderProperty());
ComboBox<T> relations = new ComboBox<>();
relations.itemsProperty().bind(model.relations());
relations.valueProperty().addListener((src, ov, nv) -> {
LOG.info("relation changed: " + nv);
});
return new VBox(10, providers, relations);
}
// ------------- manual with maps
/**
* On-the-fly creation of a RelationModel using a backing map.
*/
protected RelationModel<String> createMapRelationModel() {
RelationModel<String> model = new RelationModel<>();
Map<String, ObservableList<String>> data = new HashMap<>();
data.put("EUROPE", observableArrayList("GERMANY", "FRANCE"));
data.put("AMERICA", observableArrayList("MEXICO", "USA"));
for (String key: data.keySet()) {
model.relationProvidersProperty().add(new RelationProvider<String>() {
#Override
public ObservableList<String> getRelations() {
return data.get(key);
}
#Override
public String toString() {
return key;
}
});
}
return model;
}
//-------------------- enum
/**
* RelationModel using Enums.
*/
protected RelationModel<Object> createEnumRelationModel() {
RelationModel<Object> model = new RelationModel<Object>();
model.relationProvidersProperty().setAll(Continent.values());
return model;
}
public enum EuropeanCountry {
FRANCE, GERMANY;
}
public enum AmericanCountry {
MEXICO, CANADA, USA;
}
public enum Continent implements RelationProvider<Object> {
AMERICA(AmericanCountry.values()),
EUROPE(EuropeanCountry.values())
;
ObservableList<Object> subs;
private Continent(Object[] subs) {
this.subs = FXCollections.observableArrayList(subs);
}
#Override
public ObservableList<Object> getRelations() {
return FXCollections.unmodifiableObservableList(subs);
}
}
#Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent()));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(CombosWithCategories.class.getName());
}
Create a generic placeholder interface
public interface EnumPlaceHolder<E extends Enum<?>> {
public abstract String getDisplayValue();
public abstract E getEnum();
}
Create an implementation for all your enums. For example
public class EuropePlaceholder implements EnumPlaceHolder<Europe> {
private final Europe value;
public EuropePlaceholder(Europe pValue){
value = pValue;
}
#Override
public String getDisplayValue() {
// here you create a user-friendly version of your enum for display
return value.toString();
}
#Override
public Europe getEnum() {
return value;
}
}
Then change the type of your ComboBox<Enum> to ComboBox<EnumPlaceholder<?>> and you can add any of your implemented EnumPlaceholders to it. When retrieving the selected item you can check which one is contained via instance check
EnumPlaceholder<?> selectedItem = ...;
if(selectedItem instanceof EuropePlaceholder){
Europe selectedEuropeEnum = (Europe) selectedItem.getEnum();
} else if(....){
// check with else if for your other enums
}
And to display your enum in your combobox you call the getDisplayValue() of the EnumPlaceholder and show the returned String in your cell :)
EDIT
Tho in general i have to agree with fabians answer. You shouldn't use enums for a construct like this. Rather use a Map<> or a List<> with appropriate content and structure.

Cleaning up an Iterable when not all elements are read

Getting my feet wet on RxJava. I have a class that implements Iterable I want to convert to an Observable. Using Observable.from() seems easy. However I need to setup and tear-down the code that provides me the individual entries (the next() in the iterator.
When I run through the entire sequence, that's easy. I added the call to the hasNext() function and when there is no next I run the teardown. However one of the very promising operators I want to use is take(someNumber). If the taking stops before the Iterator runs out of items, the cleanup code never runs.
What can I do to get my cleanup running? If using something else than from(Iterable), I'm OK with that. I'm stuck on Java6 for now. To illustrate my predicament I created a minimal sample:
Update: Based on feedback not to mix Iterator and Iterable together, I updated the code below. To understand the original answers, the original code is in that gist.
Updated Test code (still bad):
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Action1;
/**
* #author stw
*
*/
public class RXTest {
/**
* #param args
*/
public static void main(String[] args) {
ComplicatedObject co = new ComplicatedObject();
Observable<FancyObject> fancy = Observable.from(co);
// if the take is less than the elements cleanup never
// runs. If you take the take out, cleanup runs
fancy.take(3).subscribe(
new Action1<FancyObject>() {
public void call(FancyObject item) {
System.out.println(item.getName());
}
},
new Action1<Throwable>() {
public void call(Throwable error) {
System.out.println("Error encountered: " + error.getMessage());
}
},
new Action0() {
public void call() {
System.out.println("Sequence complete");
}
}
);
}
}
The fancy object:
import java.util.Date;
import java.util.UUID;
/**
* #author stw
*
*/
public class FancyObject {
private String name = UUID.randomUUID().toString();
private Date created = new Date();
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Date getCreated() {
return this.created;
}
public void setCreated(Date created) {
this.created = created;
}
}
The iterator:
import java.util.Iterator;
/**
* #author stw
*
*/
public class FancyIterator implements Iterator<FancyObject> {
private final ComplicatedObject theObject;
private int fancyCount = 0;
public FancyIterator(ComplicatedObject co) {
this.theObject = co;
}
public boolean hasNext() {
return this.theObject.hasObject(this.fancyCount);
}
public FancyObject next() {
FancyObject result = this.theObject.getOne(this.fancyCount);
this.fancyCount++;
return result;
}
}
The Iterable:
import java.util.Iterator;
import java.util.Vector;
/**
* #author stw
*
*/
public class ComplicatedObject implements Iterable<FancyObject> {
private boolean isInitialized = false;
Vector<FancyObject> allOfThem = new Vector<FancyObject>();
public Iterator<FancyObject> iterator() {
return new FancyIterator(this);
}
public boolean hasObject(int whichone) {
if (!this.isInitialized) {
this.setupAccesstoFancyObject();
}
return (whichone < this.allOfThem.size());
}
public FancyObject getOne(int whichone) {
if (!this.isInitialized) {
this.setupAccesstoFancyObject();
}
if (whichone < this.allOfThem.size()) {
return this.allOfThem.get(whichone);
}
// If we ask bejond...
this.isInitialized = false;
this.teardownAccessToFancyObjects();
return null;
}
private void setupAccesstoFancyObject() {
System.out.println("Initializing fancy objects");
for (int i = 0; i < 20; i++) {
this.allOfThem.addElement(new FancyObject());
}
this.isInitialized = true;
}
private void teardownAccessToFancyObjects() {
System.out.println("I'm doing proper cleanup here");
}
}
But the real question (thx #Andreas) seem to be:
What construct can I use to create an Observable when the underlying code need setup/teardown, especially when one expects that not all elements are pulled. The Iterable just was my first idea
Update 2: Based on Dave's answer I created a gist with my working solution. The iterator isn't perfect, but it's a start.
Observable.using is used for tearing down on termination (completion or error) or unsubscription. To use it you need to make the tear-down code accessible so that your source observable can look like this:
source = Observable.using(
resourceFactory,
observableFactory,
resourceDisposer);
With your code it might look like this:
source = Observable.using(
() -> new ComplicatedObject(),
co -> Observable.from(co),
co -> co.tearDown());
If you want that kind of control you need to separate the implementation of Iterable from Iterator. Iterable means the class can provide an Iterator that is meaningful in whatever fashion makes sense for the class.
However, if you implement Iterator in the same class, then you are stuck with only ever having one Iterator for each instance of ComplicatedObject. The correct approach is to implement
class FancyObjectIterator implements Iterator<FancyObject>
{
...
}
separately from ComplicatedObject so you can merely discard the partially-used iterators when you are done with them. ComplicatedObject should implement only Iterable<FancyObject>.
If you object to that approach because the iterator has more state that needs special cleanup, then something is wrong with your design. The only state an Iterator should be aware of is the current position in the base "collection", for a very loose definition of "collection" and "position" since the concept of an iterator can apply to much more than typical collections.
You cannot implement Iterator and Iterable at the same time, since Iterable.iterator() must return a new Iterator or every call.
Code is allowed to iterate the same Iterable multiple times in parallel.
Example: An over-simplified way to find duplicate elements in an Iterable:
Iterable<MyObject> myIterable = ...;
for (MyObject myObj1 : myIterable) {
for (MyObject myObj2 : myIterable) {
if (myObj1 != myObj2 && myObj1.equals(myObj2)) {
// found duplicate
}
}
}
The enhanced for loops used here will each use an Iterator.
As you can see, each Iterator must maintain it's own independent position. Therefore, the iterator() method needs to return a new object, with it's own state.
And for your question on cleanup code, an Iterator does not have a close() method. Iterator state should not require cleanup. If they absolutely must, a finalizer can do it, but finalizers may take a very long time to be invoked. The general recommendation for finalizers is: DON'T.

Transform an ObervableValue

I need to transform an ObservableValue.
For instance: I have an ObervableStringValue and I want to display the length of the string in a JavaFX controll. When I do: value.get().length() i just get an int, but i need an ObservableValue.
So i quickly wrote a wrapper my own:
/**
* Wraps an ObervableValue and offers a transformed value of it.
*
* #param <F> From type.
* #param <T> To type.
*/
public class TransformedValue<F, T> extends ObservableValueBase<T>
{
private final ObservableValue<F> original;
private final Function<F, T> function;
/**
* #param original ObservableValue to transform.
* #param function Transform function.
*/
public TransformedValue(ObservableValue<F> original, Function<F, T> function)
{
this.original = original;
this.function = function;
original.addListener((observable, oldValue, newValue) -> fireValueChangedEvent());
}
#Override
public T getValue()
{
return function.apply(original.getValue());
}
}
Usage:
new TransformedValue<>(someObservableStringValue, s -> s.length());
Here my questions:
Is my approach totaly stupid?
Is there a JavaFX way to do this?
Is there a third party library to do this?
Any suggestions to my code? (e.g. unregister listener)
Edit:
The example with String.length() was too simple, so here is the big story:
I have an ObservableList of sensors. Every sensor provides one ore more measurements, depending on type of sensor. The properties of a sensor are ObservabeValues. I display the sensors and their current measurements in a TreeTableView. Every sensor has its node and its measurements as subnodes. If will now focus on the timestamp column.
Initialisation of TreeTableColumns:
...
sensorTreeTimestamp.setCellValueFactory(cell -> cell.getValue().getValue().getTimestamp());
...
As there are totally different datatypes in the TreeTableView I have a own class SensorTreeValue to hold the data:
private static class SensorTreeValue
{
...
private final ObservableValue<String> timestamp;
...
It has one constructor to represent a sensor and one for a measurement:
private SensorTreeValue(Sensor sensor)
{
...
timestamp = new TransformedValue<>(sensor.getLastSeen(), (time) -> Utils.formatDateTime(time));
}
private SensorTreeValue(Sensor sensor, ValueType valueType)
{
...
timestamp = new TransformedValue<>(sensor.getLastMeasurement(), measure -> Utils.formatDateTime(measure.getTime()));
}
I know there is a asString(format) function. But this is not enough because I still need to get the time out of the measurement and I didn't find a format string to transform a date to a locale formatted string.
I also could place the logic into the CellValueFactory but there I would have to do a type check if its a Sensor or a Measurement.
Have a look at the EasyBind framework, which provides exactly this kind of functionality and whole lot more.
The example you suggest (creating an ObservableValue<Integer> representing the length of an ObservableValue<String>) is an example on the home page for the framework:
ObservableValue<String> text = ... ;
ObservableValue<Integer> textLength = EasyBind.map(text, String::length);
Use cases such as getting a "property of a property" are also shown on the project home page linked above.
Here's a way of doing it, but your structure is too complicated. I don't know how you're adding items to your table. If it's a TreeTableView<SensorTreeItem> I think it's too complicated. I would only go the trouble of a custom item like that if there were multiple levels of nodes. Like for a file/directory view, it's necessary.
I would make every line in the table a measurement but have the sensor name in the measurement class. Then when adding to the table you make a simple node for the sensor name if it doesn't already exist and add it to that node.
import java.util.Date;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleLongProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FXTest extends Application {
public static void main(String[] args) { launch(args); }
#Override
public void start(Stage stage) {
Label lbl1 = new Label();
Sensor sens1 = new Sensor();
SensorTreeValue stv1 = new SensorTreeValue(sens1);
lbl1.textProperty().bind(stv1.timeStamp.concat(" sens1"));
Label lbl2 = new Label();
Sensor sens2 = new Sensor();
SensorTreeValue stv2 = new SensorTreeValue(sens2, 0);
lbl2.textProperty().bind(stv2.timeStamp.concat(" sens2"));
Scene scene = new Scene(new VBox(5, lbl1, lbl2));
stage.setScene(scene);
stage.show();
Timeline timer = new Timeline(new KeyFrame(
javafx.util.Duration.millis(1000), ae -> {
sens1.lastSeen.set(System.currentTimeMillis());
sens2.lastMeasurement.get().time.set(System.currentTimeMillis());
}));
timer.setCycleCount(Animation.INDEFINITE);
timer.play();
}
private static class SensorTreeValue {
private final SimpleStringProperty timeStamp = new SimpleStringProperty();
private SensorTreeValue(Sensor sensor) {
//you can bind or set a property, not an ObsValue<T>
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastSeen.get()).toString();
},sensor.lastSeen));
}
private SensorTreeValue(Sensor sensor, int valueType) {
timeStamp.bind(Bindings.createStringBinding(() -> {
return new Date(sensor.lastMeasurement.get().time.get()).toString();
},sensor.lastMeasurement.get().time));
}
}
private static class Sensor{
private SimpleLongProperty lastSeen = new SimpleLongProperty();
private SimpleObjectProperty<Measure> lastMeasurement = new SimpleObjectProperty<>(new Measure());
}
private static class Measure{
private SimpleLongProperty time = new SimpleLongProperty();
private SimpleLongProperty value = new SimpleLongProperty();
}
}

Categories

Resources