package com.austinv11.collectiveframework.multithreading;
import com.austinv11.collectiveframework.utils.ReflectionUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
/**
* This class is used to simplify multithreading for performance by delegating
* multiple concurrent calculations across a variable amount of threads
*/
public class HeavyCalculations {
private static int calculationNumber = 0;
private List<CalculationThread> threads = new ArrayList<CalculationThread>();
private ConcurrentHashMap<ICalculations, Future> futures = new ConcurrentHashMap<ICalculations, Future>();
private boolean isKill = false;
/**
* Constructor for HeavyCalculations
* @param numberOfThreads The number of threads to delegate calculations to
*/
public HeavyCalculations(int numberOfThreads) {
for (int i = 0; i < numberOfThreads; i++)
threads.add(new CalculationThread());
}
private void removeCalculation(ICalculations calculations) {
for (CalculationThread thread : threads)
if (thread.calculations.contains(calculations)) {
thread.calculations.remove(calculations);
return;
}
}
private void delegate(ICalculations calculations) {
int minCalculations = -1;
CalculationThread thread = null;
for (CalculationThread thread1 : threads) {
if ((minCalculations == -1 || minCalculations > thread1.calculations.size())) {
minCalculations = thread1.calculations.size();
thread = thread1;
}
}
thread.calculations.add(calculations);
}
/**
* Adds a calculation to the queues
* @param calculation The calculation it MUST implement {@link ICalculations}
* @return A future representing the eventual calculation {@link Future#get()} returns the object passed,
* although after the calculations finished
*/
public <T> Future<T> addCalculation(T calculation) {
ICalculations calculations = (ICalculations) calculation;
delegate(calculations);
Future<T> future = new FutureImpl<T>(calculations, calculation);
futures.put(calculations, future);
return future;
}
/**
* Adds a calculation to the queues by wrapping an object to implement {@link ICalculations}
* <b>This method is discouraged! It is meant for objects where it isn't possible to implement {@link ICalculations}</b>
* @param calculation The object representing the calculations
* @param methodToCalculate The method to call for calculations
* @param params The parameters for the method
* @return A future representing the eventual calculation {@link Future#get()} returns the object passed,
* although after the calculations finished
*/
public <T> Future<T> addCalculation(T calculation, String methodToCalculate, Object... params) {
ICalculations calculations = new ICalculationsWrapper(calculation, methodToCalculate, params);
delegate(calculations);
Future<T> future = new FutureImpl<T>(calculations, calculation);
futures.put(calculations, future);
return future;
}
/**
* Adds a calculation to the queues by wrapping a class to implement {@link ICalculations}, used for static methods
* when the class can't be instantiated
* <b>This method is discouraged! It is meant for objects where it isn't possible to implement {@link ICalculations}</b>
* @param calculationClass The class representing the calculations
* @param methodToCalculate The method to call for calculations
* @param params The parameters for the method
* @return A future representing the eventual calculation {@link Future#get()} returns null
*/
public Future<ICalculations> addCalculation(Class calculationClass, String methodToCalculate, Object... params) {
ICalculations calculations = new ICalculationsWrapper(calculationClass, methodToCalculate, params);
delegate(calculations);
Future<ICalculations> future = new FutureImpl<ICalculations>(calculations, null);
futures.put(calculations, future);
return future;
}
/**
* Stops further calculations from occurring
* <b>You can no longer add calculations after this!</b>
*/
public void kill() {
for (CalculationThread thread : threads)
thread.disable(true);
isKill = true;
}
/**
* Use this to check if you could add calculations
* @return True if this is dead
*/
public boolean isDead() {
return isKill;
}
private class CalculationThread extends SimpleRunnable {
private int id;
public ConcurrentLinkedDeque<ICalculations> calculations = new ConcurrentLinkedDeque<ICalculations>();
public CalculationThread() {
id = calculationNumber++;
this.start();
}
@Override
public void run() {
if (!calculations.isEmpty()) {
ICalculations calculation = calculations.pop();
calculation.doCalculation();
((FutureImpl)futures.get(calculation)).setDone();
}
}
@Override
public String getName() {
return "Calculation Thread #"+id;
}
}
private class ICalculationsWrapper implements ICalculations {
private Object object;
private String methodName;
private Object[] params;
public ICalculationsWrapper(Object object, String methodName, Object[] params) {
this.object = object;
this.methodName = methodName;
this.params = params;
}
@Override
public void doCalculation() {
try {
Method m;
if (object instanceof Class)
m = ReflectionUtils.getDeclaredOrNormalMethod(methodName, (Class) object);
else
m = ReflectionUtils.getDeclaredOrNormalMethod(methodName, object.getClass());
m.invoke(object instanceof Class ? null : object, params);
} catch (Exception e) {
e.printStackTrace();
}
}
}
private class FutureImpl<V> implements Future<V> {
private volatile ICalculations calculations;
private Object object;
private boolean isCancelled = false;
public volatile boolean isDone = false;
public FutureImpl(ICalculations calculations, Object object) {
this.calculations = calculations;
this.object = object;
}
public void setDone() {
isDone = true;
futures.remove(calculations);
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
isCancelled = true;
removeCalculation(calculations);
return true;
}
@Override
public boolean isCancelled() {
return isCancelled;
}
@Override
public boolean isDone() {
return isDone;
}
@Override
public V get() throws InterruptedException, ExecutionException {
while (!isDone) {}
if (object != null)
return (V) object;
return null;
}
@Override
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
timeout = unit.toMillis(timeout);
while (!isDone && timeout > 0) {
timeout--;
this.wait(1);
}
if (object != null)
return (V) object;
return null;
}
}
}