package ml.puredark.hviewer.ui.customs;
import android.support.annotation.Nullable;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.internal.Supplier;
import com.facebook.datasource.AbstractDataSource;
import com.facebook.datasource.DataSource;
import com.facebook.datasource.DataSubscriber;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.NotThreadSafe;
import net.jcip.annotations.ThreadSafe;
import java.util.Collections;
import java.util.Set;
import java.util.WeakHashMap;
@NotThreadSafe
public class RetainingDataSourceSupplier<T> implements Supplier<DataSource<T>> {
private final Set<RetainingDataSource> mDataSources =
Collections.newSetFromMap(new WeakHashMap<RetainingDataSource, Boolean>());
private Supplier<DataSource<T>> mCurrentDataSourceSupplier = null;
@Override
public DataSource<T> get() {
RetainingDataSource dataSource = new RetainingDataSource();
dataSource.setSupplier(mCurrentDataSourceSupplier);
mDataSources.add(dataSource);
return dataSource;
}
public void setSupplier(Supplier<DataSource<T>> supplier) {
mCurrentDataSourceSupplier = supplier;
for (RetainingDataSource dataSource : mDataSources) {
dataSource.setSupplier(supplier);
}
}
@ThreadSafe
private class RetainingDataSource extends AbstractDataSource<T> {
@GuardedBy("RetainingDataSource.this")
@Nullable
private DataSource<T> mDataSource = null;
public void setSupplier(@Nullable Supplier<DataSource<T>> supplier) {
// early return without calling {@code supplier.get()} in case we are closed
if (isClosed()) {
return;
}
DataSource<T> oldDataSource;
DataSource<T> newDataSource = (supplier != null) ? supplier.get() : null;
synchronized (RetainingDataSource.this) {
if (isClosed()) {
oldDataSource = newDataSource;
newDataSource = null;
} else {
oldDataSource = mDataSource;
mDataSource = newDataSource;
}
}
if (newDataSource != null) {
newDataSource.subscribe(new InternalDataSubscriber(), CallerThreadExecutor.getInstance());
}
closeSafely(oldDataSource);
}
@Override
@Nullable
public synchronized T getResult() {
return (mDataSource != null) ? mDataSource.getResult() : null;
}
@Override
public synchronized boolean hasResult() {
return (mDataSource != null) && mDataSource.hasResult();
}
@Override
public boolean close() {
DataSource<T> dataSource;
synchronized (RetainingDataSource.this) {
// it's fine to call {@code super.close()} within a synchronized block because we don't
// implement {@link #closeResult()}, but perform result closing ourselves.
if (!super.close()) {
return false;
}
dataSource = mDataSource;
mDataSource = null;
}
closeSafely(dataSource);
return true;
}
private void onDataSourceNewResult(DataSource<T> dataSource) {
if (dataSource == mDataSource) {
setResult(null, false);
}
}
private void onDataSourceFailed(DataSource<T> dataSource) {
// do not propagate failure
}
private void onDatasourceProgress(DataSource<T> dataSource) {
if (dataSource == mDataSource) {
setProgress(dataSource.getProgress());
}
}
private void closeSafely(DataSource<T> dataSource) {
if (dataSource != null) {
dataSource.close();
}
}
private class InternalDataSubscriber implements DataSubscriber<T> {
@Override
public void onNewResult(DataSource<T> dataSource) {
if (dataSource.hasResult()) {
RetainingDataSource.this.onDataSourceNewResult(dataSource);
} else if (dataSource.isFinished()) {
RetainingDataSource.this.onDataSourceFailed(dataSource);
}
}
@Override
public void onFailure(DataSource<T> dataSource) {
RetainingDataSource.this.onDataSourceFailed(dataSource);
}
@Override
public void onCancellation(DataSource<T> dataSource) {
}
@Override
public void onProgressUpdate(DataSource<T> dataSource) {
RetainingDataSource.this.onDatasourceProgress(dataSource);
}
}
}
}