Large diffs are not rendered by default.

@@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (c) 2012 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;

import java.util.Collections;
import java.util.Map;

import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.IBreakpointsListener;
import org.eclipse.debug.core.model.IBreakpoint;

/**
*
*/
public class BreakpointsListener implements IBreakpointsListener {

public enum EventType { ADDED, REMOVED, CHANGED };

public interface EventTester {
boolean checkEvent(EventType type, IBreakpoint bp, Map<String, Object> deltaAttributes);
}

private static final EventTester ANY_EVENT_TESTER = new EventTester() {
public boolean checkEvent(EventType type, IBreakpoint bp, Map<String,Object> deltaAttributes) {
return true;
}
};

private EventTester fTester = ANY_EVENT_TESTER;
private boolean fWaiting = false;


public BreakpointsListener() {
DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this);
}

public void dispose() {
DebugPlugin.getDefault().getBreakpointManager().removeBreakpointListener(this);
}

public void startWaiting() {
fWaiting = true;
}
public void setTester(EventTester tester) {
fTester = tester;
startWaiting();
}

public void breakpointsAdded(IBreakpoint[] bps) {
Map<String,Object> emptyAttrs = Collections.emptyMap();
for (IBreakpoint bp : bps) {
if (fTester.checkEvent(EventType.ADDED, bp, emptyAttrs)) {
synchronized(this) {
fWaiting = false;
notifyAll();
}
return;
}
}
}

public void breakpointsRemoved(IBreakpoint[] bps, IMarkerDelta[] deltas) {
for (int i =0; i < bps.length; i++) {
Map<String,Object> attributes = Collections.emptyMap();
if (deltas[i] != null) {
attributes = deltas[i].getAttributes();
}
if (fTester.checkEvent(EventType.REMOVED, bps[i], attributes)) {
synchronized(this) {
fWaiting = false;
notifyAll();
}
return;
}
}
}

public void breakpointsChanged(IBreakpoint[] bps, IMarkerDelta[] deltas) {
for (int i =0; i < bps.length; i++) {
Map<String,Object> attributes = Collections.emptyMap();
if (deltas[i] != null) {
attributes = deltas[i].getAttributes();
}
if (fTester.checkEvent(EventType.CHANGED, bps[i], attributes)) {
synchronized(this) {
fWaiting = false;
notifyAll();
}
return;
}
}
}


public void waitForEvent() throws InterruptedException {
synchronized(this) {
while(fWaiting) {
wait();
}
}
}
}
@@ -1,115 +1,101 @@
package org.eclipse.tcf.debug.test;

import java.util.Collection;
import java.math.BigInteger;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;

import org.eclipse.cdt.debug.core.CDIDebugModel;
import org.eclipse.cdt.debug.core.model.ICBreakpoint;
import org.eclipse.cdt.debug.core.model.ICBreakpointType;
import org.eclipse.cdt.debug.core.model.ICLineBreakpoint;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.debug.core.model.IBreakpoint;
import org.eclipse.tcf.debug.test.BreakpointsListener.EventTester;
import org.eclipse.tcf.debug.test.BreakpointsListener.EventType;
import org.eclipse.tcf.debug.test.services.RunControlCM.ContextState;
import org.eclipse.tcf.debug.test.util.Transaction;
import org.eclipse.tcf.services.IBreakpoints;
import org.eclipse.tcf.services.IRunControl.RunControlContext;
import org.eclipse.tcf.internal.debug.ui.launch.TCFLaunchContext;
import org.eclipse.tcf.protocol.Protocol;
import org.eclipse.tcf.services.ILineNumbers.CodeArea;
import org.eclipse.tcf.services.ISymbols.Symbol;
import org.junit.Assert;

