Permalink
Browse files

Added flow control API and demo

  • Loading branch information...
1 parent cbd37e1 commit 2b509c05d41c6f68cb5a7003cd129003a9fecbd0 @heiko-braun heiko-braun committed Mar 11, 2013
View
40 build/app/pom.xml
@@ -37,4 +37,44 @@
</plugin>
</plugins>
</build>
+
+ <profiles>
+
+ <profile>
+
+ <id>flow</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>gwt-maven-plugin</artifactId>
+ <version>2.5.0</version>
+ <configuration>
+ <inplace>true</inplace>
+ <logLevel>TRACE</logLevel>
+ <runTarget>App.html</runTarget>
+ <warSourceDirectory>war</warSourceDirectory>
+ <extraJvmArgs>-Xmx1024m -DDEBUG -XX:MaxPermSize=256M</extraJvmArgs>
+ <style>OBF</style>
+ <hostedWebapp>war</hostedWebapp>
+ <localWorkers>2</localWorkers>
+ <compileReport>false</compileReport>
+ <module>org.jboss.gwt.flow.FlowDemo</module>
+ <disableCastChecking>true</disableCastChecking>
+ <disableClassMetadata>true</disableClassMetadata>
+ <enableClosureCompiler>true</enableClosureCompiler>
+ <fragmentCount>30</fragmentCount>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
</project>
View
1 gui/pom.xml
@@ -325,6 +325,7 @@
</plugins>
</build>
</profile>
+
</profiles>
</project>
View
2 gui/src/main/java/org/jboss/as/console/App.gwt.xml
@@ -29,6 +29,8 @@
<inherits name="org.jboss.dmr.DMR"/>
<inherits name="org.jboss.mbui.MBUI"/>
+ <inherits name="org.jboss.gwt.flow.Flow" />
+
<inherits name="com.allen_sauer.gwt.log.gwt-log-ERROR" />
<!--
View
23 gui/src/main/java/org/jboss/gwt/flow/Flow.gwt.xml
@@ -0,0 +1,23 @@
+<!--
+ ~ JBoss, Home of Professional Open Source
+ ~ Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
+ ~ as indicated by the @author tags. All rights reserved.
+ ~ See the copyright.txt in the distribution for a
+ ~ full listing of individual contributors.
+ ~
+ ~ This copyrighted material is made available to anyone wishing to use,
+ ~ modify, copy, or redistribute it subject to the terms and conditions
+ ~ of the GNU Lesser General Public License, v. 2.1.
+ ~ This program is distributed in the hope that it will be useful, but WITHOUT A
+ ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ ~ PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ ~ You should have received a copy of the GNU Lesser General Public License,
+ ~ v.2.1 along with this distribution; if not, write to the Free Software
+ ~ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ ~ MA 02110-1301, USA.
+ -->
+
+<module>
+ <inherits name="com.google.gwt.user.User"/>
+ <source path="client" />
+</module>
View
24 gui/src/main/java/org/jboss/gwt/flow/FlowDemo.gwt.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ JBoss, Home of Professional Open Source
+ ~ Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
+ ~ as indicated by the @author tags. All rights reserved.
+ ~ See the copyright.txt in the distribution for a
+ ~ full listing of individual contributors.
+ ~
+ ~ This copyrighted material is made available to anyone wishing to use,
+ ~ modify, copy, or redistribute it subject to the terms and conditions
+ ~ of the GNU Lesser General Public License, v. 2.1.
+ ~ This program is distributed in the hope that it will be useful, but WITHOUT A
+ ~ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ ~ PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
+ ~ You should have received a copy of the GNU Lesser General Public License,
+ ~ v.2.1 along with this distribution; if not, write to the Free Software
+ ~ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ ~ MA 02110-1301, USA.
+ -->
+
+<module rename-to="app">
+ <inherits name="org.jboss.gwt.flow.Flow"/>
+ <source path="client" />
+ <entry-point class='org.jboss.gwt.flow.client.FlowDemo'/>
+</module>
View
15 gui/src/main/java/org/jboss/gwt/flow/client/Control.java
@@ -0,0 +1,15 @@
+package org.jboss.gwt.flow.client;
+
+/**
+ * Execution control handle passed into functions
+ *
+ * @author Heiko Braun
+ * @date 3/8/13
+ */
+public interface Control<C> {
+
+ void proceed();
+ void abort();
+ C getContext();
+}
+
View
20 gui/src/main/java/org/jboss/gwt/flow/client/Ctx.java
@@ -0,0 +1,20 @@
+package org.jboss.gwt.flow.client;
+
+/**
+ * Shared context.
+ *
+ * @author Heiko Braun
+ * @date 3/11/13
+ */
+public class Ctx<T> {
+
+ private T delegate;
+
+ public Ctx(T delegate) {
+ this.delegate = delegate;
+ }
+
+ public T get() {
+ return delegate;
+ }
+}
View
330 gui/src/main/java/org/jboss/gwt/flow/client/FlowControl.java
@@ -0,0 +1,330 @@
+package org.jboss.gwt.flow.client;
+
+import com.google.gwt.core.client.Scheduler;
+
+/**
+ * Flow control functions for GWT.
+ * Integrates with the default GWT scheduling mechanism.
+ *
+ * @author Heiko Braun
+ * @date 3/8/13
+ */
+public class FlowControl {
+
+ private final static Ctx<Object> EMPTY_CONTEXT = new Ctx<Object>(new Object()) {};
+
+ protected FlowControl() {
+ }
+
+ // ----------- API -----------
+
+ /**
+ * Run an array of functions in series, each one running once the previous function has completed.
+ * If any functions in the series pass an error to its callback,
+ * no more functions are run and outcome for the series is immediately called with the value of the error.
+ *
+ * @param outcome
+ * @param functions
+ */
+ public static void series(final Outcome outcome, final Function... functions)
+ {
+ new FlowControl()._series(outcome, EMPTY_CONTEXT, functions);
+ }
+
+ /**
+ * Runs an array of functions in series, working on a shared context.
+ * However, if any of the functions pass an error to the callback,
+ * the next function is not executed and the outcome is immediately called with the error.
+ *
+ * @param outcome
+ * @param context
+ * @param functions
+ */
+ public static void waterfall(final Outcome outcome, Ctx context, final Function... functions)
+ {
+ new FlowControl()._series(outcome, context, functions);
+ }
+
+ /**
+ * Run an array of functions in parallel, without waiting until the previous function has completed.
+ * If any of the functions pass an error to its callback, the outcome is immediately called with the value of the error.
+ *
+ * @param outcome
+ * @param functions
+ */
+ public static void parallel(final Outcome outcome, final Function... functions)
+ {
+ new FlowControl()._parallel(outcome, EMPTY_CONTEXT, functions);
+ }
+
+ /**
+ * Repeatedly call function, while condition is met. Calls the callback when stopped, or an error occurs.
+ *
+ * @param condition
+ * @param outcome
+ * @param function
+ */
+ public static void whilst(Precondition condition, final Outcome outcome, final Function function)
+ {
+ new FlowControl()._whilst(condition, outcome, EMPTY_CONTEXT, function);
+ }
+
+
+ // ----------- Implementation -----------
+
+ private void _series(final Outcome outcome, final Ctx context, final Function... functions) {
+
+ final SequentialControl<Object> ctrl = new SequentialControl<Object>(context.get(), functions);
+
+ // select first task
+ ctrl.proceed();
+
+ Scheduler.get().scheduleIncremental(new Scheduler.RepeatingCommand() {
+ @Override
+ public boolean execute() {
+
+ if(ctrl.isDrained())
+ {
+ Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
+ @Override
+ public void execute() {
+ outcome.isSuccess(context.get());
+ }
+ });
+
+ return false;
+ }
+ else if(ctrl.isAborted())
+ {
+ Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
+ @Override
+ public void execute() {
+ outcome.isFailure();
+ }
+ });
+ return false;
+ }
+ else
+ {
+ ctrl.nextUnlessPending();
+ return true;
+ }
+ }
+ });
+ }
+
+ private void _parallel(final Outcome outcome, final Ctx context, final Function... functions) {
+ final CountingControl<Object> ctrl = new CountingControl<Object>(context.get(), functions);
+
+ Scheduler.get().scheduleIncremental(new Scheduler.RepeatingCommand() {
+ @Override
+ public boolean execute() {
+
+ if(ctrl.hasFinishedAll())
+ {
+ Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
+ @Override
+ public void execute() {
+ if (ctrl.isAborted())
+ outcome.isFailure();
+ else
+ outcome.isSuccess(context.get());
+
+ }
+ });
+
+ return false;
+ }
+ else
+ {
+ // one after the other until all are active
+ ctrl.next();
+ return true;
+ }
+ }
+ });
+ }
+
+ private void _whilst(Precondition condition, final Outcome outcome, final Ctx context, final Function function) {
+
+ final GuardedControl ctrl = new GuardedControl(condition, context.get());
+
+ Scheduler.get().scheduleIncremental(new Scheduler.RepeatingCommand() {
+ @Override
+ public boolean execute() {
+
+ if(!ctrl.shouldProceed())
+ {
+ Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
+ @Override
+ public void execute() {
+ if (ctrl.isAborted())
+ outcome.isFailure();
+ else
+ outcome.isSuccess(context.get());
+
+ }
+ });
+
+ return false;
+ }
+ else
+ {
+ function.execute(ctrl);
+ return true;
+ }
+ }
+ });
+ }
+
+
+ private class SequentialControl<C> implements Control {
+ protected Function[] functions;
+ protected Function next = null;
+ protected boolean isDrained;
+ protected boolean isAborted;
+ protected int i = 0;
+ protected boolean pending = false;
+ protected C context;
+
+ SequentialControl(C context, Function[] functions) {
+ this.functions = functions;
+ this.context = context;
+ }
+
+ @Override
+ public C getContext() {
+ return context;
+ }
+
+ @Override
+ public void proceed() {
+ if(i>=functions.length)
+ {
+ next = null;
+ isDrained = true;
+ }
+ else
+ {
+ next = functions[i];
+ i++;
+ }
+
+ this.pending = false;
+ }
+
+ @Override
+ public void abort() {
+ this.isAborted = true;
+ this.pending = false;
+ }
+
+ public boolean isAborted() {
+ return isAborted;
+ }
+
+ public boolean isDrained() {
+ return isDrained;
+ }
+
+ public void nextUnlessPending() {
+
+ if(!pending)
+ {
+ pending = true;
+ next.execute(this);
+ }
+ }
+ }
+
+ class CountingControl<C> implements Control
+ {
+ protected Function[] functions;
+ protected Function next = null;
+ protected boolean isAborted;
+
+ protected C context;
+
+ private int index = 0;
+ private int finished = 0;
+
+ CountingControl(C context, Function... functions) {
+ this.functions = functions;
+ this.context = context;
+ }
+
+ @Override
+ public C getContext() {
+ return context;
+ }
+
+ public void next() {
+ if(index<functions.length)
+ {
+ functions[index].execute(this);
+ index++;
+ }
+ }
+
+ @Override
+ public void proceed() {
+ increment();
+ }
+
+ private void increment() {
+ ++finished;
+ }
+
+ @Override
+ public void abort() {
+ increment();
+ isAborted = true;
+ }
+
+ public boolean isAborted() {
+ return isAborted;
+ }
+
+ public boolean hasFinishedAll()
+ {
+ return isAborted || finished>=functions.length;
+ }
+
+
+ }
+
+ class GuardedControl<C> implements Control
+ {
+ private Precondition condition;
+ private boolean isAborted;
+ private C context;
+
+ GuardedControl(Precondition condition, C context) {
+ this.condition = condition;
+ this.context = context;
+ }
+
+ @Override
+ public void proceed() {
+ // ignore
+ }
+
+ public boolean shouldProceed()
+ {
+ return condition.isMet() && !isAborted;
+ }
+ @Override
+ public void abort() {
+ this.isAborted = true;
+ }
+
+ public boolean isAborted() {
+ return isAborted;
+ }
+
+ @Override
+ public C getContext() {
+ return context;
+ }
+ }
+}
View
265 gui/src/main/java/org/jboss/gwt/flow/client/FlowDemo.java
@@ -0,0 +1,265 @@
+package org.jboss.gwt.flow.client;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.Random;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.RootLayoutPanel;
+import com.google.gwt.user.client.ui.TextArea;
+import com.google.gwt.user.client.ui.VerticalPanel;
+
+/**
+ * @author Heiko Braun
+ * @date 3/11/13
+ */
+public class FlowDemo implements EntryPoint {
+
+
+ private TextArea output;
+
+ @Override
+ public void onModuleLoad() {
+
+ output = new TextArea();
+ output.setVisibleLines(5);
+ output.setCharacterWidth(60);
+
+ VerticalPanel btns = new VerticalPanel();
+ btns.getElement().setAttribute("style", "vertical-align:center");
+ btns.add(output);
+ btns.add(new Button("Series", new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent clickEvent) {
+ runSeries();
+ }
+ }));
+
+ btns.add(new Button("Waterfall", new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent clickEvent) {
+ runWaterfall();
+ }
+ }));
+
+ btns.add(new Button("Parallel", new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent clickEvent) {
+
+ runParallel();
+
+ }
+ }));
+
+ btns.add(new Button("Whilst", new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent clickEvent) {
+
+ runWhilst();
+
+ }
+ }));
+
+ RootLayoutPanel.get().add(btns);
+ }
+
+ private void runSeries() {
+
+ final Function first = new GenericFunction("s.1");
+ final Function second = new GenericFunction("s.2");
+
+ final Outcome genericOutcome = new Outcome() {
+ @Override
+ public void isFailure() {
+ Window.alert("Outcome is failure");
+ }
+
+ @Override
+ public void isSuccess(Object o) {
+ Window.alert("Outcome is success: ");
+
+ }
+ };
+
+ FlowControl.series(genericOutcome, first, second);
+ }
+
+ private void runWaterfall() {
+
+ final Function third = new SpecificFunction("w.1");
+ final Function fourth = new SpecificFunction("w.2");
+
+ final Outcome<StringBuffer> specificOutcome = new Outcome<StringBuffer>() {
+ @Override
+ public void isFailure() {
+ Window.alert("Outcome is failure");
+ }
+
+ @Override
+ public void isSuccess(StringBuffer sb ) {
+ Window.alert("Outcome is success: "+sb);
+
+ }
+ };
+
+ FlowControl.waterfall(specificOutcome, new Ctx<StringBuffer>(new StringBuffer()), third, fourth);
+
+ }
+
+ private void runWhilst()
+ {
+
+ clearOutput();
+ final int max = 3;
+ final IncrementFunction function = new IncrementFunction();
+
+ final Outcome outcome = new Outcome() {
+ @Override
+ public void isFailure() {
+ append("<Whilst failed>");
+ }
+
+ @Override
+ public void isSuccess(Object context) {
+ append("<Whilst success>");
+ }
+ };
+
+ FlowControl.whilst(new Precondition() {
+ @Override
+ public boolean isMet() {
+ return function.getCounter()<max;
+ }
+ }, outcome, function);
+ }
+
+ private void runParallel() {
+
+ clearOutput();
+
+ Function p1 = new RandomTimedFunction("p.1", false);
+ Function p2 = new RandomTimedFunction("p.2", false);
+ Function p3 = new RandomTimedFunction("p.3", true);
+
+ Outcome parallelOutcome = new Outcome() {
+ @Override
+ public void isFailure() {
+ append("<Parallel failed>");
+ }
+
+ @Override
+ public void isSuccess(Object context) {
+ append("<Parallel success>");
+ }
+ };
+
+ FlowControl.parallel(parallelOutcome, p1, p2, p3);
+
+ }
+
+ class GenericFunction implements Function {
+ final String name;
+
+ GenericFunction(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void execute(Control control) {
+ boolean shouldContinue = Window.confirm("Step "+name+", continue?");
+
+ if(!shouldContinue)
+ control.abort();
+ else
+ control.proceed();
+
+ }
+ }
+
+ class SpecificFunction implements Function<StringBuffer> {
+ final String name;
+
+ SpecificFunction(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void execute(Control<StringBuffer> control) {
+ boolean shouldContinue = Window.confirm("Step "+name+", continue?");
+ control.getContext().append(",").append(name).append("-result");
+
+ if(!shouldContinue)
+ control.abort();
+ else
+ control.proceed();
+
+ }
+ }
+
+ class IncrementFunction implements Function {
+
+ private int counter;
+
+ IncrementFunction() {
+ this.counter = 0;
+ }
+
+ @Override
+ public void execute(Control control) {
+ append(counter+"");
+ increment();
+
+ }
+
+ public void increment() {
+ counter++;
+ }
+
+ public int getCounter() {
+ return counter;
+ }
+ }
+
+ class RandomTimedFunction implements Function {
+ private final String name;
+ private final int delayMillis;
+ private final boolean shouldFail;
+
+ RandomTimedFunction(String name, boolean shouldFail) {
+ this.name = name;
+ this.delayMillis = Random.nextInt(20) * 100;
+ this.shouldFail = shouldFail;
+ }
+
+ @Override
+ public void execute(final Control control) {
+
+ Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
+ @Override
+ public boolean execute() {
+
+ append("Finished " + name + " (" + delayMillis + ")");
+
+ if(shouldFail)
+ control.abort();
+ else
+ control.proceed();
+ return false;
+ }
+ }, delayMillis);
+
+ }
+ }
+
+ private void append(String text){
+ StringBuffer sb = new StringBuffer(output.getText()).append("\n");
+ sb.append(text);
+ output.setText(sb.toString());
+ }
+
+ private void clearOutput(){
+ output.setText("");
+ }
+}
View
11 gui/src/main/java/org/jboss/gwt/flow/client/Function.java
@@ -0,0 +1,11 @@
+package org.jboss.gwt.flow.client;
+
+/**
+ * An execution delegate able to control the outcome.
+ *
+ * @author Heiko Braun
+ * @date 3/8/13
+ */
+public interface Function<C> {
+ void execute(Control<C> control);
+}
View
15 gui/src/main/java/org/jboss/gwt/flow/client/Outcome.java
@@ -0,0 +1,15 @@
+package org.jboss.gwt.flow.client;
+
+/**
+ * The final outcome of the controlled flow.
+ *
+ * @author Heiko Braun
+ * @date 3/8/13
+ */
+public interface Outcome<C> {
+
+ void isFailure();
+
+ void isSuccess(C context);
+
+}
View
11 gui/src/main/java/org/jboss/gwt/flow/client/Precondition.java
@@ -0,0 +1,11 @@
+package org.jboss.gwt.flow.client;
+
+/**
+ * A guard clause for certain flow semantics.
+ *
+ * @author Heiko Braun
+ * @date 3/11/13
+ */
+public interface Precondition {
+ boolean isMet();
+}

0 comments on commit 2b509c0

Please sign in to comment.