Skip to content

Instantly share code, notes, and snippets.

@markzyu
Last active August 29, 2015 14:25
Show Gist options
  • Save markzyu/8533bbdabd565ca8aaf7 to your computer and use it in GitHub Desktop.
Save markzyu/8533bbdabd565ca8aaf7 to your computer and use it in GitHub Desktop.
Guess how you can utilize this on Android! (Please use Java 7 and retrolambda for a nice lambda support. Java 8's lambda support might malfunction)
package zyu19.prototypes.paperplane.connect;
import java.util.ArrayList;
import zyu19.prototypes.paperplane.connect.config.*;
public class ActionChain<ThisType extends ActionChain> implements ErrorHolder, FakePromise<ThisType> {
//------------- public functions (ErrorHolder Interface) ----------------
private Exception mCause;
@Override
public final Exception getCause() {
return mCause;
}
@Override
public final void retry() {
iterate();
}
//------------- public functions (FakePromise Interface) ----------------
@Override
public final ThisType start() {
clearIterationState();
iterate();
return (ThisType)this;
}
@Override
public final ThisType fail(Consumer<ErrorHolder> onFailure) {
mCurrentOnFailure = onFailure;
return (ThisType)this;
}
@Override
public <In, Out> ThisType then(boolean runOnWorkerThread, PureAction<In, Out> action) {
mActionSequence.add(new ActionConfig(action, mCurrentOnFailure, runOnWorkerThread));
return (ThisType)this;
}
@Override
public final <In, Out> ThisType netThen(PureAction<In, Out> action) {
mActionSequence.add(new ActionConfig(action, mCurrentOnFailure, true));
return (ThisType)this;
}
@Override
public final <In, Out> ThisType uiThen(PureAction<In, Out> action) {
mActionSequence.add(new ActionConfig(action, mCurrentOnFailure, false));
return (ThisType)this;
}
//------------- Constructors ----------------
private final Consumer mOnSuccess;
private Consumer<ErrorHolder> mCurrentOnFailure;
private ArrayList<ActionConfig> mActionSequence = new ArrayList<>();
protected final ThreadPolicy mThreadPolicy;
protected ActionChain(ThreadPolicy threadPolicy, Consumer<?> onSuccess) {
mThreadPolicy = threadPolicy;
mOnSuccess = onSuccess;
}
protected ActionChain(ThreadPolicy threadPolicy, Consumer<?> onSuccess, Consumer<ErrorHolder> onFailure) {
mThreadPolicy = threadPolicy;
mOnSuccess = onSuccess;
mCurrentOnFailure = onFailure;
}
//------------------------ [Private] Iteration State -------------------------------
private int mNextAction;
private Object mLastActionOutput;
private boolean isOnSuccessCalled;
private final void clearIterationState() {
mNextAction = 0;
mLastActionOutput = null;
isOnSuccessCalled = false;
}
private final boolean isIterationOver() {
if (isOnSuccessCalled)
return true;
else if (mNextAction >= mActionSequence.size()) {
mThreadPolicy.switchAndRun(mOnSuccess, mLastActionOutput);
isOnSuccessCalled = true;
return true;
} else return false;
}
//------------------------ [Private] iterate and iterator ---------------------------
private final Consumer<ThreadPolicy> mIterator = (threadPolicy) -> {
synchronized (ActionChain.this) {
if (isIterationOver())
return;
ActionConfig action = mActionSequence.get(mNextAction);
try {
mLastActionOutput = action.pureAction.process(mLastActionOutput);
} catch (Exception err) {
mCause = err;
threadPolicy.switchAndRun(action.errorHandler, this);
return;
}
mNextAction++;
iterate();
}
};
private final void iterate() {
synchronized (this) {
if (isIterationOver())
return;
ActionConfig action = mActionSequence.get(mNextAction);
workOnProperThread(() -> mIterator.consume(mThreadPolicy), action);
}
}
//------------------------ policy ----------------------------
/**
* This function decides which thread a task defined by actionConfig should run on. <br>
* Note: onSuccess and onFailure will always run on the thread selected by ThreadChanger, regardless
* of this function's configuration.
*/
protected void workOnProperThread(Runnable task, ActionConfig actionConfig) {
if (actionConfig.runOnWorkerThread)
mThreadPolicy.runWorker(task);
else mThreadPolicy.switchAndRun(task);
}
}
package zyu19.prototypes.paperplane.connect.config;
import zyu19.prototypes.paperplane.connect.ActionChain;
/**
* @author Zhongzhi Yu
* Created on 7/21/2015.
*/
public class ActionConfig<In, Out> {
final public PureAction<In, Out> pureAction;
final public Consumer<ActionChain> errorHandler;
final public boolean runOnWorkerThread;
public ActionConfig(PureAction<In, Out> pureAction, Consumer<ActionChain> errorHandler, boolean runOnWorkerThread) {
this.pureAction = pureAction;
this.errorHandler = errorHandler;
this.runOnWorkerThread = runOnWorkerThread;
}
}
package zyu19.prototypes.paperplane.connect.config;
/**
* For use with retro-lambda and Java 7.
* Not annotated, so it probably cannot be instantiated with lambda in Java 8.
* @author Zhongzhi Yu
* Created on 6/28/2015.
*/
public interface Consumer<ArgType> {
void consume(ArgType arg);
}
package zyu19.prototypes.paperplane.connect.config;
/**
* ErrorHolder Interface.
* <p>
* Basically, instances of this class is used like Exception. But the following instructions
* are very important.
* <p>
* Instances of this type will be passed to the user when an error occurred,
* to inform the user to decide whether the error is recoverable.
* <p>
* If possibly recoverable, the user should check the type of that error and
* decide whether to use this ErrorHolder to recover the halted process.
* <p>
* If not recoverable at all, the user can record the error.
* <p>
* NOTE: The user must retry through this ErrorHolder within the 'Consumer' callback.
* Calling the function again, from the outside, will NOT succeed.
* <p>
* NOTE: ThreadPolicy will ensure that this class runs on the thread specified
* by ThreadChanger.
* <p>
* WARNING: It should be avoided to pass ErrorHolder to threads other than
* the thread of the 'Consumer' callback.
*
* @author Zhongzhi Yu
* Created on 7/2/2015.
* Modified on 7/21/2015.
* Isolated as Interface on 7/24/2015.
*/
public interface ErrorHolder {
Exception getCause();
void retry();
}
package zyu19.prototypes.paperplane.connect.config;
/**
* Behaviors of this interface does NOT follow the standard of Promise-then.
* @author Zhongzhi Yu
* Created on 7/24/2015.
*/
public interface FakePromise <ThisType extends FakePromise> {
// start running this promise. Once started, the promise should NOT be started again.
ThisType start();
// The following function must not be declared.
// Because onSuccess handler should be a final field in implementations.
// <AnsType> ThisType done(Consumer<AnsType> onSuccess);
// set the onFailure callback for the PureAction callbacks added afterwards.
ThisType fail(Consumer<ErrorHolder> onFailure);
// then().then().then()
<In, Out> ThisType then(boolean runOnWorkerThread, PureAction<In, Out> action);
<In, Out> ThisType netThen(PureAction<In, Out> action);
<In, Out> ThisType uiThen(PureAction<In, Out> action);
}
Copyright (c) 2015, Zhongzhi Yu
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Zhongzhi Yu nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package zyu19.prototypes.paperplane.connect.config;
/**
* @author Zhongzhi Yu
* Created on 7/20/2015.
*/
public interface PureAction<In, Out> {
Out process(In input) throws Exception;
}
package zyu19.prototypes.paperplane.connect.config;
/**
* @author Zhongzhi Yu
* Created on 5/15/2015.
*/
public interface ThreadChanger {
<ArgType> void runCallbackOnWantedThread(Runnable runnable);
}
package zyu19.prototypes.paperplane.connect.config;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.ExecutorService;
/**
* This class encapsulates Guava's ListeningExecutorService to simply the process to
* create an asynchronous call. Full lambda support for use with Retro-lambda.
*
* @author ZhongzhiYu
* Created on 5/15/2015.
* Separated as a single file by Zhongzhi Yu on 5/17/2015,
* Largely modified on 6/28/2015.
*/
public class ThreadPolicy {
private final ThreadChanger mThreadChanger;
private final ListeningExecutorService mListeningExecutorService;
public ThreadPolicy(ThreadChanger threadChanger, ExecutorService multiThreadConfig) {
mListeningExecutorService = MoreExecutors.listeningDecorator(multiThreadConfig);
mThreadChanger = threadChanger;
}
public ThreadPolicy(ExecutorService multiThreadConfig) {
mListeningExecutorService = MoreExecutors.listeningDecorator(multiThreadConfig);
mThreadChanger = null;
}
public void runWorker(Runnable operation) {
mListeningExecutorService.submit(operation);
}
public <ArgType> void switchAndRun(Consumer<ArgType> consumer, ArgType arg) {
if (consumer == null)
return;
if (mThreadChanger == null)
consumer.consume(arg);
else {
mThreadChanger.runCallbackOnWantedThread(() -> {
consumer.consume(arg);
});
}
}
public void switchAndRun(Runnable runnable) {
if (runnable == null)
return;
if (mThreadChanger == null)
runnable.run();
else {
mThreadChanger.runCallbackOnWantedThread(runnable);
}
}
}
@markzyu
Copy link
Author

markzyu commented Jul 26, 2015

The following picture is the illustration of how this class (is supposed to) run different PureAction code blocks on "W" (Worker) thread and "User" (UI/Main) thread.

illustration

© 2015 Zhongzhi Yu. All rights reserved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment