Skip to content

Commit

Permalink
Added builder variant for creating a binding
Browse files Browse the repository at this point in the history
  • Loading branch information
hjohn committed Jul 12, 2014
1 parent 401205c commit 28b02c1
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 57 deletions.
222 changes: 166 additions & 56 deletions Mediasystem/src/main/java/hs/mediasystem/util/MapBindings.java
Expand Up @@ -16,6 +16,7 @@
import javafx.beans.binding.IntegerBinding;
import javafx.beans.binding.LongBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
Expand Down Expand Up @@ -182,6 +183,10 @@ protected T computeValue() {
};
}

public static Builder get(Observable root) {
return new Builder(root);
}

private static class Helper {
private final Observable[] observables;
private final Property[] properties;
Expand All @@ -193,7 +198,7 @@ public void invalidated(Observable observable) {
};
private final WeakInvalidationListener observer = new WeakInvalidationListener(listener);
private final Observable root;
private final Object[] steps;
private final Step[] steps;
private final Binding<?> binding;

public Helper(Binding<?> binding, final Observable root, final Object... steps) {
Expand All @@ -204,14 +209,19 @@ public Helper(Binding<?> binding, final Observable root, final Object... steps)
properties = new Property[this.steps.length];
}

private static Object[] expandSteps(Observable root, Object[] steps) {
private static Step[] expandSteps(Observable root, Object[] steps) {
List<Object> expandedSteps = new ArrayList<>();
int indicesRequired = root instanceof ObservableValue ? 0 : 1;

for(int i = 0; i < steps.length; i++) {
Object step = steps[i];

if(indicesRequired-- <= 0) {
if(step instanceof Step) {
expandedSteps.add(step);
continue;
}

if(!(step instanceof String)) {
throw new IllegalArgumentException("expected String at step " + i + ": " + step);
}
Expand All @@ -223,23 +233,25 @@ private static Object[] expandSteps(Observable root, Object[] steps) {
throw new IllegalArgumentException("invalid step format at step " + i + ": " + step);
}

expandedSteps.add(subStep);

if(subStep.endsWith("[]")) {
expandedSteps.add(new Then(subStep.substring(0, subStep.length() - 2)));
indicesRequired++;
}
else {
expandedSteps.add(new Then(subStep));
}
}
}
else {
expandedSteps.add(step);
expandedSteps.add(new IndexLookup(step, i));
}
}

if(indicesRequired > 0) {
throw new IllegalArgumentException("insufficient indices specified in steps: missing " + indicesRequired + " indices");
}

return expandedSteps.toArray(new Object[expandedSteps.size()]);
return expandedSteps.toArray(new Step[expandedSteps.size()]);
}

private void unregisterListeners() {
Expand All @@ -254,7 +266,6 @@ private void unregisterListeners() {

protected Object computeValue() {
Observable observable = root;
boolean indexedStep = false;

for(int index = 0; index <= steps.length; index++) {
observables[index] = observable;
Expand All @@ -266,12 +277,6 @@ protected Object computeValue() {

Object value = observable instanceof ObservableValue ? ((ObservableValue<?>)observable).getValue() : observable;

if(indexedStep && observable instanceof ObservableValue && !(value instanceof Observable)) {
throw new RuntimeBindException("map or list expected at step " + (index - 1) + ": " + steps[index - 1] + " : " + observable);
}

indexedStep = steps[index] instanceof String && ((String)steps[index]).endsWith("[]") && observable instanceof ObservableValue ? true : false;

try {
if(properties[index] == null || !value.getClass().equals(properties[index].getBeanClass())) {
properties[index] = new Property(value.getClass(), steps[index]);
Expand All @@ -293,73 +298,178 @@ protected Object computeValue() {

private static class Property {
private final Class<?> cls;
private final Object name;
private final Step step;

public Property(Class<?> cls, Step name) {
this.cls = cls;
this.step = name;
}

public Observable getObservable(Object bean) {
try {
return step.execute(cls, bean);
}
catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
}

public Class<?> getBeanClass() {
return cls;
}
}

private interface Step {
Observable execute(Class<?> cls, Object bean) throws IllegalAccessException, InvocationTargetException;
}

private static class Then implements Step {
private final String propertyName;

private Method method;
private Field field;

public Property(Class<?> cls, Object name) {
this.cls = cls;

if(name instanceof String) {
String s = (String)name;
public Then(String propertyName) {
this.propertyName = propertyName;
}

if(s.endsWith("[]")) {
this.name = s.substring(0, s.length() - 2);
@Override
public Observable execute(Class<?> cls, Object bean) throws IllegalAccessException, InvocationTargetException {
if(method == null && field == null) {
try {
method = cls.getMethod(propertyName + "Property");
}
else {
this.name = name;
catch(NoSuchMethodException e) {
try {
field = cls.getField(propertyName);
}
catch(NoSuchFieldException e2) {
throw new RuntimeBindException("No such property found: " + propertyName + " in class: " + cls);
}
}
}
else {
this.name = name;
}

return method != null ? (Observable)method.invoke(bean) : (Observable)field.get(bean);
}
}

private static class IndexLookup implements Step {
private final Object obj;
private final int stepNumber;

public IndexLookup(Object obj, int stepNumber) {
this.obj = obj;
this.stepNumber = stepNumber;
}

@SuppressWarnings("unchecked")
public Observable getObservable(Object bean) {
try {
if(bean instanceof ObservableMap) {
return Bindings.valueAt((ObservableMap<Object, ?>)bean, name);
@Override
public Observable execute(Class<?> cls, Object bean) throws IllegalAccessException, InvocationTargetException {
if(bean instanceof ObservableMap) {
return Bindings.valueAt((ObservableMap<Object, ?>)bean, obj);
}

if(bean instanceof ObservableList) {
if(!(obj instanceof Integer)) {
throw new RuntimeBindException("index for ObservableList (" + bean + ") must be of type Integer: " + obj.getClass());
}

if(bean instanceof ObservableList) {
if(!(name instanceof Integer)) {
throw new RuntimeBindException("index for ObservableList (" + bean + ") must be of type Integer: " + name.getClass());
}
int index = (Integer)obj;

int index = (Integer)name;
if(index < 0) {
throw new RuntimeBindException("index for ObservableList (" + bean + ") cannot be negative: " + index);
}

if(index < 0) {
throw new RuntimeBindException("index for ObservableList (" + bean + ") cannot be negative: " + index);
}
return Bindings.valueAt((ObservableList<?>)bean, index);
}

return Bindings.valueAt((ObservableList<?>)bean, index);
}
throw new RuntimeBindException("map or list expected at step " + (stepNumber - 1) + ", but got: " + cls + ": " + bean);
}
}

public static class Builder {
private final Observable root;
private final List<Object> steps = new ArrayList<>();

public Builder(Observable root) {
this.root = root;
}

public Builder then(String propertyName) {
steps.add(propertyName);
return this;
}

public Builder thenOrDefault(String propertyName, Object defaultValue) {
steps.add(new ThenOrDefault(propertyName, defaultValue));
return this;
}

if(name instanceof String) {
if(method == null && field == null) {
public Builder lookup(Object valueOrIndex) {
steps.add(new IndexLookup(valueOrIndex, steps.size()));
return this;
}

public StringBinding asStringBinding() {
return MapBindings.selectString(root, steps.toArray(new Object[steps.size()]));
}

public <T> ObjectBinding<T> asObjectBinding() {
return MapBindings.select(root, steps.toArray(new Object[steps.size()]));
}

private static class ThenOrDefault implements Step {
private final String propertyName;
private final Object defaultValue;

private Method method;
private Field field;

public ThenOrDefault(String propertyName, Object defaultValue) {
this.propertyName = propertyName;
this.defaultValue = defaultValue;
}

@Override
public Observable execute(Class<?> cls, Object bean) throws IllegalAccessException, InvocationTargetException {
if(method == null && field == null) {
try {
method = cls.getMethod(propertyName + "Property");
}
catch(NoSuchMethodException e) {
try {
method = cls.getMethod(name + "Property");
field = cls.getField(propertyName);
}
catch(NoSuchMethodException e) {
field = cls.getField((String)name);
catch(NoSuchFieldException e2) {
return new ObservableValue<Object>() {
@Override
public void addListener(InvalidationListener listener) {
}

@Override
public void removeListener(InvalidationListener listener) {
}

@Override
public void addListener(ChangeListener<? super Object> listener) {
}

@Override
public void removeListener(ChangeListener<? super Object> listener) {
}

@Override
public Object getValue() {
return defaultValue;
}
};
}
}

return method != null ? (Observable)method.invoke(bean) : (Observable)field.get(bean);
}

throw new IllegalArgumentException("expected string: " + name);
}
catch(NoSuchFieldException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
return null;
return method != null ? (Observable)method.invoke(bean) : (Observable)field.get(bean);
}
}

public Class<?> getBeanClass() {
return cls;
}
}
}
Expand Up @@ -115,7 +115,7 @@ public void shouldGetEpisodeNumberWithCombinedStep() {
public void shouldThrowExceptionWhenMapExpectedButNotEncountered() {
ObjectBinding<Object> select = MapBindings.select(file1.videoProperty(), "episode[]", "episodeNumber"); // episode is not a map

assertNull(select.get());
select.get();
}

/*
Expand Down Expand Up @@ -392,4 +392,44 @@ public void shouldOnlyRegisterListenerOnce() {

assertEquals(1, mock.getListenerCount());
}

/*
* Builder tests
*/

@Test
public void shouldGetTitleWithBuilder() {
StringBinding stringBinding = MapBindings.get(file1.videoProperty()).then("title").asStringBinding();

assertEquals("Alice", stringBinding.get());
}

@Test
public void shouldGetDefaultWithBuilder() {
StringBinding stringBinding = MapBindings.get(file1.videoProperty()).thenOrDefault("nonExistingField", "Alice").asStringBinding();

assertEquals("Alice", stringBinding.get());
}

@Test
public void shouldGetNullDefaultWithBuilder() {
StringBinding stringBinding = MapBindings.get(file1.videoProperty()).thenOrDefault("nonExistingField", null).asStringBinding();

assertNull(stringBinding.get());
}

@Test
public void shouldGetListValueWithBuilder() {
ObjectBinding<Object> select = MapBindings.get(file1.videoProperty()).then("list").lookup(1).asObjectBinding();

assertEquals("B", select.get());
}

@Test
public void shouldGetMapValueWithBuilder() {
ObjectBinding<Object> select = MapBindings.get(file1.videoProperty()).then("dataMap").lookup(YouTube.class).then("rating").asObjectBinding();

assertEquals(9, select.get());
}

}

0 comments on commit 28b02c1

Please sign in to comment.