package by.istin.android.xcore.service;
import android.os.Bundle;
import android.os.ResultReceiver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import by.istin.android.xcore.utils.Log;
public class RequestExecutor {
/*
* Gets the number of available cores
* (not always the same as the maximum number of cores)
*/
private static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
public static final int DEFAULT_POOL_SIZE = Math.max(NUMBER_OF_CORES, 3);
public static final boolean IS_LOG_ENABLED = false;
public static abstract class ExecuteRunnable implements Runnable {
private class StatusBundle {
private StatusResultReceiver.Status mStatus;
private Bundle mBundle;
}
private final String mKey;
private final List<ResultReceiver> mResultReceivers = new CopyOnWriteArrayList<ResultReceiver>();
private boolean isRunning = false;
private final List<StatusBundle> mPrevStatuses = Collections.synchronizedList(new ArrayList<StatusBundle>());
private final Object mPrevStatusLock = new Object();
public ExecuteRunnable(ResultReceiver resultReceiver) {
super();
mKey = createKey();
if (resultReceiver != null) {
synchronized (mPrevStatusLock) {
mResultReceivers.add(resultReceiver);
}
}
}
public abstract String createKey();
private String getKey() {
return mKey;
}
@Override
public boolean equals(Object o) {
return getKey().equals(((ExecuteRunnable)o).getKey());
}
@Override
public int hashCode() {
return getKey().hashCode();
}
public List<ResultReceiver> getResultReceivers() {
return mResultReceivers;
}
protected void addResultReceiver(ResultReceiver resultReceiver) {
synchronized (mPrevStatusLock) {
mResultReceivers.add(resultReceiver);
for (StatusBundle prevStatus : mPrevStatuses) {
resultReceiver.send(prevStatus.mStatus.ordinal(), prevStatus.mBundle);
}
}
}
protected void addResultReceiver(List<ResultReceiver> resultReceivers) {
for (ResultReceiver resultReceiver : resultReceivers) {
addResultReceiver(resultReceiver);
}
}
public void sendStatus(StatusResultReceiver.Status status, Bundle bundle) {
synchronized (mPrevStatusLock) {
if (mResultReceivers != null) {
for (ResultReceiver resultReceiver : mResultReceivers) {
resultReceiver.send(status.ordinal(), bundle);
}
}
StatusBundle statusBundle = new StatusBundle();
statusBundle.mBundle = bundle;
statusBundle.mStatus = status;
mPrevStatuses.add(statusBundle);
}
}
protected abstract void onDone();
}
private ExecutorService mExecutor;
private final Object mLock = new Object();
private final List<ExecuteRunnable> queue = Collections.synchronizedList(new ArrayList<ExecuteRunnable>());
private final int mThreadPoolSize;
private final BlockingQueue<Runnable> mWorkQueue;
public RequestExecutor(int threadPoolSize, BlockingQueue<Runnable> workQueue) {
mThreadPoolSize = threadPoolSize;
mWorkQueue = workQueue;
recreateExecutor();
}
private void recreateExecutor() {
mExecutor = new ThreadPoolExecutor(mThreadPoolSize, mThreadPoolSize, 0L, TimeUnit.MILLISECONDS, mWorkQueue);
}
public void execute(ExecuteRunnable executeRunnable) {
synchronized (mLock) {
if (!queue.contains(executeRunnable)) {
queue.add(executeRunnable);
if (IS_LOG_ENABLED)
Log.xd(this, "queue: add new " + executeRunnable.getKey());
} else {
int index = queue.indexOf(executeRunnable);
ExecuteRunnable oldRunnable = queue.get(index);
oldRunnable.addResultReceiver(executeRunnable.getResultReceivers());
executeRunnable = oldRunnable;
if (IS_LOG_ENABLED)
Log.xd(this, "queue: up to top old " + executeRunnable.getKey());
}
if (IS_LOG_ENABLED)
Log.xd(this, "queue size: " + queue.size() + " " + executeRunnable.getKey());
if (executeRunnable.isRunning) {
if (IS_LOG_ENABLED)
Log.xd(this, "queue: already running connect " + executeRunnable.getKey());
return;
}
final ExecuteRunnable finalRunnable = executeRunnable;
mExecutor.execute(new Runnable() {
@Override
public void run() {
finalRunnable.isRunning = true;
if (IS_LOG_ENABLED)
Log.xd(RequestExecutor.this, "queue: start run " + finalRunnable.getKey());
finalRunnable.run();
synchronized (mLock) {
queue.remove(finalRunnable);
if (IS_LOG_ENABLED)
Log.xd(RequestExecutor.this, "queue: finish and remove, size: " + queue.size() + " " + finalRunnable.getKey());
finalRunnable.onDone();
}
}
});
}
}
public boolean isEmpty() {
return queue.isEmpty();
}
public void stop(ResultReceiver resultReceiver) {
try {
mExecutor.shutdownNow();
} catch (RuntimeException e) {
if (IS_LOG_ENABLED)
Log.xd(this, "stop error during shutdown");
e.printStackTrace();
} catch (Exception e) {
if (IS_LOG_ENABLED)
Log.xd(this, "stop error during shutdown");
e.printStackTrace();
}
if (IS_LOG_ENABLED)
Log.xd(this, "stop send info to receiver");
resultReceiver.send(0, null);
if (IS_LOG_ENABLED)
Log.xd(this, "stop start recreation");
recreateExecutor();
if (IS_LOG_ENABLED)
Log.xd(this, "stop done");
}
}