@SuppressWarnings("restriction")
public class BreakpointsTest extends AbstractTcfUITest
{
private String fTestId;
private RunControlContext fTestCtx;
private String fProcessId = "";
private String fThreadId = "";
private RunControlContext fThreadCtx;
private BreakpointsListener fBpListener;

@Override
protected void setUp() throws Exception {
super.setUp();
fBpListener = new BreakpointsListener();

// CDT Breakpoint integration depends on the TCF-CDT breakpoint
// integration to be active. This is normally triggered by selecting
// a stack frame in the UI. Here force activation of the plugin
// artificially. None of the cdt integration packages are exported, so
// use the TCF Launch Context extension point indirectly to force the
// plugin to load.
TCFLaunchContext.getLaunchContext(null);
}

private void createBreakpoint(final String bpId, final String testFunc) throws InterruptedException, ExecutionException {
new Transaction<Object>() {
private Map<String,Object> fBp;

{
fBp = new TreeMap<String,Object>();
fBp.put(IBreakpoints.PROP_ID, bpId);
fBp.put(IBreakpoints.PROP_ENABLED, Boolean.TRUE);
fBp.put(IBreakpoints.PROP_LOCATION, testFunc);
}

@Override
protected Object process() throws InvalidCacheException, ExecutionException {
@SuppressWarnings("unchecked")
Map<String, Object>[] bps = (Map<String, Object>[])new Map[] { fBp };
validate( fBreakpointsCM.set(bps, this) );

return null;
}
}.get();
@Override
protected void tearDown() throws Exception {
fBpListener.dispose();
super.tearDown();
}

private void checkBreakpointForErrors(final String bpId, final String processId) throws InterruptedException, ExecutionException {
new Transaction<Object>() {
private CodeArea getFunctionCodeArea(String functionName) throws Exception {
return new Transaction<CodeArea>() {
@Override
protected Object process() throws InvalidCacheException, ExecutionException {
// Wait for breakpoint status event and validate it.
Map<String, Object> status = validate( fBreakpointsCM.getStatus(bpId) );
String s = (String)status.get(IBreakpoints.STATUS_ERROR);
if (s != null) {
Assert.fail("Invalid BP status: " + s);
}
@SuppressWarnings("unchecked")
Collection<Map<String,Object>> list = (Collection<Map<String,Object>>)status.get(IBreakpoints.STATUS_INSTANCES);
if (list != null) {
String err = null;
for (Map<String,Object> map : list) {
String ctx = (String)map.get(IBreakpoints.INSTANCE_CONTEXT);
if (processId.equals(ctx) && map.get(IBreakpoints.INSTANCE_ERROR) != null)
err = (String)map.get(IBreakpoints.INSTANCE_ERROR);
}
if (err != null) {
Assert.fail("Invalid BP status: " + s);
}
}
return null;
protected CodeArea process() throws InvalidCacheException, ExecutionException {
ContextState state = validate ( fRunControlCM.getState(fThreadId) );
String symId = validate ( fSymbolsCM.find(fProcessId, new BigInteger(state.pc), "tcf_test_func0") );
Symbol sym = validate ( fSymbolsCM.getContext(symId) );
CodeArea[] area = validate ( fLineNumbersCM.mapToSource(
fProcessId,
sym.getAddress(),
new BigInteger(sym.getAddress().toString()).add(BigInteger.valueOf(1))) );
return area[0];
}
}.get();
}

private void startProcess() throws InterruptedException, ExecutionException {
new Transaction<Object>() {
protected Object process() throws Transaction.InvalidCacheException ,ExecutionException {
fTestId = validate( fDiagnosticsCM.runTest(getDiagnosticsTestName(), this) );
fTestCtx = validate( fRunControlCM.getContext(fTestId) );
fProcessId = fTestCtx.getProcessID();
// Create the cache to listen for exceptions.
fRunControlCM.waitForContextException(fTestId, fTestRunKey);

if (!fProcessId.equals(fTestId)) {
fThreadId = fTestId;
} else {
String[] threads = validate( fRunControlCM.getChildren(fProcessId) );
fThreadId = threads[0];
}
fThreadCtx = validate( fRunControlCM.getContext(fThreadId) );

Assert.assertTrue("Invalid thread context", fThreadCtx.hasState());
return new Object();
};
}.get();
}


private void initProcessModel(String bpId, String testFunc) throws Exception {
createBreakpoint(bpId, testFunc);
fDebugViewListener.reset();
private ICLineBreakpoint createLineBreakpoint(String file, int line) throws CoreException, ExecutionException, InterruptedException {
// Initiate wait for the context changed event.
final Object contextChangedWaitKey = new Object();
Protocol.invokeAndWait(new Runnable() { public void run() {
fBreakpointsCM.waitContextAdded(contextChangedWaitKey);
}});

startProcess();
fDebugViewListener.waitTillFinished(MODEL_CHANGED_COMPLETE | CONTENT_SEQUENCE_COMPLETE | LABEL_SEQUENCE_COMPLETE | LABEL_UPDATES);
}

public void testCreateBreakpoint() throws Exception {
String bpId = "TestStepBP";
initProcessModel(bpId, "tcf_test_func0");
final ICLineBreakpoint bp = CDIDebugModel.createLineBreakpoint(file, ResourcesPlugin.getWorkspace().getRoot(), ICBreakpointType.REGULAR, line, true, 0, "", true);

Map<String, Object>[] addedBps = new Transaction<Map<String, Object>[]>() {
protected Map<String, Object>[] process() throws InvalidCacheException ,ExecutionException {
return validate(fBreakpointsCM.waitContextAdded(contextChangedWaitKey));
}

}.get();

fBpListener.setTester(new EventTester() {
public boolean checkEvent(EventType type, IBreakpoint testBp, Map<String, Object> deltaAttributes) {
return (type == EventType.CHANGED && bp == testBp);
}
});

fBpListener.waitForEvent();

Assert.assertEquals(1, addedBps.length);
Assert.assertEquals(1, bp.getMarker().getAttribute(ICBreakpoint.INSTALL_COUNT, -1));

//CDIDebugModel.createFunctionBreakpoint();
return bp;
}

public void testContextAddedOnLineBrakpointCreate() throws Exception {
initProcessModel("tcf_test_func0");

checkBreakpointForErrors(bpId, fProcessId);
CodeArea bpCodeArea = getFunctionCodeArea("tcf_test_func0");
ICLineBreakpoint bp = createLineBreakpoint(bpCodeArea.file, bpCodeArea.start_line);
}

}

Large diffs are not rendered by default.

@@ -0,0 +1,255 @@
/*******************************************************************************
* Copyright (c) 2006, 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;

import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.eclipse.tcf.debug.test.util.Callback;
import org.eclipse.tcf.debug.test.util.Callback.ICanceledListener;
import org.eclipse.tcf.debug.test.util.DataCallback;
import org.eclipse.tcf.debug.test.util.Query;
import org.eclipse.tcf.protocol.Protocol;
import org.junit.Test;

/**
* Tests that exercise the Query object.
*/
public class QueryTests extends TestCase{

public void testSimpleGet() throws InterruptedException, ExecutionException {
Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(DataCallback<Integer> rm) {
rm.setData(1);
rm.done();
}
};
// Check initial state
Assert.assertTrue(!q.isDone());
Assert.assertTrue(!q.isCancelled());

q.invoke();
Assert.assertEquals(1, (int)q.get());

// Check final state
Assert.assertTrue(q.isDone());
Assert.assertTrue(!q.isCancelled());

}

public void testGetError() throws InterruptedException, ExecutionException {
final String error_message = "Test Error";

Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(DataCallback<Integer> rm) {
rm.setError(new Throwable(error_message)); //$NON-NLS-1$
rm.done();
}
};

// Check initial state
Assert.assertTrue(!q.isDone());
Assert.assertTrue(!q.isCancelled());

q.invoke();

try {
q.get();
Assert.fail("Expected exception");
} catch (ExecutionException e) {
Assert.assertEquals(e.getCause().getMessage(), error_message);
}

// Check final state
Assert.assertTrue(q.isDone());
Assert.assertTrue(!q.isCancelled());

}

public void testGetWithMultipleDispatches() throws InterruptedException, ExecutionException {
Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(final DataCallback<Integer> rm) {
Protocol.invokeLater(new Runnable() {
public void run() {
rm.setData(1);
rm.done();
}
@Override
public String toString() { return super.toString() + "\n getWithMultipleDispatchesTest() second runnable"; } //$NON-NLS-1$
});
}
@Override
public String toString() { return super.toString() + "\n getWithMultipleDispatchesTest() first runnable (query)"; } //$NON-NLS-1$
};
q.invoke();
Assert.assertEquals(1, (int)q.get());
}

public void testExceptionOnGet() throws InterruptedException, ExecutionException {
Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(final DataCallback<Integer> rm) {
rm.setError(new Throwable("")); //$NON-NLS-1$
rm.done();
}
};

q.invoke();

try {
q.get();
Assert.fail("Excpected ExecutionExeption");
} catch (ExecutionException e) {
} finally {
Assert.assertTrue(q.isDone());
Assert.assertTrue(!q.isCancelled());
}
}

public void testCancelBeforeWaiting() throws InterruptedException, ExecutionException {
final Query<Integer> q = new Query<Integer>() {
@Override protected void execute(final DataCallback<Integer> rm) {
Assert.fail("Query was cancelled, it should not be called."); //$NON-NLS-1$
rm.done();
}
};

// Cancel before invoking the query.
q.cancel(false);

Assert.assertTrue(q.isDone());
Assert.assertTrue(q.isCancelled());

// Start the query.
q.invoke();



// Block to retrieve data
try {
q.get();
} catch (CancellationException e) {
return; // Success
} finally {
Assert.assertTrue(q.isDone());
Assert.assertTrue(q.isCancelled());
}
Assert.assertTrue("CancellationException should have been thrown", false); //$NON-NLS-1$
}

public void testCancelWhileWaiting() throws InterruptedException, ExecutionException {
final DataCallback<?>[] rmHolder = new DataCallback<?>[1];
final Boolean[] cancelCalled = new Boolean[] { Boolean.FALSE };

final Query<Integer> q = new Query<Integer>() {
@Override protected void execute(final DataCallback<Integer> rm) {
synchronized (rmHolder) {
rmHolder[0] = rm;
rmHolder.notifyAll();
}
}
};

// Start the query.
q.invoke();

// Wait until the query is started
synchronized (rmHolder) {
while(rmHolder[0] == null) {
rmHolder.wait();
}
}

// Add a cancel listener to the query RM
rmHolder[0].addCancelListener(new ICanceledListener() {

public void requestCanceled(Callback rm) {
cancelCalled[0] = Boolean.TRUE;
}
});

// Cancel running request.
q.cancel(false);

Assert.assertTrue(cancelCalled[0]);
Assert.assertTrue(rmHolder[0].isCanceled());
Assert.assertTrue(q.isCancelled());
Assert.assertTrue(q.isDone());

// Retrieve data
try {
q.get();
} catch (CancellationException e) {
return; // Success
} finally {
Assert.assertTrue(q.isDone());
Assert.assertTrue(q.isCancelled());
}

// Complete rm and query.
@SuppressWarnings("unchecked")
DataCallback<Integer> drm = (DataCallback<Integer>)rmHolder[0];
drm.setData(new Integer(1));
rmHolder[0].done();

// Try to retrieve data again, it should still result in
// cancellation exception.
try {
q.get();
} catch (CancellationException e) {
return; // Success
} finally {
Assert.assertTrue(q.isDone());
Assert.assertTrue(q.isCancelled());
}


Assert.assertTrue("CancellationException should have been thrown", false); //$NON-NLS-1$
}


public void testGetTimeout() throws InterruptedException, ExecutionException {
final Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(final DataCallback<Integer> rm) {
// Call done with a delay of 1 second, to avoid stalling the tests.
Protocol.invokeLater(
60000,
new Runnable() {
public void run() { rm.done(); }
});
}
};

q.invoke();

// Note: no point in checking isDone() and isCancelled() here, because
// the value could change on timing.

try {
q.get(1, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
return; // Success
} finally {
Assert.assertFalse("Query should not be done yet, it should have timed out first.", q.isDone()); //$NON-NLS-1$
}
Assert.assertTrue("TimeoutException should have been thrown", false); //$NON-NLS-1$
}

}

Large diffs are not rendered by default.

@@ -0,0 +1,167 @@
/*******************************************************************************
* Copyright (c) 2006 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;

import java.util.Arrays;
import java.util.concurrent.ExecutionException;

import junit.framework.Assert;
import junit.framework.TestCase;

import org.eclipse.tcf.debug.test.util.CallbackCache;
import org.eclipse.tcf.debug.test.util.DataCallback;
import org.eclipse.tcf.debug.test.util.Query;
import org.eclipse.tcf.debug.test.util.Transaction;
import org.eclipse.tcf.protocol.Protocol;

/**
* Tests that exercise the Transaction object.
*/
public class TransactionTests extends TestCase {
final static private int NUM_CACHES = 5;

TestCache[] fTestCaches = new TestCache[NUM_CACHES];
DataCallback<?>[] fRetrieveRms = new DataCallback<?>[NUM_CACHES];

class TestCache extends CallbackCache<Integer> {

final private int fIndex;

public TestCache(int index) {
fIndex = index;
}

@Override
protected void retrieve(DataCallback<Integer> rm) {
synchronized(TransactionTests.this) {
fRetrieveRms[fIndex] = rm;
TransactionTests.this.notifyAll();
}
}

}

class TestSingleTransaction extends Transaction<Integer> {

@Override
protected Integer process() throws InvalidCacheException, ExecutionException {
validate(fTestCaches[0]);
return fTestCaches[0].getData();
}
}

class TestSumTransaction extends Transaction<Integer> {
@Override
protected Integer process() throws InvalidCacheException, ExecutionException {
validate(fTestCaches);

int sum = 0;
for (CallbackCache<Integer> cache : fTestCaches) {
sum += cache.getData();
}
return sum;
}
}

/**
* There's no rule on how quickly the cache has to start data retrieval
* after it has been requested. It could do it immediately, or it could
* wait a dispatch cycle, etc..
*/
private void waitForRetrieveRm(boolean all) {
synchronized(this) {
if (all) {
while (Arrays.asList(fRetrieveRms).contains(null)) {
try {
wait();
} catch (InterruptedException e) {
return;
}
}
} else {
while (fRetrieveRms[0] == null) {
try {
wait();
} catch (InterruptedException e) {
return;
}
}
}
}
}

public void setUp() throws ExecutionException, InterruptedException {
for (int i = 0; i < fTestCaches.length; i++) {
fTestCaches[i] = new TestCache(i);
}
}

public void tearDown() throws ExecutionException, InterruptedException {
fRetrieveRms = new DataCallback<?>[NUM_CACHES];
fTestCaches = new TestCache[NUM_CACHES];
}

public void testSingleTransaction() throws InterruptedException, ExecutionException {
final TestSingleTransaction testTransaction = new TestSingleTransaction();
// Request data from cache
Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(DataCallback<Integer> rm) {
testTransaction.request(rm);
}
};
q.invoke();

// Wait until the cache starts data retrieval.
waitForRetrieveRm(false);

// Set the data to caches.
Protocol.invokeAndWait(new Runnable() {
public void run() {
((DataCallback<Integer>)fRetrieveRms[0]).setData(1);
fRetrieveRms[0].done();
}
});

Assert.assertEquals(1, (int)q.get());
}

public void testSumTransaction() throws InterruptedException, ExecutionException {

final TestSumTransaction testTransaction = new TestSumTransaction();
// Request data from cache
Query<Integer> q = new Query<Integer>() {
@Override
protected void execute(DataCallback<Integer> rm) {
testTransaction.request(rm);
}
};
q.invoke();

// Wait until the cache starts data retrieval.
waitForRetrieveRm(true);


// Set the data to caches.
Protocol.invokeAndWait(new Runnable() {
public void run() {
for (DataCallback<?> rm : fRetrieveRms) {
((DataCallback<Integer>)rm).setData(1);
rm.done();
}
}
});

q.invoke();
Assert.assertEquals(NUM_CACHES, (int)q.get());
}

}
Expand Up @@ -20,6 +20,7 @@
import junit.framework.Assert;

