Currently, what I want to do is use JAXB generated POJO to bind every Java properties to JavaFX components. To do that, I proceeded as followed :
I changed the default generation of JAXB to add PropertyChangeSupport to make POJO support binding.
I created a kind of factory which take a class instance at input and return a Map where the key is the property itself and the value the JavaFX component binded with the value of the property.
The returned map is displayed in a JFXPanel.
Here's a sample of my factory :
public static Map<Field, Node> createComponents(Object obj) throws NoSuchMethodException
{
Map<Field, Node> map = new LinkedHashMap<Field, Node>();
for (final Field field : obj.getClass().getDeclaredFields())
{
#SuppressWarnings("rawtypes")
Class fieldType = field.getType();
if (fieldType.equals(boolean.class) || (fieldType.equals(Boolean.class))) //Boolean
{
map.put(field, createBool(obj, field));
}
else if (fieldType.equals(int.class) || (fieldType.equals(Integer.class))) //Integer
{
map.put(field, createInt(obj, field));
}
else if (fieldType.equals(BigInteger.class)) //BigInteger
{
map.put(field, createBigInt(obj, field));
}
else if (fieldType.equals(long.class) || fieldType.equals(Long.class)) //Long
{
map.put(field, createLong(obj, field));
}
else if (fieldType.equals(String.class)) //String
{
map.put(field, createString(obj, field));
}
...
}
return map;
}
public static Node createBool(Object obj, final Field field) throws NoSuchMethodException
{
System.out.println(field.getType().getSimpleName() + " spotted");
JavaBeanBooleanProperty boolProperty = JavaBeanBooleanPropertyBuilder.create().bean(obj).name(field.getName()).build();
boolProperty.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> arg0, Boolean arg1, Boolean arg2)
{
prettyPrinter(field, arg1, arg2);
}
});
CheckBox cb = new CheckBox();
cb.setText(" : " + field.getName());
cb.selectedProperty().bindBidirectional(boolProperty);
return cb;
}
public static Node createInt(Object obj, final Field field) throws NoSuchMethodException
{
System.out.println(field.getType().getSimpleName() + " spotted");
JavaBeanIntegerProperty intProperty = JavaBeanIntegerPropertyBuilder.create().bean(obj).name(field.getName()).build();
StringProperty s = new SimpleStringProperty();
StringConverter sc = new IntegerStringConverter();
Bindings.bindBidirectional(s, intProperty, sc);
s.addListener(new ChangeListener<String>() {
#Override
public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2)
{
prettyPrinter(field, arg1, arg2);
}
});
TextField tf = new TextField();
tf.textProperty().bindBidirectional(s);
return tf;
}
So, the problem I have is : In the most case when I change, for example, a textField the POJO property doesn't notice. But in some case, when I change the order of the fields in the POJO every listener will notice any change.
Here's an example of what the GUI looks like with the followed Personne class (which currently works)
public class Personne
{
private int taille;
private Boolean lol;
private long pointure;
private BigInteger age;
private boolean zombified;
private String name;
private PropertyChangeSupport _changeSupport = new PropertyChangeSupport(this);
public Boolean getLol()
{
return this.lol;
}
public long getPointure()
{
return this.pointure;
}
public int getTaille()
{
return taille;
}
public boolean getZombified()
{
return zombified;
}
public BigInteger getAge()
{
return age;
}
public String getName()
{
return name;
}
public void setPointure(long pointure)
{
final long prev = this.pointure;
this.pointure = pointure;
_changeSupport.firePropertyChange("pointure", prev, pointure);
}
public void setTaille(int taille)
{
final int prev = this.taille;
this.taille = taille;
_changeSupport.firePropertyChange("taille", prev, taille);
}
public void setAge(BigInteger age)
{
final BigInteger prev = this.age;
this.age = age;
_changeSupport.firePropertyChange("age", prev, age);
}
public void setName(String name)
{
final String prev = this.name;
this.name = name;
_changeSupport.firePropertyChange("name", prev, name);
}
public void setLol(Boolean lol)
{
final Boolean prev = this.lol;
this.lol = lol;
_changeSupport.firePropertyChange("lol", prev, lol);
}
public void setZombified(boolean zombified)
{
final boolean prev = this.zombified;
this.zombified = zombified;
_changeSupport.firePropertyChange("zombified", prev, zombified);
}
public void addPropertyChangeListener(final PropertyChangeListener listener)
{
_changeSupport.addPropertyChangeListener(listener);
}
}
I'm wondering how can the property order influence the binding like that. Furthermore, I noticed that if I want to return my nodes wrapped in HBox the binding doesn't work no more.
I think that I'm doing someting wrong, but I can't figure out what.
Your JavaBeanIntegerProperty and JavaBeanBooleanProperty are being garbage collected too early.
The method Property.bind(Observable) makes the property hold a strong reference to the observable, but the observable only holds a weak reference to the property! (it registers a Listener on the Observable that only has a WeakReference back to the Property). Likewise, when you call Bindings.bindBidirectional(Property, Property), both properties hold weak references to each other! This is an extremely important detail that is hard to find in the documentation.
If you only ever interact with JavaFX objects, this is not a problem because a JavaFX object naturally holds strong references to all its Properties. But if you are using JavaBeanIntegerProperty to wrap a bean property from a legacy object, then the bean does not hold a strong reference to the JavaBeanIntegerProperty, so after gc the Property will disappear and stop updating the bean! I think this is a design bug in the JavaBeanIntegerProperty.
The solution is to assign the JavaBeanIntegerProperty to a field and store it for as long as you want the binding to keep updating the bean.
Alternatively, you can write your own Property subclasses that do something to ensure that a strong reference exists from the bean to the Property (e.g. addPropertyChangeListener to the bean to listen forever).
Related
We're updating a Hibernate (3.6) application that defines a custom type for money, extending org.hibernate.type.ImmutableType. It's been fairly straightforward to make it instead extend AbstractSingleColumnStandardBasicType and create a Java type descriptor to store Money as BigInteger.
However, various parts of the application use HQL queries that perform aggregate functions (usually SUM) on money fields. The old style, extending ImmutableType, automatically converted the result to Money, but with the new style, that's not happening; the result is a Long.
Does anyone know how to make Hibernate custom types automatically convert the result of aggregate functions?
Old user type:
public class MoneyUserType extends ImmutableType {
private final BigIntegerType bigIntegerType = new BigIntegerType();
#Override
public Object fromStringValue(final String string) {
final BigInteger bigInteger = (BigInteger) bigIntegerType.fromStringValue(string);
return Money.inCents(bigInteger);
}
#Override
public Object get(final ResultSet rs, final String name) throws SQLException {
final BigInteger bigInteger = (BigInteger) bigIntegerType.get(rs, name);
if (null == bigInteger) {
return null;
}
return Money.inCents(bigInteger);
}
#Override
public void set(final PreparedStatement st, final Object object, final int index) throws SQLException {
final Money money = (Money) object;
bigIntegerType.set(st, money.getAmountInCents(), index);
}
#Override
public int sqlType() {
return bigIntegerType.sqlType();
}
#Override
public String toString(final Object object) {
final Money money = (Money) object;
return bigIntegerType.toString(money.getAmountInCents());
}
public String getName() {
return Money.class.getName();
}
#SuppressWarnings("unchecked")
public Class getReturnedClass() {
return Money.class;
}
}
New user type:
public class MoneyUserType extends AbstractSingleColumnStandardBasicType<Money> {
public MoneyUserType() {
super(BigIntTypeDescriptor.INSTANCE, MoneyJavaTypeDescriptor.INSTANCE);
}
#Override
public String getName() {
return Money.class.getName();
}
}
public class MoneyJavaTypeDescriptor extends AbstractTypeDescriptor<Money> {
public static final MoneyJavaTypeDescriptor INSTANCE = new MoneyJavaTypeDescriptor();
public MoneyJavaTypeDescriptor() {
super(Money.class, ImmutableMutabilityPlan.INSTANCE);
}
#Override
public Money fromString(final String string) {
final BigInteger bigInteger = BigIntegerTypeDescriptor.INSTANCE.fromString(string);
return Money.inCents(bigInteger);
}
#Override
public <X> X unwrap(Money value, Class<X> type, WrapperOptions options) {
if (value == null) {
return null;
}
if (type.isAssignableFrom(BigInteger.class)) {
return (X) value.getAmountInCents();
}
if (type.isAssignableFrom(Long.class)) {
return (X) Long.valueOf(value.getAmountInCents().longValue());
}
if (type.isAssignableFrom(Integer.class)) {
return (X) Integer.valueOf(value.getAmountInCents().intValue());
}
throw unknownUnwrap(type);
}
#Override
public <X> Money wrap(X value, WrapperOptions options) {
if (value == null) {
return null;
}
if (Number.class.isInstance(value)) {
return Money.inCents((Number) value);
}
throw unknownWrap(value.getClass());
}
#Override
public String toString(final Money money) {
return BigIntegerTypeDescriptor.INSTANCE.toString(money.getAmountInCents());
}
}
Looks like we can work around this by adding extra setters on our DTOs and putting aliases in the queries as per Custom type / converter in conjunction with Hibernate's Transformers.aliasToBean
It's not ideal but it can be made to work.
You can add an INSTANCE static field to your MoneyUserType:
public class MoneyUserType extends AbstractSingleColumnStandardBasicType<Money> {
public static final MoneyUserType INSTANCE = new MoneyUserType();
// ...
}
then add for example the following function to your custom hibernate dialect:
public class MyPostgreSQLDialect extends PostgreSQL10Dialect
{
public MyPostgreSQLDialect()
{
registerFunction("sum_money", new StandardSQLFunction("sum", MoneyUserType.INSTANCE));
}
}
declare this dialect in your persistence.xml or hibernate.cfg.xml
and then you will be able to use the sum_money function in your hql:
Money sum = entityManager.createQuery(
"select sum_money(m.money) from MoneyEntity m",
Money.class
).getSingleResult();
How do I get the value of watched which is "New Value"?
Code from here:
import java.util.Observable;
import java.util.Observer;
class ObservedObject extends Observable {
private String watchedValue;
public ObservedObject(String value) {
watchedValue = value;
}
public void setValue(String value) {
// if value has changed notify observers
if(!watchedValue.equals(value)) {
watchedValue = value;
// mark as value changed
setChanged();
}
}
}
public class ObservableDemo implements Observer {
public String name;
public ObservableDemo(String name) {
this.name = name;
}
public static void main(String[] args) {
// create watched and watcher objects
ObservedObject watched = new ObservedObject("Original Value");
// watcher object listens to object change
ObservableDemo watcher = new ObservableDemo("Watcher");
// add observer to the watched object
watched.addObserver(watcher);
// trigger value change
System.out.println("setValue method called...");
watched.setValue("New Value");
// check if value has changed
if(watched.hasChanged())
System.out.println("Value changed" + new value of watched!); // that's what I want. ("New Value")
else
System.out.println("Value not changed");
}
public void update(Observable obj, Object arg) {
System.out.println("Update called");
}
}
Output:
setValue method called...
Value changed
Line 40:
System.out.println("Value changed" + new value of watched!); // that's what I want.
Wanted Output:
setValue method called...
Value changed to New Value
You already have a setter method in the form of setValue so now just implement a corresponding getter method:
import java.util.Observable;
import java.util.Observer;
class ObservedObject extends Observable {
private String watchedValue;
public ObservedObject(String value) {
this.watchedValue = value;
}
public String getWatchedValue() {
return watchedValue;
}
public void setWatchedValue(String value) {
if (!watchedValue.equals(value)) {
this.watchedValue = value;
setChanged();
}
}
}
public class ObservableDemo implements Observer {
public String name;
public ObservableDemo(String name) {
this.name = name;
}
public static void main(String[] args) {
ObservedObject watched = new ObservedObject("Original Value");
ObservableDemo watcher = new ObservableDemo("Watcher");
watched.addObserver(watcher);
System.out.println("setValue method called...");
watched.setWatchedValue("New Value");
if (watched.hasChanged()) {
System.out.println("Value changed: " + watched.getWatchedValue());
} else
System.out.println("Value not changed");
}
public void update(Observable obj, Object arg) {
System.out.println("Update called");
}
}
Output:
setValue method called...
Value changed: New Value
You can access the new value from the Observable by providing a getter function like given below and call the notifyObservers(); inside the setValue(String value)
class ObservedObject extends Observable {
private String watchedValue;
public ObservedObject(String value) {
watchedValue = value;
}
public void setValue(String value) {
// if value has changed notify observers
if(!watchedValue.equals(value)) {
watchedValue = value;
// mark as value changed
setChanged();
notifyObservers();
}
}
public String getValue(){
return watchedValue ;
}
}
And modify the below method to get the new value
public void update(Observable obj, Object arg) {
System.out.println("Update called :"+obj.getValue());
}
I have a class structure like :
public interface DBReader {
public Map<String, String> read(String primaryKey, String valueOfPrimaryKey,
boolean scanIndexForward, boolean consistentRead, int maxPageSize);
public int getA(String ___);
public int getB(String ___);
public int getC(String ___);
}
public class DynamoDBReader implements DBReader {
private DynamoDB dynamoDB;
private String tableName;
private Table table;
private int throughput;
private DynamoDBReader(Builder builder) {
this.throughput = builder.throughput;
this.tableName = builder.tableName;
this.dynamoDB = builder.dynamoDB;
this.table = dynamoDB.getTable(builder.tableName);
if (table == null) {
throw new InvalidParameterException(String.format("Table %s doesn't exist.", tableName));
}
}
#Override
public int getA(String ____) {
read(_________);
}
return ________;
}
#Override
public int getB(String ____) {
read(_________);
}
return ________;
}
#Override
public int getC(String ____) {
read(_________);
}
return ________;
}
#Override
public Map<String, String> read(String primaryKey, String valueOfPrimaryKey, boolean scanIndexForward,
boolean consistentRead, int maxPageSize) {
QuerySpec spec = new QuerySpec()
.withHashKey(primaryKey, valueOfPrimaryKey)
.withScanIndexForward(scanIndexForward)
.withConsistentRead(consistentRead)
.withMaxPageSize(maxPageSize);
ItemCollection<QueryOutcome> items = table.query(spec);
Iterator<Item> itemIterator = items.firstPage().iterator();
Map<String, String> itemValues = new HashMap<String, String>();
while (itemIterator.hasNext()) {
Item item = itemIterator.next();
}
return itemValues;
}
}
#VisibleForTesting
protected void setTable(Table table) {
this.table = table;
}
/**
* Returns a new builder.
*/
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String tableName;
private int throughput;
private DynamoDB dynamoDB;
private Builder() { }
public Builder tableName(String tableName) {
this.tableName = tableName;
return this;
}
public Builder throughput(int throughput) {
this.throughput = throughput;
return this;
}
public Builder dynamoDB(DynamoDB dynamoDB) {
this.dynamoDB = dynamoDB;
return this;
}
public DynamoDBReader build() {
if (tableName == null) {
throw new InvalidParameterException("Table name can't be null.");
}
if (throughput <= 0) {
throw new InvalidParameterException("Throughput should be > 0.");
}
if (dynamoDB == null) {
throw new InvalidParameterException("dynamoDB can't be null.");
}
return new DynamoDBReader(this);
}
}
}
Problem : getA(), getB(), getC() are only valid for specific tableNames. For a table getA() is Valid but getB() and getC() wont make any sense.
How to couple method names with table name so that someone with a table name knows which function is valid.
Solution to create subclasses for different getters doesn't look a great idea to me.
Solution to create subclasses for different getters doesn't look a great idea to me.
Can you please elaborate why?
I hear that all the time, 'I don't like it...', 'This seems ugly...', 'It shouldn't do that'. Reasons for not liking a particular solution should be backed by objective reasons, not personal opinions. Most of the time our intuition as developers tells us that something is wrong when it is actually violating some software development principle. But sometimes it is just plain old personal feeling without any particular logical reason. When that happens I like to get to specifics.
Your solution violates a basic software principle called SRP.
Having table modules will be much better solution.
I try to use DataBinding on SWT Widgets.
I´m wondering if there is a way to connect a Combo Box to an underlying String in the model.
So I have a String in the Model and a Combo on my View?
As the standard way is not working:
//View
DataBindingContext ctx = new DataBindingContext();
IObservableValue target1 = WidgetProperties.singleSelectionIndex().observe(combo);
IObservableValue model1 = BeanProperties.value(OutputVariable.class, "type").observe(outputVariable);
ctx.bindValue(target1, model1);
//Model
public void setType(String type) {
//TYPES is a constant with the possible Combo values
if (contains(TYPES, type)) {
String oldType = this.type;
this.type = type;
firePropertyChange("type", oldType, this.type);
}else {
throw new IllegalArgumentException();
}
}
I tried to use the fireIndexedPropertyChangeMethod which didn't worked either.
Is there a way to connect those two together? Maybe I have to use another WidgetProperties or BeanProperties method?
As a workaround I could maybe use a new Property in the model, which defines the combo selection index, connect this to the Combo and transfer changes of this index to the type Property and vice versa. But that seems not as a great solution to me.
Edit:
The Solution with a selectionIndex Property is working. But a cleaner method would still be nice as now a type Property change in the model has to reset the selectionIndex too and vice versa.
I have a clean solution now, which is to use a Converter.
//View
IObservableValue comboObservable = WidgetProperties.singleSelectionIndex().observe(combo);
IObservableValue viewTypeObservable = BeanProperties.value(DebugModel.class, "type").observe(debugModel);
IConverter viewTypeToIntConverter = createViewTypeToIntConverter();
UpdateValueStrategy toTargetStrategy = new UpdateValueStrategy();
toTargetStrategy.setConverter(viewTypeToIntConverter);
IConverter intToViewTypeConverter = createIntToViewTypeConverter();
UpdateValueStrategy toModelStrategy = new UpdateValueStrategy();
toModelStrategy.setConverter(intToViewTypeConverter);
DataBindingContext context = new DataBindingContext();
context.bindValue(comboObservable, viewTypeObservable, toModelStrategy, toTargetStrategy);
//Converter
private IConverter createIntToViewTypeConverter() {
return new IConverter() {
#Override
public Object convert(Object value) {
if(value instanceof Integer) {
for(ViewType type : ViewType.values()) {
if(type.toString().equals(ViewType.getStringAtIndex((int)value))) {
return type;
}
}
}
throw new IllegalArgumentException("We need an Integer to convert it but got an " + value.getClass());
}
#Override
public Object getFromType() {
return Integer.class;
}
#Override
public Object getToType() {
return ViewType.class;
}
};
}
private IConverter createViewTypeToIntConverter() {
return new IConverter() {
#Override
public Object convert(Object value) {
if(value instanceof ViewType) {
String[] viewTypes = ViewType.getStringValues();
for(int i=0;i<viewTypes.length;i++) {
if(viewTypes[i].equals(((ViewType)value).toString())) {
return i;
}
}
}
throw new IllegalArgumentException("We need a View Type to be converted but got a " + value.getClass());
}
#Override
public Object getFromType() {
return ViewType.class;
}
#Override
public Object getToType() {
return Integer.class;
}
};
}
//Model
public class DebugModel extends ModelObject {
private ViewType type;
public ViewType getType() {
return type;
}
public void setType(ViewType type) {
firePropertyChange("type", this.type, this.type = type);
}
}
//Just to complete the example, be sure the Model class extends a ModelObject class like this
public class ModelObject {
private PropertyChangeSupport changeSupport = new PropertyChangeSupport(
this);
public void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(String propertyName,
PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(propertyName, listener);
}
protected void firePropertyChange(String propertyName, Object oldValue,
Object newValue) {
changeSupport.firePropertyChange(propertyName, oldValue, newValue);
}
protected void fireIndexedPropertyChange(String propertyName, int index, Object oldValue, Object newValue) {
changeSupport.fireIndexedPropertyChange(propertyName, index, oldValue, newValue);
}
}
Of course you can outsource the Converters to custom classes, I used it this way just to show a quick solution here.
I'm trying to implement a Set which is ordered by the count of additions like this:
public class App {
public static void main(String args[]) {
FrequencyOrderedTreeSet<String> set = new FrequencyOrderedTreeSet<String>();
set.add("bar");
set.add("foo");
set.add("foo");
Iterator<String> i = set.iterator();
while (i.hasNext()) {
System.out.print(i.next());
}
// prints "foobar"
}
}
I've created a protected class FrequencyOrderedTreeSet.Element which implements Comparable and has a T entry and an int frequency property and extended TreeSet<FrequencyOrderedTreeSet.Element> with FrequencyOrderedTreeSet<T> and overrode the compareTo and equals methods on the Element.
One problem is that I can't override the add() method because of type erasure problems and also I can't call instanceof Element in the equals method, because in case object given to it is an Element, I have to compare their entries, but if it's not, I have to compare the object itself to this.entry.
In the add method I create a new element, find the element with the same entry in the set, set the frequency on the new element to "old+1", remove the old one and add the new one. I'm not even sure this is the best way to do this or if it would work even because the other problems I described.
The question is: what's the best way to implement such data structure? In case I'm somehow on the right track - how can I circumvent the problems I've mentioned above?
Here's a basic implementation. It's not the most optimal and will take some more work if you want to implement the full Set interface.
public class FrequencySet<T> implements Iterable<T>
{
private TreeSet<T> set;
private HashMap<T, Integer> elements = new HashMap<T, Integer>();
public FrequencySet()
{
set = new TreeSet<T>(new Comparator<T>()
{
public int compare(T o1, T o2)
{
return elements.get(o2)-elements.get(o1);
}
});
}
public void add(T t)
{
Integer i = elements.get(t);
elements.put(t, i == null ? 1 : i+1);
set.remove(t);
set.add(t);
}
public Iterator<T> iterator() {return set.iterator();}
public static void main(String [] args)
{
FrequencySet<String> fset = new FrequencySet<String>();
fset.add("foo");
fset.add("bar");
fset.add("foo");
for (String s : fset)
System.out.print(s);
System.out.println();
fset.add("bar");
fset.add("bar");
for (String s : fset)
System.out.print(s);
}
}
The key is in the add method. We change the counter for the given object (which changes the relation order), remove it from the backing set and put it back in.
This works the other way (count is increased when you use GET)
#SuppressWarnings("rawtypes")
final class Cache implements Comparable {
private String key;
private String value;
private int counter;
public String getValue() {
counter++;
return value;
}
private void setValue(String value) { this.value = value; }
public String getKey() { return key; }
private void setKey(String key) { this.key = key; }
public int getCounter() { return counter; }
public void setCounter(int counter) { this.counter = counter; }
public Cache(String key, String value) {
this.setKey(key);
this.setValue(value);
setCounter(0);
}
#Override
public int compareTo(Object arg0) {
if(!(arg0 instanceof Cache)) {
throw new ClassCastException();
}
return this.getCounter() - ((Cache) arg0).getCounter();
}
}