/*******************************************************************************
* Copyright (c) 2008, 2012 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.tcf.debug.test.util;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CancellationException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.tcf.debug.test.services.ResetMap.IResettable;
import org.eclipse.tcf.protocol.Protocol;
/**
* 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
*/
public abstract class AbstractCache<V> implements ICache<V>, IResettable {
private static final Throwable INVALID_STATUS = new Throwable("Cache invalid"); //$NON-NLS-1$
private class RequestCanceledListener implements Callback.ICanceledListener {
public void requestCanceled(final Callback canceledRm) {
invokeInDispatchThread(new Runnable() {
public void run() {
handleCanceledRm(canceledRm);
}
});
}
};
private RequestCanceledListener fRequestCanceledListener = new RequestCanceledListener();
private boolean fValid;
private V fData;
private Throwable fError = INVALID_STATUS;
private Object fWaitingList;
/**
* 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();
protected void invokeInDispatchThread(Runnable runnable) {
if (Protocol.isDispatchThread()) {
runnable.run();
} else {
Protocol.invokeLater(runnable);
}
}
/**
* 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>
*/
abstract protected void canceled();
public boolean isValid() {
return fValid;
}
public V getData() {
if (!isValid()) {
throw new IllegalStateException("Cache is not valid. Cache data can be read only when cache is valid."); //$NON-NLS-1$
}
return fData;
}
public Throwable getError() {
if (!isValid()) {
throw new IllegalStateException("Cache is not valid. Cache status can be read only when cache is valid."); //$NON-NLS-1$
}
return fError;
}
public void wait(Callback rm) {
assert Protocol.isDispatchThread();
boolean first = false;
synchronized (this) {
if (fWaitingList == null) {
first = true;
fWaitingList = rm;
} else if (fWaitingList instanceof Callback[]) {
Callback[] waitingList = (Callback[])fWaitingList;
int waitingListLength = waitingList.length;
int i;
for (i = 0; i < waitingListLength; i++) {
if (waitingList[i] == null) {
waitingList[i] = rm;
break;
}
}
if (i == waitingListLength) {
Callback[] newWaitingList = new Callback[waitingListLength + 1];
System.arraycopy(waitingList, 0, newWaitingList, 0, waitingListLength);
newWaitingList[waitingListLength] = rm;
fWaitingList = newWaitingList;
}
} else {
Callback[] newWaitingList = new Callback[2];
newWaitingList[0] = (Callback)fWaitingList;
newWaitingList[1] = rm;
fWaitingList = newWaitingList;
}
}
rm.addCancelListener(fRequestCanceledListener);
if (first && !isValid()) {
retrieve();
}
}
private void completeWaitingRms() {
Object waiting = null;
synchronized(this) {
waiting = fWaitingList;
fWaitingList = null;
}
if (waiting != null) {
if (waiting instanceof Callback) {
completeWaitingRm((Callback)waiting);
} else if (waiting instanceof Callback[]) {
Callback[] waitingList = (Callback[])waiting;
for (int i = 0; i < waitingList.length; i++) {
if (waitingList[i] != null) {
completeWaitingRm(waitingList[i]);
}
}
}
waiting = null;
}
}
private void completeWaitingRm(Callback rm) {
if (!isValid() && fError == null) {
rm.setError(INVALID_STATUS);
} else {
rm.setError(fError);
}
rm.removeCancelListener(fRequestCanceledListener);
rm.done();
}
private void handleCanceledRm(final Callback rm) {
boolean found = false;
boolean waiting = false;
synchronized (this) {
if (rm.equals(fWaitingList)) {
found = true;
waiting = false;
fWaitingList = null;
} else if(fWaitingList instanceof Callback[]) {
Callback[] waitingList = (Callback[])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.setError(new CancellationException());
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.
*/
protected boolean isCanceled() {
Object waitingList = null;
synchronized (this) {
if (fWaitingList instanceof Callback[]) {
waitingList = new Callback[((Callback[])fWaitingList).length];
System.arraycopy(fWaitingList, 0, waitingList, 0, ((Callback[]) fWaitingList).length);
} else {
waitingList = fWaitingList;
}
}
boolean canceled;
List<Callback> canceledRms = null;
if (waitingList instanceof Callback) {
if ( ((Callback)waitingList).isCanceled() ) {
canceledRms = new ArrayList<Callback>(1);
canceledRms.add((Callback)waitingList);
canceled = true;
} else {
canceled = false;
}
} else if(waitingList instanceof Callback[]) {
canceled = true;
Callback[] waitingListArray = (Callback[])waitingList;
for (int i = 0; i < waitingListArray.length; i++) {
if (waitingListArray[i] != null) {
if (waitingListArray[i].isCanceled()) {
if (canceledRms == null) {
canceledRms = new ArrayList<Callback>(1);
}
canceledRms.add( waitingListArray[i] );
} else {
canceled = false;
}
}
}
} else {
assert waitingList == null;
canceled = true;
}
if (canceledRms != null) {
final List<Callback> _canceledRms = canceledRms;
Protocol.invokeLater(new Runnable() {
public void run() {
for (Callback 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.
*/
public void reset() {
set(null, INVALID_STATUS, false);
}
/**
* Sets data and error values into the cache, and optionally puts in valid
* state. 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.
* @param error The status that should be returned to any clients waiting for
* cache data and for clients requesting data until the cache is
* invalidated
* @param valid Whether the cache should bet set in valid state. If false,
* any callback waiting for data are completed but the cache
* is moved back to invalid state.
*
* @see #reset
*/
public void set(V data, Throwable error, boolean valid) {
assert Protocol.isDispatchThread();
fData = data;
fError = error;
fValid = valid;
completeWaitingRms();
}
}