diff options
Diffstat (limited to 'plugins/org.eclipse.tcf.core/src/org/eclipse/tcf/util/TCFDataCache.java')
-rw-r--r-- | plugins/org.eclipse.tcf.core/src/org/eclipse/tcf/util/TCFDataCache.java | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/plugins/org.eclipse.tcf.core/src/org/eclipse/tcf/util/TCFDataCache.java b/plugins/org.eclipse.tcf.core/src/org/eclipse/tcf/util/TCFDataCache.java new file mode 100644 index 000000000..93a4ee3f3 --- /dev/null +++ b/plugins/org.eclipse.tcf.core/src/org/eclipse/tcf/util/TCFDataCache.java @@ -0,0 +1,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(); +} |