/*******************************************************************************
 * Copyright (c) 2004, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.debug.ui.memory;

import java.math.BigInteger;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IMemoryBlock;
import org.eclipse.debug.core.model.IMemoryBlockExtension;
import org.eclipse.debug.core.model.MemoryByte;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
import org.eclipse.debug.internal.ui.DebugUIMessages;
import org.eclipse.debug.internal.ui.DebugUIPlugin;
import org.eclipse.debug.internal.ui.IInternalDebugUIConstants;
import org.eclipse.debug.internal.ui.memory.IMemoryBlockConnection;
import org.eclipse.debug.internal.ui.memory.IPersistableDebugElement;
import org.eclipse.debug.internal.ui.preferences.IDebugPreferenceConstants;
import org.eclipse.debug.internal.ui.views.memory.MemoryViewUtil;
import org.eclipse.debug.internal.ui.views.memory.renderings.AbstractBaseTableRendering;
import org.eclipse.debug.internal.ui.views.memory.renderings.CopyTableRenderingToClipboardAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.FormatTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.FormatTableRenderingDialog;
import org.eclipse.debug.internal.ui.views.memory.renderings.GoToAddressAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.PrintTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.ReformatAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.ResetToBaseAddressAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingCellModifier;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingContentInput;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingContentProvider;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingLabelProvider;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingLabelProviderEx;
import org.eclipse.debug.internal.ui.views.memory.renderings.TableRenderingLine;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.IDebugUIConstants;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.IBasicPropertyConstants;
import org.eclipse.jface.viewers.ICellModifier;
import org.eclipse.jface.viewers.IColorProvider;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.TableCursor;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.FocusAdapter;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.TraverseEvent;
import org.eclipse.swt.events.TraverseListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.PropertyDialogAction;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.part.PageBook;

/**
 * Abstract implementation of a table rendering.
 * <p>
 * Clients should subclass from this class if they wish to provide a
 * table rendering.
 * </p>
 * <p>
 *
 * The label of the rendering is constructed by retrieving the expression from
 * <code>IMemoryBlockExtension</code>.  For IMemoryBlock, the label is constructed
 * using the memory block's start address.
 *
 * This rendering manages the change states of its memory bytes if the memory
 * block does not opt to manage the change states.  For IMemoryBlockExtension, if
 * the memory block returns false when #supportsChangeManagement() is called, this
 * rendering will calculate the change state for each byte when its content is updated.
 * Clients may manages the change states of its memory block by returning true when
 * #supportsChangeManagement() is called.  This will cause this rendering to stop
 * calculating the change states of the memory block.  Instead it would rely on the
 * attributes returned in the MemoryByte array to determine if a byte has changed.
 * For IMemoryBlock, this rendering will manage the change states its content.
 *
 *  When firing change event, be aware of the following:
 *  - whenever a change event is fired, the content provider for Memory View
 *    view checks to see if memory has actually changed.
 *  - If memory has actually changed, a refresh will commence.  Changes to the memory block
 *    will be computed and will be shown with the delta icons.
 *  - If memory has not changed, content will not be refreshed.  However, previous delta information
 * 	  will be erased.  The screen will be refreshed to show that no memory has been changed.  (All
 *    delta icons will be removed.)
 *
 * Please note that these APIs will be called multiple times by the Memory View.
 * To improve performance, debug adapters need to cache the content of its memory block and only
 * retrieve updated data when necessary.
 * </p>

 * @since 3.1
 */
public abstract class AbstractTableRendering extends AbstractBaseTableRendering implements IPropertyChangeListener, IResettableMemoryRendering{

	/**
	 *  Property identifier for the selected address in a table rendering
	 *  This property is used for synchronization between renderings.
	 */
	public static final String PROPERTY_SELECTED_ADDRESS = "selectedAddress"; //$NON-NLS-1$

	/**
	 * Property identifier for the column size in a table rendering
	 * This property is used for synchronization between renderings.
	 */
	public static final String PROPERTY_COL_SIZE = "columnSize"; //$NON-NLS-1$

	/**
	 * Property identifier for the top row address in a table rendering.
	 * This property is used for synchronization between renderings.
	 */
	public static final String PROPERTY_TOP_ADDRESS = "topAddress"; //$NON-NLS-1$

	/**
	 * Property identifier for the row size in a table rendering
	 * This property is used for synchronization between renderings.
	 * @since 3.2
	 */
	public static final String PROPERTY_ROW_SIZE = "rowSize"; //$NON-NLS-1$

	private static final int BUFFER_THRESHOLD = 1;			// threshold value
	private static final int BUFFER_START = 0;				// flag to indicate asking for threshold at buffer start
	private static final int BUFFER_END = 1;				// flat to indicate asking for threshold at buffer end

	private PageBook fPageBook;
	private TableViewer fTableViewer;
	private TextViewer fTextViewer;

	private int fBytePerLine;								// number of bytes per line: 16
	private int fColumnSize;								// number of bytes per column:  1,2,4,8
	private int fAddressableSize;

	private boolean fIsShowingErrorPage;

	private TableRenderingContentProvider fContentProvider;
	private BigInteger fSelectedAddress;
	private TableRenderingContentInput fContentInput;
	private TableRenderingCellModifier fCellModifier;
	private boolean fIsCreated;
	private CellEditor[] fEditors;
	private String fLabel;
	private TableCursor fTableCursor;
	private boolean fIsDisposed;
	private TraverseListener fCursorTraverseListener;
	private KeyAdapter fCursorKeyAdapter;
	private BigInteger fTopRowAddress;

	private CopyTableRenderingToClipboardAction fCopyToClipboardAction;
	private GoToAddressAction fGoToAddressAction;
	private ResetToBaseAddressAction fResetMemoryBlockAction;
	private PrintTableRenderingAction fPrintViewTabAction;
	private ReformatAction fReformatAction;
	private ToggleAddressColumnAction fToggleAddressColumnAction;
	private EventHandleLock fEvtHandleLock = new EventHandleLock();
	private TableEditor fCursorEditor;
	private FocusAdapter fEditorFocusListener;
	private MouseAdapter fCursorMouseListener;
	private KeyAdapter fEditorKeyListener;
	private SelectionAdapter fCursorSelectionListener;
	private IWorkbenchAdapter fWorkbenchAdapter;
	private IMemoryBlockConnection fConnection;

	private boolean fIsShowAddressColumn = true;
	private SelectionAdapter fScrollbarSelectionListener;

	private PropertyDialogAction fPropertiesAction;

	private int fPageSize;
	private NextPageAction fNextAction;
	private PrevPageAction fPrevAction;

	private Shell fToolTipShell;
	private FormatTableRenderingAction fFormatRenderingAction;

	private IMenuListener fMenuListener;

	private int fPreBuffer;
	private int fPostBuffer;

	private class EventHandleLock
	{
		Object fOwner;

		public boolean acquireLock(Object client)
		{
			if (fOwner == null)
			{
				fOwner = client;
				return true;
			}
			return false;
		}

		public boolean releaseLock(Object client)
		{
			if (fOwner == client)
			{
				fOwner = null;
				return true;
			}
			return false;
		}

	}


	private class ToggleAddressColumnAction extends Action {

		public ToggleAddressColumnAction() {
			super();
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IDebugUIConstants.PLUGIN_ID
					+ ".ShowAddressColumnAction_context"); //$NON-NLS-1$
			updateActionLabel();
		}

		/*
		 * (non-Javadoc)
		 *
		 * @see org.eclipse.jface.action.IAction#run()
		 */
		@Override
		public void run() {
			fIsShowAddressColumn = !fIsShowAddressColumn;
			resizeColumnsToPreferredSize();
			updateActionLabel();
		}

