Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 93a4ee3f332a05dc9810b76b5889024612890e52 (plain) (blame)
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
/*******************************************************************************
 * Copyright (c) 2008, 2010 Wind River Systems, Inc. and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.tm.tcf.util;

import org.eclipse.tm.tcf.protocol.IChannel;
import org.eclipse.tm.tcf.protocol.IToken;
import org.eclipse.tm.tcf.protocol.Protocol;

/**
 * Objects of this class are used to cache TCF remote data.
 * The cache is asynchronous state machine. The states are:
 *  1. Valid - cache is in sync with remote data, use getError() and getData() to get cached data;
 *  2. Invalid - cache is out of sync, start data retrieval by calling validate();
 *  3. Pending - cache is waiting result of a command that was sent to remote peer;
 *  4. Disposed - cache is disposed and cannot be used to store data.
 *
 * A cache instance can be created on any data type that needs to be cached.
 * Examples might be context children list, context properties, context state, memory data,
 * register data, symbol, variable, etc.
 * Clients of cache items can register for cache changes, but don’t need to think about any particular events
 * since that is handled by the cache item itself.
 *
 * A typical cache client should implement Runnable interface.
 * The implementation of run() method should:
 *
 * Validate all cache items required for client task.
 * If anything is invalid then client should not alter any shared data structures,
 * should discard any intermediate results and register (wait) for changes of invalid cache instance(s) state.
 * When cache item state changes, client is invoked again and full validation is restarted.
 * Once everything is valid, client completes its task in a single dispatch cycle.
 *
 * Note: clients should never retain copies of remote data across dispatch cycles!
 * Such data would get out of sync and compromise data consistency.
 * All remote data and everything derived from remote data should be kept in cache items
 * that implement proper event handling and can keep data consistent across dispatch cycles.
 *
 * @param <V> - type of data to be stored in the cache.
 */
public abstract class TCFDataCache<V> implements Runnable {

    private Throwable error;
    private boolean valid;
    private boolean posted;
    private boolean disposed;
    private V data;

    protected final IChannel channel;
    protected IToken command;

    private Runnable[] waiting_list = null;
    private int waiting_cnt;

    public TCFDataCache(IChannel channel) {
        assert channel != null;
        this.channel = channel;
    }

    private void post() {
        if (posted) return;
        if (waiting_cnt == 0) return;
        Protocol.invokeLater(this);
        posted = true;
    }

    /**
     * @return true if cache contains up-to-date data or error.
     */
    public boolean isValid() {
        return valid;
    }

    /**
     * @return true if data retrieval command is in progress.
     */
    public boolean isPending() {
        return command != null;
    }

    /**
     * @return true if cache is disposed.
     */
    public boolean isDisposed() {
        return disposed;
    }

    /**
     * @return error object if data retrieval ended with an error, or null if retrieval was successful.
     * Note: It is prohibited to call this method when cache is not valid.
     */
    public Throwable getError() {
        assert valid;
        return error;
    }

    /**
     * @return cached data object.
     * Note: It is prohibited to call this method when cache is not valid.
     */
    public V getData() {
        assert Protocol.isDispatchThread();
        assert valid;
        return data;
    }

    /**
     * Notify waiting clients about cache state change and remove them from wait list.
     * It is responsibility of clients to check if the state change was one they are waiting for.
     * Clients are not intended to call this method.
     */
    public final void run() {
        assert Protocol.isDispatchThread();
        posted = false;
        if (waiting_cnt > 0) {
            int cnt = waiting_cnt;
            Runnable[] arr = waiting_list;
            waiting_list = null;
            waiting_cnt = 0;
            for (int i = 0; i < cnt; i++) {
                Runnable r = arr[i];
                if (r instanceof TCFDataCache<?> && ((TCFDataCache<?>)r).posted) continue;
                r.run();
            }
            if (waiting_list == null) waiting_list = arr;
        }
    }

    /**
     * Add a client call-back to cache wait list.
     * Client call-backs are activated when cache state changes.
     * Call-backs are removed from waiting list after that.
     * It is responsibility of clients to check if the state change was one they are waiting for.
     * @param cb - a call-back object
     */
    public void wait(Runnable cb) {
        assert Protocol.isDispatchThread();
        assert !disposed;
        assert !valid;
        if (cb != null && !isWaiting(cb)) {
            if (waiting_list == null) waiting_list = new Runnable[8];
            if (waiting_cnt >= waiting_list.length) {
                Runnable[] tmp = new Runnable[waiting_cnt * 2];
                System.arraycopy(waiting_list, 0, tmp, 0, waiting_list.length);
                waiting_list = tmp;
            }
            waiting_list[waiting_cnt++] = cb;
        }
    }

