Last active
December 19, 2015 05:29
-
-
Save narrowtux/5904815 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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