		/**
		 *
		 */
		private void updateActionLabel() {
			if (fIsShowAddressColumn) {
				setText(DebugUIMessages.ShowAddressColumnAction_0);
			} else {
				setText(DebugUIMessages.ShowAddressColumnAction_1);
			}
		}
	}


	private class NextPageAction extends Action
	{
		private NextPageAction()
		{
			super();
			setText(DebugUIMessages.AbstractTableRendering_4);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IDebugUIConstants.PLUGIN_ID + ".NextPageAction_context"); //$NON-NLS-1$
		}

		@Override
		public void run() {
			BigInteger address = fContentInput.getLoadAddress();
			address = address.add(BigInteger.valueOf(getPageSizeInUnits()));
			handlePageStartAddressChanged(address);
		}
	}

	private class PrevPageAction extends Action
	{
		private PrevPageAction()
		{
			super();
			setText(DebugUIMessages.AbstractTableRendering_6);
			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IDebugUIConstants.PLUGIN_ID + ".PrevPageAction_context"); //$NON-NLS-1$
		}

		@Override
		public void run() {
			BigInteger address = fContentInput.getLoadAddress();
			address = address.subtract(BigInteger.valueOf(getPageSizeInUnits()));
			handlePageStartAddressChanged(address);
		}
	}

	/**
	 * Constructs a new table rendering of the specified type.
	 *
	 * @param renderingId memory rendering type identifier
	 */
	public AbstractTableRendering(String renderingId) {
		super(renderingId);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
	 */
	@Override
	public void propertyChange(PropertyChangeEvent event) {
		// if memory view table font has changed
		if (event.getProperty().equals(IInternalDebugUIConstants.FONT_NAME))
		{
			if (!fIsDisposed)
			{
				Font memoryViewFont = JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME);
				setFont(memoryViewFont);
			}
			return;
		}

		if (event.getProperty().equals(IDebugUIConstants.PREF_PADDED_STR) ||
			event.getProperty().equals(IDebugUIConstants.PREF_MEMORY_HISTORY_KNOWN_COLOR) ||
			event.getProperty().equals(IDebugUIConstants.PREF_MEMORY_HISTORY_UNKNOWN_COLOR))
		{
			if (!fIsDisposed)
			{
				fTableViewer.refresh();
				fTableCursor.redraw();
			}
			return;
		}

		Object evtSrc = event.getSource();

		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE) ||
			event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE) ||
			event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE)) {
			// always update page size, only refresh if the table is visible
			getPageSizeFromPreference();
		}

		// do not handle event if the rendering is displaying an error
		if (isDisplayingError()) {
			return;
		}

		// do not handle property change event if the rendering is not visible
		if (!isVisible()) {
			return;
		}

		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM)) {
			handleDyanicLoadChanged();
			return;
		}

		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE)) {
			if (!isDynamicLoad())
			{
				// only refresh if in non-autoload mode
				refresh();
			}
			return;
		}

		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE) ||
			event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE)) {
			if (isDynamicLoad())
			{
				// only refresh if in non-autoload mode
				refresh();
			}
			return;
		}

		if (evtSrc == this) {
			return;
		}

		if (!(evtSrc instanceof IMemoryRendering)) {
			return;
		}

		IMemoryRendering rendering = (IMemoryRendering)evtSrc;
		IMemoryBlock memoryBlock = rendering.getMemoryBlock();

		// do not handle event from renderings displaying other memory blocks
		if (memoryBlock != getMemoryBlock()) {
			return;
		}

		String propertyName = event.getProperty();
		Object value = event.getNewValue();

		if (propertyName.equals(AbstractTableRendering.PROPERTY_SELECTED_ADDRESS) && value instanceof BigInteger)
		{
			selectedAddressChanged((BigInteger)value);
		}
		else if (propertyName.equals(AbstractTableRendering.PROPERTY_COL_SIZE) && value instanceof Integer)
		{
			columnSizeChanged(((Integer)value).intValue());
		}
		else if (propertyName.equals(AbstractTableRendering.PROPERTY_ROW_SIZE) && value instanceof Integer)
		{
			rowSizeChanged(((Integer)value).intValue());
		}
		else if (propertyName.equals(AbstractTableRendering.PROPERTY_TOP_ADDRESS) && value instanceof BigInteger)
		{
			if (needMoreLines())
			{
				if (isDynamicLoad()) {
					reloadTable(getTopVisibleAddress(), false);
				}
			}
			topVisibleAddressChanged((BigInteger)value, false);
		}
		else if (propertyName.equals(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS) && value instanceof BigInteger)
		{
			handlePageStartAddressChanged((BigInteger)value);
		}
	}

	private void handleDyanicLoadChanged() {

		// if currently in dynamic load mode, update page
		// start address
		updateSyncPageStartAddress();

		updateDynamicLoadProperty();
		if (isDynamicLoad())
		{
			refresh();
		}
		else
		{
			BigInteger pageStart = (BigInteger)getSynchronizedProperty(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS);
			if (pageStart == null) {
				pageStart = fTopRowAddress;
			}
			handlePageStartAddressChanged(pageStart);
		}
	}

	private void updateDynamicLoadProperty() {

		boolean value = DebugUIPlugin
				.getDefault()
				.getPreferenceStore()
				.getBoolean(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM);

		if (value != isDynamicLoad())
		{
			fContentProvider.setDynamicLoad(value);

			if (!fIsDisposed) {
				if (isDynamicLoad()) {
					fContentInput.setPostBuffer(20);
					fContentInput.setPreBuffer(20);
					fContentInput.setNumLines(getNumberOfVisibleLines());

				} else {
					fContentInput.setPostBuffer(0);
					fContentInput.setPreBuffer(0);
					fContentInput.setNumLines(fPageSize);
				}
			}
		}
	}

	/**
	 * Handle top visible address change event from synchronizer
	 * @param address the address
	 * @param force if the notification should be forced
	 */
	private void topVisibleAddressChanged(final BigInteger address, boolean force)
	{
		// do not handle event if rendering is not visible
		// continue to handle event if caller decides to force the rendering
		// to move to the top visible address even when the rendering
		// is not visible
		if (!isVisible() && !force) {
			return;
		}

		// do not handle event if the base address of the memory
		// block has changed, wait for debug event to update to
		// new location
		if (isBaseAddressChanged()) {
			return;
		}

		if (!address.equals(fTopRowAddress))
		{
			fTopRowAddress = address;
			updateSyncTopAddress();
			if (getMemoryBlock() instanceof IMemoryBlockExtension)
			{
				handleTopAddressChangedforExtended(address);
			}
			else
			{
				handleTopAddressChangedForSimple(address);
			}
		}
	}

	/**
	 * @param address the address
	 */
	private void handleTopAddressChangedForSimple(final BigInteger address) {
		// IMemoryBlock support
		int index = findAddressIndex(address);
		Table table = fTableViewer.getTable();
		if (index >= 0)
		{
			setTopIndex(table,  index);
		}

		if (isAddressVisible(fSelectedAddress)) {
			fTableCursor.setVisible(true);
		} else {
			fTableCursor.setVisible(false);
		}

	}

	/**
	 * @param address the address
	 */
	private void handleTopAddressChangedforExtended(final BigInteger address) {

		Object evtLockClient = new Object();
		try
		{
		if (!fEvtHandleLock.acquireLock(evtLockClient)) {
			return;
		}

		if (!isAddressOutOfRange(address))
		{
			Table table = fTableViewer.getTable();
			int index = findAddressIndex(address);
			int startThreshold = getBufferThreshold(BUFFER_START);
			int endThrreshold = getBufferThreshold(BUFFER_END);
			if (index >= startThreshold && table.getItemCount() - (index+getNumberOfVisibleLines()) >= endThrreshold)
			{
				// update cursor position
				setTopIndex(table, index);
			}
			else
			{
				int numInBuffer = table.getItemCount();
				if (index < getBufferThreshold(BUFFER_START))
				{
					if(isAtTopLimit())
					{
						setTopIndex(table, index);
					}
					else
					{
						if (isDynamicLoad() && getBufferThreshold(BUFFER_START) > 0) {
							reloadTable(address, false);
						} else {
							setTopIndex(table, index);
						}
					}
				}
				else if ((numInBuffer-(index+getNumberOfVisibleLines())) <= getBufferThreshold(BUFFER_END))
				{
					if (!isAtBottomLimit() && isDynamicLoad() && getBufferThreshold(BUFFER_END) > 0) {
						reloadTable(address, false);
					} else {
						setTopIndex(table, index);
					}
				}
			}
		}
		else
		{
			// approaching limit, reload table
			reloadTable(address, false);
		}

		if (isAddressVisible(fSelectedAddress)) {
			fTableCursor.setVisible(true);
		} else {
			fTableCursor.setVisible(false);
		}
		}
		finally
		{
			fEvtHandleLock.releaseLock(evtLockClient);
		}
	}

	/**
	 * @param value the new value
	 */
	private void selectedAddressChanged(BigInteger value) {

		// do not handle event if the base address of the memory
		// block has changed, wait for debug event to update to
		// new location
		if (isBaseAddressChanged()) {
			return;
		}

		try {
			// do not handle event if the event is out of range and the
			// rendering is in non-dynamic-load mode, otherwise, will
			// cause rendering to continue to scroll when it shouldn't
			if (isDynamicLoad()) {
				goToAddress(value);
			} else if (!isAddressOutOfRange(value)) {
				goToAddress(value);
			}
		} catch (DebugException e) {
			// do nothing
		}
	}

	private void handlePageStartAddressChanged(BigInteger address)
	{
		// do not handle if in dynamic mode
		if (isDynamicLoad()) {
			return;
		}

		if (fContentInput == null) {
			return;
		}

		if (!(getMemoryBlock() instanceof IMemoryBlockExtension)) {
			return;
		}

		// do not handle event if the base address of the memory
		// block has changed, wait for debug event to update to
		// new location
		if (isBaseAddressChanged()) {
			return;
		}

		if(fContentProvider.getBufferTopAddress().equals(address)) {
			return;
		}

		BigInteger start = fContentInput.getStartAddress();
		BigInteger end = fContentInput.getEndAddress();

		// smaller than start address, load at start address
		if (address.compareTo(start) < 0)
		{
			if (isAtTopLimit()) {
				return;
			}

			address = start;
		}

		// bigger than end address, no need to load, already at top
		if (address.compareTo(end) > 0)
		{
			if (isAtBottomLimit()) {
				return;
			}

			address = end.subtract(BigInteger.valueOf(getPageSizeInUnits()));
		}

		fContentInput.setLoadAddress(address);
		refresh();
		updateSyncPageStartAddress();
		setTopIndex(fTableViewer.getTable(), 0);
		fTopRowAddress = address;
		updateSyncTopAddress();

		BigInteger selectedAddress = (BigInteger)getSynchronizedProperty(AbstractTableRendering.PROPERTY_SELECTED_ADDRESS);
		if (selectedAddress != null)
		{
			fSelectedAddress = selectedAddress;
			if (!isAddressOutOfRange(fSelectedAddress))
			{
				setCursorAtAddress(fSelectedAddress);
				fTableCursor.setVisible(true);
			}
			else
			{
				fTableCursor.setVisible(false);
			}
		}
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.memory.IMemoryRendering#createControl(org.eclipse.swt.widgets.Composite)
	 */
	@Override
	public Control createControl(Composite parent) {

		fPageBook = new PageBook(parent, SWT.NONE);
		createErrorPage(fPageBook);
		createTableViewer(fPageBook);

		fTableViewer.getTable().redraw();
		createToolTip();

		return fPageBook;
	}

	/**
	 * Create the table viewer and other support controls
	 * for this rendering.
	 *
	 * @param parent parent composite
	 */
	private void createTableViewer(Composite parent) {

		fTableViewer= new TableViewer(parent, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.HIDE_SELECTION | SWT.BORDER);

		TableRenderingLabelProvider labelProvider;
		if (hasCustomizedDecorations()) {
			labelProvider = new TableRenderingLabelProviderEx(this);
		} else {
			labelProvider = new TableRenderingLabelProvider(this);
		}

		fTableViewer.setLabelProvider(labelProvider);

		fContentProvider = new TableRenderingContentProvider();
		fContentProvider.setDynamicLoad(DebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM));

		fTableViewer.setContentProvider(fContentProvider);
		fContentProvider.setViewer(fTableViewer);

		ScrollBar scroll = ((Table)fTableViewer.getControl()).getVerticalBar();
		scroll.setMinimum(-100);
		scroll.setMaximum(200);

		fTableViewer.getTable().setHeaderVisible(true);
		fTableViewer.getTable().setLinesVisible(true);


		// set up addressable size and figure out number of bytes required per line
		fAddressableSize = -1;
		try {
			if (getMemoryBlock() instanceof IMemoryBlockExtension) {
				fAddressableSize = ((IMemoryBlockExtension)getMemoryBlock()).getAddressableSize();
			}
		} catch (DebugException e1) {
			// log error and default to 1
			fAddressableSize = 1;
			displayError(e1);
			return;

		}
		if (getAddressableSize() < 1) {
			fAddressableSize = 1;
		}

// set up initial format
		setupInitialFormat();

// set up selected address
		setupSelectedAddress();

		// figure out top visible address
		BigInteger topVisibleAddress = getInitialTopVisibleAddress();

		getPageSizeFromPreference();


		if (isDynamicLoad())
		{
			int numLines = getNumberOfVisibleLines();
			if (numLines <= 0)
			{
				// add listener to reload when we know the number of lines to load
				fTableViewer.getTable().addPaintListener(new PaintListener() {
					@Override
					public void paintControl(PaintEvent e) {
						fTableViewer.getTable().removePaintListener(this);
						fContentInput.setNumLines(getNumberOfVisibleLines());
						reloadTable(fContentInput.getLoadAddress(), false);
						resizeColumnsToPreferredSize();
						setCursorAtAddress(fSelectedAddress);
						fTableCursor.setVisible(true);
					}});
			}
			fContentInput = new TableRenderingContentInput(this, fPreBuffer, fPostBuffer,  topVisibleAddress, numLines, false, null);
		}
		else
		{
			BigInteger addressToLoad = topVisibleAddress;

			// check synchronization service to see if we need to sync with another rendering
			Object obj = getSynchronizedProperty(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS);
			if (obj != null && obj instanceof BigInteger)
			{
				addressToLoad = (BigInteger)obj;
			}
			fContentInput = new TableRenderingContentInput(this, 0, 0, addressToLoad, fPageSize, false, null);
		}

		fTableViewer.setInput(fContentInput);

		// set up cell modifier
		fCellModifier = new TableRenderingCellModifier(this);
		fTableViewer.setCellModifier(fCellModifier);

		// SET UP FONT
		// set to a non-proportional font
		fTableViewer.getTable().setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));
		if (!(getMemoryBlock() instanceof IMemoryBlockExtension))
		{
			// If not extended memory block, do not create any buffer
			// no scrolling
			fContentInput.setPreBuffer(0);
			fContentInput.setPostBuffer(0);
		}

		// set up table cursor
		createCursor(fTableViewer.getTable(), fSelectedAddress);
		fTableViewer.getTable().addMouseListener(new MouseAdapter() {
			@Override
			public void mouseDown(MouseEvent e) {
				handleTableMouseEvent(e);
			}});

		// create pop up menu for the rendering
		createActions();
		createPopupMenu(fTableViewer.getControl());
		createPopupMenu(fTableCursor);

		fMenuListener = manager -> {
			fillContextMenu(manager);
			manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
		};
		getPopupMenuManager().addMenuListener(fMenuListener);

		// now the rendering is successfully created
		fIsCreated = true;

		//synchronize
		addRenderingToSyncService();
		synchronize();

		fTopRowAddress = getTopVisibleAddress();
		// Need to resize column after content is filled in
		// Pack function does not work unless content is not filled in
		// since the table is not able to compute the preferred size.
		resizeColumnsToPreferredSize();
		try {
			if (getMemoryBlock() instanceof IMemoryBlockExtension)
			{
				if(((IMemoryBlockExtension)getMemoryBlock()).getBigBaseAddress() == null)
				{
					DebugException e = new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.AbstractTableRendering_1, null));
					displayError(e);
				}
			}
		} catch (DebugException e1) {
			displayError(e1);
		}

		// add font change listener and update font when the font has been changed
		JFaceResources.getFontRegistry().addListener(this);
		fScrollbarSelectionListener = new SelectionAdapter() {

			@Override
			public void widgetSelected(SelectionEvent event) {
				handleScrollBarSelection();

			}};
		scroll.addSelectionListener(fScrollbarSelectionListener);
		DebugUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
	}

	private boolean validateInitialFormat()
	{
		int rowSize = getDefaultRowSize();
		int columnSize = getDefaultColumnSize();

		if (rowSize < columnSize || rowSize % columnSize != 0 || rowSize == 0 || columnSize == 0)
		{
			return false;
		}
		return true;
	}

	private BigInteger getInitialTopVisibleAddress() {
		BigInteger topVisibleAddress = (BigInteger) getSynchronizedProperty(AbstractTableRendering.PROPERTY_TOP_ADDRESS);
		if (topVisibleAddress == null)
		{
			if (getMemoryBlock() instanceof IMemoryBlockExtension)
			{
				try {
					topVisibleAddress = ((IMemoryBlockExtension)getMemoryBlock()).getBigBaseAddress();
				} catch (DebugException e1) {
					topVisibleAddress = new BigInteger("0"); //$NON-NLS-1$
				}
			}
			else
			{
				topVisibleAddress = BigInteger.valueOf(getMemoryBlock().getStartAddress());
			}
		}
		return topVisibleAddress;
	}

	private void setupSelectedAddress() {
		// figure out selected address
		BigInteger selectedAddress = (BigInteger) getSynchronizedProperty(AbstractTableRendering.PROPERTY_SELECTED_ADDRESS);
		if (selectedAddress == null)
		{
			if (getMemoryBlock() instanceof IMemoryBlockExtension) {
				try {
					selectedAddress = ((IMemoryBlockExtension) getMemoryBlock())
							.getBigBaseAddress();
				} catch (DebugException e1) {
					selectedAddress = new BigInteger("0"); //$NON-NLS-1$
				}
				if (selectedAddress == null) {
					selectedAddress = new BigInteger("0"); //$NON-NLS-1$
				}

			} else {
				long address = getMemoryBlock().getStartAddress();
				selectedAddress = BigInteger.valueOf(address);
			}
		}
		setSelectedAddress(selectedAddress);
	}

	private void setupInitialFormat() {

		boolean validated = validateInitialFormat();

		if (!validated)
		{
			// pop up dialog to ask user for default values
			StringBuffer msgBuffer = new StringBuffer(DebugUIMessages.AbstractTableRendering_20);
			msgBuffer.append(" "); //$NON-NLS-1$
			msgBuffer.append(this.getLabel());
			msgBuffer.append("\n\n"); //$NON-NLS-1$
			msgBuffer.append(DebugUIMessages.AbstractTableRendering_16);
			msgBuffer.append("\n"); //$NON-NLS-1$
			msgBuffer.append(DebugUIMessages.AbstractTableRendering_18);
			msgBuffer.append("\n\n"); //$NON-NLS-1$

			int bytePerLine = fBytePerLine;
			int columnSize = fColumnSize;

			// initialize this value to populate the dialog properly
			fBytePerLine = getDefaultRowSize() / getAddressableSize();
			fColumnSize = getDefaultColumnSize() / getAddressableSize();

			FormatTableRenderingDialog dialog = new FormatTableRenderingDialog(this, DebugUIPlugin.getShell());
			dialog.openError(msgBuffer.toString());

			// restore to original value before formatting
			fBytePerLine = bytePerLine;
			fColumnSize = columnSize;

			bytePerLine = dialog.getRowSize() * getAddressableSize();
			columnSize = dialog.getColumnSize() * getAddressableSize();

			format(bytePerLine, columnSize);
		}
		else
		{
			// Row size is stored as number of addressable units in preference store
			int bytePerLine = getDefaultRowSize();
			// column size is now stored as number of addressable units
			int columnSize = getDefaultColumnSize();

			// format memory block with specified "bytesPerLine" and "columnSize"
			boolean ok = format(bytePerLine, columnSize);

			if (!ok)
			{
				// this is to ensure that the rest of the rendering can be created
				// and we can recover from a format error
				format(bytePerLine, bytePerLine);
			}
		}
	}

	private int getDefaultColumnSize() {

		// default to global preference store
		IPreferenceStore prefStore = DebugUITools.getPreferenceStore();
		int columnSize = prefStore.getInt(IDebugPreferenceConstants.PREF_COLUMN_SIZE);
		// actual column size is number of addressable units * size of the addressable unit
		columnSize = columnSize * getAddressableSize();

		// check synchronized column size
		Integer colSize = (Integer)getSynchronizedProperty(AbstractTableRendering.PROPERTY_COL_SIZE);
		if (colSize != null)
		{
			// column size is stored as actual number of bytes in synchronizer
			int syncColSize = colSize.intValue();
			if (syncColSize > 0)
			{
				columnSize = syncColSize;
			}
		}
		else
		{
			IPersistableDebugElement elmt = getMemoryBlock().getAdapter(IPersistableDebugElement.class);
			int defaultColSize = -1;

			if (elmt != null)
			{
				if (elmt.supportsProperty(this, IDebugPreferenceConstants.PREF_COL_SIZE_BY_MODEL)) {
					defaultColSize = getDefaultFromPersistableElement(IDebugPreferenceConstants.PREF_COL_SIZE_BY_MODEL);
				}
			}

			if (defaultColSize <= 0)
			{
				// if not provided, get default by model
				defaultColSize = getDefaultColumnSizeByModel(getMemoryBlock().getModelIdentifier());
			}

			if (defaultColSize > 0) {
				columnSize = defaultColSize * getAddressableSize();
			}
		}
		return columnSize;
	}

	private int getDefaultRowSize() {

		int rowSize = DebugUITools.getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_ROW_SIZE);
		int bytePerLine = rowSize * getAddressableSize();

		// check synchronized row size
		Integer size = (Integer)getSynchronizedProperty(AbstractTableRendering.PROPERTY_ROW_SIZE);
		if (size != null)
		{
			// row size is stored as actual number of bytes in synchronizer
			int syncRowSize = size.intValue();
			if (syncRowSize > 0)
			{
				bytePerLine = syncRowSize;
			}
		}
		else
		{
			int defaultRowSize = -1;
			IPersistableDebugElement elmt = getMemoryBlock().getAdapter(IPersistableDebugElement.class);
			if (elmt != null)
			{
				if (elmt.supportsProperty(this, IDebugPreferenceConstants.PREF_ROW_SIZE_BY_MODEL))
				{
					defaultRowSize = getDefaultFromPersistableElement(IDebugPreferenceConstants.PREF_ROW_SIZE_BY_MODEL);
					return defaultRowSize * getAddressableSize();
				}
			}

			if (defaultRowSize <= 0) {
				// no synchronized property, ask preference store by id
				defaultRowSize = getDefaultRowSizeByModel(getMemoryBlock().getModelIdentifier());
			}

			if (defaultRowSize > 0) {
				bytePerLine = defaultRowSize * getAddressableSize();
			}
		}
		return bytePerLine;
	}

	private int getDefaultFromPersistableElement(String propertyId) {
		int defaultValue = -1;
		IPersistableDebugElement elmt = getMemoryBlock().getAdapter(IPersistableDebugElement.class);
		if (elmt != null)
		{
			try {
				Object valueMB = elmt.getProperty(this, propertyId);
				if (valueMB != null && !(valueMB instanceof Integer))
				{
					IStatus status = DebugUIPlugin.newErrorStatus("Model returned invalid type on " + propertyId, null); //$NON-NLS-1$
					DebugUIPlugin.log(status);
				}

				if (valueMB != null)
				{
					Integer value = (Integer)valueMB;
					defaultValue = value.intValue();
				}
			} catch (CoreException e) {
				DebugUIPlugin.log(e);
			}
		}
		return defaultValue;
	}

	private void getPageSizeFromPreference()
	{
		fPageSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE);
		fPreBuffer = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE);
		fPostBuffer = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE);
	}

	private void createCursor(Table table, BigInteger address)
	{
		fTableCursor = new TableCursor(table, SWT.NONE);
		Display display = fTableCursor.getDisplay();

		// set up cursor color
		fTableCursor.setBackground(display.getSystemColor(SWT.COLOR_LIST_SELECTION));
		fTableCursor.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));

		fTableCursor.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));
		fTableCursor.setVisible(true);

		fCursorKeyAdapter = new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e)
			 {
			 	handleCursorKeyPressed(e);
			 }
		};
		fTableCursor.addKeyListener(fCursorKeyAdapter);

		fCursorTraverseListener = e -> handleCursorTraverseEvt(e);

		fTableCursor.addTraverseListener(fCursorTraverseListener);

		fCursorMouseListener = new MouseAdapter() {
			@Override
			public void mouseDown(MouseEvent e) {
				handleCursorMouseEvent(e);
			}};
		fTableCursor.addMouseListener(fCursorMouseListener);

		// cursor may be disposed before disposed is called
		// remove listeners whenever the cursor is disposed
		fTableCursor.addDisposeListener(e -> {
			if (fTableCursor == null) {
				return;
			}
			fTableCursor.removeTraverseListener(fCursorTraverseListener);
			fTableCursor.removeKeyListener(fCursorKeyAdapter);
			fTableCursor.removeMouseListener(fCursorMouseListener);
			fTableCursor.removeSelectionListener(fCursorSelectionListener);
		});

		fCursorSelectionListener = new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {

						if (!fEvtHandleLock.acquireLock(this)) {
							return;
						}

						handleCursorMoved();

						fEvtHandleLock.releaseLock(this);

					}
				};
		fTableCursor.addSelectionListener(fCursorSelectionListener);


		setCursorAtAddress(address);

		fCursorEditor = new TableEditor (fTableViewer.getTable());
	}

	private void handleCursorTraverseEvt(TraverseEvent e){

		if (fTableCursor.getRow() == null) {
			return;
		}

		Table table = (Table)fTableCursor.getParent();
		int row = table.indexOf(fTableCursor.getRow());
		int col = fTableCursor.getColumn();
		if (col == getNumCol() && e.keyCode == SWT.ARROW_RIGHT)
		{
			if (row + 1>= table.getItemCount())
			{
				return;
			}

			row = row +1;
			col = 0;
			fTableCursor.setSelection(row, col);
		}
		if (col <= 1 && e.keyCode == SWT.ARROW_LEFT)
		{
			if (row-1 < 0)
			{
				return;
			}

			row = row - 1;
			col = getNumCol()+1;
			fTableCursor.setSelection(row, col);
		}

		Object evtLockClient = new Object();
		if (!fEvtHandleLock.acquireLock(evtLockClient)) {
			return;
		}

		handleCursorMoved();

		fEvtHandleLock.releaseLock(evtLockClient);

	}

	/**
	 * Update selected address.
	 * Load more memory if required.
	 */
	private void handleCursorMoved()
	{
		if (fIsDisposed) {
			return;
		}

		BigInteger selectedAddress = getSelectedAddressFromCursor(fTableCursor);

		// when the cursor is moved, the selected address is changed
		if (selectedAddress != null && !selectedAddress.equals(fSelectedAddress))
		{
			setSelectedAddress(selectedAddress);
			updateSyncSelectedAddress();
		}

		// now check to see if the cursor is approaching buffer limit
		TableItem item = fTableCursor.getRow();
		if (item == null) {
			return;
		}

		if (getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			int row = fTableViewer.getTable().indexOf(item);

			if (row < getBufferThreshold(BUFFER_START))
			{
				if (!isAtTopLimit() && getBufferThreshold(BUFFER_START) > 0)
				{
					if (isDynamicLoad())
					{
						refresh();
						setCursorAtAddress(fSelectedAddress);
					}
				}
			}
			else if (row >= fTableViewer.getTable().getItemCount() - getBufferThreshold(BUFFER_END))
			{
				if (!isAtBottomLimit() && getBufferThreshold(BUFFER_END) > 0)
				{
					if (isDynamicLoad())
					{
						refresh();
						setCursorAtAddress(fSelectedAddress);
					}
				}
			}
		}

		// if the cursor has moved, the top index of the table may change
		// just update the synchronization service
		BigInteger address = getTopVisibleAddress();
		if (!address.equals(fTopRowAddress))
		{
			fTopRowAddress = address;
			updateSyncTopAddress();
		}
	}

	private void handleCursorKeyPressed(KeyEvent event)
	{
		// allow edit if user hits return
		if (event.character == '\r' && event.getSource() instanceof TableCursor)
		{
			activateCellEditor(null);
			return;
		}

		if (MemoryViewUtil.isValidEditEvent(event.keyCode))
		{
			// activate edit as soon as user types something at the cursor
			if (event.getSource() instanceof TableCursor)
			{
				String initialValue = String.valueOf(event.character);
				activateCellEditor(initialValue);
				return;
			}
		}
	}

	/**
	 * Calculate selected address based on cursor's current position
	 * @param cursor the cursor
	 * @return the selected address
	 */
	private BigInteger getSelectedAddressFromCursor(TableCursor cursor)
	{
		TableItem row = cursor.getRow();
		int col = cursor.getColumn();

		return getAddressFromTableItem(row, col);
	}

	private BigInteger getAddressFromTableItem(TableItem row, int col) {
		if (row == null) {
			return null;
		}

		// get row address
		String temp = ((TableRenderingLine)row.getData()).getAddress();
		BigInteger rowAddress = new BigInteger(temp, 16);

		int offset;
		if (col > 0)
		{
			// 	get address offset
			int addressableUnit = getAddressableUnitPerColumn();
			offset = (col-1) * addressableUnit;
		}
		else
		{
			offset = 0;
		}

		return rowAddress.add(BigInteger.valueOf(offset));
	}


	/**
	 * Sets the cursor at the specified address
	 * @param address the address
	 * @return true if successful, false otherwise
	 */
	private boolean setCursorAtAddress(BigInteger address)
	{
		if (fContentProvider.getBufferTopAddress() == null) {
			return false;
		}

		// selected address is out of range, simply return false
		if (address.compareTo(fContentProvider.getBufferTopAddress()) < 0) {
			return false;
		}

		// calculate selected row address
		int addressableUnit = getAddressableUnitPerLine();
		int numOfRows = address.subtract(fContentProvider.getBufferTopAddress()).intValue()/addressableUnit;
		BigInteger rowAddress = fContentProvider.getBufferTopAddress().add(BigInteger.valueOf(numOfRows * addressableUnit));

		// try to find the row of the selected address
		int row = findAddressIndex(address);

		if (row == -1)
		{
			return false;
		}

		// calculate offset to the row address
		BigInteger offset = address.subtract(rowAddress);

		// locate column
		int colAddressableUnit = getAddressableUnitPerColumn();
		int col = ((offset.intValue()/colAddressableUnit)+1);

		if (col == 0) {
			col = 1;
		}

		fTableCursor.setSelection(row, col);

		return true;
	}


	/**
	 * Format view tab based on the bytes per line and column.
	 *
	 * @param bytesPerLine - number of bytes per line, possible values: (1 / 2 / 4 / 8 / 16 / 32 / 64 / 128) * addressableSize
	 * @param columnSize - number of bytes per column, possible values: (1 / 2 / 4 / 8 / 16 / 32 / 64 / 128) * addressableSize
	 * @return true if format is successful, false, otherwise
	 *
	 */
	@Override
	public boolean format(int bytesPerLine, int columnSize)
	{

		// selected address gets changed as the cursor is moved
		// during the reformat.
		// Back up the address and restore it later.
		BigInteger selectedAddress = fSelectedAddress;

		// bytes per cell must be divisible to bytesPerLine
		if (bytesPerLine % columnSize != 0)
		{
			return false;
		}

		if (bytesPerLine < columnSize)
		{
			return false;
		}

		// do not format if the view tab is already in that format
		if(fBytePerLine == bytesPerLine && fColumnSize == columnSize){
			return false;
		}

		fBytePerLine = bytesPerLine;
		fColumnSize = columnSize;

		Object evtLockClient = new Object();
		if (!fEvtHandleLock.acquireLock(evtLockClient)) {
			return false;
		}

		// if the tab is already created and is being reformatted
		if (fIsCreated)
		{
			if (fTableViewer == null) {
				return false;
			}

			if (fTableViewer.getTable() == null) {
				return false;
			}

			// clean up old columns
			TableColumn[] oldColumns = fTableViewer.getTable().getColumns();

			for (int i=0; i<oldColumns.length; i++)
			{
				oldColumns[i].dispose();
			}

			// clean up old cell editors
			CellEditor[] oldCellEditors = fTableViewer.getCellEditors();

			for (int i=0; i<oldCellEditors.length; i++)
			{
				oldCellEditors[i].dispose();
			}
		}

		TableColumn column0 = new TableColumn(fTableViewer.getTable(),SWT.LEFT,0);
		column0.setText(DebugUIMessages.AbstractTableRendering_2);

		// create new byte columns
		TableColumn [] byteColumns = new TableColumn[bytesPerLine/columnSize];

		String[] columnLabels = new String[0];
		IMemoryBlockTablePresentation presentation = getTablePresentationAdapter();
		if (presentation != null)
		{
			columnLabels = presentation.getColumnLabels(getMemoryBlock(), bytesPerLine, getNumCol());
		}

		// check that column labels are not null
		if (columnLabels == null) {
			columnLabels = new String[0];
		}

		for (int i=0;i<byteColumns.length; i++)
		{
			TableColumn column = new TableColumn(fTableViewer.getTable(), SWT.LEFT, i+1);

			// if the number of column labels returned is correct
			// use supplied column labels
			if (columnLabels.length == byteColumns.length)
			{
				column.setText(columnLabels[i]);
			}
			else
			{
				// otherwise, use default
				int addressableUnit = columnSize/getAddressableSize();
				if (getAddressableUnitPerColumn() >= 4)
				{
					column.setText(Integer.toHexString(i*addressableUnit).toUpperCase() +
						" - " + Integer.toHexString(i*addressableUnit+addressableUnit-1).toUpperCase()); //$NON-NLS-1$
				}
				else
				{
					column.setText(Integer.toHexString(i*addressableUnit).toUpperCase());
				}
			}
		}

		//Empty column for cursor navigation
		TableColumn emptyCol = new TableColumn(fTableViewer.getTable(),SWT.LEFT,byteColumns.length+1);
		emptyCol.setText(" "); //$NON-NLS-1$
		emptyCol.setWidth(1);
		emptyCol.setResizable(false);

		// +2 to include properties for address and navigation column
		String[] columnProperties = new String[byteColumns.length+2];
		columnProperties[0] = TableRenderingLine.P_ADDRESS;

		int addressableUnit = columnSize / getAddressableSize();

		// use column beginning offset to the row address as properties
		for (int i=1; i<columnProperties.length-1; i++)
		{
			// column properties are stored as number of addressable units from the
			// the line address
			columnProperties[i] = Integer.toHexString((i-1)*addressableUnit);
		}

		// Empty column for cursor navigation
		columnProperties[columnProperties.length-1] = " "; //$NON-NLS-1$

		fTableViewer.setColumnProperties(columnProperties);


		Table table = fTableViewer.getTable();
		fEditors = new CellEditor[table.getColumnCount()];
		for (int i=0; i<fEditors.length; i++)
		{
			fEditors[i] = new TextCellEditor(table);
		}

		// create and set cell editors
		fTableViewer.setCellEditors(fEditors);

		if (fIsCreated)
		{
			fTableViewer.refresh();
		}

		resizeColumnsToPreferredSize();
		updateSyncRowSize();
		updateSyncColSize();

		if (fIsCreated)
		{
			// for Linux GTK, this must happen after table viewer is refreshed
			int i = findAddressIndex(fTopRowAddress);

			if (i >= 0) {
				setTopIndex(fTableViewer.getTable(), i);
			}

			if (isAddressVisible(selectedAddress)) {
				// after refresh, make sure the cursor is at the correct position
				setCursorAtAddress(selectedAddress);
			}
		}

		fEvtHandleLock.releaseLock(evtLockClient);

		return true;
	}

	/**
	 * Create the error page for this rendering.
	 * The error page is used to report any error resulted from
	 * getting memory from a memory block.
	 * @param parent the parent composite
	 */
	private void createErrorPage(Composite parent)
	{
		if (fTextViewer == null)
		{
			fTextViewer = new TextViewer(parent, SWT.WRAP);
			fTextViewer.setDocument(new Document());
			StyledText styleText = fTextViewer.getTextWidget();
			styleText.setEditable(false);
			styleText.setEnabled(false);
		}
	}

	/**
	 * Displays the content of the table viewer.
	 */
	public void displayTable()
	{
		fIsShowingErrorPage = false;
		fPageBook.showPage(fTableViewer.getControl());
	}

	/**
	 * Displays an error message for the given exception.
	 *
	 * @param e exception to display
	 */
	public void displayError(DebugException e)
	{
		StyledText styleText = null;
		fIsShowingErrorPage = true;

		styleText = fTextViewer.getTextWidget();

		if (styleText != null) {
			styleText.setText(DebugUIMessages.AbstractTableRendering_3 + e.getMessage());
		}
		fPageBook.showPage(fTextViewer.getControl());

		// clear content cache if we need to display error
		fContentProvider.clearContentCache();
	}

	/**
	 * Returns whether the error page is displayed.
	 *
	 * @return whether the error page is displayed
	 */
	public boolean isDisplayingError()
	{
		return fIsShowingErrorPage;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.memory.IMemoryRendering#getControl()
	 */
	@Override
	public Control getControl() {
		return fPageBook;
	}

	/**
	 * Returns the addressable size of this rendering's memory block in bytes.
	 *
	 * @return the addressable size of this rendering's memory block in bytes
	 */
	@Override
	public int getAddressableSize() {
		return fAddressableSize;
	}

	private Object getSynchronizedProperty(String propertyId)
	{
		IMemoryRenderingSynchronizationService syncService = getMemoryRenderingContainer().getMemoryRenderingSite().getSynchronizationService();

		if (syncService == null) {
			return null;
		}

		return syncService.getProperty(getMemoryBlock(), propertyId);
	}

	/**
	 * This method estimates the number of visible lines in the rendering
	 * table.
	 * @return estimated number of visible lines in the table
	 */
	private int getNumberOfVisibleLines()
	{
		if(fTableViewer == null) {
			return -1;
		}

		Table table = fTableViewer.getTable();
		int height = fTableViewer.getTable().getSize().y;

		// when table is not yet created, height is zero
		if (height == 0)
		{
			// make use of the table viewer to estimate table size
			height = fTableViewer.getTable().getParent().getSize().y;
		}

		int numberOfLines = doGetNumberOfVisibleLines(table, height);

		if (numberOfLines <= 0)
		{
			return 0;
		}

		return numberOfLines;
	}

	/**
	 * @param table the table
	 * @param height the current height of the table
	 * @return the number of visible lines in the table
	 */
	private int doGetNumberOfVisibleLines(Table table, int height) {
		// height of border
		int border = fTableViewer.getTable().getHeaderHeight();

		// height of scroll bar
		int scroll = fTableViewer.getTable().getHorizontalBar().getSize().y;

		// height of table is table's area minus border and scroll bar height
		height = height-border-scroll;

		// calculate number of visible lines
		int lineHeight = getMinTableItemHeight(table);

		int numberOfLines = height/lineHeight;
		return numberOfLines;
	}

	private static void  setTopIndex(Table table, int index)
	{
		table.setTopIndex(index);
	}

	private void addRenderingToSyncService()
	{
		IMemoryRenderingSynchronizationService syncService = getMemoryRenderingContainer().getMemoryRenderingSite().getSynchronizationService();

		if (syncService == null) {
			return;
		}

		syncService.addPropertyChangeListener(this, null);

		// we could be in a format error even though the error is not yet displayed
		// do not update sync property in this case
		if (!isDisplayingError())
		{
			if (syncService.getSynchronizationProvider() == null) {
				syncService.setSynchronizationProvider(this);
			}

			// check if there is already synchronization info available
			Object selectedAddress =getSynchronizedProperty( AbstractTableRendering.PROPERTY_SELECTED_ADDRESS);
			Object rowSize = getSynchronizedProperty(AbstractTableRendering.PROPERTY_ROW_SIZE);
			Object colSize =getSynchronizedProperty( AbstractTableRendering.PROPERTY_COL_SIZE);
			Object topAddress =getSynchronizedProperty( AbstractTableRendering.PROPERTY_TOP_ADDRESS);

			if (!isDynamicLoad())
			{
				Object pageStartAddress = getSynchronizedProperty(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS);
				if (pageStartAddress == null) {
					updateSyncPageStartAddress();
				}
			}

			// if info is available, some other view tab has already been
			// created
			// do not overwrite info in the synchronizer if that's the case
			if (selectedAddress == null) {
				updateSyncSelectedAddress();
			}

			if (rowSize == null)
			{
				updateSyncRowSize();
			}

			if (colSize == null) {
				updateSyncColSize();
			}
			if (topAddress == null) {
				updateSyncTopAddress();
			}
		}
	}

	/**
	 * Get properties from synchronizer and synchronize settings
	 */
	private void synchronize()
	{
		if (!isDynamicLoad())
		{
			BigInteger pageStart = (BigInteger)getSynchronizedProperty(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS);
			if (pageStart != null && fContentInput != null && fContentInput.getLoadAddress() != null)
			{
				if (!fContentInput.getLoadAddress().equals(pageStart)) {
					handlePageStartAddressChanged(pageStart);
				}
			}
			else if (pageStart != null)
			{
				handlePageStartAddressChanged(pageStart);
			}
		}

		Integer rowSize = (Integer) getSynchronizedProperty(AbstractTableRendering.PROPERTY_ROW_SIZE);
		Integer columnSize = (Integer) getSynchronizedProperty(AbstractTableRendering.PROPERTY_COL_SIZE);
		BigInteger selectedAddress = (BigInteger)getSynchronizedProperty(AbstractTableRendering.PROPERTY_SELECTED_ADDRESS);
		BigInteger topAddress = (BigInteger)getSynchronizedProperty(AbstractTableRendering.PROPERTY_TOP_ADDRESS);

		if (rowSize != null)
		{
			int rSize = rowSize.intValue();
			if (rSize > 0 && rSize != fBytePerLine) {
				rowSizeChanged(rSize);
			}
		}

		if (columnSize != null) {
			int colSize = columnSize.intValue();
			if (colSize > 0 && colSize != fColumnSize) {
				columnSizeChanged(colSize);
			}
		}
		if (topAddress != null) {
			if (!topAddress.equals(getTopVisibleAddress())) {
				if (selectedAddress != null) {
					if (!fSelectedAddress.equals(selectedAddress)) {
						selectedAddressChanged(selectedAddress);
					}
				}
				topVisibleAddressChanged(topAddress, false);
			}
		}
		if (selectedAddress != null) {
			if (selectedAddress.compareTo(fSelectedAddress) != 0) {
				selectedAddressChanged(selectedAddress);
			}
		}
	}

	/**
	 * Resize column to the preferred size.
	 */
	@Override
	public void resizeColumnsToPreferredSize() {
		// pack columns
		Table table = fTableViewer.getTable();
		TableColumn[] columns = table.getColumns();

		for (int i=0 ;i<columns.length-1; i++)
		{
			columns[i].pack();
		}

		if (!fIsShowAddressColumn)
		{
			columns[0].setWidth(0);
		}
	}

	/**
	 * update selected address in synchronizer if update is true.
	 */
	private void updateSyncSelectedAddress() {

		if (!fIsCreated) {
			return;
		}
		PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractTableRendering.PROPERTY_SELECTED_ADDRESS, null, fSelectedAddress);
		firePropertyChangedEvent(event);
	}

	/**
	 * update column size in synchronizer
	 */
	private void updateSyncColSize() {

		if (!fIsCreated) {
			return;
		}

		PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractTableRendering.PROPERTY_COL_SIZE, null, Integer.valueOf(fColumnSize));
		firePropertyChangedEvent(event);
	}

	/**
	 * update column size in synchronizer
	 */
	private void updateSyncRowSize() {

		if (!fIsCreated) {
			return;
		}

		PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractTableRendering.PROPERTY_ROW_SIZE, null, Integer.valueOf(fBytePerLine));
		firePropertyChangedEvent(event);
	}

	/**
	 * update top visible address in synchronizer
	 */
	private void updateSyncTopAddress() {

		if (!fIsCreated) {
			return;
		}

		PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractTableRendering.PROPERTY_TOP_ADDRESS, null, fTopRowAddress);
		firePropertyChangedEvent(event);
	}

	private void updateSyncPageStartAddress() {

		if (!fIsCreated) {
			return;
		}

		if (isBaseAddressChanged()) {
			return;
		}

		BigInteger pageStart;
		if (isDynamicLoad())
		{
			// if dynamic loading, the page address should be the top
			// row address
			pageStart = fTopRowAddress;
		}
		else
		{
			// otherwise, the address is the buffer's start address
			pageStart = fContentProvider.getBufferTopAddress();
		}

		PropertyChangeEvent event = new PropertyChangeEvent(this, IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS, null, pageStart);
		firePropertyChangedEvent(event);
	}

	/**
	 * Fills the context menu for this rendering
	 *
	 * @param menu menu to fill
	 */
	protected void fillContextMenu(IMenuManager menu) {

		menu.add(new Separator("topMenu")); //$NON-NLS-1$
		menu.add(fResetMemoryBlockAction);
		menu.add(fGoToAddressAction);

		menu.add(new Separator());

		menu.add(fFormatRenderingAction);

		if (!isDynamicLoad() && getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			menu.add(new Separator());
			menu.add(fPrevAction);
			menu.add(fNextAction);
		}

		menu.add(new Separator());
		menu.add(fReformatAction);
		menu.add(fToggleAddressColumnAction);
		menu.add(new Separator());
		menu.add(fCopyToClipboardAction);
		menu.add(fPrintViewTabAction);
		if (fPropertiesAction != null)
		{
			menu.add(new Separator());
			menu.add(fPropertiesAction);
		}

	}

	/**
	 * Returns the number of addressable units per row.
	 *
	 * @return number of addressable units per row
	 */
	@Override
	public int getAddressableUnitPerLine() {
		return fBytePerLine / getAddressableSize();
	}

	/**
	 * Returns the number of addressable units per column.
	 *
	 * @return number of addressable units per column
	 */
	@Override
	public int getAddressableUnitPerColumn() {
		return fColumnSize / getAddressableSize();
	}

	/**
	 * Returns the number of bytes displayed in a single column cell.
	 *
	 * @return the number of bytes displayed in a single column cell
	 */
	@Override
	public int getBytesPerColumn()
	{
		return fColumnSize;
	}

	/**
	 * Returns the number of bytes displayed in a row.
	 *
	 * @return the number of bytes displayed in a row
	 */
	@Override
	public int getBytesPerLine()
	{
		return fBytePerLine;
	}

	/**
	 * Updates labels of this rendering.
	 */
	@Override
	public void updateLabels()
	{
		// update tab labels
		updateRenderingLabel(true);

		if (fTableViewer != null)
		{
			// update column labels
			setColumnHeadings();
			fTableViewer.refresh();
		}
	}


	/* Returns the label of this rendering.
	 *
	 * @return label of this rendering
	 */
	@Override
	public String getLabel() {
		if (fLabel == null) {
			fLabel = buildLabel(true);
		}

		return fLabel;
	}


	/**
	 * Updates the label of this rendering, optionally displaying the
	 * base address of this rendering's memory block.
	 *
	 * @param showAddress whether to display the base address of this
	 *  rendering's memory block in this rendering's label
	 */
	protected void updateRenderingLabel(boolean showAddress)
	{
		fLabel = buildLabel(showAddress);
		firePropertyChangedEvent(new PropertyChangeEvent(this, IBasicPropertyConstants.P_TEXT, null, fLabel));
	}

	private String buildLabel(boolean showAddress) {
		String label = IInternalDebugCoreConstants.EMPTY_STRING;
		if (getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			label = ((IMemoryBlockExtension)getMemoryBlock()).getExpression();
			if (label == null)
			{
				label = DebugUIMessages.AbstractTableRendering_8;
			}

			if (label.startsWith("&")) //$NON-NLS-1$
			 {
				label = "&" + label; //$NON-NLS-1$
			}

			try {
				if (showAddress && ((IMemoryBlockExtension)getMemoryBlock()).getBigBaseAddress() != null)
				{
					label += " : 0x"; //$NON-NLS-1$
					label += ((IMemoryBlockExtension)getMemoryBlock()).getBigBaseAddress().toString(16).toUpperCase();
				}
			} catch (DebugException e) {
				// do nothing, the label will not show the address
			}
		}
		else
		{
			long address = getMemoryBlock().getStartAddress();
			label = Long.toHexString(address).toUpperCase();
		}

		String preName = DebugUITools.getMemoryRenderingManager().getRenderingType(getRenderingId()).getLabel();

		if (preName != null)
		 {
			label += " <" + preName + ">"; //$NON-NLS-1$ //$NON-NLS-2$
		}

		return decorateLabel(label);
	}

	private void setColumnHeadings()
	{
		String[] columnLabels = new String[0];

		IMemoryBlockTablePresentation presentation = getTablePresentationAdapter();
		if (presentation != null)
		{
			columnLabels = presentation.getColumnLabels(getMemoryBlock(), fBytePerLine, getNumCol());
		}

		// check that column labels returned are not null
		if (columnLabels == null) {
			columnLabels = new String[0];
		}

		int numByteColumns = fBytePerLine/fColumnSize;

		TableColumn[] columns = fTableViewer.getTable().getColumns();

		int j=0;
		for (int i=1; i<columns.length-1; i++)
		{
			// if the number of column labels returned is correct
			// use supplied column labels
			if (columnLabels.length == numByteColumns)
			{
				columns[i].setText(columnLabels[j]);
				j++;
			}
			else
			{
				// otherwise, use default
				if (fColumnSize >= 4)
				{
					columns[i].setText(Integer.toHexString(j*fColumnSize).toUpperCase() +
							" - " + Integer.toHexString(j*fColumnSize+fColumnSize-1).toUpperCase()); //$NON-NLS-1$
				}
				else
				{
					columns[i].setText(Integer.toHexString(j*fColumnSize).toUpperCase());
				}
				j++;
			}
		}
	}

	/**
	 * Refresh the table viewer with the current top visible address.
	 * Update labels in the memory rendering.
	 */
	@Override
	public void refresh()
	{
		// refresh at start address of this memory block
		// address may change if expression is evaluated to a different value
		IMemoryBlock mem = getMemoryBlock();
		BigInteger address;

		if (mem instanceof IMemoryBlockExtension)
		{
			try {
				address = ((IMemoryBlockExtension)mem).getBigBaseAddress();
				if (address == null)
				{
					DebugException e = new DebugException(DebugUIPlugin.newErrorStatus(DebugUIMessages.AbstractTableRendering_10, null));
					displayError(e);
					return;
				}
				updateRenderingLabel(true);
				// base address has changed
				if (address.compareTo(fContentProvider.getContentBaseAddress()) != 0)
				{
					// get to new address
					setSelectedAddress(address);
					updateSyncSelectedAddress();

					reloadTable(address, true);

					if (!isDynamicLoad())
					{
						updateSyncPageStartAddress();
						setTopIndex(fTableViewer.getTable(), 0);
					}

					fTopRowAddress = getTopVisibleAddress();
					updateSyncTopAddress();

					fContentInput.updateContentBaseAddress();
				}
				else
				{
					// reload at top of table
					if (isDynamicLoad()) {
						address = getTopVisibleAddress();
					} else {
						address = fContentInput.getLoadAddress();
					}
					reloadTable(address, true);
				}
			} catch (DebugException e) {
				displayError(e);
				return;
			}
		}
		else
		{
			address = BigInteger.valueOf(mem.getStartAddress());
			reloadTable(address, true);
		}
	}

	synchronized private void reloadTable(BigInteger topAddress, boolean updateDelta){

		if (fTableViewer == null) {
			return;
		}

		try
		{
			Table table = (Table)fTableViewer.getControl();

			TableRenderingContentInput input;
			if (isDynamicLoad()) {
				input = new TableRenderingContentInput(this, fPreBuffer, fPostBuffer, topAddress, getNumberOfVisibleLines(), updateDelta, null);
			} else {
				input = new TableRenderingContentInput(this, fContentInput.getPreBuffer(), fContentInput.getPostBuffer(), topAddress, fPageSize, updateDelta, null);
			}

			fContentInput = input;
			fTableViewer.setInput(fContentInput);

			if (isDynamicLoad())
			{
				if (getMemoryBlock() instanceof IMemoryBlockExtension)
				{
					int topIdx = findAddressIndex(topAddress);

					if (topIdx != -1)
					{
						setTopIndex(table, topIdx);
					}
				}

				// cursor needs to be refreshed after reload
				if (isAddressVisible(fSelectedAddress)) {
					setCursorAtAddress(fSelectedAddress);
				}
			}
			else
			{
				if (!isAddressOutOfRange(fSelectedAddress))
				{
					setCursorAtAddress(fSelectedAddress);
					fTableCursor.setVisible(true);
				}
				else
				{
					fTableCursor.setVisible(false);
				}
			}
		}
		finally
		{
		}
	}

	private BigInteger getTopVisibleAddress() {

		if (fTableViewer == null) {
			return BigInteger.valueOf(0);
		}

		Table table = fTableViewer.getTable();
		int topIndex = getTopVisibleIndex(table);

		if (topIndex < 1) { topIndex = 0; }

		if (table.getItemCount() > topIndex)
		{
			TableRenderingLine topItem = (TableRenderingLine)table.getItem(topIndex).getData();

			String calculatedAddress = null;
			if (topItem == null)
			{
				calculatedAddress = table.getItem(topIndex).getText();
			}
			else
			{
				calculatedAddress = topItem.getAddress();
			}

			BigInteger bigInt = new BigInteger(calculatedAddress, 16);
			return bigInt;
		}
		return BigInteger.valueOf(0);
	}

	private int findAddressIndex(BigInteger address)
	{
		TableItem items[] = fTableViewer.getTable().getItems();

		for (int i=0; i<items.length; i++){

			// Again, when the table resizes, the table may have a null item
			// at then end.  This is to handle that.
			if (items[i] != null)
			{
				TableRenderingLine line = (TableRenderingLine)items[i].getData();
				BigInteger lineAddress = new BigInteger(line.getAddress(), 16);
				int addressableUnit = getAddressableUnitPerLine();
				BigInteger endLineAddress = lineAddress.add(BigInteger.valueOf(addressableUnit));

				if (lineAddress.compareTo(address) <= 0 && endLineAddress.compareTo(address) > 0)
				{
					return i;
				}
			}
		}

		return -1;
	}

	private static int getTopVisibleIndex(Table table)
	{
		int index = table.getTopIndex();

		TableItem item;
		try {
			item = table.getItem(index);
		} catch (IllegalArgumentException e) {
			return 0;
		}
		int cnt = table.getItemCount();

		while (item.getBounds(0).y < 0)
		{
			index++;
			if (index >= cnt)
			{
				index--;
				break;
			}
			item = table.getItem(index);
		}

		return index;
	}

	/**
	 * Returns this rendering's table viewer.
	 *
	 * @return the {@link TableViewer}
	 */
	public TableViewer getTableViewer()
	{
		return fTableViewer;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.memory.IMemoryRendering#dispose()
	 */
	@Override
	public void dispose() {
		try {
			// prevent rendering from being disposed again
			if (fIsDisposed) {
				return;
			}

			fIsDisposed = true;

			if (fContentProvider != null) {
				fContentProvider.dispose();
			}

			ScrollBar scroll = ((Table)fTableViewer.getControl()).getVerticalBar();
			if (scroll != null && !scroll.isDisposed()) {
				scroll.removeSelectionListener(fScrollbarSelectionListener);
			}

			if (!fTableCursor.isDisposed())
			{
				fTableCursor.removeTraverseListener(fCursorTraverseListener);
				fTableCursor.removeKeyListener(fCursorKeyAdapter);
				fTableCursor.removeMouseListener(fCursorMouseListener);
			}

			fCursorEditor.dispose();

			fTextViewer = null;
			fTableViewer = null;
			fTableCursor = null;

			// clean up cell editors
			for (int i=0; i<fEditors.length; i++)
			{
				fEditors[i].dispose();
			}

			// remove font change listener when the view tab is disposed
			JFaceResources.getFontRegistry().removeListener(this);

			// remove the view tab from the synchronizer
			IMemoryRenderingSynchronizationService syncService = getMemoryRenderingContainer().getMemoryRenderingSite().getSynchronizationService();
			if (syncService != null) {
				syncService.removePropertyChangeListener(this);
			}

			DebugUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);

			fToolTipShell.dispose();

			if (getPopupMenuManager() != null)
			{
				getPopupMenuManager().removeMenuListener(fMenuListener);
			}

			super.dispose();

		} catch (Exception e) {}
	}

	private int getNumCol() {

		int bytesPerLine = getBytesPerLine();
		int columnSize = getBytesPerColumn();

		return bytesPerLine/columnSize;
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.IMemoryViewTab#setFont(org.eclipse.swt.graphics.Font)
	 */
	private void setFont(Font font)
	{
		int oldIdx = getTopVisibleIndex(fTableViewer.getTable());

		// BUG in table, if font is changed when table is not starting
		// from the top, causes table grid-line to be misaligned.
		setTopIndex(fTableViewer.getTable(),  0);

		// set font
		fTableViewer.getTable().setFont(font);
		fTableCursor.setFont(font);

		setTopIndex(fTableViewer.getTable(),  oldIdx);

		resizeColumnsToPreferredSize();

		// update table cursor and force redraw
		setCursorAtAddress(fSelectedAddress);
	}


	/**
	 * Moves the cursor to the specified address.
	 * Will load more memory if the address is not currently visible.
	 *
	 * @param address address to position cursor at
	 * @throws DebugException if an exception occurs
	 */
	@Override
	public void goToAddress(BigInteger address) throws DebugException {
		Object evtLockClient = new Object();
		try
		{
			if (!fEvtHandleLock.acquireLock(evtLockClient)) {
				return;
			}

			// if address is within the range, highlight
			if (!isAddressOutOfRange(address))
			{
				setSelectedAddress(address);
				updateSyncSelectedAddress();
				setCursorAtAddress(fSelectedAddress);

				// force the cursor to be shown
				if (!isAddressVisible(fSelectedAddress))
				{
					int i = findAddressIndex(fSelectedAddress);
					fTableViewer.getTable().showItem(fTableViewer.getTable().getItem(i));
				}
			}
			else
			{
				// if not extended memory block
				// do not allow user to go to an address that's out of range
				if (!(getMemoryBlock() instanceof IMemoryBlockExtension))
				{
					Status stat = new Status(
					 IStatus.ERROR, DebugUIPlugin.getUniqueIdentifier(),
					 DebugException.NOT_SUPPORTED, DebugUIMessages.AbstractTableRendering_11, null
					);
					DebugException e = new DebugException(stat);
					throw e;
				}

				BigInteger startAdd = fContentInput.getStartAddress();
				BigInteger endAdd = fContentInput.getEndAddress();

				if (address.compareTo(startAdd) < 0 ||
					address.compareTo(endAdd) > 0)
				{
					Status stat = new Status(
					 IStatus.ERROR, DebugUIPlugin.getUniqueIdentifier(),
					 DebugException.NOT_SUPPORTED, DebugUIMessages.AbstractTableRendering_11, null
					);
					DebugException e = new DebugException(stat);
					throw e;
				}

				setSelectedAddress(address);
				updateSyncSelectedAddress();

				reloadTable(address, false);

				if (!isDynamicLoad())
				{
					updateSyncPageStartAddress();
				}

				// if the table is reloaded, the top address is changed in this case
				fTopRowAddress = address;
				updateSyncTopAddress();

				// set the cursor at the selected address after reload
				setCursorAtAddress(address);
			}
			fTableCursor.setVisible(true);
		}
		catch (DebugException e)
		{
			throw e;
		}
		finally
		{
			fEvtHandleLock.releaseLock(evtLockClient);
		}
	}

	/**
	 * Check if address provided is out of buffered range
	 * @param address the address
	 * @return if address is out of buffered range
	 */
	private boolean isAddressOutOfRange(BigInteger address)
	{
		return fContentProvider.isAddressOutOfRange(address);
	}

	/**
	 * Check if address is visible
	 * @param address the address
	 * @return if the given address is visible
	 */
	private boolean isAddressVisible(BigInteger address)
	{
		// if view tab is not yet created
		// cursor should always be visible
		if (!fIsCreated) {
			return true;
		}

		BigInteger topVisible = getTopVisibleAddress();
		int addressableUnit = getAddressableUnitPerLine();
		BigInteger lastVisible = getTopVisibleAddress().add(BigInteger.valueOf((getNumberOfVisibleLines() * addressableUnit) + addressableUnit));

		if (topVisible.compareTo(address) <= 0 && lastVisible.compareTo(address) > 0)
		{
			return true;
		}
		return false;
	}

	/**
	 * Create actions for this rendering
	 */
	protected void createActions() {
		fCopyToClipboardAction = new CopyTableRenderingToClipboardAction(this, fTableViewer);
		fGoToAddressAction = new GoToAddressAction(getMemoryRenderingContainer(), this);
		fResetMemoryBlockAction = new ResetToBaseAddressAction(this);
		fPrintViewTabAction = new PrintTableRenderingAction(this, fTableViewer);

		fFormatRenderingAction = new FormatTableRenderingAction(this);
		fReformatAction = new ReformatAction(this);
		fToggleAddressColumnAction = new ToggleAddressColumnAction();

		IMemoryRenderingSite site = getMemoryRenderingContainer().getMemoryRenderingSite();
		if (site.getSite().getSelectionProvider() != null)
		{
			fPropertiesAction = new PropertyDialogAction(site.getSite(),site.getSite().getSelectionProvider());
		}

		fNextAction = new NextPageAction();
		fPrevAction = new PrevPageAction();
	}

	/**
	 * Handle scrolling and reload table if necessary
	 */
	private synchronized void handleScrollBarSelection()
	{
		Object evtLockClient = new Object();
		try
		{
			if (fIsDisposed) {
				return;
			}

			BigInteger address = getTopVisibleAddress();

			if (!fTopRowAddress.equals(address))
			{
				fTopRowAddress = address;
				updateSyncTopAddress();
			}

			if (!fEvtHandleLock.acquireLock(evtLockClient)) {
				return;
			}

			if (getMemoryBlock() instanceof IMemoryBlockExtension)
			{

				if (isDynamicLoad())
				{
					if (!isAddressOutOfRange(address))
					{
						Table table = fTableViewer.getTable();
						int numInBuffer = table.getItemCount();
						int index = findAddressIndex(address);
						if (index < getBufferThreshold(BUFFER_START))
						{
							if (isAtTopLimit())
							{
								setTopIndex(table, index);
							}
							else if (getBufferThreshold(BUFFER_START) > 0)
							{
								reloadTable(address, false);
							}
						}
						else if (getBufferThreshold(BUFFER_END) != 0 &&
							(numInBuffer-(index+getNumberOfVisibleLines())) <= getBufferThreshold(BUFFER_END))
						{
							if (!isAtBottomLimit() && getBufferThreshold(BUFFER_END) > 0) {
								reloadTable(address, false);
							}
						}
					}
					else
					{
						// approaching limit, reload table
						reloadTable(address, false);
					}
				}

				if (isAddressVisible(fSelectedAddress)) {
					fTableCursor.setVisible(true);
				} else {
					fTableCursor.setVisible(false);
				}
			}
		}
		finally
		{
			fEvtHandleLock.releaseLock(evtLockClient);
		}
	}


	private boolean isAtTopLimit()
	{
		BigInteger startAddress = fContentInput.getStartAddress();
		startAddress = MemoryViewUtil.alignToBoundary(startAddress, getAddressableUnitPerLine() );

		BigInteger startBufferAddress = fContentProvider.getBufferTopAddress();
		startBufferAddress = MemoryViewUtil.alignToBoundary(startBufferAddress, getAddressableUnitPerLine());

		if (startAddress.compareTo(startBufferAddress) == 0) {
			return true;
		}

		return false;
	}

	private boolean isAtBottomLimit()
	{
		BigInteger endAddress = fContentInput.getEndAddress();
		endAddress = MemoryViewUtil.alignToBoundary(endAddress, getAddressableUnitPerLine());

		BigInteger endBufferAddress = fContentProvider.getBufferEndAddress();
		endBufferAddress = MemoryViewUtil.alignToBoundary(endBufferAddress, getAddressableUnitPerLine());

		if (endAddress.compareTo(endBufferAddress) == 0) {
			return true;
		}

		return false;
	}

	private boolean needMoreLines()
	{
		if (getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			Table table = fTableViewer.getTable();
			TableItem firstItem = table.getItem(0);
			TableItem lastItem = table.getItem(table.getItemCount()-1);

			if (firstItem == null || lastItem == null) {
				return true;
			}

			TableRenderingLine first = (TableRenderingLine)firstItem.getData();
			TableRenderingLine last = (TableRenderingLine) lastItem.getData();

			if (first == null ||last == null)
			{
				// For some reason, the table does not return the correct number
				// of table items in table.getItemCount(), causing last to be null.
				// This check is to ensure that we don't get a null pointer exception.
				return true;
			}

			BigInteger startAddress = new BigInteger(first.getAddress(), 16);
			BigInteger lastAddress = new BigInteger(last.getAddress(), 16);
			int addressableUnit = getAddressableUnitPerLine();
			lastAddress = lastAddress.add(BigInteger.valueOf(addressableUnit));

			BigInteger topVisibleAddress = getTopVisibleAddress();
			long numVisibleLines = getNumberOfVisibleLines();
			long numOfBytes = numVisibleLines * addressableUnit;

			BigInteger lastVisibleAddrss = topVisibleAddress.add(BigInteger.valueOf(numOfBytes));

			// if there are only 3 lines left at the top, refresh
			BigInteger numTopLine = topVisibleAddress.subtract(startAddress).divide(BigInteger.valueOf(addressableUnit));
			if (numTopLine.compareTo(BigInteger.valueOf(getBufferThreshold(BUFFER_START))) <= 0 && (startAddress.compareTo(BigInteger.valueOf(0)) != 0))
			{
				if (!isAtTopLimit() && getBufferThreshold(BUFFER_START) > 0) {
					return true;
				}
			}

			// if there are only 3 lines left at the bottom, refresh
			BigInteger numBottomLine = lastAddress.subtract(lastVisibleAddrss).divide(BigInteger.valueOf(addressableUnit));
			if (numBottomLine.compareTo(BigInteger.valueOf(getBufferThreshold(BUFFER_END))) <= 0)
			{
				if (!isAtBottomLimit() && getBufferThreshold(BUFFER_END) > 0) {
					return true;
				}
			}

			return false;
		}

		return false;
	}

	private void handleTableMouseEvent(MouseEvent e) {
		// figure out new cursor position based on here the mouse is pointing
		TableItem[] tableItems = fTableViewer.getTable().getItems();
		TableItem selectedRow = null;
		int colNum = -1;
		int numCol = fTableViewer.getColumnProperties().length;

		for (int j=0; j<tableItems.length; j++)
		{
			TableItem item = tableItems[j];
			for (int i=0; i<numCol; i++)
			{
				Rectangle bound = item.getBounds(i);
				if (bound.contains(e.x, e.y))
				{
					colNum = i;
					selectedRow = item;
					break;
				}
			}
		}

		// if column position cannot be determined, return
		if (colNum < 1) {
			return;
		}

		// handle user mouse click onto table
		// move cursor to new position
		if (selectedRow != null)
		{
			int row = fTableViewer.getTable().indexOf(selectedRow);
			fTableCursor.setVisible(true);
			fTableCursor.setSelection(row, colNum);

			// manually call this since we don't get an event when
			// the table cursor changes selection.
			handleCursorMoved();

			fTableCursor.setFocus();
		}
	}

	/**
	 * Handle column size changed event from synchronizer
	 * @param newColumnSize the new column size
	 */
	private void columnSizeChanged(final int newColumnSize) {
		// ignore event if view tab is disabled
		if (!isVisible()) {
			return;
		}

		Display.getDefault().asyncExec(() -> format(getBytesPerLine(), newColumnSize));
	}

	/**
	 * @param newRowSize - new row size in number of bytes
	 */
	private void rowSizeChanged(final int newRowSize)
	{
		// ignore event if view tab is disabled
		if (!isVisible()) {
			return;
		}

		int bytesPerLine = newRowSize;
		int col = getBytesPerColumn();
		if (bytesPerLine < getBytesPerColumn()) {
			col = bytesPerLine;
		}

		final int columnSize = col;
		final int rowSize = bytesPerLine;
		Display.getDefault().asyncExec(() -> format(rowSize, columnSize));
	}

	private void handleCursorMouseEvent(MouseEvent e){
		if (e.button == 1)
		{
			int col = fTableCursor.getColumn();
			if (col > 0 && col <= (getNumCol())) {
				activateCellEditor(null);
			}
		}
	}

	/**
	 * Activate cell editor and pre-fill it with initial value.
	 * If initialValue is null, use cell content as initial value
	 * @param initialValue the initial value to edit
	 */
	private void activateCellEditor(String initialValue) {

		int col = fTableCursor.getColumn();
		int row = findAddressIndex(fSelectedAddress);

		if (row < 0) {
			return;
		}
		// do not allow user to edit address column
		if (col == 0 || col > getNumCol())
		{
			return;
		}

		ICellModifier cellModifier = null;

		if (fTableViewer == null)
		{
			return;
		}
		cellModifier = fTableViewer.getCellModifier();

		TableItem tableItem = fTableViewer.getTable().getItem(row);

		Object element = tableItem.getData();
		Object property = fTableViewer.getColumnProperties()[col];
		Object value = cellModifier.getValue(element, (String)property);

		// The cell modifier canModify function always returns false if the edit action
		// is not invoked from here.  This is to prevent data to be modified when
		// the table cursor loses focus from a cell.  By default, data will
		// be changed in a table when the cell loses focus.  This is to workaround
		// this default behavior and only change data when the cell editor
		// is activated.
		((TableRenderingCellModifier)cellModifier).setEditActionInvoked(true);
		boolean canEdit = cellModifier.canModify(element, (String)property);
		((TableRenderingCellModifier)cellModifier).setEditActionInvoked(false);

		if (!canEdit) {
			return;
		}

		// activate based on current cursor position
		TextCellEditor selectedEditor = (TextCellEditor)fTableViewer.getCellEditors()[col];


		if (fTableViewer != null && selectedEditor != null)
		{
			// The control that will be the editor must be a child of the Table
			Text text = (Text)selectedEditor.getControl();

			String cellValue  = null;

			if (initialValue != null)
			{
				cellValue = initialValue;
			}
			else
			{
				cellValue = ((String)value);
			}

			text.setText(cellValue);

			fCursorEditor.horizontalAlignment = SWT.LEFT;
			fCursorEditor.grabHorizontal = true;

			// Open the text editor in selected column of the selected row.
			fCursorEditor.setEditor (text, tableItem, col);

			// Assign focus to the text control
			selectedEditor.setFocus();

			if (initialValue != null)
			{
				text.clearSelection();
			}

			text.setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));

			// add listeners for the text control
			addListeners(text);

			// move cursor below text control
			fTableCursor.moveBelow(text);
		}
	}

	/**
	 * @param text the {@link Text} widget to add the listeners to
	 */
	private void addListeners(Text text) {
		fEditorFocusListener = new FocusAdapter() {
			@Override
			public void focusLost(FocusEvent e)
			{
				handleTableEditorFocusLost(e);
			}
		};
		text.addFocusListener(fEditorFocusListener);

		fEditorKeyListener = new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent e) {
				handleKeyEventInEditor(e);
			}
		};

		text.addKeyListener(fEditorKeyListener);
	}

	/**
	 * @param text the {@link Text} widget to remove the listeners from
	 */
	private void removeListeners(Text text) {

		text.removeFocusListener(fEditorFocusListener);
		text.removeKeyListener(fEditorKeyListener);
	}

	private void handleTableEditorFocusLost(FocusEvent event)
	{
		final FocusEvent e = event;

		Display.getDefault().syncExec(() -> {
			try
			{
				int row = findAddressIndex(fSelectedAddress);
				int col = fTableCursor.getColumn();

				Text text = (Text) e.getSource();
				removeListeners(text);

				// get new value
				String newValue = text.getText();

				// modify memory at fRow and fCol
				modifyValue(row, col, newValue);

				// show cursor after modification is completed
				setCursorAtAddress(fSelectedAddress);
				fTableCursor.moveAbove(text);
				fTableCursor.setVisible(false);
				fTableCursor.setVisible(true);
			} catch (NumberFormatException e1) {
				MemoryViewUtil.openError(DebugUIMessages.MemoryViewCellModifier_failure_title,
						DebugUIMessages.MemoryViewCellModifier_data_is_invalid, null);
			}
		});
	}

	/**
	 * @param event the {@link KeyEvent}
	 */
	private void handleKeyEventInEditor(KeyEvent event) {
		final KeyEvent e = event;
		Display.getDefault().asyncExec(() -> {
			Text text = (Text) e.getSource();
			int row = findAddressIndex(fSelectedAddress);
			int col = fTableCursor.getColumn();

			try
			{
				switch (e.keyCode)
				{
				case SWT.ARROW_UP:

					// move text editor box up one row
					if (row - 1 < 0) {
						return;
					}

					// modify value for current cell
					modifyValue(row, col, text.getText());

					row--;

					// update cursor location and selection in table
					fTableCursor.setSelection(row, col);
					handleCursorMoved();

					// remove listeners when focus is lost
					removeListeners(text);
					activateCellEditor(null);
					break;
				case SWT.ARROW_DOWN:

					// move text editor box down one row

					if (row + 1 >= fTableViewer.getTable().getItemCount()) {
						return;
					}

					// modify value for current cell
					modifyValue(row, col, text.getText());

					row++;

					// update cursor location and selection in table
					fTableCursor.setSelection(row, col);
					handleCursorMoved();

					// remove traverse listener when focus is lost
					removeListeners(text);
					activateCellEditor(null);
					break;
				case 0:

					// if user has entered the max number of characters allowed in a cell, move to
					// next cell
					// Extra changes will be used as initial value for the next cell
					int numCharsPerByte = getNumCharsPerByte();
					if (numCharsPerByte > 0) {
						if (text.getText().length() > getBytesPerColumn() * numCharsPerByte)
							{
							String newValue1 = text.getText();
							text.setText(newValue1.substring(0, getBytesPerColumn() * numCharsPerByte));

							modifyValue(row, col, text.getText());

							// if cursor is at the end of a line, move to next line
							if (col >= getNumCol()) {
								col = 1;
								row++;
							} else {
								// move to next column
								row++;
							}

							// update cursor position and selected address
							fTableCursor.setSelection(row, col);
							handleCursorMoved();

							removeListeners(text);

							// activate text editor at next cell
							activateCellEditor(newValue1.substring(getBytesPerColumn() * numCharsPerByte));
							}
					}
					break;
				case SWT.ESC:

					// if user has pressed escape, do not commit the changes
					// that's why "modifyValue" is not called
					fTableCursor.setSelection(row, col);
					handleCursorMoved();

					removeListeners(text);

					// cursor needs to have focus to remove focus from cell editor
					fTableCursor.setFocus();
					break;
				default:
					numCharsPerByte = getNumCharsPerByte();
					if (numCharsPerByte > 0) {
						if (text.getText().length() > getBytesPerColumn() * numCharsPerByte)
							{
							String newValue2 = text.getText();
							text.setText(newValue2.substring(0, getBytesPerColumn() * numCharsPerByte));
							modifyValue(row, col, text.getText());
							// if cursor is at the end of a line, move to next line
							if (col >= getNumCol()) {
								col = 1;
								row++;
							} else
								{
								col++;
							}

							fTableCursor.setSelection(row, col);
							handleCursorMoved();

							removeListeners(text);

							activateCellEditor(newValue2.substring(getBytesPerColumn() * numCharsPerByte));
							}
					}
					break;
				}
			} catch (NumberFormatException e1) {
				MemoryViewUtil.openError(DebugUIMessages.MemoryViewCellModifier_failure_title,
						DebugUIMessages.MemoryViewCellModifier_data_is_invalid, null);

				fTableCursor.setSelection(row, col);
				handleCursorMoved();

				removeListeners(text);
			}
		});
	}


	/**
	 * Modify value and send new value to debug adapter
	 * @param row the row
	 * @param col the column
	 * @param newValue the new value
	 * @throws NumberFormatException if the {@link ICellModifier} cannot convert the new value to a string - in cases where it needs to do so
	 */
	private void modifyValue(int row, int col, String newValue) throws NumberFormatException
	{
		if (newValue.length() == 0)
		{
			// do not do anything if user has not entered anything
			return;
		}

		TableItem tableItem = fTableViewer.getTable().getItem(row);

		Object property = fTableViewer.getColumnProperties()[col];
		fTableViewer.getCellModifier().modify(tableItem, (String)property, newValue);
	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.memory.IMemoryRendering#becomesHidden()
	 */
	@Override
	public void becomesHidden() {

		if (isVisible() == false)
		{
			// super should always be called
			super.becomesHidden();
			return;
		}

		super.becomesHidden();

		if (getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			updateRenderingLabel(false);
		}

		// once the view tab is disabled, all deltas information becomes invalid.
		// reset changed information and recompute if data has really changed when
		// user revisits the same tab.
		fContentProvider.resetDeltas();

	}

	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.memory.IMemoryRendering#becomesVisible()
	 */
	@Override
	public void becomesVisible() {

		// do not do anything if already visible
		if (isVisible() == true)
		{
			// super should always be called
			super.becomesVisible();
			return;
		}

		super.becomesVisible();

		boolean value = DebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM);
		if (value != isDynamicLoad()) {
			// this call will cause a reload
			handleDyanicLoadChanged();
		} else {
			refresh();
		}

		synchronize();
		updateRenderingLabel(true);
	}

	/**
	 * Resets this memory rendering.
	 * The cursor will be moved to the base address of the memory block.
	 * The table will be positioned to have the base address
	 * at the top.
	 *
	 * @deprecated use <code>resetRendering</code> to reset this rendering.
	 */
	@Deprecated
	public void reset()
	{
		try {
			resetToBaseAddress();
		} catch (DebugException e) {
			MemoryViewUtil.openError(DebugUIMessages.AbstractTableRendering_12, DebugUIMessages.AbstractTableRendering_13, e); //
		}
	}

	/**
	 * Reset this rendering to the base address.
	 * The cursor will be moved to the base address of the memory block.
	 * The table will be positioned to have the base address
	 * at the top.
	 * @throws DebugException is an exception occurs
	 */
	private void resetToBaseAddress() throws DebugException
	{
		BigInteger baseAddress;

		if (getMemoryBlock() instanceof IMemoryBlockExtension)
		{
			baseAddress = ((IMemoryBlockExtension)getMemoryBlock()).getBigBaseAddress();
		}
		else
		{
			baseAddress = BigInteger.valueOf(getMemoryBlock().getStartAddress());
		}

		goToAddress(baseAddress);
		topVisibleAddressChanged(baseAddress, true);
	}

	/**
	 * Returns the currently selected address in this rendering.
	 *
	 * @return the currently selected address in this rendering
	 */
	@Override
	public BigInteger getSelectedAddress() {
		return fSelectedAddress;
	}

	/**
	 * Returns the currently selected content in this rendering as a String.
	 *
	 * @return the currently selected content in this rendering
	 */
	@Override
	public String getSelectedAsString() {

		if (isAddressOutOfRange(fSelectedAddress)) {
			return IInternalDebugCoreConstants.EMPTY_STRING;
		}

		int col = fTableCursor.getColumn();
		TableItem rowItem = fTableCursor.getRow();
		int row = fTableViewer.getTable().indexOf(rowItem);

		if (col == 0)
		{
			return rowItem.getText(0);
		}

		// check precondition
		if (col > getBytesPerLine()/getBytesPerColumn())
		{
			return IInternalDebugCoreConstants.EMPTY_STRING;
		}

		TableItem tableItem = getTableViewer().getTable().getItem(row);

		return tableItem.getText(col);
	}

	/**
	 * Returns the currently selected content in this rendering as MemoryByte.
	 *
	 * @return the currently selected content in array of MemoryByte.
	 * Returns an empty array if the selected address is out of buffered range.
	 */
	@Override
	public MemoryByte[] getSelectedAsBytes()
	{
		if (isAddressOutOfRange(fSelectedAddress)) {
			return new MemoryByte[0];
		}

		int col = fTableCursor.getColumn();
		TableItem rowItem = fTableCursor.getRow();

		// check precondition
		if (col == 0 || col > getBytesPerLine()/getBytesPerColumn())
		{
			return new MemoryByte[0];
		}

		Object data = rowItem.getData();
		if (data == null || !(data instanceof TableRenderingLine)) {
			return new MemoryByte[0];
		}

		TableRenderingLine line = (TableRenderingLine)data;
		int offset = (col-1)*(getAddressableUnitPerColumn()*getAddressableSize());
		int end = offset + (getAddressableUnitPerColumn()*getAddressableSize());

		// make a copy of the bytes to ensure that data cannot be changed
		// by caller
		MemoryByte[] bytes = line.getBytes(offset, end);
		MemoryByte[] retBytes = new MemoryByte[bytes.length];

		System.arraycopy(bytes, 0, retBytes, 0, bytes.length);

		return retBytes;
	}

	/**
	 * Returns the number of characters a byte will convert to
	 * or -1 if unknown.
	 *
	 * @return the number of characters a byte will convert to
	 *  or -1 if unknown
	 */
	@Override
	public int getNumCharsPerByte()
	{
		return -1;
	}

	private int getMinTableItemHeight(Table table){

		// Hack to get around Linux GTK problem.
		// On Linux GTK, table items have variable item height as
		// carriage returns are actually shown in a cell.  Some rows will be
		// taller than others.  When calculating number of visible lines, we
		// need to find the smallest table item height.  Otherwise, the rendering
		// underestimates the number of visible lines.  As a result the rendering
		// will not be able to get more memory as needed.
		if (MemoryViewUtil.isLinuxGTK())
		{
			// check each of the items and find the minimum
			TableItem[] items = table.getItems();
			int minHeight = table.getItemHeight();
			for (int i=0; i<items.length; i++)
			{
				minHeight = Math.min(items[i].getBounds(0).height, minHeight);
			}

			return minHeight;

		}
		return table.getItemHeight();
	}

	/* (non-Javadoc)
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapter) {

		if (adapter == IColorProvider.class) {
			return (T) getColorProviderAdapter();
		}

		if (adapter == ILabelProvider.class) {
			return (T) getLabelProviderAdapter();
		}

		if (adapter == IFontProvider.class) {
			return (T) getFontProviderAdapter();
		}

		if (adapter == IMemoryBlockTablePresentation.class) {
			return (T) getTablePresentationAdapter();
		}

		if (adapter == IWorkbenchAdapter.class)
		{
			// needed workbench adapter to fill the title of property page
			if (fWorkbenchAdapter == null) {
				fWorkbenchAdapter = new IWorkbenchAdapter() {
					@Override
					public Object[] getChildren(Object o) {
						return new Object[0];
					}

					@Override
					public ImageDescriptor getImageDescriptor(Object object) {
						return null;
					}

					@Override
					public String getLabel(Object o) {
						return AbstractTableRendering.this.getLabel();
					}

					@Override
					public Object getParent(Object o) {
						return null;
					}
				};
			}
			return (T) fWorkbenchAdapter;
		}

		if (adapter == IMemoryBlockConnection.class) {
			if (fConnection == null) {
				fConnection = () -> {
					try {
						fContentProvider.takeContentSnapshot();
						if (getMemoryBlock() instanceof IMemoryBlockExtension) {
							BigInteger address = ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress();
							if (address.compareTo(fContentProvider.getContentBaseAddress()) != 0)
							{
								// get to new address
								setSelectedAddress(address);
								updateSyncSelectedAddress();
								fTopRowAddress = address;
								fContentInput.updateContentBaseAddress();
								fContentInput.setLoadAddress(address);
							}
							fContentProvider.loadContentForExtendedMemoryBlock();
						} else {
							fContentProvider.loadContentForSimpleMemoryBlock();
						}

						// update UI asynchronously
						Display display = DebugUIPlugin.getDefault().getWorkbench().getDisplay();
						display.asyncExec(() -> {
							updateLabels();

							if (getMemoryBlock() instanceof IMemoryBlockExtension) {
								int topIdx = findAddressIndex(fTopRowAddress);
								if (topIdx != -1) {
									setTopIndex(fTableViewer.getTable(), topIdx);
								}
							}

							// cursor needs to be refreshed after reload
							if (isAddressVisible(fSelectedAddress)) {
								setCursorAtAddress(fSelectedAddress);
								fTableCursor.setVisible(true);
								fTableCursor.redraw();
							} else {
								fTableCursor.setVisible(false);
							}

							if (!isDynamicLoad()) {
								updateSyncPageStartAddress();
							}

							updateSyncTopAddress();
						});
					} catch (DebugException e) {
						displayError(e);
					}
				};
			}
			return (T) fConnection;
		}

		return super.getAdapter(adapter);
	}

	private boolean hasCustomizedDecorations()
	{
		if (getFontProviderAdapter() == null &&
			getColorProviderAdapter() == null &&
			getLabelProviderAdapter() == null) {
			return false;
		}
		return true;
	}

	private boolean isBaseAddressChanged()
	{
		try {
			IMemoryBlock mb = getMemoryBlock();
			if (mb instanceof IMemoryBlockExtension)
			{
				BigInteger baseAddress = ((IMemoryBlockExtension)mb).getBigBaseAddress();
				if (baseAddress != null)
				{
					if (!baseAddress.equals(fContentInput.getContentBaseAddress())) {
						return true;
					}
				}
			}
		} catch (DebugException e1) {
			return false;
		}
		return false;
	}

	/**
	 * Returns the color provider for this rendering's memory block or
	 * <code>null</code> if none.
	 * <p>
	 * By default a color provider is obtained by asking this rendering's
	 * memory block for its {@link IColorProvider} adapter. When the color
	 * provider is queried for color information, it is provided with a
	 * {@link MemoryRenderingElement} as an argument.
	 * </p>
	 * @return the color provider for this rendering's memory block,
	 *  or <code>null</code>
	 */
	protected IColorProvider getColorProviderAdapter()
	{
		return getMemoryBlock().getAdapter(IColorProvider.class);
	}

	/**
	 * Returns the label provider for this rendering's memory block or
	 * <code>null</code> if none.
	 * <p>
	 * By default a label provider is obtained by asking this rendering's
	 * memory block for its {@link ILabelProvider} adapter. When the label
	 * provider is queried for label information, it is provided with a
	 * {@link MemoryRenderingElement} as an argument.
	 * </p>
	 * @return the label provider for this rendering's memory block,
	 *  or <code>null</code>
	 */
	protected ILabelProvider getLabelProviderAdapter()
	{
		return getMemoryBlock().getAdapter(ILabelProvider.class);
	}

	/**
	 * Returns the font provider for this rendering's memory block or
	 * <code>null</code> if none.
	 * <p>
	 * By default a font provider is obtained by asking this rendering's
	 * memory block for its {@link IFontProvider} adapter. When the font
	 * provider is queried for font information, it is provided with a
	 * {@link MemoryRenderingElement} as an argument.
	 * </p>
	 * @return the font provider for this rendering's memory block,
	 *  or <code>null</code>
	 */
	protected IFontProvider getFontProviderAdapter()
	{
		return getMemoryBlock().getAdapter(IFontProvider.class);
	}

	/**
	 * Returns the table presentation for this rendering's memory block or
	 * <code>null</code> if none.
	 * <p>
	 * By default a table presentation is obtained by asking this rendering's
	 * memory block for its {@link IMemoryBlockTablePresentation} adapter.
	 * </p>
	 * @return the table presentation for this rendering's memory block,
	 *  or <code>null</code>
	 */
	protected IMemoryBlockTablePresentation getTablePresentationAdapter()
	{
		return getMemoryBlock().getAdapter(IMemoryBlockTablePresentation.class);
	}

	private boolean isDynamicLoad()
	{
		return fContentProvider.isDynamicLoad();
	}

	private int getPageSizeInUnits()
	{
		return fPageSize * getAddressableUnitPerLine();
	}

	private void setSelectedAddress(BigInteger address)
	{
		fSelectedAddress = address;
	}

	/**
	 * Setup the viewer so it supports hovers to show the offset of each field
	 */
	private void createToolTip() {

		fToolTipShell = new Shell(DebugUIPlugin.getShell(), SWT.ON_TOP | SWT.RESIZE );
		GridLayout gridLayout = new GridLayout();
		gridLayout.numColumns = 1;
		gridLayout.marginWidth = 2;
		gridLayout.marginHeight = 0;
		fToolTipShell.setLayout(gridLayout);
		fToolTipShell.setBackground(fTableViewer.getTable().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));

		final Control toolTipControl = createToolTipControl(fToolTipShell);

		if (toolTipControl == null)
		{
			// if client decide not to use tooltip support
			fToolTipShell.dispose();
			return;
		}

		MouseTrackAdapter listener = new MouseTrackAdapter(){

			private TableItem fTooltipItem = null;
			private int fCol = -1;

			@Override
			public void mouseExit(MouseEvent e){

				if (!fToolTipShell.isDisposed()) {
					fToolTipShell.setVisible(false);
				}
				fTooltipItem = null;
			}

			@Override
			public void mouseHover(MouseEvent e){

				Point hoverPoint = new Point(e.x, e.y);
				Control control = null;

				if (e.widget instanceof Control) {
					control = (Control)e.widget;
				}

				if (control == null) {
					return;
				}

				hoverPoint = control.toDisplay(hoverPoint);
				TableItem item = getItem(hoverPoint);
				int column = getColumn(hoverPoint);

				//Only if there is a change in hover
				if(this.fTooltipItem != item || fCol != column){

					//Keep Track of the latest hover
					fTooltipItem = item;
					fCol = column;

					if(item != null){
						toolTipAboutToShow(toolTipControl, fTooltipItem, column);

						//Setting location of the tooltip
						Rectangle shellBounds = fToolTipShell.getBounds();
						shellBounds.x = hoverPoint.x;
						shellBounds.y = hoverPoint.y + item.getBounds(0).height;

						fToolTipShell.setBounds(shellBounds);
						fToolTipShell.pack();

						fToolTipShell.setVisible(true);
					}
					else {
						fToolTipShell.setVisible(false);
					}
				}
			}
		};

		fTableViewer.getTable().addMouseTrackListener(listener);
		fTableCursor.addMouseTrackListener(listener);
	}

	/**
	 * Bug with table widget,BUG 113015, the widget is not able to return the correct
	 * table item if SWT.FULL_SELECTION is not on when the table is created.
	 * Created the following function to work around the problem.
	 * We can remove this method when the bug is fixed.
	 * @param point the {@link Point} to get the {@link TableItem} from
	 * @return the table item where the point is located, return null if the item cannot be located.
	 */
	private TableItem getItem(Point point)
	{
		TableItem[] items = fTableViewer.getTable().getItems();
		for (int i=0; i<items.length; i++)
		{
			Point start = new Point(items[i].getBounds(0).x, items[i].getBounds(0).y);
			start = fTableViewer.getTable().toDisplay(start);
			Point end = new Point(start.x + items[i].getBounds(0).width, start.y + items[i].getBounds(0).height);

			if (start.y < point.y && point.y < end.y) {
				return items[i];
			}
		}
		return null;
	}

	/**
	 * Method for figuring out which column the point is located.
	 * @param point the {@link Point} to et the column number for
	 * @return the column index where the point is located, return -1 if column is not found.
	 */
	private int getColumn(Point point) {
		int colCnt = fTableViewer.getTable().getColumnCount();
		if(fTableViewer.getTable().getItemCount() > 0) {
			TableItem item = fTableViewer.getTable().getItem(0);
			Point start, end;
			for (int i=0; i<colCnt; i++) {
				start = new Point(item.getBounds(i).x, item.getBounds(i).y);
				start = fTableViewer.getTable().toDisplay(start);
				end = new Point(start.x + item.getBounds(i).width, start.y + item.getBounds(i).height);
				if (start.x < point.x && end.x > point.x) {
					return i;
				}
			}
		}
		return -1;
	}

	/**
	 * Creates the control used to display tool tips for cells in this table. By default
	 * a label is used to display the address of the cell. Clients may override this
	 * method to create custom tooltip controls.
	 * <p>
	 * Also see the methods <code>getToolTipText(...)</code> and
	 * <code>toolTipAboutToShow(...)</code>.
	 * </p>
	 * @param composite parent for the tooltip control
	 * @return the tooltip control to be displayed
	 * @since 3.2
	 */
	protected Control createToolTipControl(Composite composite) {
		Control fToolTipLabel = new Label(composite, SWT.NONE);
		fToolTipLabel.setForeground(fTableViewer.getTable().getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND));
		fToolTipLabel.setBackground(fTableViewer.getTable().getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND));
		fToolTipLabel.setLayoutData(new GridData(GridData.FILL_HORIZONTAL |
				GridData.VERTICAL_ALIGN_CENTER));
		return fToolTipLabel;
	}


	/* (non-Javadoc)
	 * @see org.eclipse.debug.ui.memory.IResettableMemoryRendering#resetRendering()
	 */
	@Override
	public void resetRendering() throws DebugException {
		resetToBaseAddress();
	}

	/**
	 * Called when the tool tip is about to show in this rendering.
	 * Clients who overrides <code>createTooltipControl</code> may need to
	 * also override this method to ensure that the tooltip shows up properly
	 * in their customized control.
	 * <p>
	 * By default a text tooltip is displayed, and the contents for the tooltip
	 * are generated by the <code>getToolTipText(...)</code> method.
	 * </p>
	 * @param toolTipControl - the control for displaying the tooltip
	 * @param item - the table item where the mouse is pointing.
	 * @param col - the column at which the mouse is pointing.
	 * @since 3.2
	 */
	protected void toolTipAboutToShow(Control toolTipControl, TableItem item,
			int col) {
		if (toolTipControl instanceof Label) {
			BigInteger address = getAddressFromTableItem(item, col);
			if (address != null) {
				Object data = item.getData();
				if (data instanceof TableRenderingLine) {
					TableRenderingLine line = (TableRenderingLine) data;

					if (col > 0) {
						int start = (col - 1) * getBytesPerColumn();
						int end = start + getBytesPerColumn();
						MemoryByte[] bytes = line.getBytes(start, end);

						String str = getToolTipText(address, bytes);

						if (str != null) {
							((Label) toolTipControl).setText(str);
						}
					} else {
						String str = getToolTipText(address,
								new MemoryByte[] {});

						if (str != null) {
							((Label) toolTipControl).setText(str);
						}
					}
				}
			}
		}
	}

	/**
	 * Returns the text to display in a tool tip at the specified address
	 * for the specified bytes. By default the address of the bytes is displayed.
	 * Subclasses may override.
	 *
	 * @param address address of cell that tool tip is displayed for
	 * @param bytes the bytes in the cell
	 * @return the tooltip text for the memory bytes located at the specified
	 *         address
	 * @since 3.2
	 */
	protected String getToolTipText(BigInteger address, MemoryByte[] bytes)
	{
		StringBuffer buf = new StringBuffer("0x"); //$NON-NLS-1$
		buf.append(address.toString(16).toUpperCase());

		return buf.toString();
	}


	private String getRowPrefId(String modelId) {
		String rowPrefId = IDebugPreferenceConstants.PREF_ROW_SIZE + ":" + modelId; //$NON-NLS-1$
		return rowPrefId;
	}

	private String getColumnPrefId(String modelId) {
		String colPrefId = IDebugPreferenceConstants.PREF_COLUMN_SIZE + ":" + modelId; //$NON-NLS-1$
		return colPrefId;
	}

	/**
	 * @param modelId the debug model identifier
	 * @return default number of addressable units per line for the model
	 */
	private int getDefaultRowSizeByModel(String modelId)
	{
		int row = DebugUITools.getPreferenceStore().getInt(getRowPrefId(modelId));
		if (row == 0)
		{
			DebugUITools.getPreferenceStore().setValue(getRowPrefId(modelId), IDebugPreferenceConstants.PREF_ROW_SIZE_DEFAULT);
		}

		row = DebugUITools.getPreferenceStore().getInt(getRowPrefId(modelId));
		return row;

	}

	/**
	 * @param modelId the debug model identifier
	 * @return default number of addressable units per column for the model
	 */
	private int getDefaultColumnSizeByModel(String modelId)
	{
		int col = DebugUITools.getPreferenceStore().getInt(getColumnPrefId(modelId));
		if (col == 0)
		{
			DebugUITools.getPreferenceStore().setValue(getColumnPrefId(modelId), IDebugPreferenceConstants.PREF_COLUMN_SIZE_DEFAULT);
		}

		col = DebugUITools.getPreferenceStore().getInt(getColumnPrefId(modelId));
		return col;
	}

	private int getBufferThreshold(int startOrEnd)
	{
		if (startOrEnd == BUFFER_START)
		{
			if (BUFFER_THRESHOLD > fPreBuffer) {
				return fPreBuffer;
			}
			return BUFFER_THRESHOLD;
		}

		if (BUFFER_THRESHOLD > fPostBuffer) {
			return fPostBuffer;
		}

		return BUFFER_THRESHOLD;
	}


	/**
	 * Returns text for the given memory bytes at the specified address for the specified
	 * rendering type. This is called by the label provider for.
	 * Subclasses must override.
	 *
	 * @param renderingTypeId rendering type identifier
	 * @param address address where the bytes belong to
	 * @param data the bytes
	 * @return a string to represent the memory. Cannot not return <code>null</code>.
	 * 	Returns a string to pad the cell if the memory cannot be converted
	 *  successfully.
	 */
	@Override
	abstract public String getString(String renderingTypeId, BigInteger address, MemoryByte[] data);

	/**
	 * Returns bytes for the given text corresponding to bytes at the given
	 * address for the specified rendering type. This is called by the cell modifier
	 * when modifying bytes in a memory block.
	 * Subclasses must convert the string value to an array of bytes.  The bytes will
	 * be passed to the debug adapter for memory block modification.
	 * Returns <code>null</code> if the bytes cannot be formatted properly.
	 *
	 * @param renderingTypeId rendering type identifier
	 * @param address address the bytes begin at
	 * @param currentValues current values of the data in bytes format
	 * @param newValue the string to be converted to bytes
	 * @return the bytes converted from a string
	 */
	@Override
	abstract public byte[] getBytes(String renderingTypeId, BigInteger address, MemoryByte[] currentValues, String newValue);


}

