blob: e5b230788424d2205b0a37b92b50721a68af602c [file] [log] [blame]
package com.intellij.openapi.util;
import com.intellij.openapi.Disposable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
public abstract class AsyncValueLoader<T> {
private final AtomicReference<AsyncResult<T>> ref = new AtomicReference<AsyncResult<T>>();
private volatile long modificationCount;
private volatile long loadedModificationCount;
private final Runnable doneHandler = new Runnable() {
@Override
public void run() {
loadedModificationCount = modificationCount;
}
};
@NotNull
public final AsyncResult<T> get() {
return get(true);
}
public final void reset() {
AsyncResult<T> oldValue = ref.getAndSet(null);
if (oldValue != null) {
rejectAndDispose(oldValue);
}
}
private void rejectAndDispose(@NotNull AsyncResult<T> asyncResult) {
try {
if (!asyncResult.isProcessed()) {
asyncResult.setRejected();
}
}
finally {
T result = asyncResult.getResult();
if (result != null) {
disposeResult(result);
}
}
}
protected void disposeResult(@NotNull T result) {
if (result instanceof Disposable) {
Disposer.dispose((Disposable)result, false);
}
}
public final boolean has() {
AsyncResult<T> result = ref.get();
return result != null && result.isDone() && result.getResult() != null;
}
@NotNull
public final AsyncResult<T> get(boolean checkFreshness) {
AsyncResult<T> asyncResult = ref.get();
if (asyncResult == null) {
if (!ref.compareAndSet(null, asyncResult = new AsyncResult<T>())) {
return ref.get();
}
}
else if (!asyncResult.isProcessed()) {
// if current asyncResult is not processed, so, we don't need to check cache state
return asyncResult;
}
else if (asyncResult.isDone()) {
if (!checkFreshness || isUpToDate(asyncResult.getResult())) {
return asyncResult;
}
if (!ref.compareAndSet(asyncResult, asyncResult = new AsyncResult<T>())) {
AsyncResult<T> valueFromAnotherThread = ref.get();
while (valueFromAnotherThread == null) {
if (ref.compareAndSet(null, asyncResult)) {
callLoad(asyncResult);
return asyncResult;
}
else {
valueFromAnotherThread = ref.get();
}
}
return valueFromAnotherThread;
}
}
callLoad(asyncResult);
return asyncResult;
}
/**
* if result was rejected, by default this result will not be canceled - call get() will return rejected result instead of attempt to load again,
* but you can change this behavior - return true if you want to cancel result on reject
*/
protected boolean isCancelOnReject() {
return false;
}
private void callLoad(final @NotNull AsyncResult<T> result) {
if (isCancelOnReject()) {
result.doWhenRejected(new Runnable() {
@Override
public void run() {
ref.compareAndSet(result, null);
}
});
}
result.doWhenDone(doneHandler);
try {
load(result);
}
catch (Throwable e) {
ref.compareAndSet(result, null);
rejectAndDispose(result);
//noinspection InstanceofCatchParameter
throw e instanceof RuntimeException ? ((RuntimeException)e) : new RuntimeException(e);
}
}
protected abstract void load(@NotNull AsyncResult<T> result) throws IOException;
protected boolean isUpToDate(@Nullable T result) {
return loadedModificationCount == modificationCount;
}
public final void set(@NotNull T result) {
AsyncResult<T> oldValue = ref.getAndSet(AsyncResult.done(result));
if (oldValue != null) {
rejectAndDispose(oldValue);
}
}
public final void markDirty() {
modificationCount++;
}
}