Skip to content

Instantly share code, notes, and snippets.

@narrowtux
Last active December 19, 2015 05:29
Show Gist options
  • Save narrowtux/5904815 to your computer and use it in GitHub Desktop.
Save narrowtux/5904815 to your computer and use it in GitHub Desktop.
package de.hs.settlers.util;
import java.lang.reflect.Field;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableSet;
import javafx.collections.SetChangeListener;
/**
* Contains methods and classes to automate bidirectional associations using
* JavaFX' property beans system
*
*/
public class AssociationUtils {
/*
* ONE-TO-MANY
*/
/**
* Prepares this object for a bidirectional one-to-many association.<br>
* Call this method on the many-side
*
* @param thisObject
* the current object instance, usually this
* @param otherProperty
* the property in this object that has to be linked to Other
* @param iInPropertyFieldName
* the field name where the ObservableSet is stored in the other
* object
* @param otherType
* the class of the Other type
*/
public static <Me, Other> void iInOne(Me thisObject,
Property<Other> otherProperty, String iInPropertyFieldName,
Class<Other> otherType) {
try {
otherProperty.addListener(new IInOne<Me, Other>(thisObject,
getField(iInPropertyFieldName, otherType)));
} catch (SecurityException e) {
e.printStackTrace();
}
}
/**
* Prepares this object for a bidirectional one-to-many association. <br>
* Call this method on the one-side
*
* @param thisObject
* the current object instance, usually this
* @param otherProperty
* the set property that contains Other objects
* @param manyOfMePropertyFieldName
* the field name of the Property in the other object
* @param otherType
* the class of the Other type
*/
public static <Me, Other> void manyInMe(Me thisObject,
ObservableSet<Other> otherProperty,
String manyOfMePropertyFieldName, Class<Other> otherType) {
try {
otherProperty.addListener(new ManyInMe<Me, Other>(thisObject,
getField(manyOfMePropertyFieldName, otherType)));
} catch (SecurityException e) {
e.printStackTrace();
}
}
/**
* I am one of many objects in Other
*/
private static class IInOne<Me, Other> implements ChangeListener<Other> {
Field fieldIn;
Me me;
public IInOne(Me me, Field fieldIn) {
super();
this.fieldIn = fieldIn;
this.me = me;
}
@SuppressWarnings("unchecked")
@Override
public void changed(ObservableValue<? extends Other> object,
Other oldValue, Other newValue) {
if (oldValue != null) {
try {
ObservableSet<Me> in = (ObservableSet<Me>) fieldIn
.get(oldValue);
in.remove(me);
} catch (IllegalArgumentException | IllegalAccessException
| ClassCastException e) {
e.printStackTrace();
}
}
if (newValue != null) {
try {
ObservableSet<Me> in = (ObservableSet<Me>) fieldIn
.get(newValue);
in.add(me);
} catch (IllegalArgumentException | IllegalAccessException
| ClassCastException e) {
e.printStackTrace();
}
}
}
}
/**
* Many objects of Other are in a property of Me
*
* @param <Me>
* @param <Other>
*/
private static class ManyInMe<Me, Other> implements
SetChangeListener<Other> {
Field fieldIn;
Me me;
public ManyInMe(Me me, Field fieldIn) {
super();
this.me = me;
this.fieldIn = fieldIn;
}
@SuppressWarnings("unchecked")
@Override
public void onChanged(
javafx.collections.SetChangeListener.Change<? extends Other> change) {
if (change.wasAdded()) {
try {
Property<Me> in = (Property<Me>) fieldIn.get(change
.getElementAdded());
in.setValue(me);
} catch (IllegalArgumentException | IllegalAccessException
| ClassCastException e) {
e.printStackTrace();
}
}
if (change.wasRemoved()) {
try {
Property<Me> in = (Property<Me>) fieldIn.get(change
.getElementRemoved());
in.setValue(null);
} catch (IllegalArgumentException | IllegalAccessException
| ClassCastException e) {
e.printStackTrace();
}
}
}
}
/*
* ONE-TO-ONE
*/
/**
* Prepares the given thisObject for a bidirectional one-to-one association. <br>
* Call this methods on both sides
*
* @param thisObject
* the current object instance, usually this
* @param otherProperty
* the property that contains the Other object
* @param meInPropertyFieldName
* the field name of the Property in the Other object
* @param type
* the class of the Other type
*/
public static <Me, Other> void oneToOne(Me thisObject,
Property<Other> otherProperty, String meInPropertyFieldName,
Class<Other> type) {
try {
otherProperty.addListener(new OneInOne(thisObject, getField(meInPropertyFieldName, type)));
} catch (SecurityException e) {
e.printStackTrace();
}
}
private static class OneInOne<Me, Other> implements ChangeListener<Other> {
Me me;
Field fieldOther;
public OneInOne(Me me, Field fieldOther) {
super();
this.me = me;
this.fieldOther = fieldOther;
}
@SuppressWarnings("unchecked")
@Override
public void changed(ObservableValue<? extends Other> object,
Other oldValue, Other newValue) {
if (oldValue != null) {
try {
Property<Me> other = (Property<Me>) fieldOther
.get(oldValue);
other.setValue(null);
} catch (IllegalArgumentException | IllegalAccessException
| ClassCastException e) {
e.printStackTrace();
}
}
if (newValue != null) {
try {
Property<Me> other = (Property<Me>) fieldOther
.get(newValue);
other.setValue(me);
} catch (IllegalArgumentException | IllegalAccessException
| ClassCastException e) {
e.printStackTrace();
}
}
}
}
private static Field getField(String name, Class<?> in) {
Field ret = null;
while (true) {
try {
ret = in.getDeclaredField(name);
break;
} catch (NoSuchFieldException | SecurityException e) {
in = in.getSuperclass();
if (in == null || in.equals(Object.class)) {
return null;
}
}
}
ret.setAccessible(true);
return ret;
}
}
package de.hs.settlers.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.LinkedHashSet;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
import org.junit.Test;
public class AssociationUtilsTest {
@Test
public void testOneToManyAssoc() {
OneToManyA a = new OneToManyA();
OneToManyB b = new OneToManyB();
OneToManyB b2 = new OneToManyB();
assertNull(a.getMyB());
assertFalse(b.getAllAs().contains(a));
a.setMyB(b);
assertTrue(b.getAllAs().contains(a));
b.getAllAs().remove(a);
assertNull(a.getMyB());
a = new OneToManyA();
b = new OneToManyB();
b.getAllAs().add(a);
assertEquals(b, a.getMyB());
a.setMyB(null);
assertFalse(b.getAllAs().contains(a));
a.setMyB(b);
assertTrue(b.getAllAs().contains(a));
a.setMyB(b2);
assertFalse(b.getAllAs().contains(a));
assertTrue(b2.getAllAs().contains(a));
}
private static class OneToManyA {
private SimpleObjectProperty<OneToManyB> myB = new SimpleObjectProperty<>();
{
AssociationUtils.iInOne(this, myB, "allAs", OneToManyB.class);
}
public OneToManyB getMyB() {
return myB.get();
}
public void setMyB(OneToManyB b) {
myB.setValue(b);
}
public ObjectProperty<OneToManyB> myBProperty() {
return myB;
}
}
private static class OneToManyB {
private ObservableSet<OneToManyA> allAs = FXCollections.observableSet(new LinkedHashSet<OneToManyA>());
{
AssociationUtils.manyInMe(this, allAs, "myB", OneToManyA.class);
}
public ObservableSet<OneToManyA> getAllAs() {
return allAs;
}
}
@Test
public void testOneToOneAssoc() {
OneToOneA a = new OneToOneA();
OneToOneA a2 = new OneToOneA();
OneToOneB b = new OneToOneB();
a.setB(b);
assertEquals(a, b.getA());
a.setB(null);
assertNull(b.getA());
a = new OneToOneA();
b = new OneToOneB();
b.setA(a);
assertEquals(b, a.getB());
b.setA(null);
assertNull(a.getB());
b.setA(a);
assertEquals(b, a.getB());
b.setA(a2);
assertNull(a.getB());
assertEquals(b, a2.getB());
}
private static class OneToOneA {
private SimpleObjectProperty<OneToOneB> b = new SimpleObjectProperty<>();
{
AssociationUtils.oneToOne(this, b, "a", OneToOneB.class);
}
public void setB(OneToOneB b) {
this.b.set(b);
}
public OneToOneB getB() {
return b.get();
}
}
private static class OneToOneB {
private SimpleObjectProperty<OneToOneA> a = new SimpleObjectProperty<>();
{
AssociationUtils.oneToOne(this, a, "b", OneToOneA.class);
}
public void setA(OneToOneA a) {
this.a.set(a);
}
public OneToOneA getA() {
return a.get();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment