blob: 36e9f8c61703f4f1e9e857cf708fee3ba6bf397f [file] [log] [blame]
/*******************************************************************************
* 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.cdt.dsf.concurrent;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
/**
* 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 RequestCache<List<V>> implements Comparable<Request> {
long fOffset;
int fCount;
@Override
protected void retrieve(DataRequestMonitor<java.util.List<V>> rm) {
RangeCache.this.retrieve(fOffset, fCount, rm);
}
Request(long offset, int count) {
super(fExecutor);
fOffset = offset;
fCount = count;
}
@Override
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, CoreException {
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();
}
}
}
}
private final ImmediateInDsfExecutor fExecutor;
/**
* Requests currently held by this cache. The requests should be for
* non-overlapping ranges of elements.
*/
private SortedSet<Request> fRequests = new TreeSet<Request>();
public RangeCache(ImmediateInDsfExecutor executor) {
fExecutor = executor;
}
/**
* 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, DataRequestMonitor<List<V>> rm);
/**
* Returns a cache for the range of requested data.
*
* @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 fExecutor.getDsfExecutor().isInExecutorThread();
List<Request> requests = getRequests(offset, count);
RequestCache<List<V>> range = new RequestCache<List<V>>(fExecutor) {
@Override
protected void retrieve(DataRequestMonitor<List<V>> rm) {
new RangeTransaction(offset, count).request(rm);
}
};
boolean valid = true;
MultiStatus status = new MultiStatus(DsfPlugin.PLUGIN_ID, 0, "", null); //$NON-NLS-1$
for (ICache<?> request : requests) {
if (request.isValid()) {
if (!request.getStatus().isOK()) {
status.add(request.getStatus());
}
} else {
valid = false;
break;
}
}
if (valid) {
if (status.isOK()) {
range.set(makeElementsListFromRequests(requests, offset, count), Status.OK_STATUS);
} else {
if (status.getChildren().length == 1) {
range.set(null, status.getChildren()[0]);
} else {
range.set(null, status);
}
}
}
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 status Status object to set to cache.
*/
protected void set(long offset, int count, List<V> data, IStatus status) {
for (Request request : fRequests) {
if (!request.isValid()) {
request.set(null, Status.OK_STATUS);
}
}
fRequests.clear();
Request request = new Request(offset, count);
request.set(data, status);
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) {
retVal.add( request.getData().get((int)(index - request.fOffset)) );
index ++;
} else {
requestIdx++;
}
}
return retVal;
}
}