Browse Source

Change how ObservableController works to support JFormattedTextField.
TODO: Test cases, increment version number to 1.0.0

Sam Jaffe 5 years ago
parent
commit
5eadbca08c

+ 119 - 52
src/main/lombok/org/leumasjaffe/observer/ObservableController.java

@@ -6,12 +6,13 @@ import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
+import javax.swing.JFormattedTextField;
 import javax.swing.text.JTextComponent;
 
 import org.leumasjaffe.event.AnyActionDocumentListener;
 
-import lombok.experimental.FieldDefaults;
-import lombok.AccessLevel;
+import lombok.experimental.UtilityClass;
+import lombok.NonNull;
 
 /**
  * An ObservableListener object that also provides an update link in the other direction.
@@ -21,22 +22,103 @@ import lombok.AccessLevel;
  * This allows you to have a text input field attached to your model (like with a JavaBean),
  * that also fires off notifications to other interested listeners when you edit the text.
  */
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class ObservableController<S extends JTextComponent, T extends Observable> extends ObservableListener<S, T> {
-	BiFunction<String, T, Boolean> func;
+@UtilityClass
+public class ObservableController {
+	
+	private static class TextFieldController<S extends JTextComponent, T extends Observable> extends ObservableListener<S, T> {
+		BiFunction<String, T, Boolean> func;
+		
+		public TextFieldController(@NonNull final S comp,
+				@NonNull final BiFunction<String, T, Boolean> func,
+				@NonNull final BiConsumer<? super S, ? super T> update,
+				final Consumer<T> onEmpty) {
+			super(comp, update);
+			this.func = func;
+			if (onEmpty == null) {
+				AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
+			} else {
+				AnyActionDocumentListener.emptyOrText( comp, 
+						e -> onEmpty.accept( impl.getModel() ), 
+						evt -> accept( ) );
+			}
+		}
+		
+		public TextFieldController(@NonNull final S comp,
+				@NonNull final Function<T, String> get,
+				@NonNull final BiConsumer<T, String> set,
+				final Consumer<T> onEmpty) {
+			this(comp, (t, u) -> {
+				if (t.equals(get.apply(u))) return false;
+				set.accept(u, t);
+				return true;
+			}, (c, u) -> {
+				if (c.getText().equals(get.apply(u))) return;
+				c.setText(get.apply(u).toString());
+			}, onEmpty);
+		}
+
+		private boolean update() {
+			return func.apply( impl.getComponent().getText( ), impl.getModel() );
+		}
+
+		private void accept() {
+			Objects.requireNonNull( impl.getModel() );
+			if ( update( ) ) {
+				impl.notifySubscribers(impl.getModel());
+			}
+		}
+	}
+	
+	private static class FormattedTextFieldController<S extends JFormattedTextField, T extends Observable, V> extends ObservableListener<S, T> {
+		BiFunction<V, T, Boolean> func;
+		
+		public FormattedTextFieldController(@NonNull final S comp,
+				@NonNull final Function<T, V> get,
+				@NonNull final BiConsumer<T, V> set,
+				V onEmpty) {
+			super(comp, (c, u) -> {
+				if (Objects.equals(c.getValue(), get.apply(u))) return;
+				c.setValue(get.apply(u));
+			});
+			this.func = (t, u) -> {
+				if (t.equals(get.apply(u))) return false;
+				set.accept(u, t);
+				return true;
+			};
+			
+			if (onEmpty == null) {
+				AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
+			} else {
+				AnyActionDocumentListener.emptyOrText( comp, 
+						e -> set.accept( impl.getModel(), onEmpty ), 
+						evt -> accept( ) );
+			}
+		}
 
+		@SuppressWarnings("unchecked")
+		private boolean update() {
+			return func.apply( (V) impl.getComponent().getValue( ), impl.getModel() );
+		}
+
+		private void accept() {
+			Objects.requireNonNull( impl.getModel() );
+			if ( update( ) ) {
+				impl.notifySubscribers(impl.getModel());
+			}
+		}
+	}
+	
 	/**
 	 * Construct an ObservableController that modifies its model whenever the linked
 	 * JTextComponent is updated. Ignores changes when the text document is empty.
 	 * @param comp A GUI object that is to be coupled with the model
-	 * @param func An update function to modify the model from text content
-	 * @param update The standard callback to set the component when the model changes elsewhere
+	 * @param updateModel An update function to modify the model from text content
+	 * @param updateComp The standard callback to set the component when the model changes elsewhere
 	 */
-	public ObservableController(final S comp, final BiFunction<String, T, Boolean> func,
-			final BiConsumer<? super S, ? super T> update) {
-		super(comp, update);
-		this.func = func;
-		AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
+	public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
+			@NonNull final S comp, @NonNull final BiFunction<String, T, Boolean> updateModel,
+			@NonNull final BiConsumer<? super S, ? super T> updateComp) {
+		return new TextFieldController<>(comp, updateModel, updateComp, null);
 	}
 	
 	/**
@@ -48,13 +130,11 @@ public class ObservableController<S extends JTextComponent, T extends Observable
 	 * @param onEmpty A special handler to set model value when we delete the content of the
 	 * component.
 	 */
-	public ObservableController(final S comp, final BiFunction<String, T, Boolean> func,
-			final BiConsumer<? super S, ? super T> update, final Consumer<T> onEmpty) {
-		super(comp, update);
-		this.func = func;
-		AnyActionDocumentListener.emptyOrText( comp, 
-				e -> onEmpty.accept( impl.getModel() ), 
-				evt -> accept( ) );
+	public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
+			@NonNull final S comp, @NonNull final BiFunction<String, T, Boolean> func,
+			@NonNull final BiConsumer<? super S, ? super T> update,
+			Consumer<T> onEmpty) {
+		return new TextFieldController<>(comp, func, update, onEmpty);
 	}
 	
 	/**
@@ -65,18 +145,10 @@ public class ObservableController<S extends JTextComponent, T extends Observable
 	 * @param get A model getter function to be called on the observed object
 	 * @param set A model setter function to be called on the observed object
 	 */
-	public ObservableController(final S comp, final Function<T, String> get,
-			final BiConsumer<T, String> set) {
-		super(comp, (c, u) -> {
-			if (c.getText().equals(get.apply(u))) return;
-			c.setText(get.apply(u));
-		});
-		this.func = (t, u) -> {
-			if (t.equals(get.apply(u))) return false;
-			set.accept(u, t);
-			return true;
-		};
-		AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
+	public <S extends JTextComponent, T extends Observable, V> ObservableListener<S, T> from(
+			@NonNull final S comp, @NonNull final Function<T, String> get,
+			@NonNull final BiConsumer<T, String> set) {
+		return from(comp, get, set, null);
 	}
 	
 	/**
@@ -87,27 +159,22 @@ public class ObservableController<S extends JTextComponent, T extends Observable
 	 * @param get A model getter function to be called on the observed object
 	 * @param set A model setter function to be called on the observed object
 	 */
-	public ObservableController(final S comp, final Function<T, String> get,
-			final BiConsumer<T, String> set, final Consumer<T> onEmpty) {
-		super(comp, (c, u) -> c.setText(get.apply(u)));
-		this.func = (t, u) -> {
-			if (t.equals(get.apply(u))) return false;
-			set.accept(u, t);
-			return true;
-		};
-		AnyActionDocumentListener.emptyOrText( comp, 
-				e -> onEmpty.accept( impl.getModel() ), 
-				evt -> accept( ) );
+	public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
+			@NonNull final S comp, @NonNull final Function<T, String> get,
+			@NonNull final BiConsumer<T, String> set, Consumer<T> onEmpty) {
+		return new TextFieldController<S, T>(comp, get, set, onEmpty);
 	}
-
-	private boolean update() {
-		return func.apply( impl.getComponent().getText( ), impl.getModel() );
-	}
-
-	private void accept() {
-		Objects.requireNonNull( impl.getModel() );
-		if ( update( ) ) {
-			impl.notifySubscribers(impl.getModel());
-		}
+	
+	/**
+	 * Construct an ObservableController that ties together a JFormattedTextField with
+	 * the member of an observable object such that updates flow between the model
+	 * and the JFormattedTextField. Ignores changes when the text document is empty.
+	 * @param comp The component being watched
+	 * @param get A model getter function to be called on the observed object
+	 * @param set A model setter function to be called on the observed object
+	 */
+	public <S extends JFormattedTextField, T extends Observable, V> ObservableListener<S, T> from(
+			@NonNull final S comp, @NonNull final Function<T, V> get, @NonNull final BiConsumer<T, V> set) {
+		return new FormattedTextFieldController<>(comp, get, set, null);
 	}
 }

+ 0 - 114
src/main/lombok/org/leumasjaffe/observer/ObservableFmtController.java

@@ -1,114 +0,0 @@
-package org.leumasjaffe.observer;
-
-import java.util.Objects;
-import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
-import java.util.function.Consumer;
-import java.util.function.Function;
-
-import javax.swing.JFormattedTextField;
-
-import org.leumasjaffe.event.AnyActionDocumentListener;
-
-import lombok.experimental.FieldDefaults;
-import lombok.AccessLevel;
-
-/**
- * An ObservableListener object that also provides an update link in the other direction.
- * The normal Listener updates some (UI) component when the model object updates. This
- * object adds the feature to update the model from an update to UI component.
- *
- * This allows you to have a formatted input field attached to your model (like with a JavaBean),
- * that also fires off notifications to other interested listeners when you edit the text.
- */
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
-public class ObservableFmtController<S extends JFormattedTextField, T extends Observable, V> extends ObservableListener<S, T> {
-	BiFunction<V, T, Boolean> func;
-
-	/**
-	 * Construct an ObservableController that modifies its model whenever the linked
-	 * JFormattedTextField is updated. Ignores changes when the text document is empty.
-	 * @param comp A GUI object that is to be coupled with the model
-	 * @param func An update function to modify the model from text content
-	 * @param update The standard callback to set the component when the model changes elsewhere
-	 */
-	public ObservableFmtController(final S comp, final BiFunction<V, T, Boolean> func,
-			final BiConsumer<? super S, ? super T> update) {
-		super(comp, update);
-		this.func = func;
-		AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
-	}
-	
-	/**
-	 * Construct an ObservableController that modifies its model whenever the linked
-	 * JFormattedTextField is updated, including deleting all of the text.
-	 * @param comp A GUI object that is to be coupled with the model
-	 * @param func An update function to modify the model from text content
-	 * @param update The standard callback to set the component when the model changes elsewhere
-	 * @param onEmpty A special handler to set model value when we delete the content of the
-	 * component.
-	 */
-	public ObservableFmtController(final S comp, final BiFunction<V, T, Boolean> func,
-			final BiConsumer<? super S, ? super T> update, final Consumer<T> onEmpty) {
-		super(comp, update);
-		this.func = func;
-		AnyActionDocumentListener.emptyOrText( comp, 
-				e -> onEmpty.accept( impl.getModel() ), 
-				evt -> accept( ) );
-	}
-	
-	/**
-	 * Construct an ObservableController that ties together a JTextComponent with
-	 * the member of an observable object such that updates flow between the model
-	 * and the JFormattedTextField. Ignores changes when the text document is empty.
-	 * @param comp The component being watched
-	 * @param get A model getter function to be called on the observed object
-	 * @param set A model setter function to be called on the observed object
-	 */
-	public ObservableFmtController(final S comp, final Function<T, V> get,
-			final BiConsumer<T, V> set) {
-		super(comp, (c, u) -> {
-			if (c.getValue().equals(get.apply(u))) return;
-			c.setValue(get.apply(u));
-		});
-		this.func = (t, u) -> {
-			if (t.equals(get.apply(u))) return false;
-			set.accept(u, t);
-			return true;
-		};
-		AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
-	}
-	
-	/**
-	 * Construct an ObservableController that ties together a JTextComponent with
-	 * the member of an observable object such that updates flow between the model
-	 * and the JFormattedTextField, including deleting all of the text.
-	 * @param comp The component being watched
-	 * @param get A model getter function to be called on the observed object
-	 * @param set A model setter function to be called on the observed object
-	 */
-	public ObservableFmtController(final S comp, final Function<T, V> get,
-			final BiConsumer<T, V> set, final Consumer<T> onEmpty) {
-		super(comp, (c, u) -> c.setValue(get.apply(u)));
-		this.func = (t, u) -> {
-			if (t.equals(get.apply(u))) return false;
-			set.accept(u, t);
-			return true;
-		};
-		AnyActionDocumentListener.emptyOrText( comp, 
-				e -> onEmpty.accept( impl.getModel() ), 
-				evt -> accept( ) );
-	}
-
-	@SuppressWarnings("unchecked")
-	private boolean update() {
-		return func.apply( (V) impl.getComponent().getValue( ), impl.getModel() );
-	}
-
-	private void accept() {
-		Objects.requireNonNull( impl.getModel() );
-		if ( update( ) ) {
-			impl.notifySubscribers(impl.getModel());
-		}
-	}
-}