Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/RangeCache.java')
-rw-r--r--tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/RangeCache.java329
1 files changed, 329 insertions, 0 deletions
diff --git a/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/RangeCache.java b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/RangeCache.java
new file mode 100644
index 000000000..3ef3f4b27
--- /dev/null
+++ b/tests/plugins/org.eclipse.tcf.debug.test/src/org/eclipse/tcf/debug/test/util/RangeCache.java
@@ -0,0 +1,329 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Wind River Systems 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.tcf.debug.test.util;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.tcf.protocol.Protocol;
+
+/**
+ * Cache for efficiently retrieving overlapping ranges of elements from an
+ * asynchronous data source. The results of a range request (see
+ * {@link #getRange(long, int)}) are kept around and reused in subsequent
+ * requests. E.g., two individual, initial requests for 4 bytes at offsets 0 and
+ * 4 will relieve a subsequent request for 4 bytes at offset 2 from having to
+ * asynchronously retrieve the data from the source. The results from the first
+ * two range requests are used to service the third.
+ *
+ * <p>
+ * Clients of this cache should call {@link #getRange(long, int)} to get a cache
+ * for that given range of elements. Sub-classes must implement
+ * {@link #retrieve(long, int, DataRequestMonitor)} to retrieve data from the
+ * asynchronous data source.
+ *
+ *
+ *
+ * @since 2.2
+ */
+abstract public class RangeCache<V> {
+
+ private class Request extends CallbackCache<List<V>> implements Comparable<Request> {
+ long fOffset;
+ int fCount;
+ @Override
+ protected void retrieve(DataCallback<java.util.List<V>> rm) {
+ RangeCache.this.retrieve(fOffset, fCount, rm);
+ }
+
+ Request(long offset, int count) {
+ fOffset = offset;
+ fCount = count;
+ }
+
+ public int compareTo(RangeCache<V>.Request o) {
+ if (fOffset > o.fOffset) {
+ return 1;
+ } else if (fOffset == o.fOffset) {
+ return 0;
+ } else /*if (fOffset < o.fOffset)*/ {
+ return -1;
+ }
+ }
+
+ @Override
+ public boolean equals(Object _o) {
+ if (_o instanceof RangeCache<?>.Request) {
+ RangeCache<?>.Request o = (RangeCache<?>.Request)_o;
+ return fOffset == o.fOffset && fCount == o.fCount;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return (int)fOffset^fCount;
+ }
+
+ @Override
+ public String toString() {
+ return "" + fOffset + "(" + fCount + ") -> " + (fOffset + fCount); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+
+ /**
+ * This transaction class implements the main logic of the range cache.
+ * It examines the current requests held by the cache and and creates
+ * requests ones as needed. Once the requests are all valid it returns
+ * the completed data to the client.
+ */
+ private class RangeTransaction extends Transaction<List<V>> {
+
+ long fOffset;
+ int fCount;
+
+ RangeTransaction(long offset, int count) {
+ fOffset = offset;
+ fCount = count;
+ }
+ @Override
+ protected List<V> process() throws InvalidCacheException, ExecutionException {
+ clearCanceledRequests();
+
+ List<Request> transactionRequests = getRequests(fOffset, fCount);
+ validate(transactionRequests);
+
+ return makeElementsListFromRequests(transactionRequests, fOffset, fCount);
+ }
+
+ private void clearCanceledRequests() {
+ for (Iterator<Request> itr = fRequests.iterator(); itr.hasNext();) {
+ Request request = itr.next();
+ if (!request.isValid() && request.isCanceled()) {
+ itr.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Requests currently held by this cache. The requests should be for
+ * non-overlapping ranges of elements.
+ */
+
+ private SortedSet<Request> fRequests = new TreeSet<Request>();
+
+ /**
+ * Retrieves data from the data source.
+ *
+ * @param offset Offset in data range where the requested list of data should start.
+ * @param count Number of elements requests.
+ * @param rm Callback for the data.
+ */
+ protected abstract void retrieve(long offset, int count, DataCallback<List<V>> rm);
+
+ /**
+ * Returns a cache for the range of requested data. The cache object needs
+ * to be retrieved from range cache every time that the cache is a
+ *
+ * @param offset Offset in data range where the requested list of data should start.
+ * @param count Number of elements requests.
+ * @return Cache object for the requested data.
+ */
+ public ICache<List<V>> getRange(final long offset, final int count) {
+ assert Protocol.isDispatchThread();
+
+ List<Request> requests = getRequests(offset, count);
+
+ CallbackCache<List<V>> range = new CallbackCache<List<V>>() {
+ @Override
+ protected void retrieve(DataCallback<List<V>> rm) {
+ new RangeTransaction(offset, count).request(rm);
+ }
+ };
+
+ boolean valid = true;
+ List<Throwable> errors = null;
+ for (ICache<?> request : requests) {
+ if (request.isValid()) {
+ if (request.getError() != null) {
+ errors = errors != null ? errors : new ArrayList<Throwable>(requests.size());
+ errors.add(request.getError());
+ }
+ } else {
+ valid = false;
+ break;
+ }
+ }
+
+ if (valid) {
+ if (errors == null) {
+ range.set(makeElementsListFromRequests(requests, offset, count), null, true);
+ } else {
+ if (errors.size() == 1) {
+ range.set(null, errors.get(0), true);
+ } else {
+ AggregateError aggregateError = new AggregateError("Multiple Errors");
+ for (Throwable error : errors) aggregateError.add(error);
+ }
+ }
+ }
+
+ return range;
+ }
+
+ /**
+ * Sets the given list and status to the cache. Subsequent range requests
+ * that fall in its the range will return the given data. Requests outside
+ * of its range will trigger a call to {@link #retrieve(long, int, DataRequestMonitor)}.<br>
+ * The given data parameter can be <code>null</code> if the given status
+ * parameter contains an error. In this case all requests in the given
+ * range will return the error.
+ *
+ * @param offset Offset of the given data to set to cache.
+ * @param count Count of the given data to set to cache.
+ * @param data List of elements to set to cache. Can be <code>null</code>.
+ * @param error Error object to set to cache or <code>null</code> if none.
+ */
+ public void set(long offset, int count, List<V> data, Throwable error) {
+ for (Request request : fRequests) {
+ if (!request.isValid()) {
+ request.set(null, null, false);
+ }
+ }
+ fRequests.clear();
+ Request request = new Request(offset, count);
+ request.set(data, error, true);
+ fRequests.add(request);
+ }
+
+ /**
+ * Forces the cache into an invalid state. If there are any pending
+ * requests, their will continue and their results will be cached.
+ */
+ protected void reset() {
+ for (Iterator<Request> itr = fRequests.iterator(); itr.hasNext();) {
+ Request request = itr.next();
+ if (request.isValid()) {
+ request.reset();
+ itr.remove();
+ }
+ }
+ }
+
+ private List<Request> getRequests(long fOffset, int fCount) {
+ List<Request> requests = new ArrayList<Request>(1);
+
+ // Create a new request for the data to retrieve.
+ Request current = new Request(fOffset, fCount);
+
+ current = adjustRequestHead(current, requests, fOffset, fCount);
+ if (current != null) {
+ current = adjustRequestTail(current, requests, fOffset, fCount);
+ }
+ if (current != null) {
+ requests.add(current);
+ fRequests.add(current);
+ }
+ return requests;
+ }
+
+ // Adjust the beginning of the requested range of data. If there
+ // is already an overlapping range in front of the requested range,
+ // then use it.
+ private Request adjustRequestHead(Request request, List<Request> transactionRequests, long offset, int count) {
+ SortedSet<Request> headRequests = fRequests.headSet(request);
+ if (!headRequests.isEmpty()) {
+ Request headRequest = headRequests.last();
+ long headEndOffset = headRequest.fOffset + headRequest.fCount;
+ if (headEndOffset > offset) {
+ transactionRequests.add(headRequest);
+ request.fCount = (int)(request.fCount - (headEndOffset - offset));
+ request.fOffset = headEndOffset;
+ }
+ }
+ if (request.fCount > 0) {
+ return request;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Adjust the end of the requested range of data.
+ * @param current
+ * @param transactionRequests
+ * @return
+ */
+ private Request adjustRequestTail(Request current, List<Request> transactionRequests, long offset, int count) {
+ // Create a duplicate of the tailSet, in order to avoid a concurrent modification exception.
+ List<Request> tailSet = new ArrayList<Request>(fRequests.tailSet(current));
+
+ // Iterate through the matching requests and add them to the requests list.
+ for (Request tailRequest : tailSet) {
+ if (tailRequest.fOffset < current.fOffset + count) {
+ // found overlapping request add it to list
+ if (tailRequest.fOffset <= current.fOffset) {
+ // next request starts off at the beginning of current request
+ transactionRequests.add(tailRequest);
+ current.fOffset = tailRequest.fOffset + tailRequest.fCount;
+ current.fCount = ((int)(offset - current.fOffset)) + count ;
+ if (current.fCount <= 0) {
+ return null;
+ }
+ } else {
+ current.fCount = (int)(tailRequest.fOffset - current.fOffset);
+ transactionRequests.add(current);
+ fRequests.add(current);
+ current = null;
+ transactionRequests.add(tailRequest);
+ long tailEndOffset = tailRequest.fOffset + tailRequest.fCount;
+ long rangeEndOffset = offset + count;
+ if (tailEndOffset >= rangeEndOffset) {
+ return null;
+ } else {
+ current = new Request(tailEndOffset, (int)(rangeEndOffset - tailEndOffset));
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ return current;
+ }
+
+ private List<V> makeElementsListFromRequests(List<Request> requests, long offset, int count) {
+ List<V> retVal = new ArrayList<V>(count);
+ long index = offset;
+ long end = offset + count;
+ int requestIdx = 0;
+ while (index < end ) {
+ Request request = requests.get(requestIdx);
+ if (index < request.fOffset + request.fCount) {
+ int dataIndex = (int)(index - request.fOffset);
+ if (dataIndex < request.getData().size()) {
+ retVal.add( request.getData().get(dataIndex) );
+ } else {
+ // If request didnt' return enough data, pad range with nulls.
+ retVal.add(null);
+ }
+ index ++;
+ } else {
+ requestIdx++;
+ }
+ }
+ return retVal;
+ }
+}

Back to the top