summaryrefslogtreecommitdiff
path: root/platform/core-api/src/com/intellij/openapi/util/AsyncValueLoader.java
blob: f3b13dc6963cd312d365d481314049bf30972faa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.intellij.openapi.util;

import com.intellij.openapi.Disposable;
import org.jetbrains.annotations.NotNull;

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(@NotNull 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++;
  }
}