/******************************************************************************* * Copyright (c) 2007, 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.internal.debug.ui.model; import java.util.LinkedList; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.core.runtime.Status; 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.IElementCompareRequest; import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementMementoRequest; import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate; import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta; import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext; import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerInputUpdate; import org.eclipse.swt.graphics.FontData; import org.eclipse.tcf.debug.ui.ITCFObject; import org.eclipse.tcf.internal.debug.model.TCFLaunch; import org.eclipse.tcf.protocol.IChannel; import org.eclipse.tcf.protocol.Protocol; import org.eclipse.tcf.util.TCFDataCache; import org.eclipse.ui.IWorkbenchPart; /** * TCFNode is base class for all TCF debug model elements. */ public abstract class TCFNode extends PlatformObject implements ITCFObject, Comparable<TCFNode> { protected final String id; protected final TCFNode parent; protected final TCFModel model; protected final TCFLaunch launch; protected final IChannel channel; private boolean disposed; /** * An extension of TCFDataCache class that is automatically disposed when the node is disposed. */ protected abstract class TCFData<V> extends TCFDataCache<V> { TCFData(IChannel channel) { super(channel); addDataCache(this); } @Override public void dispose() { removeDataCache(this); super.dispose(); } } private LinkedList<TCFDataCache<?>> caches; /** * Constructor for a root node. There should be exactly one root in the model. * @param model */ protected TCFNode(TCFModel model) { id = null; parent = null; launch = model.getLaunch(); channel = model.getChannel(); this.model = model; } /** * Constructor for a node other then root. Node ID must be unique. * @param parent - parent node. * @param id - node ID. */ protected TCFNode(TCFNode parent, String id) { assert Protocol.isDispatchThread(); assert parent != null; assert id != null; assert !parent.disposed; this.parent = parent; this.id = id; model = parent.model; model.addNode(id, this); launch = model.getLaunch(); channel = model.getChannel(); } /** * Register a data cache object that caches data for this node. * @param c - a TCFData object. */ final void addDataCache(TCFDataCache<?> c) { if (caches == null) caches = new LinkedList<TCFDataCache<?>>(); caches.add(c); } /** * Unregister a data cache object that caches children for this node. * @param c - a TCFData object. */ final void removeDataCache(TCFDataCache<?> c) { if (caches != null) caches.remove(c); } /** * Flush (reset) all node data caches. */ void flushAllCaches() { if (caches == null) return; for (TCFDataCache<?> c : caches) c.reset(); } /** * Dispose this node and its children. The node is removed from the model. */ void dispose() { assert !disposed; while (caches != null && caches.size() > 0) { caches.getLast().dispose(); } if (parent != null && parent.caches != null) { for (TCFDataCache<?> c : parent.caches) { if (c instanceof TCFChildren) ((TCFChildren)c).onNodeDisposed(id); } } if (id != null) { assert model.getNode(id) == this; model.removeNode(id); } disposed = true; } /** * Check if node is disposed. * @return true if disposed. */ public final boolean isDisposed() { return disposed; } /** * Get TCFModel that owns this node. * @return TCFModel object */ public TCFModel getModel() { return model; } /** * Get IChannel of TCFModel that owns this node. * @return IChannel object */ public IChannel getChannel() { return channel; } /** * Get TCF ID of the node. * @return TCF ID */ public String getID() { return id; } /** * Returns an object which is an instance of the given class * associated with this object. Returns <code>null</code> if * no such object can be found. * * @param adapter the class to adapt to * @return the adapted object or <code>null</code> * @see IAdaptable#getAdapter(Class) */ @Override @SuppressWarnings("rawtypes") public Object getAdapter(final Class adapter) { if (adapter.isInstance(this)) return this; if (adapter.isInstance(model)) return model; Object o = model.getAdapter(adapter, TCFNode.this); if (o != null) return o; return Platform.getAdapterManager().loadAdapter(this, adapter.getName()); } /** * Get parent node. * @return parent node or null if the node is a root */ public final TCFNode getParent() { return parent; } /** * Get parent node in given presentation context. * @param ctx - presentation context. * @return parent node or null if the node is a root */ public TCFNode getParent(IPresentationContext ctx) { assert Protocol.isDispatchThread(); return parent; } /** * Retrieve children count for a presentation context. * @param update - children count update request. */ final void update(final IChildrenCountUpdate update) { new TCFRunnable(model, update) { public void run() { if (!done) { if (!update.isCanceled()) { if (!disposed && channel.getState() == IChannel.STATE_OPEN) { if (!getData(update, this)) return; } else { update.setChildCount(0); } update.setStatus(Status.OK_STATUS); } done(); } } }; } /** * Retrieve children for a presentation context. * @param update - children update request. */ final void update(final IChildrenUpdate update) { new TCFRunnable(model, update) { public void run() { if (!done) { if (!update.isCanceled()) { if (!disposed && channel.getState() == IChannel.STATE_OPEN) { if (!getLockedData(update, this)) return; } update.setStatus(Status.OK_STATUS); } done(); } } }; } /** * Check if the node has children in a presentation context. * @param update - "has children" update request. */ final void update(final IHasChildrenUpdate update) { new TCFRunnable(model, update) { public void run() { if (!done) { if (!update.isCanceled()) { if (!disposed && channel.getState() == IChannel.STATE_OPEN) { if (!getLockedData(update, this)) return; } else { update.setHasChilren(false); } update.setStatus(Status.OK_STATUS); } done(); } } }; } /** * Retrieve node label for a presentation context. * @param update - label update request. */ final void update(final ILabelUpdate update) { new TCFRunnable(model, update) { public void run() { if (!done) { if (!update.isCanceled()) { if (!disposed && channel.getState() == IChannel.STATE_OPEN) { if (!getLockedData(update, this)) return; getFontData(update, update.getPresentationContext().getId()); } else { update.setLabel("...", 0); } update.setStatus(Status.OK_STATUS); } done(); } } }; } /** * Retrieve viewer input object for a presentation context. * Allows a view to translate the active debug context into an appropriate viewer input element. * @param update - input update request. */ final void update(final IViewerInputUpdate update) { new TCFRunnable(model, update) { public void run() { if (!done) { if (!update.isCanceled()) { if (!disposed && channel.getState() == IChannel.STATE_OPEN) { if (!getData(update, this)) return; } else { update.setInputElement(TCFNode.this); } update.setStatus(Status.OK_STATUS); } done(); } } }; } /** * Retrieve children count for a presentation context. * If the context is locked, return snapshot data. * Otherwise return live data from the target. * The method is always called on TCF dispatch thread. * @param update - children count update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ final boolean getLockedData(IChildrenCountUpdate update, Runnable done) { TCFSnapshot snapshot = model.getSnapshot(update.getPresentationContext()); if (snapshot != null) return snapshot.getData(update, this, done); return getData(update, done); } /** * Retrieve children for a presentation context. * If the context is locked, return snapshot data. * Otherwise return live data from the target. * The method is always called on TCF dispatch thread. * @param update - children update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ final boolean getLockedData(IChildrenUpdate update, Runnable done) { TCFSnapshot snapshot = model.getSnapshot(update.getPresentationContext()); if (snapshot != null) return snapshot.getData(update, this, done); return getData(update, done); } /** * Check if the node has children in a presentation context. * If the context is locked, return snapshot data. * Otherwise return live data from the target. * The method is always called on TCF dispatch thread. * @param update - "has children" update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ final boolean getLockedData(IHasChildrenUpdate update, Runnable done) { TCFSnapshot snapshot = model.getSnapshot(update.getPresentationContext()); if (snapshot != null) return snapshot.getData(update, this, done); return getData(update, done); } /** * Retrieve node label for a presentation context. * If the context is locked, return snapshot data. * Otherwise return live data from the target. * The method is always called on TCF dispatch thread. * @param update - label update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ final boolean getLockedData(ILabelUpdate update, Runnable done) { TCFSnapshot snapshot = model.getSnapshot(update.getPresentationContext()); if (snapshot != null) return snapshot.getData(update, this, done); return getData(update, done); } /** * Retrieve children count for a presentation context. * The method is always called on TCF dispatch thread. * @param update - children count update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ protected boolean getData(IChildrenCountUpdate update, Runnable done) { update.setChildCount(0); return true; } /** * Retrieve children for a presentation context. * The method is always called on TCF dispatch thread. * @param update - children update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ protected boolean getData(IChildrenUpdate update, Runnable done) { return true; } /** * Check if the node has children in a presentation context. * The method is always called on TCF dispatch thread. * @param update - "has children" update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ protected boolean getData(IHasChildrenUpdate update, Runnable done) { update.setHasChilren(false); return true; } /** * Retrieve node label for a presentation context. * The method is always called on TCF dispatch thread. * @param update - label update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ protected boolean getData(ILabelUpdate update, Runnable done) { update.setLabel(id, 0); return true; } /** * Retrieve viewer input object for a presentation context. * Allows a view to translate the active debug context into an appropriate viewer input element. * The method is always called on TCF dispatch thread. * @param update - view input update request. * @param done - client call back interface, during data waiting it is * called every time new portion of data becomes available. * @return false if waiting data retrieval, true if all done. */ protected boolean getData(IViewerInputUpdate update, Runnable done) { update.setInputElement(this); return true; } /** * Get FontData info for node label update. * @param update - label update request. */ protected void getFontData(ILabelUpdate update, String view_id) { FontData fd = TCFModelFonts.getNormalFontData(view_id); String[] cols = update.getColumnIds(); if (cols == null || cols.length == 0) { update.setFontData(fd, 0); } else { for (int i = 0; i < cols.length; i++) update.setFontData(fd, i); } } /** * Creates and stores a IMemento for the node. * A request should be cancelled if a IMemento is not supported. * @param request Specifies IMemento. */ public void encodeElement(IElementMementoRequest request) { request.getMemento().putString("TCF.ID", id); } /** * Determines whether a IMemento represents this node. * @param request Specifies previously created IMemento. */ public void compareElements(IElementCompareRequest request) { request.setEqual(id.equals(request.getMemento().getString("TCF.ID"))); } /*--------------------------------------------------------------------------------------*/ /* Misc */ /** * Flush all caches and repaint the node and its children in presentation context of given part. * @param part - workbench part that needs to be refreshed. */ public void refresh(IWorkbenchPart part) { model.flushAllCaches(); for (TCFModelProxy p : model.getModelProxies()) { if (p.getPresentationContext().getPart() != part) continue; p.addDelta(this, IModelDelta.STATE | IModelDelta.CONTENT); } } /** * Compare two nodes. * Extensions of TCFNode are expected to override this method. */ public int compareTo(TCFNode n) { return id.compareTo(n.id); } /** * Returns a simple human readable string representation of the node. */ public String toString() { String s = "[" + Integer.toHexString(hashCode()) + "] " + id; if (disposed) s += ", disposed"; return s; } }