Skip to content

Generic binding types for fluent API #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions api/src/main/java/javax/observer/ValueWillChangeEvent.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import java.util.Optional;

/**
* Created by hendrikebbers on 30.01.17.
*/

public interface ValueWillChangeEvent<V> {

Observable<V> getObservable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* TODO: should this extend {@link Property}???
* @param <T> value type
*/
public interface BidirectionalBindable<T> extends Bindable<T> {
public interface BidirectionalBindable<T, I extends BidirectionalBindable<T, I>> extends Bindable<T, I> {

/**
* Creates an bidirectional binding by binding this Bindable to the given {@link Property}.
Expand Down
6 changes: 2 additions & 4 deletions api/src/main/java/javax/observer/binding/Bindable.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@
* This interface defines objects that can be bound to an {@link Observable}. By doing so any change of
* the value of the {@link Observable} will be propagated to this object.
*
* TODO: should this extend {@link Observable}???
*
* @param <T> type of the value
*/
public interface Bindable<T> {
public interface Bindable<T, I extends Bindable<T, I>> {

/**
* Creates an unidirectional binding by binding this Bindable to the given {@link Observable}.
Expand All @@ -28,6 +26,6 @@ public interface Bindable<T> {
* @param handler the error handler
* @return this object. This can be used to create a small fluent API
*/
ConvertableBindable<T> withErrorHandler(Consumer<Throwable> handler);
I withErrorHandler(Consumer<Throwable> handler);

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* offers bindings between different value types by defining {@link Function} based converters.
* @param <T> value type
*/
public interface ConvertableBidirectionalBindable<T> extends ConvertableBindable<T>, BidirectionalBindable<T> {
public interface ConvertableBidirectionalBindable<T, I extends ConvertableBidirectionalBindable<T, I>> extends ConvertableBindable<T, I>, BidirectionalBindable<T, I> {

/**
*By defining a converter a bidirectional binding to an {@link Property} with a different value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* offers bindings between different value types by defining {@link Function} based converters.
* @param <T> value type
*/
public interface ConvertableBindable<T> extends Bindable<T> {
public interface ConvertableBindable<T, I extends ConvertableBindable<T, I>> extends Bindable<T, I> {

/**
* By defining a converter a unidirectional binding to an {@link Observable} with a different value
Expand Down
90 changes: 48 additions & 42 deletions impl/src/main/java/com/guigarage/binding/Binding.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import javax.observer.Property;
import javax.observer.Subscription;
import javax.observer.binding.ConvertableBidirectionalBindable;
import javax.observer.binding.ConvertableBindable;
import java.util.function.Consumer;
import java.util.function.Function;

Expand All @@ -14,49 +13,56 @@
*/
public class Binding {

public static <T> ConvertableBidirectionalBindable<T> bind(Property<T> property) {
return new ConvertableBidirectionalBindable<T>() {

private Consumer<Throwable> errorHandler = e -> e.printStackTrace();

private boolean bindingCalled = false;

@Override
public <U> Subscription bidirectionalTo(Property<U> toProperty, Function<U, T> converter, Function<T, U> converter2) {
Subscription subscription1 = to(toProperty, converter);
Subscription subscription2 = bind(property, toProperty, converter2);
return () -> {
subscription1.unsubscribe();
subscription2.unsubscribe();
};
}

@Override
public <U> Subscription to(Observable<U> observable, Function<U, T> converter) {
return bind(observable, property, converter);
}

private <U, V> Subscription bind(Observable<U> observable, Property<V> property, Function<U, V> converter) {
return observable.onChanged(e -> {
if(!bindingCalled) {
bindingCalled = true;
try {
property.setValue(converter.apply(e.getValue()));
} catch (Exception ex) {
errorHandler.accept(ex);
} finally {
bindingCalled = false;
}
private static class InternalConvertableBidirectionalBindable<T> implements ConvertableBidirectionalBindable<T, InternalConvertableBidirectionalBindable<T>> {

private final Property<T> property;

private Consumer<Throwable> errorHandler = e -> e.printStackTrace();

private boolean bindingCalled = false;

public InternalConvertableBidirectionalBindable(Property<T> property) {
this.property = property;
}

@Override
public <U> Subscription bidirectionalTo(Property<U> toProperty, Function<U, T> converter, Function<T, U> converter2) {
Subscription subscription1 = to(toProperty, converter);
Subscription subscription2 = bind(property, toProperty, converter2);
return () -> {
subscription1.unsubscribe();
subscription2.unsubscribe();
};
}

@Override
public <U> Subscription to(Observable<U> observable, Function<U, T> converter) {
return bind(observable, property, converter);
}

private <U, V> Subscription bind(Observable<U> observable, Property<V> property, Function<U, V> converter) {
return observable.onChanged(e -> {
if (!bindingCalled) {
bindingCalled = true;
try {
property.setValue(converter.apply(e.getValue()));
} catch (Exception ex) {
errorHandler.accept(ex);
} finally {
bindingCalled = false;
}
});
}
}
});
}

@Override
public ConvertableBindable<T> withErrorHandler(Consumer<Throwable> handler) {
this.errorHandler = errorHandler;
return this;
}
@Override
public InternalConvertableBidirectionalBindable<T> withErrorHandler(Consumer<Throwable> handler) {
this.errorHandler = errorHandler;
return this;
}
}

};
public static <T> ConvertableBidirectionalBindable<T, ?> bind(Property<T> property) {
return new InternalConvertableBidirectionalBindable<T>(property);
}
}
196 changes: 103 additions & 93 deletions impl/src/main/java/com/guigarage/binding/SwingBinding.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import javax.observer.Property;
import javax.observer.Subscription;
import javax.observer.binding.ConvertableBidirectionalBindable;
import javax.observer.binding.ConvertableBindable;
import javax.swing.SwingUtilities;
import java.awt.Component;
import java.beans.PropertyChangeListener;
Expand All @@ -27,114 +26,125 @@ public class SwingBinding {

private static Executor backgroundExecutor = Executors.newSingleThreadExecutor();

public static <T> ConvertableBidirectionalBindable<T> bind(final Component component, final String attribute) {
return new ConvertableBidirectionalBindable<T>() {
private static class InternalConvertableBidirectionalBindable<T> implements ConvertableBidirectionalBindable<T, InternalConvertableBidirectionalBindable<T>> {

private AtomicBoolean bindingCalled = new AtomicBoolean(false);
private AtomicBoolean bindingCalled = new AtomicBoolean(false);

private Consumer<Throwable> errorHandler = e -> e.printStackTrace();
private Consumer<Throwable> errorHandler = e -> e.printStackTrace();

private Lock bindingLock = new ReentrantLock();
private Lock bindingLock = new ReentrantLock();

private <U> U callLocked(Supplier<U> supplier) {
bindingLock.lock();
try {
return supplier.get();
} finally {
bindingLock.unlock();
}
}
private final Component component;

private void callLocked(Runnable runnable) {
callLocked(() -> {
runnable.run();
return null;
});
}
private final String attribute;

@Override
public <U> Subscription bidirectionalTo(Property<U> property, Function<U, T> converter, Function<T, U> converter2) {
try {
final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(attribute, component.getClass());
final PropertyEditor propertyEditor = propertyDescriptor.createPropertyEditor(component);
Subscription subscription = bind(property, converter, propertyEditor);

PropertyChangeListener listener = e -> {
if (!bindingCalled.get()) {
bindingCalled.set(true);
try {
backgroundExecutor.execute(() -> {
callLocked(() -> {
try {
property.setValue(converter2.apply((T) e.getNewValue()));
} catch (Exception ex) {
SwingUtilities.invokeLater(() -> {
errorHandler.accept(ex);
});
}
});
});
} catch (Exception e1) {
errorHandler.accept(e1);
} finally {
bindingCalled.set(false);
}
}
};
public InternalConvertableBidirectionalBindable(Component component, String attribute) {
this.component = component;
this.attribute = attribute;
}

component.addPropertyChangeListener(listener);

return () -> {
component.removePropertyChangeListener(listener);
subscription.unsubscribe();
};
} catch (Exception e1) {
throw new RuntimeException("Can not bind!", e1);
}
}

@Override
public <U> Subscription to(Observable<U> observable, Function<U, T> converter) {
try {
final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(attribute, component.getClass());
final PropertyEditor propertyEditor = propertyDescriptor.createPropertyEditor(component);
return bind(observable, converter, propertyEditor);
} catch (Exception e1) {
throw new RuntimeException("Can not bind!", e1);
}
private <U> U callLocked(Supplier<U> supplier) {
bindingLock.lock();
try {
return supplier.get();
} finally {
bindingLock.unlock();
}

private <U> Subscription bind(Observable<U> observable, Function<U, T> converter, PropertyEditor propertyEditor) {
return observable.onChanged(e -> {
callLocked(() -> {
}

private void callLocked(Runnable runnable) {
callLocked(() -> {
runnable.run();
return null;
});
}

@Override
public <U> Subscription bidirectionalTo(Property<U> property, Function<U, T> converter, Function<T, U> converter2) {
try {
final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(attribute, component.getClass());
final PropertyEditor propertyEditor = propertyDescriptor.createPropertyEditor(component);
Subscription subscription = bind(property, converter, propertyEditor);

PropertyChangeListener listener = e -> {
if (!bindingCalled.get()) {
bindingCalled.set(true);
try {
SwingUtilities.invokeAndWait(() -> {
if (!bindingCalled.get()) {
bindingCalled.set(true);
backgroundExecutor.execute(() -> {
callLocked(() -> {
try {
propertyEditor.setValue(converter.apply(e.getValue()));
} catch (Exception e1) {
errorHandler.accept(e1);
} finally {
bindingCalled.set(false);
property.setValue(converter2.apply((T) e.getNewValue()));
} catch (Exception ex) {
SwingUtilities.invokeLater(() -> {
errorHandler.accept(ex);
});
}
}
});
});
} catch (Exception e1) {
SwingUtilities.invokeLater(() -> {
errorHandler.accept(e1);
});
errorHandler.accept(e1);
} finally {
bindingCalled.set(false);
}
});
});
}
}
};

component.addPropertyChangeListener(listener);

@Override
public ConvertableBindable<T> withErrorHandler(Consumer<Throwable> handler) {
this.errorHandler = handler;
return this;
return () -> {
component.removePropertyChangeListener(listener);
subscription.unsubscribe();
};
} catch (Exception e1) {
throw new RuntimeException("Can not bind!", e1);
}
}

@Override
public <U> Subscription to(Observable<U> observable, Function<U, T> converter) {
try {
final PropertyDescriptor propertyDescriptor = new PropertyDescriptor(attribute, component.getClass());
final PropertyEditor propertyEditor = propertyDescriptor.createPropertyEditor(component);
return bind(observable, converter, propertyEditor);
} catch (Exception e1) {
throw new RuntimeException("Can not bind!", e1);
}
}

private <U> Subscription bind(Observable<U> observable, Function<U, T> converter, PropertyEditor propertyEditor) {
return observable.onChanged(e -> {
callLocked(() -> {
try {
SwingUtilities.invokeAndWait(() -> {
if (!bindingCalled.get()) {
bindingCalled.set(true);
try {
propertyEditor.setValue(converter.apply(e.getValue()));
} catch (Exception e1) {
errorHandler.accept(e1);
} finally {
bindingCalled.set(false);
}
}
});
} catch (Exception e1) {
SwingUtilities.invokeLater(() -> {
errorHandler.accept(e1);
});
}
});
});
}

@Override
public InternalConvertableBidirectionalBindable<T> withErrorHandler(Consumer<Throwable> handler) {
this.errorHandler = handler;
return this;
}

}

};
public static <T> ConvertableBidirectionalBindable<T, ?> bind(final Component component, final String attribute) {
return new InternalConvertableBidirectionalBindable<T>(component, attribute);
}
}