package org.eclipse.cdt.dsf.concurrent;
/*******************************************************************************
* Copyright (c) 2008 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
*******************************************************************************/
import java.util.ArrayList;
import java.util.List;
import org.eclipse.cdt.dsf.internal.DsfPlugin;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/**
* A base implementation of a general purpose cache. Sub classes must implement
* {@link #retrieve(DataRequestMonitor)} to fetch data from the data source.
* Sub-classes are also responsible for calling {@link #set(Object, IStatus)}
* and {@link #reset()} to manage the state of the cache in response to events
* from the data source.
* <p>
* This cache requires an executor to use. The executor is used to synchronize
* access to the cache state and data.
* </p>
* @since 2.2
*/
@ConfinedToDsfExecutor("fExecutor")
public abstract class AbstractCache<V> implements ICache<V> {
private static final IStatus INVALID_STATUS = new Status(IStatus.ERROR, DsfPlugin.PLUGIN_ID, IDsfStatusConstants.INVALID_STATE, "Cache invalid", null); //$NON-NLS-1$
private class RequestCanceledListener implements RequestMonitor.ICanceledListener {
public void requestCanceled(final RequestMonitor canceledRm) {
fExecutor.getDsfExecutor().execute(new Runnable() {
public void run() {
handleCanceledRm(canceledRm);
}
});
}
};
private RequestCanceledListener fRequestCanceledListener = new RequestCanceledListener();
private boolean fValid;
private V fData;
private IStatus fStatus = INVALID_STATUS;
@ThreadSafe
private Object fWaitingList;
private final ImmediateInDsfExecutor fExecutor;
public AbstractCache(ImmediateInDsfExecutor executor) {
fExecutor = executor;
}
public DsfExecutor getExecutor() {
return fExecutor.getDsfExecutor();
}
protected ImmediateInDsfExecutor getImmediateInDsfExecutor() {
return fExecutor;
}
/**
* Sub-classes should override this method to retrieve the cache data from
* its source. The implementation should call {@link #set(Object, IStatus)}
* to store the newly retrieved data when it arrives (or an error, if one
* occurred retrieving the data)
*
* @param rm
* Request monitor for completion of data retrieval.
*/
abstract protected void retrieve();
/**
* Called to cancel a retrieve request. This method is called when
* clients of the cache no longer need data that was requested. <br>
* Sub-classes should cancel and clean up requests to the asynchronous
* data source.
*
* <p>
* Note: Called while holding a lock to "this". No new request will start until
* this call returns.
* </p>
*/
@ThreadSafe
abstract protected void canceled();
public boolean isValid() {
return fValid;
}
public V getData() {
if (!fValid) {
throw new IllegalStateException("Cache is not valid. Cache data can be read only when cache is valid."); //$NON-NLS-1$
}
return fData;
}
public IStatus getStatus() {
if (!fValid) {
throw new IllegalStateException("Cache is not valid. Cache status can be read only when cache is valid."); //$NON-NLS-1$
}
return fStatus;
}
public void update(RequestMonitor rm) {
assert fExecutor.getDsfExecutor().isInExecutorThread();
if (!fValid) {
boolean first = false;
synchronized (this) {
if (fWaitingList == null) {
first = true;
fWaitingList = rm;
} else if (fWaitingList instanceof RequestMonitor[]) {
RequestMonitor[] waitingList = (RequestMonitor[])fWaitingList;
int waitingListLength = waitingList.length;
int i;
for (i = 0; i < waitingListLength; i++) {
if (waitingList[i] == null) {
waitingList[i] = rm;
break;
}
}
if (i == waitingListLength) {
RequestMonitor[] newWaitingList = new RequestMonitor[waitingListLength + 1];
System.arraycopy(waitingList, 0, newWaitingList, 0, waitingListLength);
newWaitingList[waitingListLength] = rm;
fWaitingList = newWaitingList;
}
} else {
RequestMonitor[] newWaitingList = new RequestMonitor[2];
newWaitingList[0] = (RequestMonitor)fWaitingList;
newWaitingList[1] = rm;
fWaitingList = newWaitingList;
}
}
rm.addCancelListener(fRequestCanceledListener);
if (first) {
retrieve();
}
} else {
rm.setStatus(fStatus);
rm.done();
}
}
private void completeWaitingRm(RequestMonitor rm) {
rm.setStatus(fStatus);
rm.removeCancelListener(fRequestCanceledListener);
rm.done();
}
private void handleCanceledRm(final RequestMonitor rm) {
boolean found = false;
boolean waiting = false;
synchronized (this) {
if (rm.equals(fWaitingList)) {
found = true;
waiting = false;
fWaitingList = null;
} else if(fWaitingList instanceof RequestMonitor[]) {
RequestMonitor[] waitingList = (RequestMonitor[])fWaitingList;
for (int i = 0; i < waitingList.length; i++) {
if (!found && rm.equals(waitingList[i])) {
waitingList[i] = null;
found = true;
}
waiting = waiting || waitingList[i] != null;
}
}
if (found && !waiting) {
canceled();
}
}
// If we have no clients waiting anymore, cancel the request
if (found) {
// We no longer need to listen to cancellations.
rm.removeCancelListener(fRequestCanceledListener);
rm.setStatus(Status.CANCEL_STATUS);
rm.done();
}
}
/**
* Returns true if there are no clients waiting for this cache or if the
* clients that are waiting, have already canceled their requests.
* <p>
* Note: Calling this method may cause the client request monitors that were
* canceled to be completed with a cancel status. If all the client request
* monitors were canceled, this method will also cause the {@link #canceled()}
* method to be called. Both of these side effects will only happen
* asynchronously after <code>isCanceled()</code> returns.
* </p>
*
* @return <code>true</code> if all clients waiting on this cache have been
* canceled, or if there are no clients waiting at all.
*/
@ThreadSafe
protected boolean isCanceled() {
boolean canceled;
List<RequestMonitor> canceledRms = null;
synchronized (this) {
if (fWaitingList instanceof RequestMonitor) {
if ( ((RequestMonitor)fWaitingList).isCanceled() ) {
canceledRms = new ArrayList<RequestMonitor>(1);
canceledRms.add((RequestMonitor)fWaitingList);
canceled = true;
} else {
canceled = false;
}
} else if(fWaitingList instanceof RequestMonitor[]) {
canceled = true;
RequestMonitor[] waitingList = (RequestMonitor[])fWaitingList;
for (int i = 0; i < waitingList.length; i++) {
if (waitingList[i] != null) {
if (waitingList[i].isCanceled()) {
if (canceledRms == null) {
canceledRms = new ArrayList<RequestMonitor>(1);
}
canceledRms.add( waitingList[i] );
} else {
canceled = false;
}
}
}
} else {
assert fWaitingList == null;
canceled = true;
}
}
if (canceledRms != null) {
final List<RequestMonitor> _canceledRms = canceledRms;
fExecutor.getDsfExecutor().execute(new DsfRunnable() {
public void run() {
for (RequestMonitor canceledRm : _canceledRms) {
handleCanceledRm(canceledRm);
}
}
});
}
return canceled;
}
/**
* Resets the cache, setting the data to null and the status to
* INVALID_STATUS. When in the invalid state, neither the data nor the
* status can be queried.
*/
protected void reset() {
if (!fValid) {
throw new IllegalStateException("Cache is not valid. Cache can be reset only when it's in a valid state"); //$NON-NLS-1$
}
fValid = false;
fData = null;
fStatus = INVALID_STATUS;
}
/**
* Moves the cache to the valid state with the given data and status. Note
* that data may be null and status may be an error status. 'Valid' simply
* means that our data is not stale. In other words, if the request to the
* source encounters an error, the cache object becomes valid all the same.
* The status indicates what error was encountered.
*
* <p>
* This method is called internally, typically in response to having
* obtained the result from the asynchronous request to the source. The
* data/status will remain valid until the cache object receives an event
* notification from the source indicating otherwise.
*
* @param data
* The data that should be returned to any clients waiting for
* cache data and for clients requesting data until the cache is
* invalidated.
* @status The status that should be returned to any clients waiting for
* cache data and for clients requesting data until the cache is
* invalidated
*
* @see #reset(Object, IStatus)
*/
protected void set(V data, IStatus status) {
assert fExecutor.getDsfExecutor().isInExecutorThread();
fData = data;
fStatus = status;
fValid = true;
Object waiting = null;
synchronized(this) {
waiting = fWaitingList;
fWaitingList = null;
}
if (waiting != null) {
if (waiting instanceof RequestMonitor) {
completeWaitingRm((RequestMonitor)waiting);
} else if (waiting instanceof RequestMonitor[]) {
RequestMonitor[] waitingList = (RequestMonitor[])waiting;
for (int i = 0; i < waitingList.length; i++) {
if (waitingList[i] != null) {
completeWaitingRm(waitingList[i]);
}
}
}
waiting = null;
}
}
}