From 1b76129ea3de16703aac5ee60cc5f5deb59a0e96 Mon Sep 17 00:00:00 2001 From: Michael Heinrichs Date: Mon, 13 Nov 2017 08:24:56 +0100 Subject: [PATCH] Replaced createActionSubscriber() functionality with simple dispatch()-methods. Removed SimpleReduxFXStore. Removed default drivers. Removed functionality to attach ReduxFXView to 3rd-party store. --- examples/colorchooser/pom.xml | 4 + .../colorchooser/app/ColorChooserApp.java | 11 +- .../colorchooser/app/updater/Updater.java | 32 ++- .../component/ColorChooserComponent.java | 12 +- .../updater/ColorChooserUpdater.java | 3 +- examples/external-store/pom.xml | 46 ---- .../examples/externalstore/ExternalStore.java | 49 ---- .../externalstore/actions/Actions.java | 43 ---- .../actions/IncCounterAction.java | 24 -- .../externalstore/actions/InitAction.java | 24 -- .../externalstore/reducer/Reducer.java | 63 ----- .../externalstore/state/AppState.java | 63 ----- .../examples/externalstore/view/MainView.java | 85 ------ .../reduxfx/examples/fxml/FxmlExample.java | 13 +- .../examples/fxml/updater/Updater.java | 32 ++- .../examples/helloworld/HelloWorld.java | 14 +- .../examples/helloworld/updater/Updater.java | 7 +- examples/menus/pom.xml | 4 + .../netopyr/reduxfx/examples/menus/Menus.java | 11 +- .../examples/menus/updater/Updater.java | 62 ++--- examples/screenswitch/pom.xml | 4 + .../examples/screenswitch/ScreenSwitch.java | 13 +- .../screenswitch/updater/Updater.java | 34 +-- examples/todomvc/pom.xml | 4 + .../reduxfx/examples/todo/TodoMVC.java | 11 +- .../examples/todo/updater/Updater.java | 242 +++++++++--------- examples/treeview/pom.xml | 4 + .../examples/treeview/TreeViewExample.java | 4 +- pom.xml | 1 - reduxfx-fxml/pom.xml | 4 + .../com/netopyr/reduxfx/fxml/ReduxFxml.java | 38 +-- .../component/impl/ComponentDriver.java | 18 +- .../reduxfx/driver/ActionSupplier.java | 9 - .../reduxfx/driver/CommandConsumer.java | 10 - .../com/netopyr/reduxfx/driver/Driver.java | 4 - .../reduxfx/driver/action/ActionDriver.java | 49 ---- .../action/command/DispatchActionCommand.java | 26 -- .../reduxfx/driver/http/HttpDriver.java | 100 -------- .../driver/http/command/HttpGetCommand.java | 49 ---- .../driver/http/command/HttpPutCommand.java | 67 ----- .../driver/properties/PropertiesDriver.java | 72 ------ .../properties/command/LoadFileCommand.java | 52 ---- .../com/netopyr/reduxfx/store/Driver.java | 10 + .../netopyr/reduxfx/store/ReduxFXStore.java | 39 +-- .../reduxfx/store/SimpleReduxFXStore.java | 27 -- reduxfx-view/pom.xml | 4 + .../reduxfx/vscenegraph/ReduxFXView.java | 69 ++--- 47 files changed, 347 insertions(+), 1219 deletions(-) delete mode 100644 examples/external-store/pom.xml delete mode 100644 examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/ExternalStore.java delete mode 100644 examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/Actions.java delete mode 100644 examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/IncCounterAction.java delete mode 100644 examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/InitAction.java delete mode 100644 examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/reducer/Reducer.java delete mode 100644 examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/state/AppState.java delete mode 100644 examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/view/MainView.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/ActionSupplier.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/CommandConsumer.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/Driver.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/ActionDriver.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/command/DispatchActionCommand.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/HttpDriver.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpGetCommand.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpPutCommand.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/PropertiesDriver.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/command/LoadFileCommand.java create mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/store/Driver.java delete mode 100644 reduxfx-store/src/main/java/com/netopyr/reduxfx/store/SimpleReduxFXStore.java diff --git a/examples/colorchooser/pom.xml b/examples/colorchooser/pom.xml index 82ec336..8829d07 100644 --- a/examples/colorchooser/pom.xml +++ b/examples/colorchooser/pom.xml @@ -32,6 +32,10 @@ com.netopyr.reduxfx reduxfx-view + + org.slf4j + slf4j-simple + \ No newline at end of file diff --git a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/ColorChooserApp.java b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/ColorChooserApp.java index 38ece36..e658d90 100644 --- a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/ColorChooserApp.java +++ b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/ColorChooserApp.java @@ -4,7 +4,7 @@ import com.netopyr.reduxfx.examples.colorchooser.app.updater.Updater; import com.netopyr.reduxfx.examples.colorchooser.app.view.MainView; import com.netopyr.reduxfx.middleware.LoggingMiddleware; -import com.netopyr.reduxfx.store.SimpleReduxFXStore; +import com.netopyr.reduxfx.store.ReduxFXStore; import com.netopyr.reduxfx.vscenegraph.ReduxFXView; import javafx.application.Application; import javafx.scene.paint.Color; @@ -21,13 +21,10 @@ public void start(Stage primaryStage) throws Exception { final AppState initialState = AppState.create().withColor(Color.VIOLET); // Setup the ReduxFX-store passing the initialState and the update-function - final SimpleReduxFXStore store = new SimpleReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); + final ReduxFXStore store = new ReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); - // Setup the ReduxFX-view passing the view-function and the primary stage that should hold the calculated view - final ReduxFXView view = ReduxFXView.createStage(MainView::view, primaryStage); - - // Connect store and view - view.connect(store.getStatePublisher(), store.createActionSubscriber()); + // Setup the ReduxFX-view passing the store, the view-function and the primary stage that should hold the calculated view + ReduxFXView.createStage(store, MainView::view, primaryStage); } public static void main(String[] args) { diff --git a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/updater/Updater.java b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/updater/Updater.java index 36274d8..1fb8a08 100644 --- a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/updater/Updater.java +++ b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/app/updater/Updater.java @@ -2,6 +2,7 @@ import com.netopyr.reduxfx.examples.colorchooser.app.actions.UpdateColorAction; import com.netopyr.reduxfx.examples.colorchooser.app.state.AppState; +import com.netopyr.reduxfx.updater.Update; import java.util.Objects; @@ -43,25 +44,28 @@ private Updater() { * @return the new {@code AppState} * @throws NullPointerException if state or action are {@code null} */ - public static AppState update(AppState state, Object action) { + public static Update update(AppState state, Object action) { Objects.requireNonNull(state, "The parameter 'state' must not be null"); Objects.requireNonNull(action, "The parameter 'action' must not be null"); - // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case - // in Java, except that it is much more flexible and returns a value. - // We check which of the cases is true and in that branch we specify the newState. - return Match(action).of( + return Update.of( - // If the action is a UpdateColorAction, we return a new AppState with the - // property color set to the new value. - Case($(instanceOf(UpdateColorAction.class)), - updateColorAction -> state.withColor(updateColorAction.getValue()) - ), + // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case + // in Java, except that it is much more flexible and returns a value. + // We check which of the cases is true and in that branch we specify the newState. + Match(action).of( - // This is the default branch of this switch-case. If an unknown action was passed to the - // updater, we simply return the old state. This is a convention, that is not needed right - // now, but will help once you start to decompose your updater. - Case($(), state) + // If the action is a UpdateColorAction, we return a new AppState with the + // property color set to the new value. + Case($(instanceOf(UpdateColorAction.class)), + updateColorAction -> state.withColor(updateColorAction.getValue()) + ), + + // This is the default branch of this switch-case. If an unknown action was passed to the + // updater, we simply return the old state. This is a convention, that is not needed right + // now, but will help once you start to decompose your updater. + Case($(), state) + ) ); } } diff --git a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/ColorChooserComponent.java b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/ColorChooserComponent.java index fb0cd6d..35781f0 100644 --- a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/ColorChooserComponent.java +++ b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/ColorChooserComponent.java @@ -8,6 +8,7 @@ import com.netopyr.reduxfx.component.ComponentBase; import com.netopyr.reduxfx.examples.colorchooser.app.view.MainView; import com.netopyr.reduxfx.middleware.LoggingMiddleware; +import com.netopyr.reduxfx.store.Driver; import com.netopyr.reduxfx.vscenegraph.ReduxFXView; import com.netopyr.reduxfx.vscenegraph.builders.Factory; import javafx.beans.property.ObjectProperty; @@ -20,7 +21,7 @@ * * It extends {@code VBox} and adds one additional JavaFX property {@link #colorProperty()}. As usual in ReduxFX, * we do not want to modify state directly and this also applies to JavaFX properties of the public interface. - * Instead we use a {@link com.netopyr.reduxfx.driver.Driver} for that. We can send commands to the driver to update + * Instead we use a {@link Driver} for that. We can send commands to the driver to update * the value of the JavaFX property. Commands are created in the {@link ColorChooserUpdater} together with the state * changes. If the value of a JavaFX property changes, our updater receives an action, which can be specified here. */ @@ -47,15 +48,12 @@ public ColorChooserComponent() { // Setup the initial state final ColorChooserState initialData = new ColorChooserState(); - // A ComponentBase is the central piece of every component implemented with ReduxFX. It creates a separate + // A component-base is the central piece of every component implemented with ReduxFX. It creates a separate // ReduxFX-store for the component and acts as the factory for all JavaFX properties. final ComponentBase componentBase = new ComponentBase<>(this, initialData, ColorChooserUpdater::update, new LoggingMiddleware<>()); - // Setup the ReduxFX-view passing the view-function and this as the root node for the generated Scenegraph - final ReduxFXView view = ReduxFXView.create(ColorChooserView::view, this); - - // Connect componentBase and view - view.connect(componentBase.getStatePublisher(), componentBase.createActionSubscriber()); + // Setup the ReduxFX-view passing the component-base the view-function and this as the root node for the generated Scenegraph + ReduxFXView.create(componentBase, ColorChooserView::view, this); // This sets up the JavaFX property of this component. The value can be changed by dispatching // ObjectChangedCommands in the ColorChooserUpdater (alongside any required state changes). The VChangeListener diff --git a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/updater/ColorChooserUpdater.java b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/updater/ColorChooserUpdater.java index ba53996..97b431a 100644 --- a/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/updater/ColorChooserUpdater.java +++ b/examples/colorchooser/src/main/java/com/netopyr/reduxfx/examples/colorchooser/component/updater/ColorChooserUpdater.java @@ -8,6 +8,7 @@ import com.netopyr.reduxfx.examples.colorchooser.component.actions.UpdateGreenAction; import com.netopyr.reduxfx.examples.colorchooser.component.actions.UpdateRedAction; import com.netopyr.reduxfx.examples.colorchooser.component.state.ColorChooserState; +import com.netopyr.reduxfx.store.Driver; import com.netopyr.reduxfx.updater.Update; import javafx.scene.paint.Color; @@ -27,7 +28,7 @@ * action and calculates the new state from that. *

* Optionally it can also create an arbitrary number of commands, which are processed by a - * {@link com.netopyr.reduxfx.driver.Driver}. Usually such a {@code Driver} has to be registered with the + * {@link Driver}. Usually such a {@code Driver} has to be registered with the * {@link com.netopyr.reduxfx.store.ReduxFXStore}-instance explicitly, but {@code ColorChooserComponent} uses a * {@link ComponentBase} which registers a driver for component-specific commands and actions automatically. * See {@link ColorChooserComponent} for more details. diff --git a/examples/external-store/pom.xml b/examples/external-store/pom.xml deleted file mode 100644 index 69bc091..0000000 --- a/examples/external-store/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - 4.0.0 - - - reduxfx-parent - com.netopyr.reduxfx - 0.4.2-SNAPSHOT - ../../pom.xml - - - external-store - com.netopyr.reduxfx.examples - - ReduxFX: 3rd-party store example - Example of using ReduxFX-View with with a 3rd-party store - https://github.com/netopyr/reduxfx - - - true - true - true - true - - - - - jitpack.io - https://jitpack.io - - - - - - com.netopyr.reduxfx - reduxfx-view - - - com.github.glung - redux-java - 1.1 - - - - - \ No newline at end of file diff --git a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/ExternalStore.java b/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/ExternalStore.java deleted file mode 100644 index 6e6d741..0000000 --- a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/ExternalStore.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.netopyr.reduxfx.examples.externalstore; - -import com.glung.redux.Store; -import com.netopyr.reduxfx.examples.externalstore.reducer.Reducer; -import com.netopyr.reduxfx.examples.externalstore.view.MainView; -import com.netopyr.reduxfx.examples.externalstore.actions.Actions; -import com.netopyr.reduxfx.examples.externalstore.state.AppState; -import com.netopyr.reduxfx.vscenegraph.ReduxFXView; -import io.reactivex.BackpressureStrategy; -import io.reactivex.Flowable; -import io.reactivex.processors.PublishProcessor; -import javafx.application.Application; -import javafx.stage.Stage; - -/** - * This is the launcher of the application. - */ -public class ExternalStore extends Application { - - @Override - public void start(Stage primaryStage) throws Exception { - // Create a store with redux-java - final Store store = (Store) Store.createStore(Reducer::reduce, AppState.create(), null); - - // ReduxFX uses reactive streams to handle its inputs and outputs - // To handle the input, we create a Publisher that contains all elements emitted by the store - final Flowable statePublisher = Flowable.create( - emitter -> store.subscribe(() -> emitter.onNext(store.getState())), - BackpressureStrategy.BUFFER - ); - - // To handle the output of the ReduxFView, we create a Subscriber that calls store.dispatch() for each action - final PublishProcessor actionSubscriber = PublishProcessor.create(); - actionSubscriber.subscribe(store::dispatch); - - // Setup the ReduxFX-view passing the view-function and the primary stage that should hold the calculated view - final ReduxFXView view = ReduxFXView.createStage(MainView::view, primaryStage); - - // Connect store and view - view.connect(statePublisher, actionSubscriber); - - // To initialize redux-java, we have to send an init-action - store.dispatch(Actions.initAction()); - } - - public static void main(String[] args) { - Application.launch(args); - } -} diff --git a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/Actions.java b/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/Actions.java deleted file mode 100644 index 8780df5..0000000 --- a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/Actions.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.netopyr.reduxfx.examples.externalstore.actions; - -import com.netopyr.reduxfx.examples.externalstore.reducer.Reducer; - -/** - * The class {@code Actions} contains factory-methods for all actions that are available in this application. - *

- * Actions are an implementation of the Command pattern. They describe what should happen within the application, - * but they do not do any changes themselves. All actions are generated in change- and invalidation-listeners, - * as well as event-handlers, and passed to the {@link Reducer}, which performs the actual change. - */ -public class Actions { - - // InitAction and IncCounterAction are stateless, therefore only a single instance can be reused - private static final InitAction INIT_ACTION = new InitAction(); - private static final IncCounterAction INC_COUNTER_ACTION = new IncCounterAction(); - - private Actions() { - } - - /** - * This method generates an {@link InitAction}. - *

- * This action is passed to the {@link Reducer} to initialize the system. - * - * @return the {@code InitAction} - */ - public static InitAction initAction() { - return INIT_ACTION; - } - - /** - * This method generates an {@link IncCounterAction}. - *

- * This action is passed to the {@link Reducer} when the button was pressed. - * - * @return the {@code IncCounterAction} - */ - public static IncCounterAction incCounterAction() { - return INC_COUNTER_ACTION; - } - -} diff --git a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/IncCounterAction.java b/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/IncCounterAction.java deleted file mode 100644 index 857bd35..0000000 --- a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/IncCounterAction.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.netopyr.reduxfx.examples.externalstore.actions; - -import com.netopyr.reduxfx.examples.externalstore.reducer.Reducer; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -/** - * A {@code IncCounterAction} is passed to the {@link Reducer} when the user clicks the button. - *

- * Actions are an implementation of the Command pattern. They describe what should happen within the application, - * but they do not do any changes themselves. Every time we want to change something in the application-state, - * we have to generate an Action and pass it to the {@link Reducer}, which performs the actual change. - */ -public final class IncCounterAction { - - IncCounterAction() { - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .toString(); - } -} diff --git a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/InitAction.java b/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/InitAction.java deleted file mode 100644 index 6d647af..0000000 --- a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/actions/InitAction.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.netopyr.reduxfx.examples.externalstore.actions; - -import com.netopyr.reduxfx.examples.externalstore.reducer.Reducer; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -/** - * An {@code InitAction} is dispatched to the {@link Reducer} to initialize the system. - *

- * Actions are an implementation of the Command pattern. They describe what should happen within the application, - * but they do not do any changes themselves. Every time we want to change something in the application-state, - * we have to generate an Action and pass it to the {@link Reducer}, which performs the actual change. - */ -public final class InitAction { - - InitAction() { - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .toString(); - } -} diff --git a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/reducer/Reducer.java b/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/reducer/Reducer.java deleted file mode 100644 index d5d279c..0000000 --- a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/reducer/Reducer.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.netopyr.reduxfx.examples.externalstore.reducer; - -import com.netopyr.reduxfx.examples.externalstore.actions.IncCounterAction; -import com.netopyr.reduxfx.examples.externalstore.state.AppState; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Objects; - -import static io.vavr.API.$; -import static io.vavr.API.Case; -import static io.vavr.API.Match; -import static io.vavr.Predicates.instanceOf; - -/** - * This class implements a reducer as defined by the Redux-architecture. It consists of a single method that - * takes the current {@link AppState} and an action and returns the new {@code AppState}. - */ -public class Reducer { - - private static final Logger LOG = LoggerFactory.getLogger(Reducer.class); - - private Reducer() { - } - - /** - * This method implements the reduce-functionality required for a Redux-store. It takes the current - * {@link AppState} and an action and returns the new {@code AppState} - * - * @param state the current {@code AppState} - * @param action the action - * @return the new {@code AppState} - */ - public static AppState reduce(AppState state, Object action) { - Objects.requireNonNull(state, "The parameter 'state' must not be null"); - Objects.requireNonNull(action, "The parameter 'action' must not be null"); - - // Here we assign the new state - final AppState newState = - - // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case - // in Java, except that it is much more flexible and returns a value. - // We check which kind of action was received and in that case-branch we specify the value that - // will be assigned to newState. - Match(action).of( - - // If the action is a IncCounterAction, we return a new AppState with the - // counter increased by one. - Case($(instanceOf(IncCounterAction.class)), - incCounterAction -> state.withCounter(state.getCounter() + 1) - ), - - // This is the default branch of this switch-case. If an unknown action was passed to the - // updater, we simply return the old state. This is a convention, that is not needed right - // now, but will help once you start to decompose your reducer. - Case($(), state) - ); - - LOG.trace("\nUpdater Old State:\n{}\nUpdater Action:\n{}\nUpdater New State:\n{}\n\n", - state, action, newState); - return newState; - } -} diff --git a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/state/AppState.java b/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/state/AppState.java deleted file mode 100644 index 2f5ab67..0000000 --- a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/state/AppState.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.netopyr.reduxfx.examples.externalstore.state; - -import com.netopyr.reduxfx.examples.externalstore.reducer.Reducer; -import com.netopyr.reduxfx.examples.externalstore.view.MainView; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -/** - * An instance of the class {@code AppState} is the root node of the state-tree. - *

- * In ReduxFX the whole application state is kept in a single, immutable data structure. This data structure is created - * in the {@link Reducer}. The {@code Reducer} gets the current state together with the action that should be performed - * and calculates the new state from that. - *

- * The new state is passed to the {@link MainView}-function, which calculates the new virtual Scenegraph. - */ -public final class AppState { - - private final int counter; - - - private AppState(int counter) { - this.counter = counter; - } - - /** - * The method {@code create} returns a new instance of {@code AppState} with the counter set to its default value {@code 0}. - * - * @return the new {@code AppState} - */ - public static AppState create() { - return new AppState(0); - } - - - /** - * This is the getter of the {@code counter}. - * - * @return the {@code counter} - */ - public int getCounter() { - return counter; - } - - /** - * The method {@code withCounter} creates a copy of this {@code AppState} with the {@code counter} set to - * the given value. - * - * @param counter the new {@code counter} - * @return the created {@code AppState} - */ - public AppState withCounter(int counter) { - return new AppState(counter); - } - - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("counter", counter) - .toString(); - } -} diff --git a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/view/MainView.java b/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/view/MainView.java deleted file mode 100644 index 743420b..0000000 --- a/examples/external-store/src/main/java/com/netopyr/reduxfx/examples/externalstore/view/MainView.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.netopyr.reduxfx.examples.externalstore.view; - -import com.netopyr.reduxfx.examples.externalstore.actions.Actions; -import com.netopyr.reduxfx.examples.externalstore.state.AppState; -import com.netopyr.reduxfx.vscenegraph.VNode; - -import java.util.Objects; - -import static com.netopyr.reduxfx.vscenegraph.VScenegraphFactory.Button; -import static com.netopyr.reduxfx.vscenegraph.VScenegraphFactory.Label; -import static com.netopyr.reduxfx.vscenegraph.VScenegraphFactory.Scene; -import static com.netopyr.reduxfx.vscenegraph.VScenegraphFactory.StackPane; -import static com.netopyr.reduxfx.vscenegraph.VScenegraphFactory.Stage; -import static com.netopyr.reduxfx.vscenegraph.VScenegraphFactory.VBox; - -/** - * The class {@code MainView} is responsible for mapping the current state of the application, an instance of - * {@link AppState}, to the respective VirtualScenegraph, which is then used to update the UI. - *

- * Every time a new application state becomes available, the method {@link #view(AppState)} is called, which creates - * a new VirtualScenegraph. A VirtualScenegraph is a data structure that describes the state of the real - * JavaFX Scenegraph. The ReduxFX runtime analyzes the VirtualScenegraph, calculates the difference between the - * current VirtualScenegraph and the new VirtualScenegraph and applies the changes. This is done transparently without - * the developer being aware of it. - *

- * The advantage of this approach is, that an application developer does not have to worry about the current state of - * the Scenegraph and eventual state changes. Instead he can simply define a fresh UI with no past. This is a lot - * simpler than working with a mutable Scenegraph directly. - *

- * The ReduxFX-API was designed to allow a declarative definition of the VirtualScenegraph. Methods starting with a - * capital letter create {@code Node}s while methods starting with a small letter define properties and events of the - * {@code Node}s. - */ -public class MainView { - - private MainView() {} - - /** - * The method {@code view} calculates a new main view for the given state. - *

- * It creates a new {@code Stage}, which contains a {@code Label} and a {@code Button}. - * The {@code Label} shows how often the button was clicked. - *

- * Please note that we do not have to deal with the old state of the Scenegraph at all. For example we do not - * have to decide when the Label needs to be updated. We simply set it according to the given state. - *

- * When the Button is clicked, the event handler {@code onAction} returns an instance of - * {@link com.netopyr.reduxfx.examples.externalstore.actions.IncCounterAction}, which will be passed to the - * {@link com.netopyr.reduxfx.examples.externalstore.reducer.Reducer} to perform the actual change. - * - * @param state the current state - * @return the root {@link VNode} of the created VirtualScenegraph - * @throws NullPointerException if {@code state} is {@code null} - */ - public static VNode view(AppState state) { - Objects.requireNonNull(state, "The parameter 'state' must not be null"); - - return Stage() - .title("Example with Redux Java Store") - .showing(true) - .scene( - Scene() - .root( - StackPane() - .padding(50, 100) - .children( - VBox() - .spacing(20) - .children( - Label() - .prefWidth(210) - // The Label shows how often the button was clicked, which is stored in state.getCounter() - .text(String.format("You clicked the button %d times", state.getCounter())), - Button() - .text("Click Me!") - // This is how an event-lister is defined. The EventListener gets the event and has to return - // the action that should be dispatched to the Updater. - // If the onAction-event is fired, we want to dispatch a IncCounterAction. - .onAction(e -> Actions.incCounterAction()) - ) - ) - ) - ); - } -} diff --git a/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/FxmlExample.java b/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/FxmlExample.java index e1b7b38..4cbc637 100644 --- a/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/FxmlExample.java +++ b/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/FxmlExample.java @@ -1,14 +1,14 @@ package com.netopyr.reduxfx.examples.fxml; +import com.netopyr.reduxfx.examples.fxml.state.AppState; +import com.netopyr.reduxfx.examples.fxml.updater.Updater; import com.netopyr.reduxfx.examples.fxml.view.MainView; import com.netopyr.reduxfx.fxml.Dispatcher; import com.netopyr.reduxfx.fxml.ReduxFxml; import com.netopyr.reduxfx.fxml.Selector; import com.netopyr.reduxfx.fxml.ViewLoader; import com.netopyr.reduxfx.middleware.LoggingMiddleware; -import com.netopyr.reduxfx.examples.fxml.state.AppState; -import com.netopyr.reduxfx.examples.fxml.updater.Updater; -import com.netopyr.reduxfx.store.SimpleReduxFXStore; +import com.netopyr.reduxfx.store.ReduxFXStore; import eu.lestard.easydi.EasyDI; import javafx.application.Application; import javafx.scene.Parent; @@ -27,14 +27,11 @@ public void start(Stage primaryStage) throws Exception { final AppState initialState = AppState.create(); // Setup the ReduxFX-store passing the initialState and the update-function - final SimpleReduxFXStore store = new SimpleReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); + final ReduxFXStore store = new ReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); // Create an instance of ReduxFxml - ReduxFxml reduxFxml = ReduxFxml.create(); - - // To setup ReduxFxml, we have to connect it with the store - reduxFxml.connect(store.getStatePublisher(), store.createActionSubscriber()); + ReduxFxml reduxFxml = ReduxFxml.create(store); // Setup dependency injection context diff --git a/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/updater/Updater.java b/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/updater/Updater.java index 367ec49..6b7a260 100644 --- a/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/updater/Updater.java +++ b/examples/fxml/src/main/java/com/netopyr/reduxfx/examples/fxml/updater/Updater.java @@ -2,6 +2,7 @@ import com.netopyr.reduxfx.examples.fxml.actions.IncCounterAction; import com.netopyr.reduxfx.examples.fxml.state.AppState; +import com.netopyr.reduxfx.updater.Update; import java.util.Objects; @@ -43,25 +44,28 @@ private Updater() { * @return the new {@code AppState} * @throws NullPointerException if state or action are {@code null} */ - public static AppState update(AppState state, Object action) { + public static Update update(AppState state, Object action) { Objects.requireNonNull(state, "The parameter 'state' must not be null"); Objects.requireNonNull(action, "The parameter 'action' must not be null"); - // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case - // in Java, except that it is much more flexible and returns a value. - // We check which of the cases is true and in that branch we specify the newState. - return Match(action).of( + return Update.of( - // If the action is a IncCounterAction, we return a new AppState with the - // counter increased by one. - Case($(instanceOf(IncCounterAction.class)), - incCounterAction -> state.withCounter(state.getCounter() + 1) - ), + // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case + // in Java, except that it is much more flexible and returns a value. + // We check which of the cases is true and in that branch we specify the newState. + Match(action).of( - // This is the default branch of this switch-case. If an unknown action was passed to the - // updater, we simply return the old state. This is a convention, that is not needed right - // now, but will help once you start to decompose your updater. - Case($(), state) + // If the action is a IncCounterAction, we return a new AppState with the + // counter increased by one. + Case($(instanceOf(IncCounterAction.class)), + incCounterAction -> state.withCounter(state.getCounter() + 1) + ), + + // This is the default branch of this switch-case. If an unknown action was passed to the + // updater, we simply return the old state. This is a convention, that is not needed right + // now, but will help once you start to decompose your updater. + Case($(), state) + ) ); } } diff --git a/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/HelloWorld.java b/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/HelloWorld.java index b4145b8..7cae89a 100644 --- a/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/HelloWorld.java +++ b/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/HelloWorld.java @@ -1,9 +1,8 @@ package com.netopyr.reduxfx.examples.helloworld; -import com.netopyr.reduxfx.examples.helloworld.view.MainView; -import com.netopyr.reduxfx.examples.helloworld.state.AppState; import com.netopyr.reduxfx.examples.helloworld.updater.Updater; -import com.netopyr.reduxfx.store.SimpleReduxFXStore; +import com.netopyr.reduxfx.examples.helloworld.view.MainView; +import com.netopyr.reduxfx.store.ReduxFXStore; import com.netopyr.reduxfx.vscenegraph.ReduxFXView; import javafx.application.Application; import javafx.stage.Stage; @@ -20,13 +19,10 @@ public void start(Stage primaryStage) throws Exception { final Integer initialCounter = 0; // Setup the ReduxFX-store passing the initialState and the update-function - final SimpleReduxFXStore store = new SimpleReduxFXStore<>(initialCounter, Updater::update); - - // Setup the ReduxFX-view passing the view-function and the primary stage that should hold the calculated view - final ReduxFXView view = ReduxFXView.createStage(MainView::view, primaryStage); + final ReduxFXStore store = new ReduxFXStore<>(initialCounter, Updater::update); - // Connect store and view - view.connect(store.getStatePublisher(), store.createActionSubscriber()); + // Setup the ReduxFX-view passing the store, the view-function and the primary stage that should hold the calculated view + ReduxFXView.createStage(store, MainView::view, primaryStage); } public static void main(String[] args) { diff --git a/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/updater/Updater.java b/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/updater/Updater.java index 33ada26..6dc4f6e 100644 --- a/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/updater/Updater.java +++ b/examples/helloworld/src/main/java/com/netopyr/reduxfx/examples/helloworld/updater/Updater.java @@ -1,6 +1,7 @@ package com.netopyr.reduxfx.examples.helloworld.updater; import com.netopyr.reduxfx.examples.helloworld.actions.IncCounterAction; +import com.netopyr.reduxfx.updater.Update; import java.util.Objects; @@ -30,10 +31,12 @@ private Updater() { * @return the new {@code AppState} * @throws NullPointerException if counter or action are {@code null} */ - public static int update(Integer counter, Object action) { + public static Update update(Integer counter, Object action) { Objects.requireNonNull(counter, "The parameter 'counter' must not be null"); Objects.requireNonNull(action, "The parameter 'action' must not be null"); - return action instanceof IncCounterAction? counter + 1 : counter; + return Update.of( + action instanceof IncCounterAction? counter + 1 : counter + ); } } diff --git a/examples/menus/pom.xml b/examples/menus/pom.xml index daa8503..c88ec32 100644 --- a/examples/menus/pom.xml +++ b/examples/menus/pom.xml @@ -32,6 +32,10 @@ com.netopyr.reduxfx reduxfx-view + + org.slf4j + slf4j-simple + \ No newline at end of file diff --git a/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/Menus.java b/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/Menus.java index 7576f26..1b217e4 100644 --- a/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/Menus.java +++ b/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/Menus.java @@ -4,7 +4,7 @@ import com.netopyr.reduxfx.examples.menus.updater.Updater; import com.netopyr.reduxfx.examples.menus.view.ViewManager; import com.netopyr.reduxfx.middleware.LoggingMiddleware; -import com.netopyr.reduxfx.store.SimpleReduxFXStore; +import com.netopyr.reduxfx.store.ReduxFXStore; import com.netopyr.reduxfx.vscenegraph.ReduxFXView; import javafx.application.Application; import javafx.stage.Stage; @@ -21,13 +21,10 @@ public void start(Stage primaryStage) throws Exception { final AppState initialState = AppState.create(); // Setup the ReduxFX-store passing the initialState and the update-function - final SimpleReduxFXStore store = new SimpleReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); + final ReduxFXStore store = new ReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); - // Setup the ReduxFX-view passing the view-function and the primary stage that should hold the calculated view - final ReduxFXView view = ReduxFXView.createStages(ViewManager::view, primaryStage); - - // Connect store and view - view.connect(store.getStatePublisher(), store.createActionSubscriber()); + // Setup the ReduxFX-view passing the store, the view-function and the primary stage that should hold the calculated view + ReduxFXView.createStages(store, ViewManager::view, primaryStage); } public static void main(String[] args) { diff --git a/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/updater/Updater.java b/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/updater/Updater.java index f8f95f1..07305b3 100644 --- a/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/updater/Updater.java +++ b/examples/menus/src/main/java/com/netopyr/reduxfx/examples/menus/updater/Updater.java @@ -4,6 +4,7 @@ import com.netopyr.reduxfx.examples.menus.actions.OpenAlertAction; import com.netopyr.reduxfx.examples.menus.actions.OpenModalAlertAction; import com.netopyr.reduxfx.examples.menus.state.AppState; +import com.netopyr.reduxfx.updater.Update; import javafx.stage.Modality; import java.util.Objects; @@ -46,42 +47,45 @@ private Updater() { * @return the new {@code AppState} * @throws NullPointerException if state or action are {@code null} */ - public static AppState update(AppState state, Object action) { + public static Update update(AppState state, Object action) { Objects.requireNonNull(state, "The parameter 'state' must not be null"); Objects.requireNonNull(action, "The parameter 'action' must not be null"); - // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case - // in Java, except that it is much more flexible and returns a value. - // We check which of the cases is true and in that branch we specify the newState. - return Match(action).of( + return Update.of( - // If the action is ab OpenAlertAction, we return a new AppState with the - // alert-visibility flag set to true and the alert-modality set to NONE. - Case($(instanceOf(OpenAlertAction.class)), - openAlertAction -> - state.withAlertVisible(true) - .withAlertModality(Modality.NONE) - ), + // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case + // in Java, except that it is much more flexible and returns a value. + // We check which of the cases is true and in that branch we specify the newState. + Match(action).of( - // If the action is ab OpenModalAlertAction, we return a new AppState with the - // alert-visibility flag set to true and the alert-modality set to APPLICATION_MODAL. - Case($(instanceOf(OpenModalAlertAction.class)), - openModalAlertAction -> - state.withAlertVisible(true) - .withAlertModality(Modality.APPLICATION_MODAL) - ), + // If the action is ab OpenAlertAction, we return a new AppState with the + // alert-visibility flag set to true and the alert-modality set to NONE. + Case($(instanceOf(OpenAlertAction.class)), + openAlertAction -> + state.withAlertVisible(true) + .withAlertModality(Modality.NONE) + ), - // The AlertWasClosedAction is used to signal that the alert was closed. We return a new AppState - // with the alert-visibility flag set to false. - Case($(instanceOf(AlertWasClosedAction.class)), - alertWasClosedAction -> - state.withAlertVisible(false) - ), + // If the action is ab OpenModalAlertAction, we return a new AppState with the + // alert-visibility flag set to true and the alert-modality set to APPLICATION_MODAL. + Case($(instanceOf(OpenModalAlertAction.class)), + openModalAlertAction -> + state.withAlertVisible(true) + .withAlertModality(Modality.APPLICATION_MODAL) + ), - // This is the default branch of this switch-case. If an unknown action was passed to the - // updater, we simply return the old state. This is a convention, that is not needed right - // now, but will help once you start to decompose your updater. - Case($(), state) + // The AlertWasClosedAction is used to signal that the alert was closed. We return a new AppState + // with the alert-visibility flag set to false. + Case($(instanceOf(AlertWasClosedAction.class)), + alertWasClosedAction -> + state.withAlertVisible(false) + ), + + // This is the default branch of this switch-case. If an unknown action was passed to the + // updater, we simply return the old state. This is a convention, that is not needed right + // now, but will help once you start to decompose your updater. + Case($(), state) + ) ); } } diff --git a/examples/screenswitch/pom.xml b/examples/screenswitch/pom.xml index a12d3f8..0fcec33 100644 --- a/examples/screenswitch/pom.xml +++ b/examples/screenswitch/pom.xml @@ -32,6 +32,10 @@ com.netopyr.reduxfx reduxfx-view + + org.slf4j + slf4j-simple + \ No newline at end of file diff --git a/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/ScreenSwitch.java b/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/ScreenSwitch.java index 780abe8..339626b 100644 --- a/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/ScreenSwitch.java +++ b/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/ScreenSwitch.java @@ -1,10 +1,10 @@ package com.netopyr.reduxfx.examples.screenswitch; +import com.netopyr.reduxfx.examples.screenswitch.state.AppState; import com.netopyr.reduxfx.examples.screenswitch.updater.Updater; import com.netopyr.reduxfx.examples.screenswitch.view.ViewManager; import com.netopyr.reduxfx.middleware.LoggingMiddleware; -import com.netopyr.reduxfx.examples.screenswitch.state.AppState; -import com.netopyr.reduxfx.store.SimpleReduxFXStore; +import com.netopyr.reduxfx.store.ReduxFXStore; import com.netopyr.reduxfx.vscenegraph.ReduxFXView; import javafx.application.Application; import javafx.stage.Stage; @@ -21,13 +21,10 @@ public void start(Stage primaryStage) throws Exception { final AppState initialState = AppState.create(); // Setup the ReduxFX-store passing the initialState and the update-function - final SimpleReduxFXStore store = new SimpleReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); - - // Setup the ReduxFX-view passing the view-function and the primary stage that should hold the calculated view - final ReduxFXView view = ReduxFXView.createStages(ViewManager::view, primaryStage); + final ReduxFXStore store = new ReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); - // Connect store and view - view.connect(store.getStatePublisher(), store.createActionSubscriber()); + // Setup the ReduxFX-view passing the store, the view-function and the primary stage that should hold the calculated view + ReduxFXView.createStages(store, ViewManager::view, primaryStage); } public static void main(String[] args) { diff --git a/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/updater/Updater.java b/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/updater/Updater.java index 748815f..5be7e18 100644 --- a/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/updater/Updater.java +++ b/examples/screenswitch/src/main/java/com/netopyr/reduxfx/examples/screenswitch/updater/Updater.java @@ -2,6 +2,7 @@ import com.netopyr.reduxfx.examples.screenswitch.actions.SwitchScreenAction; import com.netopyr.reduxfx.examples.screenswitch.state.AppState; +import com.netopyr.reduxfx.updater.Update; import java.util.Objects; @@ -43,26 +44,29 @@ private Updater() { * @return the new {@code AppState} * @throws NullPointerException if state or action are {@code null} */ - public static AppState update(AppState state, Object action) { + public static Update update(AppState state, Object action) { Objects.requireNonNull(state, "The parameter 'state' must not be null"); Objects.requireNonNull(action, "The parameter 'action' must not be null"); - // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case - // in Java, except that it is much more flexible and returns a value. - // We check which of the cases is true and in that branch we specify the newState. - return Match(action).of( + return Update.of( - // If the action is a SwitchScreenAction, we return a new AppState with the - // property screen set to the given value - Case($(instanceOf(SwitchScreenAction.class)), - switchScreenAction -> - state.withScreen(switchScreenAction.getScreen()) - ), + // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case + // in Java, except that it is much more flexible and returns a value. + // We check which of the cases is true and in that branch we specify the newState. + Match(action).of( - // This is the default branch of this switch-case. If an unknown action was passed to the - // updater, we simply return the old state. This is a convention, that is not needed right - // now, but will help once you start to decompose your updater. - Case($(), state) + // If the action is a SwitchScreenAction, we return a new AppState with the + // property screen set to the given value + Case($(instanceOf(SwitchScreenAction.class)), + switchScreenAction -> + state.withScreen(switchScreenAction.getScreen()) + ), + + // This is the default branch of this switch-case. If an unknown action was passed to the + // updater, we simply return the old state. This is a convention, that is not needed right + // now, but will help once you start to decompose your updater. + Case($(), state) + ) ); } } diff --git a/examples/todomvc/pom.xml b/examples/todomvc/pom.xml index a8fbc74..2282e5a 100644 --- a/examples/todomvc/pom.xml +++ b/examples/todomvc/pom.xml @@ -36,6 +36,10 @@ com.netopyr.reduxfx reduxfx-fontawesomefx + + org.slf4j + slf4j-simple + \ No newline at end of file diff --git a/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/TodoMVC.java b/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/TodoMVC.java index 3e83202..f8dfbc7 100644 --- a/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/TodoMVC.java +++ b/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/TodoMVC.java @@ -4,7 +4,7 @@ import com.netopyr.reduxfx.examples.todo.updater.Updater; import com.netopyr.reduxfx.examples.todo.view.MainView; import com.netopyr.reduxfx.middleware.LoggingMiddleware; -import com.netopyr.reduxfx.store.SimpleReduxFXStore; +import com.netopyr.reduxfx.store.ReduxFXStore; import com.netopyr.reduxfx.vscenegraph.ReduxFXView; import javafx.application.Application; import javafx.stage.Stage; @@ -21,13 +21,10 @@ public void start(Stage primaryStage) throws Exception { final AppState initialState = AppState.create(); // Setup the ReduxFX-store passing the initialState and the update-function - final SimpleReduxFXStore store = new SimpleReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); + final ReduxFXStore store = new ReduxFXStore<>(initialState, Updater::update, new LoggingMiddleware<>()); - // Setup the ReduxFX-view passing the view-function and the primary stage that should hold the calculated view - final ReduxFXView view = ReduxFXView.createStage(MainView::view, primaryStage); - - // Connect store and view - view.connect(store.getStatePublisher(), store.createActionSubscriber()); + // Setup the ReduxFX-view passing the store, the view-function and the primary stage that should hold the calculated view + ReduxFXView.createStage(store, MainView::view, primaryStage); } public static void main(String[] args) { diff --git a/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/updater/Updater.java b/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/updater/Updater.java index 665fbf1..c7927c9 100644 --- a/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/updater/Updater.java +++ b/examples/todomvc/src/main/java/com/netopyr/reduxfx/examples/todo/updater/Updater.java @@ -11,6 +11,7 @@ import com.netopyr.reduxfx.examples.todo.actions.SetTodoHoverAction; import com.netopyr.reduxfx.examples.todo.state.AppState; import com.netopyr.reduxfx.examples.todo.state.TodoEntry; +import com.netopyr.reduxfx.updater.Update; import java.util.Objects; @@ -52,132 +53,135 @@ private Updater() { * @return the new {@code AppState} * @throws NullPointerException if state or action are {@code null} */ - public static AppState update(AppState state, Object action) { + public static Update update(AppState state, Object action) { Objects.requireNonNull(state, "The parameter 'state' must not be null"); Objects.requireNonNull(action, "The parameter 'action' must not be null"); - // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case - // in Java, except that it is much more flexible and returns a value. - // We check which of the cases is true and in that branch we specify the newState. - return Match(action).of( - - // If the action is a NewTextFieldChangedAction, we return a new AppState with the - // property newTodoText set to the new value - Case($(instanceOf(NewTextFieldChangedAction.class)), - newTextFieldChangedAction -> - state.withNewTodoText(newTextFieldChangedAction.getText()) - ), - - // If the action is an AddTodoAction, we append a new TodoEntry to the list of todo-entries. - // The new TodoEntry will get an id that is the maximum of all used ids plus one and the - // text will be set to the value stored in property newTodoText of the current AppState. - // In addition we clear the property newTodoText. - Case($(instanceOf(AddTodoAction.class)), - state - .withNewTodoText("") - .withTodos( - state.getTodos().append( - TodoEntry.create() - .withId( - state.getTodos() - .map(TodoEntry::getId) - .max() - .getOrElse(-1) + 1 - ) - .withText(state.getNewTodoText()) + return Update.of( + + // This is part of Vavr's pattern-matching API. It works similar to the regular switch-case + // in Java, except that it is much more flexible and returns a value. + // We check which of the cases is true and in that branch we specify the newState. + Match(action).of( + + // If the action is a NewTextFieldChangedAction, we return a new AppState with the + // property newTodoText set to the new value + Case($(instanceOf(NewTextFieldChangedAction.class)), + newTextFieldChangedAction -> + state.withNewTodoText(newTextFieldChangedAction.getText()) + ), + + // If the action is an AddTodoAction, we append a new TodoEntry to the list of todo-entries. + // The new TodoEntry will get an id that is the maximum of all used ids plus one and the + // text will be set to the value stored in property newTodoText of the current AppState. + // In addition we clear the property newTodoText. + Case($(instanceOf(AddTodoAction.class)), + state + .withNewTodoText("") + .withTodos( + state.getTodos().append( + TodoEntry.create() + .withId( + state.getTodos() + .map(TodoEntry::getId) + .max() + .getOrElse(-1) + 1 + ) + .withText(state.getNewTodoText()) + ) + ) + ), + + // If the action is a DeleteTodoAction, we create a new AppState, where the TodoEntry that + // should be deleted is filtered out of the list of todo-entries. + Case($(instanceOf(DeleteTodoAction.class)), + deleteTodoAction -> state.withTodos( + state.getTodos().filter( + todoEntry -> todoEntry.getId() != deleteTodoAction.getId() ) ) - ), - - // If the action is a DeleteTodoAction, we create a new AppState, where the TodoEntry that - // should be deleted is filtered out of the list of todo-entries. - Case($(instanceOf(DeleteTodoAction.class)), - deleteTodoAction -> state.withTodos( - state.getTodos().filter( - todoEntry -> todoEntry.getId() != deleteTodoAction.getId() + ), + + // If the action is an EditTodoAction, we need to replace the TodoEntry with the given id with + // one that uses the given text. + // We do that by mapping all items in the todos-list. If the item-id does not match we re-use + // the old entry. But if the id matches, we replace the TodoEntry with one that has the text + // set to the given value. + Case($(instanceOf(EditTodoAction.class)), + editTodoAction -> state.withTodos( + state.getTodos() + .map(entry -> entry.getId() != editTodoAction.getId() ? + entry : entry.withText(editTodoAction.getText()) + ) ) - ) - ), - - // If the action is an EditTodoAction, we need to replace the TodoEntry with the given id with - // one that uses the given text. - // We do that by mapping all items in the todos-list. If the item-id does not match we re-use - // the old entry. But if the id matches, we replace the TodoEntry with one that has the text - // set to the given value. - Case($(instanceOf(EditTodoAction.class)), - editTodoAction -> state.withTodos( - state.getTodos() - .map(entry -> entry.getId() != editTodoAction.getId() ? - entry : entry.withText(editTodoAction.getText()) - ) - ) - ), - - // If the action is a CompleteTodoAction, we need to replace the TodoEntry with the given id - // with one where the completed-flag is toggled. - // We do that by mapping all items in the todos-list. If the item-id does not match - // we re-use the old entry. But if the id matches, we replace the TodoEntry with one - // where the completed flag is toggled. - Case($(instanceOf(CompleteTodoAction.class)), - completeTodoAction -> state.withTodos( - state.getTodos() - .map(entry -> entry.getId() != completeTodoAction.getId() ? - entry : entry.withCompleted(!entry.isCompleted()) - ) - ) - ), - - // If the action is a CompleteAllAction, we need to set the completed-flag of all todo-entries. - // First we calculate, if the completed-flags need to be set or cleared. This will be stored in - // the variable areAllMarked. The completed-flags need to be set, if at least one of them - // is not set. If all of the flags are set already, we need to clear them. - Case($(instanceOf(CompleteAllAction.class)), - completeAllAction -> { - final boolean areAllMarked = state.getTodos().find(entry -> !entry.isCompleted()).isEmpty(); - return state.withTodos( - state.getTodos() - .map(entry -> entry.withCompleted(!areAllMarked))); - } - ), - - // If the action is a SetFilterAction, we need to return a new AppState where the filter is - // set to the given value. - Case($(instanceOf(SetFilterAction.class)), - setFilterAction -> state.withFilter(setFilterAction.getFilter()) - ), - - // If the action is an SetTodoHoverAction, we need to replace the TodoEntry with the given id - // with one that uses the given hover-value. - // We do that by mapping all items in the todos-list. If the item-id does not match we re-use - // the old entry. But if the id matches, we replace the TodoEntry with one that has the - // hover-flag set to the given value. - Case($(instanceOf(SetTodoHoverAction.class)), - (SetTodoHoverAction setTodoHoverAction) -> state.withTodos( - state.getTodos() - .map(entry -> entry.getId() != setTodoHoverAction.getId() ? - entry : entry.withHover(setTodoHoverAction.isValue()) - ) - ) - ), - - // If the action is an SetEditModeAction, we need to replace the TodoEntry with the given id - // with one that uses the given editMode-value. - // We do that by mapping all items in the todos-list. If the item-id does not match we re-use - // the old entry. But if the id matches, we replace the TodoEntry with one that has the - // editMode-flag set to the given value. - Case($(instanceOf(SetEditModeAction.class)), - (SetEditModeAction setEditModeAction) -> state.withTodos( - state.getTodos() - .map(entry -> entry.getId() != setEditModeAction.getId() ? - entry : entry.withEditMode(setEditModeAction.isValue()) - ) - ) - ), + ), + + // If the action is a CompleteTodoAction, we need to replace the TodoEntry with the given id + // with one where the completed-flag is toggled. + // We do that by mapping all items in the todos-list. If the item-id does not match + // we re-use the old entry. But if the id matches, we replace the TodoEntry with one + // where the completed flag is toggled. + Case($(instanceOf(CompleteTodoAction.class)), + completeTodoAction -> state.withTodos( + state.getTodos() + .map(entry -> entry.getId() != completeTodoAction.getId() ? + entry : entry.withCompleted(!entry.isCompleted()) + ) + ) + ), + + // If the action is a CompleteAllAction, we need to set the completed-flag of all todo-entries. + // First we calculate, if the completed-flags need to be set or cleared. This will be stored in + // the variable areAllMarked. The completed-flags need to be set, if at least one of them + // is not set. If all of the flags are set already, we need to clear them. + Case($(instanceOf(CompleteAllAction.class)), + completeAllAction -> { + final boolean areAllMarked = state.getTodos().find(entry -> !entry.isCompleted()).isEmpty(); + return state.withTodos( + state.getTodos() + .map(entry -> entry.withCompleted(!areAllMarked))); + } + ), + + // If the action is a SetFilterAction, we need to return a new AppState where the filter is + // set to the given value. + Case($(instanceOf(SetFilterAction.class)), + setFilterAction -> state.withFilter(setFilterAction.getFilter()) + ), + + // If the action is an SetTodoHoverAction, we need to replace the TodoEntry with the given id + // with one that uses the given hover-value. + // We do that by mapping all items in the todos-list. If the item-id does not match we re-use + // the old entry. But if the id matches, we replace the TodoEntry with one that has the + // hover-flag set to the given value. + Case($(instanceOf(SetTodoHoverAction.class)), + (SetTodoHoverAction setTodoHoverAction) -> state.withTodos( + state.getTodos() + .map(entry -> entry.getId() != setTodoHoverAction.getId() ? + entry : entry.withHover(setTodoHoverAction.isValue()) + ) + ) + ), + + // If the action is an SetEditModeAction, we need to replace the TodoEntry with the given id + // with one that uses the given editMode-value. + // We do that by mapping all items in the todos-list. If the item-id does not match we re-use + // the old entry. But if the id matches, we replace the TodoEntry with one that has the + // editMode-flag set to the given value. + Case($(instanceOf(SetEditModeAction.class)), + (SetEditModeAction setEditModeAction) -> state.withTodos( + state.getTodos() + .map(entry -> entry.getId() != setEditModeAction.getId() ? + entry : entry.withEditMode(setEditModeAction.isValue()) + ) + ) + ), - // This is the default branch of this switch-case. If an unknown action was passed to the - // updater, we simply return the old state. This is a convention, that is not needed right - // now, but will help once you start to decompose your updater. - Case($(), state) + // This is the default branch of this switch-case. If an unknown action was passed to the + // updater, we simply return the old state. This is a convention, that is not needed right + // now, but will help once you start to decompose your updater. + Case($(), state) + ) ); } } diff --git a/examples/treeview/pom.xml b/examples/treeview/pom.xml index dc591b6..532a277 100644 --- a/examples/treeview/pom.xml +++ b/examples/treeview/pom.xml @@ -32,6 +32,10 @@ com.netopyr.reduxfx reduxfx-view + + org.slf4j + slf4j-simple + \ No newline at end of file diff --git a/examples/treeview/src/main/java/com/netopyr/reduxfx/examples/treeview/TreeViewExample.java b/examples/treeview/src/main/java/com/netopyr/reduxfx/examples/treeview/TreeViewExample.java index b941533..a66f03d 100644 --- a/examples/treeview/src/main/java/com/netopyr/reduxfx/examples/treeview/TreeViewExample.java +++ b/examples/treeview/src/main/java/com/netopyr/reduxfx/examples/treeview/TreeViewExample.java @@ -23,8 +23,6 @@ public void start(Stage primaryStage) throws Exception { final ReduxFXStore store = new ReduxFXStore<>(initialState, (appState, action) -> Update.of(Updater.update(appState, action))); - final ReduxFXView view = ReduxFXView.createStage(MainView::view, primaryStage); - - view.connect(store.getStatePublisher(), store.createActionSubscriber()); + ReduxFXView.createStage(store, MainView::view, primaryStage); } } diff --git a/pom.xml b/pom.xml index 33a6b10..f9affb1 100644 --- a/pom.xml +++ b/pom.xml @@ -24,7 +24,6 @@ examples/menus examples/screenswitch examples/todomvc - examples/external-store examples/fxml examples/helloworld examples/treeview diff --git a/reduxfx-fxml/pom.xml b/reduxfx-fxml/pom.xml index 48f151b..7597fd0 100644 --- a/reduxfx-fxml/pom.xml +++ b/reduxfx-fxml/pom.xml @@ -22,6 +22,10 @@ + + com.netopyr.reduxfx + reduxfx-store + org.slf4j slf4j-api diff --git a/reduxfx-fxml/src/main/java/com/netopyr/reduxfx/fxml/ReduxFxml.java b/reduxfx-fxml/src/main/java/com/netopyr/reduxfx/fxml/ReduxFxml.java index 305821d..6bb227e 100644 --- a/reduxfx-fxml/src/main/java/com/netopyr/reduxfx/fxml/ReduxFxml.java +++ b/reduxfx-fxml/src/main/java/com/netopyr/reduxfx/fxml/ReduxFxml.java @@ -1,14 +1,12 @@ package com.netopyr.reduxfx.fxml; -import io.reactivex.processors.PublishProcessor; +import com.netopyr.reduxfx.store.ReduxFXStore; import javafx.application.Platform; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.collections.ObservableList; -import org.reactivestreams.Processor; -import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import org.slf4j.Logger; @@ -27,37 +25,25 @@ public class ReduxFxml implements Selector, Dispatcher { private static final Logger LOG = LoggerFactory.getLogger(ReduxFxml.class); - private Publisher statePublisher; + private ReduxFXStore store; - private final PublishProcessor actionProcessor = PublishProcessor.create(); - - public static ReduxFxml create() { - return new ReduxFxml<>(); + private ReduxFxml(ReduxFXStore store) { + this.store = store; } - public void connect(Processor store) { - connect(store, store); - } - - public void connect(Publisher statePublisher, Subscriber actionStream) { - this.statePublisher = statePublisher; - - actionProcessor.subscribe(actionStream); + public static ReduxFxml create(ReduxFXStore store) { + return new ReduxFxml<>(store); } @Override public void dispatch(Object action) { - checkConnected(); - - actionProcessor.offer(action); + store.dispatch(action); } @Override public ObservableValue select(Function selector) { - checkConnected(); - ObjectProperty observableValue = new SimpleObjectProperty<>(); addSubscriber(newState -> { @@ -71,8 +57,6 @@ public ObservableValue select(Function selector) { @Override public ObservableList selectList(Function> selector) { - checkConnected(); - ObservableList list = FXCollections.observableArrayList(); addSubscriber(newState -> { @@ -84,15 +68,9 @@ public ObservableList selectList(Function> selector) { return list; } - private void checkConnected() { - if(statePublisher == null) { - throw new IllegalStateException("ReduxFxml has not been connected to a ReduxStore yet."); - } - } - protected void addSubscriber(Consumer subscriber) { - statePublisher.subscribe(new Subscriber() { + store.subscribe(new Subscriber() { @Override public void onSubscribe(Subscription s) { diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/component/impl/ComponentDriver.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/component/impl/ComponentDriver.java index f73036a..cb66c1e 100644 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/component/impl/ComponentDriver.java +++ b/reduxfx-store/src/main/java/com/netopyr/reduxfx/component/impl/ComponentDriver.java @@ -4,9 +4,11 @@ import com.netopyr.reduxfx.component.command.FireEventCommand; import com.netopyr.reduxfx.component.command.IntegerChangedCommand; import com.netopyr.reduxfx.component.command.ObjectChangedCommand; -import com.netopyr.reduxfx.driver.Driver; +import com.netopyr.reduxfx.store.Driver; import com.netopyr.reduxfx.updater.Command; +import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; +import io.reactivex.FlowableEmitter; import io.reactivex.processors.FlowableProcessor; import io.reactivex.processors.PublishProcessor; import javafx.beans.property.ObjectProperty; @@ -33,15 +35,21 @@ public class ComponentDriver implements Driver { private Flowable integerChangedCommandFlowable; private Flowable> objectChangedCommandFlowable; + private FlowableEmitter commandEmitter; + + public ComponentDriver() { + final Flowable commandInBox = Flowable.create(emitter -> commandEmitter = emitter, BackpressureStrategy.BUFFER); + commandInBox.subscribe(commandProcessor); + } @Override - public Subscriber getCommandSubscriber() { - return commandProcessor; + public void dispatch(Command command) { + commandEmitter.onNext(command); } @Override - public Publisher getActionPublisher() { - return actionProcessor; + public void subscribe(Subscriber subscriber) { + actionProcessor.subscribe(subscriber); } diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/ActionSupplier.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/ActionSupplier.java deleted file mode 100644 index 6cf1ace..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/ActionSupplier.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.netopyr.reduxfx.driver; - -import org.reactivestreams.Publisher; - -public interface ActionSupplier { - - Publisher getActionPublisher(); - -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/CommandConsumer.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/CommandConsumer.java deleted file mode 100644 index 5eb5daf..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/CommandConsumer.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.netopyr.reduxfx.driver; - -import com.netopyr.reduxfx.updater.Command; -import org.reactivestreams.Subscriber; - -public interface CommandConsumer { - - Subscriber getCommandSubscriber(); - -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/Driver.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/Driver.java deleted file mode 100644 index bfdf806..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/Driver.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.netopyr.reduxfx.driver; - -public interface Driver extends ActionSupplier, CommandConsumer { -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/ActionDriver.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/ActionDriver.java deleted file mode 100644 index 05b80c5..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/ActionDriver.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.netopyr.reduxfx.driver.action; - -import com.netopyr.reduxfx.driver.Driver; -import com.netopyr.reduxfx.driver.action.command.DispatchActionCommand; -import com.netopyr.reduxfx.updater.Command; -import io.reactivex.processors.FlowableProcessor; -import io.reactivex.processors.PublishProcessor; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -public class ActionDriver implements Driver { - - private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); - private static final ThreadFactory THREAD_FACTORY = runnable -> { - final Thread thread = new Thread(runnable, "ActionDriver-" + THREAD_COUNTER.incrementAndGet()); - thread.setDaemon(true); - return thread; - }; - - private final ExecutorService executorService = Executors.newCachedThreadPool(THREAD_FACTORY); - private final FlowableProcessor commandSubscriber = PublishProcessor.create(); - - private final FlowableProcessor actions = PublishProcessor.create(); - - - public ActionDriver() { - commandSubscriber - .filter(command -> command instanceof DispatchActionCommand) - .forEach(command -> - executorService.submit(() -> actions.onNext(((DispatchActionCommand)command).getAction())) - ); - } - - - @Override - public Subscriber getCommandSubscriber() { - return commandSubscriber; - } - - @Override - public Publisher getActionPublisher() { - return actions; - } -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/command/DispatchActionCommand.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/command/DispatchActionCommand.java deleted file mode 100644 index 5cb9166..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/action/command/DispatchActionCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.netopyr.reduxfx.driver.action.command; - -import com.netopyr.reduxfx.updater.Command; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -@SuppressWarnings({"WeakerAccess", "unused"}) -public class DispatchActionCommand implements Command { - - final Object action; - - public DispatchActionCommand(Object action) { - this.action = action; - } - - public Object getAction() { - return action; - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("action", action) - .toString(); - } -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/HttpDriver.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/HttpDriver.java deleted file mode 100644 index 132deaf..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/HttpDriver.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.netopyr.reduxfx.driver.http; - -import com.netopyr.reduxfx.driver.Driver; -import com.netopyr.reduxfx.driver.http.command.HttpGetCommand; -import com.netopyr.reduxfx.driver.http.command.HttpPutCommand; -import com.netopyr.reduxfx.updater.Command; -import io.reactivex.processors.FlowableProcessor; -import io.reactivex.processors.PublishProcessor; -import io.vavr.control.Try; -import okhttp3.Headers; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; - -@SuppressWarnings("unused") -public class HttpDriver implements Driver { - - private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - - private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); - private static final ThreadFactory THREAD_FACTORY = runnable -> { - final Thread thread = new Thread(runnable, "HttpDriver-" + THREAD_COUNTER.incrementAndGet()); - thread.setDaemon(true); - return thread; - }; - - private final OkHttpClient client; - - private final ExecutorService executorService = Executors.newCachedThreadPool(THREAD_FACTORY); - - private final FlowableProcessor commandSubscriber = PublishProcessor.create(); - - private final FlowableProcessor actions = PublishProcessor.create(); - - public HttpDriver() { - this.client = new OkHttpClient(); - - commandSubscriber - .filter(command -> command instanceof HttpGetCommand) - .forEach(command -> httpGet((HttpGetCommand) command)); - - commandSubscriber - .filter(command -> command instanceof HttpPutCommand) - .forEach(command -> httpPut((HttpPutCommand) command)); - } - - @Override - public Publisher getActionPublisher() { - return actions; - } - - @Override - public Subscriber getCommandSubscriber() { - return commandSubscriber; - } - - private void httpGet(HttpGetCommand command) { - final Headers.Builder headersBuilder = new Headers.Builder(); - command.getHeaders().forEach(tuple -> headersBuilder.add(tuple._1, tuple._2)); - final Request request = new Request.Builder() - .url(command.getUrl()) - .headers(headersBuilder.build()) - .build(); - - executorService.submit(() -> { - final Try result = Try.of( - () -> client.newCall(request).execute().body().string() - ); - actions.onNext(command.getActionMapper().apply(result)); - }); - } - - private void httpPut(HttpPutCommand command) { - final Headers.Builder headersBuilder = new Headers.Builder(); - command.getHeaders().forEach(tuple -> headersBuilder.add(tuple._1, tuple._2)); - final RequestBody body = RequestBody.create(JSON, command.getBody()); - final Request request = new Request.Builder() - .put(body) - .url(command.getUrl()) - .headers(headersBuilder.build()) - .build(); - - executorService.submit(() -> { - final Try result = Try.of( - () -> client.newCall(request).execute().body().string() - ); - command.getActionMapper().forEach( - mapper -> mapper.apply(result) - ); - }); - } -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpGetCommand.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpGetCommand.java deleted file mode 100644 index 5d763a6..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpGetCommand.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.netopyr.reduxfx.driver.http.command; - -import com.netopyr.reduxfx.updater.Command; -import io.vavr.collection.HashMap; -import io.vavr.collection.Map; -import io.vavr.control.Try; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -import java.net.URL; -import java.util.function.Function; - -@SuppressWarnings({"WeakerAccess", "unused"}) -public final class HttpGetCommand implements Command { - - private final URL url; - private final Map headers; - private final Function, Object> actionMapper; - - public HttpGetCommand(URL url, Function, Object> actionMapper) { - this(url, HashMap.empty(), actionMapper); - } - public HttpGetCommand(URL url, Map headers, Function, Object> actionMapper) { - this.url = url; - this.headers = headers; - this.actionMapper = actionMapper; - } - - public URL getUrl() { - return url; - } - - public Map getHeaders() { - return headers; - } - - public Function, Object> getActionMapper() { - return actionMapper; - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("url", url) - .append("headers", headers) - .append("actionMapper", actionMapper) - .toString(); - } -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpPutCommand.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpPutCommand.java deleted file mode 100644 index ce93499..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/http/command/HttpPutCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.netopyr.reduxfx.driver.http.command; - -import com.netopyr.reduxfx.updater.Command; -import io.vavr.collection.HashMap; -import io.vavr.collection.Map; -import io.vavr.control.Option; -import io.vavr.control.Try; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -import java.net.URL; -import java.util.function.Function; - -@SuppressWarnings({"WeakerAccess", "unused"}) -public final class HttpPutCommand implements Command { - - private final URL url; - private final Map headers; - private final String body; - private final Option, Object>> actionMapper; - - public HttpPutCommand(URL url, String body, Function, Object> actionMapper) { - this(url, HashMap.empty(), body, actionMapper); - } - public HttpPutCommand(URL url, Map headers, String body, Function, Object> actionMapper) { - this.url = url; - this.headers = headers; - this.body = body; - this.actionMapper = Option.of(actionMapper); - } - - public HttpPutCommand(URL url, String body) { - this(url, HashMap.empty(), body); - } - public HttpPutCommand(URL url, Map headers, String body) { - this.url = url; - this.headers = headers; - this.body = body; - this.actionMapper = Option.none(); - } - - public URL getUrl() { - return url; - } - - public Map getHeaders() { - return headers; - } - - public String getBody() { - return body; - } - - public Option, Object>> getActionMapper() { - return actionMapper; - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("url", url) - .append("headers", headers) - .append("body", body) - .append("actionMapper", actionMapper) - .toString(); - } -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/PropertiesDriver.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/PropertiesDriver.java deleted file mode 100644 index 3fdc611..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/PropertiesDriver.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.netopyr.reduxfx.driver.properties; - -import com.netopyr.reduxfx.driver.Driver; -import com.netopyr.reduxfx.driver.properties.command.LoadFileCommand; -import com.netopyr.reduxfx.updater.Command; -import io.reactivex.processors.FlowableProcessor; -import io.reactivex.processors.PublishProcessor; -import io.vavr.Tuple; -import io.vavr.collection.HashMap; -import io.vavr.collection.Map; -import io.vavr.control.Try; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; - -import java.io.Reader; -import java.nio.file.Files; -import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; - -public class PropertiesDriver implements Driver { - - private static final AtomicInteger THREAD_COUNTER = new AtomicInteger(); - private static final ThreadFactory THREAD_FACTORY = runnable -> { - final Thread thread = new Thread(runnable, "PropertiesDriver-" + THREAD_COUNTER.incrementAndGet()); - thread.setDaemon(true); - return thread; - }; - - private final ExecutorService executorService = Executors.newCachedThreadPool(THREAD_FACTORY); - - private final FlowableProcessor commandSubscriber = PublishProcessor.create(); - - private final FlowableProcessor actions = PublishProcessor.create(); - - public PropertiesDriver() { - commandSubscriber - .filter(command -> LoadFileCommand.class.equals(command.getClass())) - .forEach(command -> loadFile((LoadFileCommand) command)); - } - - @Override - public Publisher getActionPublisher() { - return actions; - } - - @Override - public Subscriber getCommandSubscriber() { - return commandSubscriber; - } - - private void loadFile(LoadFileCommand command) { - executorService.submit(() -> { - final Try> result = Try.of(() -> { - final Properties properties = new Properties(); - try (final Reader reader = Files.newBufferedReader(command.getPath(), command.getCharset())) { - properties.load(reader); - } - return HashMap.ofEntries( - properties.entrySet().stream() - .filter(entry -> entry.getKey() instanceof String && entry.getValue() instanceof String) - .map(entry -> Tuple.of((String) entry.getKey(), (String) entry.getValue())) - .collect(Collectors.toList()) - ); - }); - actions.onNext(command.getActionMapper().apply(result)); - }); - } -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/command/LoadFileCommand.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/command/LoadFileCommand.java deleted file mode 100644 index 026cae4..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/driver/properties/command/LoadFileCommand.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.netopyr.reduxfx.driver.properties.command; - -import com.netopyr.reduxfx.updater.Command; -import io.vavr.collection.Map; -import io.vavr.control.Try; -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -import java.nio.charset.Charset; -import java.nio.file.Path; -import java.util.function.Function; - -@SuppressWarnings({"WeakerAccess", "unused"}) -public class LoadFileCommand implements Command { - - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - public static final Charset UTF_8 = Charset.forName("UTF-8"); - - private final Path path; - private final Charset charset; - private final Function>, Object> actionMapper; - - public LoadFileCommand(Path path, Function>, Object> actionMapper) { - this(path, ISO_8859_1, actionMapper); - } - public LoadFileCommand(Path path, Charset charset, Function>, Object> actionMapper) { - this.path = path; - this.charset = charset; - this.actionMapper = actionMapper; - } - - public Path getPath() { - return path; - } - - public Charset getCharset() { - return charset; - } - - public Function>, Object> getActionMapper() { - return actionMapper; - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("path", path) - .append("charset", charset) - .append("actionMapper", actionMapper) - .toString(); - } -} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/Driver.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/Driver.java new file mode 100644 index 0000000..7d948b6 --- /dev/null +++ b/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/Driver.java @@ -0,0 +1,10 @@ +package com.netopyr.reduxfx.store; + +import com.netopyr.reduxfx.updater.Command; +import org.reactivestreams.Publisher; + +public interface Driver extends Publisher { + + void dispatch(Command command); + +} diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/ReduxFXStore.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/ReduxFXStore.java index 252d46e..b840258 100644 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/ReduxFXStore.java +++ b/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/ReduxFXStore.java @@ -1,9 +1,5 @@ package com.netopyr.reduxfx.store; -import com.netopyr.reduxfx.driver.Driver; -import com.netopyr.reduxfx.driver.action.ActionDriver; -import com.netopyr.reduxfx.driver.http.HttpDriver; -import com.netopyr.reduxfx.driver.properties.PropertiesDriver; import com.netopyr.reduxfx.middleware.Middleware; import com.netopyr.reduxfx.updater.Command; import com.netopyr.reduxfx.updater.Update; @@ -12,18 +8,16 @@ import io.reactivex.FlowableEmitter; import io.reactivex.processors.BehaviorProcessor; import io.reactivex.processors.FlowableProcessor; -import io.reactivex.processors.PublishProcessor; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import java.util.function.BiFunction; -@SuppressWarnings({"unused", "WeakerAccess"}) -public class ReduxFXStore { +public class ReduxFXStore implements Publisher { private final Flowable statePublisher; private final Flowable commandPublisher; - private FlowableEmitter> emitter; + private FlowableEmitter actionEmitter; @SafeVarargs @@ -31,9 +25,7 @@ public ReduxFXStore(S initialState, BiFunction> updater, Mi final BiFunction> chainedUpdater = applyMiddlewares(updater, middlewares); final Publisher actionPublisher = - Flowable.mergeDelayError( - Flowable.create(actionPublisherEmitter -> this.emitter = actionPublisherEmitter, BackpressureStrategy.BUFFER) - ); + Flowable.create(actionEmitter -> this.actionEmitter = actionEmitter, BackpressureStrategy.BUFFER); final FlowableProcessor> updateProcessor = BehaviorProcessor.create(); @@ -46,8 +38,6 @@ public ReduxFXStore(S initialState, BiFunction> updater, Mi commandPublisher = updateProcessor .map(Update::getCommands) .flatMapIterable(commands -> commands); - - registerDefaultDrivers(); } private BiFunction> applyMiddlewares(BiFunction> updater, Middleware[] middlewares) { @@ -59,26 +49,17 @@ private BiFunction> applyMiddlewares(BiFunction createActionSubscriber() { - final PublishProcessor actionSubscriber = PublishProcessor.create(); - emitter.onNext(actionSubscriber); - return actionSubscriber; + public void dispatch(Object action) { + actionEmitter.onNext(action); } - public Publisher getStatePublisher() { - return statePublisher; + @Override + public void subscribe(Subscriber subscriber) { + statePublisher.subscribe(subscriber); } public final void register(Driver driver) { - commandPublisher.subscribe(driver.getCommandSubscriber()); - emitter.onNext(driver.getActionPublisher()); + Flowable.fromPublisher(driver).subscribe(this::dispatch); + commandPublisher.subscribe(driver::dispatch); } - - - private void registerDefaultDrivers() { - register(new PropertiesDriver()); - register(new HttpDriver()); - register(new ActionDriver()); - } - } \ No newline at end of file diff --git a/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/SimpleReduxFXStore.java b/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/SimpleReduxFXStore.java deleted file mode 100644 index cd8e901..0000000 --- a/reduxfx-store/src/main/java/com/netopyr/reduxfx/store/SimpleReduxFXStore.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.netopyr.reduxfx.store; - -import com.netopyr.reduxfx.middleware.Middleware; -import com.netopyr.reduxfx.updater.Update; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; - -import java.util.function.BiFunction; - -public class SimpleReduxFXStore { - - private final ReduxFXStore store; - - @SafeVarargs - public SimpleReduxFXStore(S initialState, BiFunction updater, Middleware... middlewares) { - this.store = new ReduxFXStore<>(initialState, (S state, Object action) -> Update.of(updater.apply(state, action)), middlewares); - } - - public Subscriber createActionSubscriber() { - return store.createActionSubscriber(); - } - - public Publisher getStatePublisher() { - return store.getStatePublisher(); - } - -} diff --git a/reduxfx-view/pom.xml b/reduxfx-view/pom.xml index 4b94337..77f732f 100644 --- a/reduxfx-view/pom.xml +++ b/reduxfx-view/pom.xml @@ -22,6 +22,10 @@ + + com.netopyr.reduxfx + reduxfx-store + org.apache.commons commons-lang3 diff --git a/reduxfx-view/src/main/java/com/netopyr/reduxfx/vscenegraph/ReduxFXView.java b/reduxfx-view/src/main/java/com/netopyr/reduxfx/vscenegraph/ReduxFXView.java index 6a7d067..714a0e9 100644 --- a/reduxfx-view/src/main/java/com/netopyr/reduxfx/vscenegraph/ReduxFXView.java +++ b/reduxfx-view/src/main/java/com/netopyr/reduxfx/vscenegraph/ReduxFXView.java @@ -1,21 +1,18 @@ package com.netopyr.reduxfx.vscenegraph; +import com.netopyr.reduxfx.store.ReduxFXStore; import com.netopyr.reduxfx.vscenegraph.impl.differ.Differ; import com.netopyr.reduxfx.vscenegraph.impl.differ.patches.Patch; import com.netopyr.reduxfx.vscenegraph.impl.patcher.Patcher; import com.netopyr.reduxfx.vscenegraph.property.VProperty; -import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; +import io.vavr.collection.Map; +import io.vavr.collection.Vector; +import io.vavr.control.Option; import javafx.application.Platform; import javafx.scene.Group; import javafx.scene.layout.Pane; import javafx.stage.Stage; -import io.vavr.collection.Map; -import io.vavr.collection.Vector; -import io.vavr.control.Option; -import org.reactivestreams.Processor; -import org.reactivestreams.Publisher; -import org.reactivestreams.Subscriber; import java.util.function.Function; @@ -27,58 +24,50 @@ public class ReduxFXView { public static ReduxFXView createStages( + ReduxFXStore store, Function view, Stage primaryStage) { final Stages stages = new Stages(primaryStage); final Option initialVNode = Option.of(Stages().children(Stage())); - return new ReduxFXView<>(initialVNode, view, stages); + return new ReduxFXView<>(store, view, stages, initialVNode); } public static ReduxFXView createStage( + ReduxFXStore store, Function view, Stage primaryStage) { - return new ReduxFXView<>(Option.none(), view, primaryStage); + return new ReduxFXView<>(store, view, primaryStage, Option.none()); } public static ReduxFXView create( + ReduxFXStore store, Function view, Stage primaryStage) { final Function stageView = state -> Stage().scene(Scene().root(view.apply(state))); - return new ReduxFXView<>(Option.none(), stageView, primaryStage); + return new ReduxFXView<>(store, stageView, primaryStage, Option.none()); } public static ReduxFXView create( + ReduxFXStore store, Function view, Group group) { - return new ReduxFXView<>(Option.none(), view, group); + return new ReduxFXView<>(store, view, group, Option.none()); } public static ReduxFXView create( + ReduxFXStore store, Function view, Pane pane) { - return new ReduxFXView<>(Option.none(), view, pane); + return new ReduxFXView<>(store, view, pane, Option.none()); } - private final Option initialVNode; - private final Function view; - private final Object javaFXRoot; - private ReduxFXView( - Option initialVNode, + ReduxFXStore store, Function view, - Object javaFXRoot) { - this.initialVNode = initialVNode; - this.view = view; - this.javaFXRoot = javaFXRoot; - } - - public void connect(Processor store) { - connect(store, store); - } - - public void connect(Publisher statePublisher, Subscriber actionSubscriber) { + Object javaFXRoot, + Option initialVNode) { final Flowable> vScenegraphStream = - Flowable.fromPublisher(statePublisher) + Flowable.fromPublisher(store) .map(view::apply) .map(Option::of) .startWith(initialVNode); @@ -88,19 +77,15 @@ public void connect(Publisher statePublisher, Subscriber actionSubscr final Flowable paramsStream = vScenegraphStream.zipWith(patchesStream, PatchParams::new); - Flowable.create( - emitter -> - paramsStream.forEach( - params -> { - if (Platform.isFxApplicationThread()) { - Patcher.patch(emitter::onNext, javaFXRoot, params.vRoot, params.patches); - } else { - Platform.runLater(() -> Patcher.patch(emitter::onNext, javaFXRoot, params.vRoot, params.patches)); - } - } - ), - BackpressureStrategy.BUFFER - ).subscribe(actionSubscriber); + paramsStream.forEach( + params -> { + if (Platform.isFxApplicationThread()) { + Patcher.patch(store::dispatch, javaFXRoot, params.vRoot, params.patches); + } else { + Platform.runLater(() -> Patcher.patch(store::dispatch, javaFXRoot, params.vRoot, params.patches)); + } + } + ); } private static class PatchParams {