import org.eclipse.debug.internal.ui.viewers.model.ILabelUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.ITreeModelViewer;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
Expand All @@ -29,7 +30,6 @@
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDeltaVisitor;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
import org.eclipse.jface.viewers.TreePath;
Expand Down
Expand Up @@ -287,8 +287,8 @@ public void contextAdded(Map<String, Object>[] bps) {

// TODO: avoid iterating over all entries, use separate list for events.
for (Map.Entry<Key<?>, ICache<?>> entry: fMap.entrySet()) {
if (entry.getKey() instanceof IdEventKey) {
IdEventKey<?> eventKey = (IdEventKey<?>)entry.getKey();
if (entry.getKey() instanceof EventKey) {
EventKey<?> eventKey = (EventKey<?>)entry.getKey();
if ( ContextAddedCache.class.equals( eventKey.getCacheClass() ) ) {
((ContextAddedCache)entry.getValue()).eventReceived(bps);
}
Expand All @@ -312,8 +312,8 @@ public void contextChanged(Map<String, Object>[] bps) {

// TODO: avoid iterating over all entries, use separate list for events.
for (Map.Entry<Key<?>, ICache<?>> entry: fMap.entrySet()) {
if (entry.getKey() instanceof IdEventKey) {
IdEventKey<?> eventKey = (IdEventKey<?>)entry.getKey();
if (entry.getKey() instanceof EventKey) {
EventKey<?> eventKey = (EventKey<?>)entry.getKey();
if ( ContextChangedCache.class.equals( eventKey.getCacheClass() ) ) {
((ContextChangedCache)entry.getValue()).eventReceived(bps);
}
Expand Down Expand Up @@ -342,6 +342,16 @@ public void contextRemoved(String[] ids) {
PropertiesCache cache = mapCache(new PropertiesCacheKey(id));
cache.resetProperties();
}

// TODO: avoid iterating over all entries, use separate list for events.
for (Map.Entry<Key<?>, ICache<?>> entry: fMap.entrySet()) {
if (entry.getKey() instanceof EventKey) {
EventKey<?> eventKey = (EventKey<?>)entry.getKey();
if ( ContextRemovedCache.class.equals( eventKey.getCacheClass() ) ) {
((ContextRemovedCache)entry.getValue()).eventReceived(ids);
}
}
}
}


Expand Down
@@ -0,0 +1,232 @@
/*******************************************************************************sbsb
* Copyright (c) 2012 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.services;

import java.util.Map;
import java.util.concurrent.ExecutionException;

import org.eclipse.tcf.debug.test.util.CallbackCache;
import org.eclipse.tcf.debug.test.util.DataCallback;
import org.eclipse.tcf.debug.test.util.ICache;
import org.eclipse.tcf.debug.test.util.TokenCache;
import org.eclipse.tcf.debug.test.util.Transaction;
import org.eclipse.tcf.debug.test.util.TransactionCache;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.services.ILineNumbers;
import org.eclipse.tcf.services.ILineNumbers.CodeArea;
import org.eclipse.tcf.services.IMemoryMap;
import org.eclipse.tcf.services.IMemoryMap.MemoryMapListener;
import org.eclipse.tcf.services.IRunControl.RunControlContext;
import org.eclipse.tcf.services.IRunControl.RunControlListener;

/**
*
*/
public class LineNumbersCM extends AbstractCacheManager {

private ResetMap fMemContextResetMap = new ResetMap();
private ILineNumbers fService;
private IMemoryMap fMemoryMap;
private RunControlCM fRunControlCM;

public LineNumbersCM(ILineNumbers lineNumbers, IMemoryMap memMap, RunControlCM runControlCM) {
fService = lineNumbers;
fMemoryMap = memMap;
fMemoryMap.addListener(fMemoryMapListener);
fRunControlCM = runControlCM;
fRunControlCM.addListener(fRunControlListener);
}

@Override
public void dispose() {
fRunControlCM.removeListener(fRunControlListener);
fMemoryMap.removeListener(fMemoryMapListener);
super.dispose();
}

private abstract class LineNumbersTokenCache<V> extends TokenCache<V> {
abstract protected String getId();

protected void set(IToken token, V data, Throwable error) {
fMemContextResetMap.addValid(getId(), this);
super.set(token, data, error);
}
}

abstract private class MapToSourceKey<V> extends IdKey<V> {
private final Number fStartAdddress;
private final Number fEndAddress;

public MapToSourceKey(Class<V> cacheClass, String id, Number startAddress, Number endAddress) {
super(cacheClass, id);
fStartAdddress = startAddress;
fEndAddress = endAddress;
}

@Override
public boolean equals(Object obj) {
if (super.equals(obj) && obj instanceof MapToSourceKey<?>) {
MapToSourceKey<?> other = (MapToSourceKey<?>)obj;
return fStartAdddress.equals(other.fStartAdddress) && fEndAddress.equals(other.fEndAddress);
}
return false;
}
@Override
public int hashCode() {
return super.hashCode() + fStartAdddress.hashCode() + fEndAddress.hashCode();
}
}

public ICache<CodeArea[]> mapToSource(final String context_id, final Number start_address, final Number end_address) {
class MyCache extends TransactionCache<ILineNumbers.CodeArea[]> {
private String fId = context_id;

@Override
protected CodeArea[] process() throws InvalidCacheException, ExecutionException {
RunControlContext rcContext = validate(fRunControlCM.getContext(fId));
String mem_id = rcContext.getProcessID();
if (mem_id == null) {
// TODO: is this the correct fall-back. Should we save the parent ID for reset?
mem_id = fId;
}
return validate( doMapToSource(mem_id, start_address, end_address) );
}
}

return mapCache(new MapToSourceKey<MyCache>(MyCache.class, context_id, start_address, end_address) {
@Override MyCache createCache() { return new MyCache(); }
});
}

private ICache<CodeArea[]> doMapToSource(final String mem_id, final Number start_address, final Number end_address) {
class MyCache extends LineNumbersTokenCache<CodeArea[]> implements ILineNumbers.DoneMapToSource {
@Override
protected String getId() {
return mem_id;
}
@Override
protected IToken retrieveToken() {
return fService.mapToSource(mem_id, start_address, end_address, this);
}

public void doneMapToSource(IToken token, Exception error, CodeArea[] areas) {
set(token, areas, error);
}

};

return mapCache(new MapToSourceKey<MyCache>(MyCache.class, mem_id, start_address, end_address) {
@Override MyCache createCache() { return new MyCache(); }
});

}

abstract private class MapToMemoryKey<V> extends IdKey<V> {
private final String fFile;
private final int fLine;
private final int fColumn;

public MapToMemoryKey(Class<V> cacheClass, String id, String file, int line, int col) {
super(cacheClass, id);
fFile = file;
fLine = line;
fColumn = col;
}

@Override
public boolean equals(Object obj) {
if (super.equals(obj) && obj instanceof MapToMemoryKey<?>) {
MapToMemoryKey<?> other = (MapToMemoryKey<?>)obj;
return fFile.equals(other.fFile) && fLine == other.fLine && fColumn == other.fColumn;
}
return false;
}
@Override
public int hashCode() {
return super.hashCode() + fFile.hashCode()^fLine^(fColumn + 1);
}
}

public ICache<CodeArea[]> mapToMemory(final String context_id, final String file, final int line, final int column) {
class MyCache extends TransactionCache<ILineNumbers.CodeArea[]> {
private String fId = context_id;

protected CodeArea[] process() throws InvalidCacheException, ExecutionException {
RunControlContext rcContext = validate(fRunControlCM.getContext(fId));
String mem_id = rcContext.getProcessID();
if (mem_id == null) {
// TODO: is this the correct fall-back. Should we save the parent ID for reset?
mem_id = fId;
}
return validate( doMapToMemory(mem_id, file, line, column) );
}
}

return mapCache(new MapToMemoryKey<MyCache>(MyCache.class, context_id, file, line, column) {
@Override MyCache createCache() { return new MyCache(); }
});
}

private ICache<CodeArea[]> doMapToMemory(final String mem_id, final String file, final int line, final int column) {
class MyCache extends LineNumbersTokenCache<CodeArea[]> implements ILineNumbers.DoneMapToMemory {
@Override
protected String getId() {
return mem_id;
}
@Override
protected IToken retrieveToken() {
return fService.mapToMemory(mem_id, file, line, column, this);
}
public void doneMapToMemory(IToken token, Exception error, CodeArea[] areas) {
set(token, areas, error);
}

};

return mapCache(new MapToMemoryKey<MyCache>(MyCache.class, mem_id, file, line, column) {
@Override MyCache createCache() { return new MyCache(); }
});

}

interface DoneMapToMemory {
void doneMapToMemory(IToken token, Exception error, CodeArea[] areas);
}

private RunControlListener fRunControlListener = new RunControlListener() {

public void contextRemoved(String[] context_ids) {
for (String id : context_ids) {
resetContext(id);
}
}

public void contextAdded(RunControlContext[] contexts) {}
public void contextChanged(RunControlContext[] contexts) {}
public void contextSuspended(String context, String pc, String reason, Map<String, Object> params) {}
public void contextResumed(String context) {}
public void containerSuspended(String context, String pc, String reason, Map<String, Object> params,
String[] suspended_ids) {}
public void containerResumed(String[] context_ids) {}
public void contextException(String context, String msg) {}
};

private void resetContext(String id) {
fMemContextResetMap.reset(id);
}

private MemoryMapListener fMemoryMapListener = new MemoryMapListener() {
public void changed(String context_id) {
resetContext(context_id);
}
};

}
Expand Up @@ -90,14 +90,14 @@ public boolean isValid() {
}

public V getData() {
if (!fValid) {
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 (!fValid) {
if (!isValid()) {
throw new IllegalStateException("Cache is not valid. Cache status can be read only when cache is valid."); //$NON-NLS-1$
}
return fError;
Expand All @@ -106,42 +106,37 @@ public Throwable getError() {
public void update(Callback rm) {
assert Protocol.isDispatchThread();

if (!fValid) {
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;
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;
}
} else {
Callback[] newWaitingList = new Callback[2];
newWaitingList[0] = (Callback)fWaitingList;
newWaitingList[1] = rm;
}
if (i == waitingListLength) {
Callback[] newWaitingList = new Callback[waitingListLength + 1];
System.arraycopy(waitingList, 0, newWaitingList, 0, waitingListLength);
newWaitingList[waitingListLength] = rm;
fWaitingList = newWaitingList;
}
}
rm.addCancelListener(fRequestCanceledListener);
if (first) {
retrieve();
} else {
Callback[] newWaitingList = new Callback[2];
newWaitingList[0] = (Callback)fWaitingList;
newWaitingList[1] = rm;
fWaitingList = newWaitingList;
}
} else {
rm.setError(fError);
rm.done();
}
rm.addCancelListener(fRequestCanceledListener);
if (first && !isValid()) {
retrieve();
}
}

Expand All @@ -167,7 +162,12 @@ private void completeWaitingRms() {
}

private void completeWaitingRm(Callback rm) {
rm.setError(fError);
if (!isValid() && fError == null) {
rm.setError(INVALID_STATUS);
} else {
rm.setError(fError);
}

rm.removeCancelListener(fRequestCanceledListener);
rm.done();
}
Expand Down
Expand Up @@ -39,15 +39,19 @@ public interface ICache<V> {
public Throwable getError();

/**
* Asks the cache to update its value from the source. If the cache is
* already valid, the request is completed immediately, otherwise data will
* first be retrieved from the source. Typically, this method is called by a
* client after it discovers the cache is invalid via {@link #isValid()}
* Asks the cache to update its value from the source. Typically, this
* method is called by a client after it discovers the cache is invalid
* via {@link #isValid()}.
*
* @param rm
* RequestMonitor that is called when cache becomes valid.
* <p>If the cache is already valid, the cache is not updated again from
* source. Instead the callback is completed next time the cache state is
* changed. Clients can use this feature to be notified when the cache is
* being reset.
* </p>
* @param cb
* Callback that is called when cache becomes valid.
*/
public void update(Callback rm);
public void update(Callback cb);

/**
* Returns <code>true</code> if the cache is currently valid. I.e.
Expand Down
Expand Up @@ -17,6 +17,7 @@
import java.util.concurrent.TimeoutException;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.tcf.protocol.Protocol;

/**
* @since 2.2
Expand Down Expand Up @@ -62,6 +63,36 @@ public void request(DataCallback<V> rm) {
execute();
}

protected void preProcess() {}

protected void postProcess(boolean done, V data, Throwable error) {}

protected boolean processUnchecked() {
try {
// Execute the transaction logic
V data = process();

// No exception means all cache objects used by the transaction
// were valid and up to date. Complete the request
setData(data);
return true;
}
catch (InvalidCacheException e) {
// At least one of the cache objects was stale/unset. Keep the
// request monitor in the incomplete state, thus leaving our client
// "waiting" (asynchronously). We'll get called again once the cache
// objects are updated, thus re-starting the whole transaction
// attempt.
return false;
}
catch (Throwable e) {
// At least one of the cache objects encountered a failure obtaining
// the data from the source. Complete the request.
setError(e);
return true;
}
}

/**
* The transaction logic--code that tries to synchronously make use of,
* usually, multiple data points that are normally obtained asynchronously.
Expand All @@ -83,6 +114,24 @@ public void request(DataCallback<V> rm) {
*/
abstract protected V process() throws InvalidCacheException, ExecutionException;

/**
* Can be called only while in process().
* @param data
*/
protected void setData(V data) {
assert Protocol.isDispatchThread();
fRm.setData(data);
}

/**
* Can be called only while in process().
* @param data
*/
protected void setError(Throwable error) {
assert Protocol.isDispatchThread();
fRm.setError(error);
}

/**
* Method which invokes the transaction logic and handles any exception that
* may result. If that logic encounters a stale/unset cache object, then we
Expand All @@ -95,32 +144,15 @@ private void execute() {
fRm = null;
return;
}

try {
// Execute the transaction logic
V data = process();

// No exception means all cache objects used by the transaction
// were valid and up to date. Complete the request
fRm.setData(data);
fRm.done();
fRm = null;
}
catch (InvalidCacheException e) {
// At least one of the cache objects was stale/unset. Keep the
// request monitor in the incomplete state, thus leaving our client
// "waiting" (asynchronously). We'll get called again once the cache
// objects are updated, thus re-starting the whole transaction
// attempt.
}
catch (Throwable e) {
// At least one of the cache objects encountered a failure obtaining
// the data from the source. Complete the request.
fRm.setError(e);

preProcess();
if (processUnchecked()) {
postProcess(true, fRm.getData(), fRm.getError());
fRm.done();
fRm = null;
} else {
postProcess(false, null, null);
}

}

/**
Expand Down
@@ -0,0 +1,111 @@
/*******************************************************************************
* Copyright (c) 2012 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.List;
import java.util.concurrent.ExecutionException;


/**
*
*/
public abstract class TransactionCache<V> extends Transaction<V> implements ICache<V> {

private List<ICache<?>> fDependsOn;
private List<Callback> fDependsOnCallbacks;

private CallbackCache<V> fCache = new CallbackCache<V>() {
@Override
protected void retrieve(DataCallback<V> rm) {
request(rm);
}
};

public V getData() {
return fCache.getData();
}

public Throwable getError() {
return fCache.getError();
}

public void update(Callback rm) {
fCache.update(rm);
}

public boolean isValid() {
return fCache.isValid();
}

@Override
protected void preProcess() {
super.preProcess();

if (fDependsOnCallbacks != null) {
for (Callback cb : fDependsOnCallbacks) {
cb.cancel();
}
fDependsOnCallbacks = null;
}
fDependsOn = new ArrayList<ICache<?>>(4);
}

protected void postProcess(boolean done, V data, Throwable error) {
if (done) {
fDependsOnCallbacks = new ArrayList<Callback>(fDependsOn.size());
for (ICache<?> cache : fDependsOn) {
assert cache.isValid();
cache.update(new Callback() {
@Override
protected void handleCompleted() {
if (!isCancelled()) {
fCache.reset();
for (Callback cb : fDependsOnCallbacks) {
if (cb == this) continue;
cb.cancel();
}
}
}
});
}
} else {
fDependsOn = null;
}
super.postProcess(done, data, error);
}

/**
* Can be called while in {@link #process()}
* @param cache
*/
public void addDependsOn(ICache cache) {
fDependsOn.add(cache);
}

public <T> T validate(ICache<T> cache) throws InvalidCacheException, ExecutionException {
if (cache.isValid()) {
addDependsOn(cache);
}
return super.validate(cache);
}

@Override
public void validate(Iterable caches) throws InvalidCacheException, ExecutionException {
for (Object cacheObj : caches) {
ICache<?> cache = (ICache<?>)cacheObj;
if (cache.isValid()) {
addDependsOn(cache);
}
}
super.validate(caches);
}
}