Skip to content

Add a Future Class, Callback and Delegate #4

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions async/src/main/java/net/devintia/commons/async/Callback.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package net.devintia.commons.async;

/**
* A simple method invocation that returns a result. If you don't care about the result use a {@link Delegate}.
*
* @author Digot
* @version 1.0
* @param <T> The type of the argument and the result of the Callback
*/
public interface Callback<T> {

/**
* Invokes the callback with the given argument and return the result of it
* @param arg The argument to pass
* @return the result of the Callback
*/
T invoke( T arg );

}
18 changes: 18 additions & 0 deletions async/src/main/java/net/devintia/commons/async/Delegate.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package net.devintia.commons.async;

/**
* A simple method invocation. If you care about the result use a {@link Callback}.
*
* @author Digot
* @version 1.0
* @param <T> The type of the argument and the result of the Callback
*/
public interface Delegate<T> {

/**
* Invokes the delegate with the given argument
* @param arg The argument to pass
*/
void invoke( T arg );

}
178 changes: 178 additions & 0 deletions async/src/main/java/net/devintia/commons/async/Future.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package net.devintia.commons.async;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

/**
* A basic future class that supports listeners.
*
* @author Digot
* @version 1.0
* @param <T> The type of the result
*/
public class Future<T> {

private FutureState state;
private T result;
private Throwable failCause;
private Set<FutureListener<T>> registeredListeners;

/**
* Creates a new Future instance
*/
public Future() {
this.state = FutureState.PENDING;
this.registeredListeners = new HashSet<>();
}

/**
* Creates a Future that immediately resolves with a null result
* @param <T> The type of the result
* @return the created Future
*/
public static <T> Future<T> createNull() {
//Create a future and resolve it immediately
Future<T> future = new Future<>();
future.resolve( null );
return future;
}



/**
* Adds a {@link FutureListener} that listens to the result of the Future. If the Future is already finished when the listener is added, it gets called instead
*
* @param listener The listener that should be added
*/
public synchronized void addListener( FutureListener<T> listener ) {
switch ( this.state ) {
case PENDING: this.registeredListeners.add( listener );
break;
case RESOLVED: listener.onResolved( this.result );
break;
case FAILED: listener.onFailed( this.failCause );
break;
default: break;
}
}

/**
* Finishes the Future and signals, that the execution of the corresponding task has failed
*
* @param cause The reason why the Future failed
*/
public synchronized void fail( Throwable cause ) {
//Set the fail cause and switch state
this.failCause = cause;
this.state = FutureState.FAILED;

//Notify threads and call the listeners
this.finish();
}

/**
* Finishes the Future and signals, that the execution of the corresponding task has succeeded
*
* @param result The result of the task
*/
public synchronized void resolve( T result ) {
//Set the result and switch state
this.result = result;
this.state = FutureState.RESOLVED;

//Notify threads and call the listeners
this.finish();
}

/**
* Tries to retrieve the result of the Future without any time out. If the Future is pending, it waits until the Future is done.
*
* @return the result of the Future
* @throws InterruptedException When the method call was interrupted while waiting for the result
* @throws ExecutionException When the future failed to resolve
* @throws TimeoutException Won't throw in this overload
*/
public synchronized T get() throws InterruptedException, ExecutionException, TimeoutException {
return this.get( 0L, TimeUnit.MILLISECONDS );
}

/**
* Tries to retrieve the result of the Future with time out. If the Future is pending, it waits until the Future is done.
*
* @param timeout The amount of time in the given TimeUnit the thread should wait for the result
* @param timeUnit The TimeUnit of the timeout value
* @return the result of the Future
* @throws InterruptedException When the method call was interrupted while waiting for the result
* @throws ExecutionException When the future failed to resolve
* @throws TimeoutException When the result takes too long to resolve
*/
public synchronized T get( long timeout, TimeUnit timeUnit ) throws ExecutionException, InterruptedException, TimeoutException {
switch ( this.state ) {
case RESOLVED: return this.result;
case FAILED: throw new ExecutionException( "Future failed to resolve", this.failCause );
case PENDING:
default:
if( timeout == 0 ) {
//No timeout, wait until we have the result and then return it
this.wait();
return this.get();
}
else {
//Wait for the result within the given timeout time and return it.
this.wait( timeUnit.toMillis( timeout ) );
if( !this.isDone() ) throw new TimeoutException( "Future took too long!" );
return this.get();
}

}
}

/**
* @return Whether the Future is done or not
*/
public synchronized boolean isDone() {
return this.state != FutureState.PENDING;
}

/**
* @return Whether the execution of the Future was successful or not
*/
public synchronized boolean isSuccess() {
return this.state == FutureState.RESOLVED;
}

/**
* @return Whether the execution of the Future was a failure or not
*/
public synchronized boolean isFailed() {
return this.state == FutureState.FAILED;
}

private synchronized void finish() {
//Make sure that the future is done
if( !this.isDone() ) {
throw new IllegalStateException( "Still pending" );
}

//Call all listeners
for ( FutureListener<T> registeredCallback : this.registeredListeners ) {
switch ( this.state ) {
case RESOLVED: registeredCallback.onResolved( this.result );
break;
case FAILED: registeredCallback.onFailed( this.failCause );
break;
default: break;
}
}

//Notify waiting threads
this.notifyAll();
}

private enum FutureState {
PENDING, RESOLVED, FAILED
}
}
26 changes: 26 additions & 0 deletions async/src/main/java/net/devintia/commons/async/FutureListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package net.devintia.commons.async;

/**
* A generic listener, that is called when a {@link Future} was either resolved or failed.
*
* @author Digot
* @version 1.0
* @param <T> The type of the result of the corresponding {@link Future}
*/
public interface FutureListener<T> {

/**
* Called when the task of the corresponding {@link Future } was resolved
*
* @param arg The result object
*/
void onResolved( T arg );

/**
* Called when the execution of task of the corresponding {@link Future } failed
*
* @param cause The failure cause
*/
void onFailed( Throwable cause );

}
28 changes: 28 additions & 0 deletions async/src/main/java/net/devintia/commons/async/Timer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.devintia.commons.async;

/**
* Used to measure how long the execution of a code block takes
*
* @author Digot
* @version 1.0
*/
public class Timer implements AutoCloseable {

private final long startMs;
private final String topic;

/**
* The default constructor for the timer
*
* @param topic The topic of the measure
*/
public Timer ( String topic ) {
this.topic = topic;
this.startMs = System.currentTimeMillis();
}

@Override
public void close ( ) {
System.out.println( this.topic + " took " + ( System.currentTimeMillis() - this.startMs ) + "ms" );
}
}