    /**
     * Return true if a client call-back is waiting for state changes of this cache item.
     * @param cb - a call-back object.
     * @return true if 'cb' is in the wait list.
     */
    public boolean isWaiting(Runnable cb) {
        if (waiting_list == null) return false;
        for (int i = 0; i < waiting_cnt; i++) {
            if (waiting_list[i] == cb) return true;
        }
        return false;
    }

    /**
     * Initiate data retrieval if the cache is not valid.
     * @return true if the cache is already valid
     */
    public boolean validate() {
        assert Protocol.isDispatchThread();
        if (disposed || channel.getState() != IChannel.STATE_OPEN) {
            command = null;
            valid = true;
            error = null;
            data = null;
        }
        else {
            if (command != null) return false;
            if (!valid && !startDataRetrieval()) return false;
        }
        assert valid;
        assert command == null;
        post();
        return true;
    }

    /**
     * If the cache is not valid, initiate data retrieval and
     * add a client call-back to cache wait list.
     * Client call-backs are activated when cache state changes.
     * Call-backs are removed from waiting list after that.
     * It is responsibility of clients to check if the state change was one they are waiting for.
     * If the cache is valid do nothing and return true.
     * @param cb - a call-back object
     * @return true if the cache is already valid
     */
    public boolean validate(Runnable cb) {
        if (!validate()) {
            wait(cb);
            return false;
        }
        return true;
    }

    /**
     * Start cache pending state.
     * Pending state is when the cache is waiting for a TCF command to return results.
     * @param command - TCF command handle.
     */
    public void start(IToken command) {
        assert !valid;
        assert command != null;
        assert this.command == null;
        this.command = command;
    }

    /**
     * End cache pending state, but not mark the cache as valid.
     * @param command - TCF command handle.
     */
    public void done(IToken command) {
        if (this.command != command) return;
        assert !valid;
        this.command = null;
        post();
    }

    /**
     * End cache pending state and mark the cache as valid.
     * If 'token' != null, the data represent results from a completed command.
     * The data should be ignored if current cache pending command is not same as 'token'.
     * It can happen if the cache was reset or canceled during data retrieval.
     * @param token - pending command handle or null.
     * @param error - data retrieval error or null
     * @param data - up-to-date data object
     */
    public void set(IToken token, Throwable error, V data) {
        assert Protocol.isDispatchThread();
        if (token != null && command != token) return;
        command = null;
        if (!disposed) {
            assert !valid;
            if (channel.getState() != IChannel.STATE_OPEN) {
                error = null;
                data = null;
            }
            this.error = error;
            this.data = data;
            valid = true;
        }
        post();
    }

    /**
     * Force cache to become valid, cancel pending data retrieval if any.
     * @param data - up-to-date data object
     */
    public void reset(V data) {
        assert Protocol.isDispatchThread();
        if (command != null) {
            command.cancel();
            command = null;
        }
        if (!disposed) {
            this.data = data;
            error = null;
            valid = true;
        }
        post();
    }

    /**
     * Invalidate the cache.
     * If retrieval is in progress - let it continue.
     */
    public void reset() {
        assert Protocol.isDispatchThread();
        if (!disposed) {
            error = null;
            valid = false;
            data = null;
        }
        post();
    }

    /**
     * Invalidate the cache.
     * Cancel pending data retrieval if any.
     */
    public void cancel() {
        reset();
        if (command != null) {
            command.cancel();
            command = null;
        }
    }

    /**
     * Dispose the cache.
     * Cancel pending data retrieval if any.
     */
    public void dispose() {
        cancel();
        valid = true;
        disposed = true;
    }

    @Override
    public String toString() {
        StringBuffer bf = new StringBuffer();
        bf.append('[');
        if (valid) bf.append("valid,");
        if (disposed) bf.append("disposed,");
        if (posted) bf.append("posted,");
        if (error != null) bf.append("error,");
        bf.append("data=");
        bf.append(data == null ? "null" : data.toString());
        bf.append(']');
        return bf.toString();
    }

    /**
     * Sub-classes should override this method to implement actual data retrieval logic.
     * @return true is all done, false if retrieval is in progress.
     */
    protected abstract boolean startDataRetrieval();
}

Back to the top