/*******************************************************************************
 *  Copyright (c) 2006, 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
 *     Teodor Madan (Freescale) -  Bug 292360 -  [Memory View] platform renderings do not implement correctly IMemoryRendering#getControl
 *     Teodor Madan (Freescale) -  Bug 292426 -  [Memory View] platform renderings cannot be repositioned from non-UI thread through calls to IRepositionableMemoryRendering#goToAddress
 *     Teodor Madan (Freescale) & Jeremiah Swan (IBM) - Bug 300036 -  [Memory View] NPE in AbstractAsyncTableRendering#getSelectedAddress on rendering creation
 *******************************************************************************/

package org.eclipse.debug.internal.ui.memory.provisional;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.Command;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
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.IPersistableDebugElement;
import org.eclipse.debug.internal.ui.preferences.IDebugPreferenceConstants;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
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.AbstractVirtualContentTableModel;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncCopyTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncPrintTableRenderingAction;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncTableRenderingCellModifier;
import org.eclipse.debug.internal.ui.views.memory.renderings.AsyncTableRenderingViewer;
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.GoToAddressComposite;
import org.eclipse.debug.internal.ui.views.memory.renderings.IPresentationErrorListener;
import org.eclipse.debug.internal.ui.views.memory.renderings.IVirtualContentListener;
import org.eclipse.debug.internal.ui.views.memory.renderings.MemorySegment;
import org.eclipse.debug.internal.ui.views.memory.renderings.PendingPropertyChanges;
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.TableRenderingContentDescriptor;
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.debug.ui.memory.AbstractTableRendering;
import org.eclipse.debug.ui.memory.IMemoryBlockTablePresentation;
import org.eclipse.debug.ui.memory.IMemoryRendering;
import org.eclipse.debug.ui.memory.IMemoryRenderingContainer;
import org.eclipse.debug.ui.memory.IMemoryRenderingSite;
import org.eclipse.debug.ui.memory.IMemoryRenderingSynchronizationService;
import org.eclipse.debug.ui.memory.IMemoryRenderingType;
import org.eclipse.debug.ui.memory.IResettableMemoryRendering;
import org.eclipse.debug.ui.memory.MemoryRenderingElement;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.IDialogConstants;
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.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TextCellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
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.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.Button;
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.Menu;
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.IWorkbench;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.commands.ICommandService;
import org.eclipse.ui.contexts.IContextActivation;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.dialogs.PropertyDialogAction;
import org.eclipse.ui.model.IWorkbenchAdapter;
import org.eclipse.ui.part.PageBook;
import org.eclipse.ui.progress.UIJob;

/**
 * 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.2
 */
public abstract class AbstractAsyncTableRendering 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 = AbstractTableRendering.PROPERTY_SELECTED_ADDRESS;

	/**
	 * 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 = AbstractTableRendering.PROPERTY_COL_SIZE;

	/**
	 * 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 = AbstractTableRendering.PROPERTY_TOP_ADDRESS;

	private static final String ID_ASYNC_TABLE_RENDERING_CONTEXT = "org.eclipse.debug.ui.memory.abstractasynctablerendering"; //$NON-NLS-1$
	private static final String ID_GO_TO_ADDRESS_COMMAND = "org.eclipse.debug.ui.command.gotoaddress"; //$NON-NLS-1$
	private static final String ID_NEXT_PAGE_COMMAND = "org.eclipse.debug.ui.command.nextpage"; //$NON-NLS-1$
	private static final String ID_PREV_PAGE_COMMAND = "org.eclipse.debug.ui.command.prevpage"; //$NON-NLS-1$

	/**
	 * Property identifier for the row size in a table rendering This property
	 * is used for synchronization between renderings.
	 */
	public static final String PROPERTY_ROW_SIZE = AbstractTableRendering.PROPERTY_ROW_SIZE;

	private static final int DEFAULT_BUFFER_THRESHOLD = 1;

	private boolean fActivated = false;

	// TODO: review use of MemorySegment, need to be careful to ensure flexible
	// hierarchy

	private class ToggleAddressColumnAction extends Action {

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

		@Override
		public void run() {
			fIsShowAddressColumn = !fIsShowAddressColumn;
			if (!fIsShowAddressColumn) {
				fTableViewer.getTable().getColumn(0).setWidth(0);
			} else {
				fTableViewer.getTable().getColumn(0).pack();
			}
			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 = fContentDescriptor.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 = fContentDescriptor.getLoadAddress();
			address = address.subtract(BigInteger.valueOf(getPageSizeInUnits()));
			handlePageStartAddressChanged(address);
		}
	}

	private class RenderingGoToAddressAction extends GoToAddressAction {
		public RenderingGoToAddressAction(IMemoryRenderingContainer container, AbstractBaseTableRendering rendering) {
			super(container, rendering);
		}

		@Override
		public void run() {
			showGoToAddressComposite();
		}
	}

	private class SwitchPageJob extends UIJob {
		private final Object fLock = new Object();
		private boolean fShowMessagePage = false;
		private String fMessage = IInternalDebugCoreConstants.EMPTY_STRING;

		private SwitchPageJob() {
			super("SwitchPageJob");//$NON-NLS-1$
			setSystem(true);
		}

		private void setShowMessagePage(boolean showMsg) {
			synchronized (fLock) {
				fShowMessagePage = showMsg;
			}
		}

		private void setMessage(String message) {
			synchronized (fLock) {
				fMessage = message;
			}
		}

		@Override
		public IStatus runInUIThread(IProgressMonitor monitor) {

			if (fPageBook.isDisposed()) {
				return Status.OK_STATUS;
			}

			String msgToShow = null;
			boolean showMsgPage = false;
			synchronized (fLock) {
				msgToShow = fMessage;
				showMsgPage = fShowMessagePage;
			}

			if (showMsgPage) {
				StyledText styleText = null;
				fShowMessage = true;

				styleText = fTextViewer.getTextWidget();

				if (styleText != null) {
					styleText.setText(msgToShow);
				}
				fPageBook.showPage(fTextViewer.getControl());
			} else {
				fShowMessage = false;
				fPageBook.showPage(fTableViewer.getControl().getParent());
			}
			return Status.OK_STATUS;
		}
	}

	private static class SerialByObjectRule implements ISchedulingRule {
		private Object fObject = null;

		public SerialByObjectRule(Object lock) {
			fObject = lock;
		}

		@Override
		public boolean contains(ISchedulingRule rule) {
			return rule == this;
		}

		@Override
		public boolean isConflicting(ISchedulingRule rule) {
			if (rule instanceof SerialByObjectRule) {
				SerialByObjectRule rRule = (SerialByObjectRule) rule;
				return fObject == rRule.fObject;
			}
			return false;
		}
	}

	private PageBook fPageBook;
	private AsyncTableRenderingViewer fTableViewer;
	private TextViewer fTextViewer;
	private Shell fToolTipShell;
	private MemoryViewPresentationContext fPresentationContext;
	private int fAddressableSize;
	private TableRenderingContentDescriptor fContentDescriptor;
	private int fBytePerLine;
	private int fColumnSize;
	private boolean fShowMessage = false;
	private String fLabel;
	private IWorkbenchAdapter fWorkbenchAdapter;
	private int fPageSize;
	private int fPreBufferSize = -1;
	private int fPostBufferSize = -1;
	private SashForm fSashForm;
	private GoToAddressComposite fGoToAddressComposite;

	// actions
	private GoToAddressAction fGoToAddressAction;
	private PrintTableRenderingAction fPrintViewTabAction;
	private CopyTableRenderingToClipboardAction fCopyToClipboardAction;
	private FormatTableRenderingAction fFormatRenderingAction;
	private ReformatAction fReformatAction;
	private ToggleAddressColumnAction fToggleAddressColumnAction;
	private ResetToBaseAddressAction fResetMemoryBlockAction;
	private PropertyDialogAction fPropertiesDialogAction;
	private NextPageAction fNextAction;
	private PrevPageAction fPrevAction;

	private final ArrayList<IContextActivation> fContext = new ArrayList<>();
	private AbstractHandler fGoToAddressHandler;

	private AbstractHandler fNextPageHandler;
	private AbstractHandler fPrevPageHandler;

	private boolean fIsCreated = false;
	private boolean fIsDisposed = false;
	private boolean fIsShowAddressColumn = true;

	private final SwitchPageJob fSwitchPageJob = new SwitchPageJob();
	private boolean fError = false;

	private PendingPropertyChanges fPendingSyncProperties;

	// list of menu listeners for popupMenuMgr.
	private ArrayList<IMenuListener> fMenuListeners;
	private MenuManager fMenuMgr;

	private final ISchedulingRule serialByRenderingRule = new SerialByObjectRule(this);

	/**
	 * Identifier for an empty group preceding all context menu actions (value
	 * <code>"popUpBegin"</code>).
	 */
	public static final String EMPTY_MEMORY_GROUP = "popUpBegin"; //$NON-NLS-1$

	/**
	 * Identifier for an empty group following navigation actions in the
	 * rendering (value <code>navigationGroup</code>).
	 */
	public static final String EMPTY_NAVIGATION_GROUP = "navigationGroup"; //$NON-NLS-1$

	/**
	 * Identifier for an empty group following actions that are only applicable
	 * in non-auto loading mode (value <code>nonAutoLoadGroup</code>).
	 */
	public static final String EMPTY_NON_AUTO_LOAD_GROUP = "nonAutoLoadGroup"; //$NON-NLS-1$

	/**
	 * Identifier for an empty group following properties actions (value
	 * <code>propertyGroup</code>).
	 */
	public static final String EMPTY_PROPERTY_GROUP = "propertyGroup"; //$NON-NLS-1$

	private final ISelectionChangedListener fViewerSelectionChangedListener = event -> {
		updateSyncTopAddress(getTopVisibleAddress());
		updateSyncSelectedAddress(getSelectedAddress());
	};

	private final SelectionAdapter fScrollBarSelectionListener = new SelectionAdapter() {
		@Override
		public void widgetSelected(SelectionEvent e) {
			updateSyncTopAddress(getTopVisibleAddress());
		}
	};

	private final IModelChangedListener fModelChangedListener = (delta, proxy) -> {
		if (delta.getElement() == getMemoryBlock()) {
			showTable();
			updateRenderingLabel(isVisible());
		}
	};

	private final IVirtualContentListener fViewerListener = new IVirtualContentListener() {

		private int startThreshold;
		private int endThreshold;

		@Override
		public void handledAtBufferStart() {
			if (getMemoryBlock() instanceof IMemoryBlockExtension) {
				if (isDynamicLoad() && startThreshold != 0) {
					BigInteger address = getTopVisibleAddress();
					if (address != null && !isAtTopLimit()) {
						reloadTable(address);
					}
				}
			}
		}

		@Override
		public void handleAtBufferEnd() {
			if (getMemoryBlock() instanceof IMemoryBlockExtension) {
				if (isDynamicLoad() && endThreshold != 0) {
					BigInteger address = getTopVisibleAddress();
					if (address != null && !isAtBottomLimit()) {
						reloadTable(address);
					}
				}
			}
		}

		@Override
		public int getThreshold(int bufferEndOrStart) {

			int threshold = DEFAULT_BUFFER_THRESHOLD;

			if (bufferEndOrStart == IVirtualContentListener.BUFFER_START) {
				if (threshold > getPreBufferSize()) {
					threshold = getPreBufferSize();
				}
			} else {
				if (threshold > getPostBufferSize()) {
					threshold = getPostBufferSize();
				}
			}

			if (bufferEndOrStart == IVirtualContentListener.BUFFER_START) {
				startThreshold = threshold;
			} else {
				endThreshold = threshold;
			}

			return threshold;
		}
	};

	private final IPresentationErrorListener fPresentationErrorListener = (monitor,
			status) -> showMessage(status.getMessage());

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

	@Override
	public void resetRendering() throws DebugException {
		if (!fIsCreated) {
			return;
		}

		BigInteger baseAddress = fContentDescriptor.getContentBaseAddress();

		fTableViewer.setSelection(baseAddress);
		reloadTable(baseAddress);
		fTableViewer.setTopIndex(baseAddress);
		if (!isDynamicLoad()) {
			updateSyncPageStartAddress(baseAddress);
		}

		updateSyncSelectedAddress(baseAddress);
		updateSyncTopAddress(baseAddress);
	}

	@Override
	public Control createControl(Composite parent) {

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

		return fPageBook;
	}

	/**
	 * Create the error page of this rendering
	 *
	 * @param parent the parent to add the page to
	 */
	private void createMessagePage(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);
		}
	}

	/**
	 * @param parent the parent composite
	 */
	private void createTableViewer(final Composite parent) {
		StringBuilder buffer = new StringBuilder();
		IMemoryRenderingType type = DebugUITools.getMemoryRenderingManager().getRenderingType(getRenderingId());
		buffer.append(type.getLabel());
		buffer.append(": "); //$NON-NLS-1$
		buffer.append(DebugUIMessages.AbstractAsyncTableRendering_2);

		Job job = new Job(buffer.toString()) {

			@Override
			protected IStatus run(IProgressMonitor monitor) {

				// gather information from memory block
				initAddressableSize();
				final BigInteger topVisibleAddress = getInitialTopVisibleAddress();
				BigInteger mbBaseAddress = null;
				try {
					mbBaseAddress = getMemoryBlockBaseAddress();
				} catch (DebugException e) {
					fError = true;
					showMessage(e.getMessage());
				}

				// if it takes too long to get the base address, and user has
				// canceled
				// remove this rendering.
				if (monitor.isCanceled()) {
					getMemoryRenderingContainer().removeMemoryRendering(AbstractAsyncTableRendering.this);
					return Status.CANCEL_STATUS;
				}

				final BigInteger finalMbBaseAddress = mbBaseAddress;
				final BigInteger initialSelectedAddress = getInitialSelectedAddress();

				if (monitor.isCanceled()) {
					getMemoryRenderingContainer().removeMemoryRendering(AbstractAsyncTableRendering.this);
					return Status.CANCEL_STATUS;
				}

				createContentDescriptor(topVisibleAddress);

				// if it takes too long to get other information, and user has
				// canceled
				// remove this rendering.
				if (monitor.isCanceled()) {
					getMemoryRenderingContainer().removeMemoryRendering(AbstractAsyncTableRendering.this);
					return Status.CANCEL_STATUS;
				}

				// batch update on UI thread
				UIJob uiJob = new UIJob("Create Table Viewer UI Job") { //$NON-NLS-1$
					@Override
					public IStatus runInUIThread(IProgressMonitor progressMonitor) {

						if (fPageBook.isDisposed()) {
							return Status.OK_STATUS;
						}

						fSashForm = new SashForm(parent, SWT.VERTICAL);
						fTableViewer = new AsyncTableRenderingViewer(AbstractAsyncTableRendering.this, fSashForm, SWT.VIRTUAL | SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.HIDE_SELECTION | SWT.BORDER);

						GridData data = new GridData(GridData.FILL_BOTH);
						fTableViewer.getControl().setLayoutData(data);

						createGoToAddressComposite(fSashForm);
						hideGotoAddressComposite();

						IMemoryRenderingSite site = getMemoryRenderingContainer().getMemoryRenderingSite();
						IMemoryRenderingContainer container = getMemoryRenderingContainer();
						fPresentationContext = new MemoryViewPresentationContext(site, container, AbstractAsyncTableRendering.this);
						fTableViewer.setContext(fPresentationContext);

						// must call this after the context is created as the
						// information is stored in the context
						getDynamicLoadFromPreference();
						getPageSizeFromPreference();

						int numberOfLines = getNumLinesToLoad();
						fContentDescriptor.setNumLines(numberOfLines);

						if (numberOfLines == 0) {
							// if the table viewer is not initialized yet, add
							// listener
							// and load table when we can get the height of the
							// table
							fTableViewer.getTable().addPaintListener(new PaintListener() {
								@Override
								public void paintControl(PaintEvent e) {
									fTableViewer.getTable().removePaintListener(this);
									fContentDescriptor.setNumLines(getNumLinesToLoad());
									refresh();
								}
							});
						}

						BigInteger baseAddress = finalMbBaseAddress;
						if (baseAddress == null) {
							baseAddress = BigInteger.ZERO;
						}

						if (!(getMemoryBlock() instanceof IMemoryBlockExtension) || !isDynamicLoad()) {
							// If not extended memory block, do not create any
							// buffer
							// no scrolling
							fContentDescriptor.setPreBuffer(0);
							fContentDescriptor.setPostBuffer(0);
						}

						setupInitialFormat();
						fTableViewer.setCellModifier(newInternalCellModifier());
						fTableViewer.getTable().setHeaderVisible(true);
						fTableViewer.getTable().setLinesVisible(true);
						fTableViewer.addPresentationErrorListener(fPresentationErrorListener);
						fTableViewer.setInput(getMemoryBlock());
						fTableViewer.resizeColumnsToPreferredSize();
						fTableViewer.setTopIndex(topVisibleAddress);

						fTableViewer.setSelection(initialSelectedAddress);

						// SET UP FONT
						// set to a non-proportional font
						fTableViewer.getTable().setFont(JFaceResources.getFont(IInternalDebugUIConstants.FONT_NAME));

						if (!fError) {
							showTable();
						}

						fTableViewer.addVirtualContentListener(fViewerListener);

						// create context menu
						// create pop up menu for the rendering
						createActions();
						IMenuListener menuListener = AbstractAsyncTableRendering.this::fillContextMenu;
						createPopupMenu(fTableViewer.getControl(), menuListener);
						createPopupMenu(fTableViewer.getCursor(), menuListener);

						fTableViewer.addSelectionChangedListener(fViewerSelectionChangedListener);
						fTableViewer.getTable().getVerticalBar().addSelectionListener(fScrollBarSelectionListener);

						// add resize listener to figure out number of visible
						// lines
						// so that the viewer get refreshed with the correct
						// number of lines on the
						// next refresh request
						fTableViewer.getTable().addListener(SWT.Resize, event -> {
							if (!fTableViewer.getTable().isDisposed()) {
								fContentDescriptor.setNumLines(getNumLinesToLoad());
							}
						});

						createToolTip();

						if (isActivated()) {
							activatePageActions();
						}

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

						return Status.OK_STATUS;
					}
				};
				uiJob.setSystem(true);
				uiJob.schedule();

				return Status.OK_STATUS;

			}
		};

		job.schedule();
	}

	/**
	 * Create popup menu for this rendering
	 *
	 * @param control - control to create the popup menu for
	 * @param menuListener - listener to notify when popup menu is about to show
	 */
	private void createPopupMenu(Control control, IMenuListener menuListener) {
		IMemoryRenderingContainer container = getMemoryRenderingContainer();
		if (fMenuMgr == null) {
			fMenuMgr = new MenuManager("#PopupMenu"); //$NON-NLS-1$
			fMenuMgr.setRemoveAllWhenShown(true);
			IMemoryRenderingSite site = container.getMemoryRenderingSite();
			String menuId = container.getId();

			ISelectionProvider selProvider = site.getSite().getSelectionProvider();

			addMenuListener(menuListener);

			site.getSite().registerContextMenu(menuId, fMenuMgr, selProvider);
		}

		addMenuListener(menuListener);

		Menu popupMenu = fMenuMgr.createContextMenu(control);
		control.setMenu(popupMenu);
	}

	private void addMenuListener(IMenuListener menuListener) {
		if (fMenuListeners == null) {
			fMenuListeners = new ArrayList<>();
		}

		if (!fMenuListeners.contains(menuListener)) {
			fMenuMgr.addMenuListener(menuListener);
			fMenuListeners.add(menuListener);
		}
	}

	private BigInteger getInitialSelectedAddress() {
		// figure out selected address
		BigInteger selectedAddress = (BigInteger) getSynchronizedProperty(AbstractAsyncTableRendering.PROPERTY_SELECTED_ADDRESS);
		if (selectedAddress == null) {
			if (getMemoryBlock() instanceof IMemoryBlockExtension) {
				try {
					selectedAddress = ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress();
				} catch (DebugException e) {
					selectedAddress = BigInteger.ZERO;
				}

				if (selectedAddress == null) {
					selectedAddress = BigInteger.ZERO;
				}

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

	private void addListeners() {
		DebugUIPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(this);
		addRenderingToSyncService();
		JFaceResources.getFontRegistry().addListener(this);
	}

	private void removeListeners() {
		DebugUIPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this);
		removeRenderingFromSyncService();
		JFaceResources.getFontRegistry().removeListener(this);

		if (fMenuListeners != null) {
			Iterator<IMenuListener> iter = fMenuListeners.iterator();
			while (iter.hasNext()) {
				fMenuMgr.removeMenuListener(iter.next());
			}

			fMenuListeners.clear();
		}
	}

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

		if (syncService == null) {
			return;
		}

		syncService.addPropertyChangeListener(this, null);
	}

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

		if (syncService == null) {
			return;
		}

		syncService.removePropertyChangeListener(this);
	}

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

		}
		if (fAddressableSize < 1) {
			DebugUIPlugin.logErrorMessage("Invalid addressable size"); //$NON-NLS-1$
			fAddressableSize = 1;
		}
	}

	private BigInteger getInitialTopVisibleAddress() {
		BigInteger topVisibleAddress = (BigInteger) getSynchronizedProperty(AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS);
		if (topVisibleAddress == null) {
			if (getMemoryBlock() instanceof IMemoryBlockExtension) {
				try {
					topVisibleAddress = ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress();
				} catch (DebugException e1) {
					topVisibleAddress = BigInteger.ZERO;
				}
			} else {
				topVisibleAddress = BigInteger.valueOf(getMemoryBlock().getStartAddress());
			}
		}
		return topVisibleAddress;
	}

	private void setupInitialFormat() {

		boolean validated = validateInitialFormat();

		if (!validated) {
			// pop up dialog to ask user for default values
			StringBuilder msgBuffer = new StringBuilder(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 boolean validateInitialFormat() {
		int rowSize = getDefaultRowSize();
		int columnSize = getDefaultColumnSize();

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

	@Override
	public Control getControl() {
		return fPageBook;
	}

	@Override
	public void propertyChange(PropertyChangeEvent event) {
		if (!fIsCreated) {
			return;
		}

		// 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;
		}

		Object evtSrc = event.getSource();

		// always update page size, only refresh if the table is visible
		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE)) {
			getPageSizeFromPreference();
		}
		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE)) {
			getPreBufferSizeFromPreference();
			fContentDescriptor.setPreBuffer(getPreBufferSize());
		}
		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE)) {
			getPostBufferSizeFromPreference();
			fContentDescriptor.setPostBuffer(getPostBufferSize());
		}

		// do not handle event if the rendering is displaying an error
		// or if it's not visible
		if (isDisplayingError() || !isVisible()) {
			handlePropertiesChangeWhenHidden(event);
			return;
		}

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

		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE) || event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE)) {
			if (!fIsDisposed) {
				fTableViewer.refresh(true);
			}
			return;
		}

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

		if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE)) {
			if (!isDynamicLoad()) {
				int pageSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE);
				handlePageSizeChanged(pageSize);
			}
			return;
		}

		if (evtSrc == this) {
			return;
		}

		if (evtSrc instanceof IMemoryRendering) {
			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(AbstractAsyncTableRendering.PROPERTY_SELECTED_ADDRESS) && value instanceof BigInteger) {
			selectedAddressChanged((BigInteger) value);
		} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_COL_SIZE) && value instanceof Integer) {
			columnSizeChanged(((Integer) value).intValue());
		} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_ROW_SIZE) && value instanceof Integer) {
			rowSizeChanged(((Integer) value).intValue());
		} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS) && value instanceof BigInteger) {
			topVisibleAddressChanged((BigInteger) value);
		} else if (propertyName.equals(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS) && value instanceof BigInteger) {
			handlePageStartAddressChanged((BigInteger) value);
		}
	}

	/**
	 * @param pageSize the new page size
	 */
	private void handlePageSizeChanged(int pageSize) {
		fPageSize = pageSize;
		// only refresh if in non-auto-load mode
		fContentDescriptor.setNumLines(pageSize);
		refresh();
	}

	private void handlePropertiesChangeWhenHidden(PropertyChangeEvent event) {
		if (fPendingSyncProperties == null) {
			return;
		}

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

		if (event.getSource() instanceof IMemoryRendering) {
			IMemoryRendering rendering = (IMemoryRendering) event.getSource();
			if (rendering == this || rendering.getMemoryBlock() != getMemoryBlock()) {
				return;
			}
		}

		if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_COL_SIZE) && value instanceof Integer) {
			fPendingSyncProperties.setColumnSize(((Integer) value).intValue());
		} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_ROW_SIZE) && value instanceof Integer) {
			fPendingSyncProperties.setRowSize(((Integer) value).intValue());
		}

		else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_SELECTED_ADDRESS) && value instanceof BigInteger) {
			fPendingSyncProperties.setSelectedAddress((BigInteger) value);
		} else if (propertyName.equals(AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS) && value instanceof BigInteger) {
			fPendingSyncProperties.setTopVisibleAddress((BigInteger) value);
		} else if (propertyName.equals(IInternalDebugUIConstants.PROPERTY_PAGE_START_ADDRESS) && value instanceof BigInteger) {
			fPendingSyncProperties.setPageStartAddress((BigInteger) value);
		} else if (event.getProperty().equals(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE)) {
			int pageSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PAGE_SIZE);
			fPendingSyncProperties.setPageSize(pageSize);
		}
	}

	private void topVisibleAddressChanged(final BigInteger address) {
		final Runnable runnable = () -> {
			if (fTableViewer.getTable().isDisposed()) {
				return;
			}

			doTopVisibleAddressChanged(address);
		};
		runOnUIThread(runnable);
	}

	/**
	 * @param address the changed address
	 */
	private void doTopVisibleAddressChanged(final BigInteger address) {
		if (fIsDisposed) {
			return;
		}

		if (!isDynamicLoad()) {
			fTableViewer.setTopIndex(address);
			fTableViewer.topIndexChanged();
			return;
		}

		if (!isAtTopBuffer(address) && !isAtBottomBuffer(address)) {
			fTableViewer.setTopIndex(address);
			fTableViewer.topIndexChanged();
		} else {
			reloadTable(address);
		}
	}

	private boolean isAtBottomBuffer(BigInteger address) {
		int idx = fTableViewer.indexOf(address);
		if (idx < 0) {
			return true;
		}

		int bottomIdx = idx + getNumberOfVisibleLines();
		int elementsCnt = fTableViewer.getVirtualContentModel().getElements().length;
		int numLinesLeft = elementsCnt - bottomIdx;

		if (numLinesLeft < fViewerListener.getThreshold(IVirtualContentListener.BUFFER_END)) {
			return true;
		}

		return false;
	}

	private boolean isAtTopBuffer(BigInteger address) {
		int topIdx = fTableViewer.indexOf(address);
		if (topIdx < fViewerListener.getThreshold(IVirtualContentListener.BUFFER_START)) {
			return true;
		}

		return false;
	}

	private void runOnUIThread(final Runnable runnable) {
		if (Display.getCurrent() != null) {
			runnable.run();
		} else {
			UIJob job = new UIJob("Async Table Rendering UI Job") { //$NON-NLS-1$

				@Override
				public IStatus runInUIThread(IProgressMonitor monitor) {
					runnable.run();
					return Status.OK_STATUS;
				}
			};
			job.setSystem(true);
			job.schedule();
		}
	}

	private void selectedAddressChanged(final BigInteger address) {
		Runnable runnable = () -> {

			if (fTableViewer.getTable().isDisposed()) {
				return;
			}

			// call this to make the table viewer to reload when needed
			int i = fTableViewer.indexOf(address);
			if (i < 0) {
				// the model may not have been populated yet,
				// try to predict if the address will be in the buffer
				boolean contained = isAddressBufferred(address);
				if (!contained) {
					topVisibleAddressChanged(address);
				}
			}
			fTableViewer.setSelection(address);
		};

		runOnUIThread(runnable);
	}

	private boolean isAddressBufferred(BigInteger address) {
		// figure out the buffer top address
		BigInteger loadAddress = fContentDescriptor.getLoadAddress();
		loadAddress = MemoryViewUtil.alignToBoundary(loadAddress, getAddressableUnitPerLine());
		int unitPerLine = getAddressableUnitPerLine();

		loadAddress = loadAddress.subtract(BigInteger.valueOf(getPreBufferSize() * unitPerLine));

		// figure out the buffer end address
		int numLines = fContentDescriptor.getNumLines();
		BigInteger bufferEnd = loadAddress.add(BigInteger.valueOf(fContentDescriptor.getPostBuffer() * unitPerLine));
		bufferEnd = bufferEnd.add(BigInteger.valueOf(numLines * unitPerLine + unitPerLine));

		// see if the address is contained based on current content descriptor
		if (address.compareTo(loadAddress) >= 0 && address.compareTo(bufferEnd) <= 0) {
			return true;
		}

		return false;
	}

	private void setFont(Font font) {
		// set font
		fTableViewer.getTable().setFont(font);
		fTableViewer.getCursor().setFont(font);
	}

	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 col size
		Integer colSize = (Integer) getSynchronizedProperty(AbstractAsyncTableRendering.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(AbstractAsyncTableRendering.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;
	}

	/**
	 * 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);
	}

	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;
	}

	/**
	 * @param modelId the {@link String} model id
	 * @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 {@link String} model id
	 * @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 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;
	}

	/**
	 * 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) {

		// 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;
		formatViewer();

		updateSyncRowSize();
		updateSyncColSize();

		return true;
	}

	/**
	 * 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();
	}

	/**
	 * 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;
		}

		if (height == 0) {
			return 0;
		}

		// 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;

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

		return numberOfLines;
	}

	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 (TableItem item : items) {
				if (item.getData() != null) {
					minHeight = Math.min(item.getBounds(0).height, minHeight);
				}
			}

			return minHeight;

		}
		return table.getItemHeight();
	}

	private BigInteger getMemoryBlockBaseAddress() throws DebugException {
		if (getMemoryBlock() instanceof IMemoryBlockExtension) {
			return ((IMemoryBlockExtension) getMemoryBlock()).getBigBaseAddress();
		} else {
			return BigInteger.valueOf(getMemoryBlock().getStartAddress());
		}
	}

	/**
	 * Displays the given message on the error page
	 *
	 * @param message - the message to display
	 */
	protected void showMessage(final String message) {
		fSwitchPageJob.setShowMessagePage(true);
		fSwitchPageJob.setMessage(message);
		fSwitchPageJob.schedule();
	}

	/**
	 * 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;
	}

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

	/**
	 * Displays the content of the table viewer.
	 */
	public void showTable() {
		fSwitchPageJob.setShowMessagePage(false);
		fSwitchPageJob.schedule();
	}

	private BigInteger getTopVisibleAddress() {

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

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

		if (topIndex < 0) {
			return null;
		}

		if (table.getItemCount() > topIndex) {
			MemorySegment topItem = (MemorySegment) table.getItem(topIndex).getData();
			if (topItem != null) {
				return topItem.getAddress();
			}
		}
		return null;
	}

	private synchronized void reloadTable(final BigInteger topAddress) {

		if (DebugUIPlugin.DEBUG_DYNAMIC_LOADING) {
			DebugUIPlugin.trace(this + " reload at: " + topAddress.toString(16)); //$NON-NLS-1$
		}
		fContentDescriptor.setLoadAddress(topAddress);
		fContentDescriptor.setNumLines(getNumLinesToLoad());
		fTableViewer.setTopIndex(topAddress);
		fTableViewer.refresh();

	}

	private boolean isAtTopLimit() {
		BigInteger startAddress = fContentDescriptor.getStartAddress();
		startAddress = MemoryViewUtil.alignToBoundary(startAddress, getAddressableUnitPerLine());
		AbstractVirtualContentTableModel model = fTableViewer.getVirtualContentModel();

		if (model != null) {
			Object key = model.getKey(0);
			if (key instanceof BigInteger) {
				BigInteger startBufferAddress = (BigInteger) key;
				startBufferAddress = MemoryViewUtil.alignToBoundary(startBufferAddress, getAddressableUnitPerLine());

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

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

		AbstractVirtualContentTableModel model = fTableViewer.getVirtualContentModel();
		if (model != null) {
			int numElements = model.getElements().length;
			Object key = model.getKey(numElements - 1);
			if (key instanceof BigInteger) {
				BigInteger endBufferAddress = (BigInteger) key;
				endBufferAddress = MemoryViewUtil.alignToBoundary(endBufferAddress, getAddressableUnitPerLine());

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

		return false;
	}

	private void formatViewer() {

		fTableViewer.disposeColumns();
		fTableViewer.disposeCellEditors();
		doFormatTable();
		fTableViewer.setColumnHeaders(getColumnProperties());
		fTableViewer.showColumnHeader(true);

		Table table = fTableViewer.getTable();
		int colCnt = table.getColumnCount();
		CellEditor[] editors = new CellEditor[fTableViewer.getTable().getColumnCount()];
		for (int i = 0; i < colCnt; i++) {
			editors[i] = createCellEditor(table, i);
		}

		fTableViewer.setCellEditors(editors);

		fTableViewer.formatViewer();

		// This resize needs to happen after the viewer has finished
		// getting the labels.
		// This fix is a hack to delay the resize until the viewer has a chance
		// to get
		// the setData event from the UI thread. Otherwise, the columns will be
		// squeezed together.
		UIJob job = new UIJob("resize to fit") { //$NON-NLS-1$
			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {
				resizeColumnsToPreferredSize();
				return Status.OK_STATUS;
			}
		};

		job.setSystem(true);
		job.schedule();
	}

	private void doFormatTable() {
		int bytesPerLine = getBytesPerLine();
		int columnSize = getBytesPerColumn();
		int numColumns = bytesPerLine / columnSize;

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

		// create new byte columns
		TableColumn[] byteColumns = new TableColumn[numColumns];
		for (int i = 0; i < byteColumns.length; i++) {
			TableColumn column = new TableColumn(table, SWT.LEFT, i + 1);
			byteColumns[i] = column;
		}

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

		// allow clients to override column labels
		setColumnHeadings();

	}

	private String[] getColumnProperties() {
		int numColumns = getAddressableUnitPerLine() / getAddressableUnitPerColumn();
		// +2 to include properties for address and navigation column
		String[] columnProperties = new String[numColumns + 2];
		columnProperties[0] = TableRenderingLine.P_ADDRESS;

		int addressableUnit = getAddressableUnitPerColumn();

		// 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$
		return columnProperties;
	}

	/**
	 * Create a cell editor from the specified composite and column.
	 *
	 * @param composite parent composite that the cell editor is to be created
	 *            from.
	 * @param column the column where the cell editor is required
	 * @return the cell editor for editing memory
	 *
	 * @since 3.3
	 *
	 */
	protected CellEditor createCellEditor(Composite composite, int column) {

		return new TextCellEditor(composite);
	}

	private ICellModifier newInternalCellModifier() {
		return new AsyncTableRenderingCellModifier(this, createCellModifier());
	}

	/**
	 * Create a custom cell modifier for this rendering. Return null if the
	 * default cell modifier is to be used.
	 *
	 * @return the cell modifier for this rendering, or <code>null</code> if the
	 *         default cell modifier is to be used.
	 *
	 * @since 3.3
	 *
	 */
	protected ICellModifier createCellModifier() {
		return null;
	}

	@Override
	public void dispose() {

		if (fIsDisposed) {
			return;
		}

		fIsDisposed = true;

		removeListeners();

		if (fMenuMgr != null) {
			fMenuMgr.removeAll();
			fMenuMgr.dispose();
			fMenuMgr = null;
		}

		if (fTableViewer != null) {
			if (fViewerListener != null) {
				fTableViewer.removeVirtualContentListener(fViewerListener);
			}

			if (fPresentationErrorListener != null) {
				fTableViewer.removePresentationErrorListener(fPresentationErrorListener);
			}

			fTableViewer.removeSelectionChangedListener(fViewerSelectionChangedListener);
			fTableViewer.getTable().getVerticalBar().removeSelectionListener(fScrollBarSelectionListener);

			fTableViewer.dispose();
		}

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

		if (fToolTipShell != null && !fToolTipShell.isDisposed()) {
			fToolTipShell.dispose();
			fToolTipShell = null;
		}

		fIsDisposed = true;

		super.dispose();
	}

	/**
	 * 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(final boolean showAddress) {
		Job job = new Job("Update Rendering Label") { //$NON-NLS-1$
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				if (fIsDisposed) {
					return Status.OK_STATUS;
				}
				fLabel = buildLabel(showAddress);
				firePropertyChangedEvent(new PropertyChangeEvent(AbstractAsyncTableRendering.this, IBasicPropertyConstants.P_TEXT, null, fLabel));
				return Status.OK_STATUS;
			}
		};
		job.setSystem(true);
		job.setRule(serialByRenderingRule);
		job.schedule();
	}

	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);
	}

	/*
	 * Returns the label of this rendering.
	 * @return label of this rendering
	 */
	@Override
	public String getLabel() {

		if (fLabel == null) {
			fLabel = DebugUIMessages.AbstractAsyncTableRendering_1;
			updateRenderingLabel(isVisible());
		}

		return fLabel;
	}

	@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 == IModelChangedListener.class) {
			return (T) fModelChangedListener;
		}

		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 AbstractAsyncTableRendering.this.getLabel();
					}

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

		if (adapter == TableRenderingContentDescriptor.class) {
			return (T) getContentDescriptor();
		}

		return super.getAdapter(adapter);
	}

	/**
	 * 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;
	}

	/**
	 * Create actions for this rendering
	 */
	protected void createActions() {

		fCopyToClipboardAction = new AsyncCopyTableRenderingAction(this, fTableViewer);
		fGoToAddressAction = new RenderingGoToAddressAction(getMemoryRenderingContainer(), this);
		fResetMemoryBlockAction = new ResetToBaseAddressAction(this);

		fPrintViewTabAction = new AsyncPrintTableRenderingAction(this, fTableViewer);

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

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

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

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

		Object key = fTableViewer.getSelectionKey();

		if (key != null && key instanceof BigInteger) {
			return (BigInteger) key;
		}

		return null;
	}

	/**
	 * 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 (getSelectedAddress() == null) {
			return new MemoryByte[0];
		}

		Object key = fTableViewer.getSelectionKey();
		AbstractVirtualContentTableModel model = fTableViewer.getVirtualContentModel();

		if (model != null) {
			model = (AbstractVirtualContentTableModel) fTableViewer.getModel();
			int row = model.indexOfKey(key);
			Object element = model.getElement(row);
			int col = model.columnOf(element, key);

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

			if (!(element instanceof MemorySegment)) {
				return new MemoryByte[0];
			}

			MemorySegment line = (MemorySegment) element;
			int offset = (col - 1) * (getAddressableUnitPerColumn() * getAddressableSize());

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

			System.arraycopy(bytes, 0, retBytes, 0, bytes.length);
			return retBytes;
		}
		return new MemoryByte[0];
	}

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

		if (getSelectedAddress() == null) {
			return IInternalDebugCoreConstants.EMPTY_STRING;
		}

		MemoryByte[] bytes = getSelectedAsBytes();
		if (bytes.length > 0) {
			return getString(this.getRenderingId(), getSelectedAddress(), bytes);
		} else {
			return IInternalDebugCoreConstants.EMPTY_STRING;
		}

	}

	/**
	 * 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(final BigInteger address) throws DebugException {

		if (!fIsCreated || fTableViewer.getVirtualContentModel() == null) {
			return;
		}

		final int keyIndex = fTableViewer.getVirtualContentModel().indexOfKey(address);

		if (keyIndex < 0) {
			// 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 = fContentDescriptor.getStartAddress();
			BigInteger endAdd = fContentDescriptor.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;
			}
		}

		Runnable runnable = () -> {
			showTable();
			if (keyIndex >= 0) {
				// address is within range, set cursor and reveal
				fTableViewer.setSelection(address);
				updateSyncTopAddress(getTopVisibleAddress());
				updateSyncSelectedAddress(address);
			} else {
				// load at the address
				fTableViewer.setSelection(address);
				reloadTable(address);

				updateSyncSelectedAddress(address);
				if (!isDynamicLoad()) {
					updateSyncPageStartAddress(address);
				}
				updateSyncTopAddress(address);
			}
		};
		runOnUIThread(runnable);
	}

	/**
	 * Refresh the table viewer with the current top visible address. Update
	 * labels in the memory rendering.
	 */
	@Override
	public void refresh() {
		if (!fIsCreated) {
			return;
		}

		fTableViewer.refresh();
	}

	/**
	 * Resize column to the preferred size.
	 */
	@Override
	public void resizeColumnsToPreferredSize() {
		if (!fIsCreated) {
			return;
		}

		fTableViewer.resizeColumnsToPreferredSize();
		if (!fIsShowAddressColumn) {
			final TableColumn column = fTableViewer.getTable().getColumn(0);
			column.addControlListener(new ControlListener() {

				@Override
				public void controlMoved(ControlEvent e) {
				}

				@Override
				public void controlResized(ControlEvent e) {
					column.removeControlListener(this);
					column.setWidth(0);
				}
			});
		}
	}

	/**
	 * Updates labels of this rendering.
	 */
	@Override
	public void updateLabels() {
		if (!fIsCreated) {
			return;
		}

		UIJob job = new UIJob("updateLabels") { //$NON-NLS-1$

			@Override
			public IStatus runInUIThread(IProgressMonitor monitor) {

				// do not handle if the rendering is already disposed
				if (fPageBook.isDisposed()) {
					return Status.OK_STATUS;
				}

				// update tab labels
				updateRenderingLabel(true);

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

					// rebuild cache and force labels to be refreshed
					fTableViewer.formatViewer();
				}
				return Status.OK_STATUS;
			}
		};
		job.setSystem(true);
		job.schedule();
	}

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

		menu.add(new Separator(EMPTY_MEMORY_GROUP));
		menu.add(new Separator());
		menu.add(fResetMemoryBlockAction);
		menu.add(fGoToAddressAction);
		menu.add(new Separator(EMPTY_NAVIGATION_GROUP));

		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(EMPTY_NON_AUTO_LOAD_GROUP));
		}

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

		menu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
	}

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

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

	private void getPreBufferSizeFromPreference() {
		fPreBufferSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_PRE_BUFFER_SIZE);
	}

	private void getPostBufferSizeFromPreference() {
		fPostBufferSize = DebugUIPlugin.getDefault().getPreferenceStore().getInt(IDebugPreferenceConstants.PREF_TABLE_RENDERING_POST_BUFFER_SIZE);
	}

	private void updateDynamicLoadProperty() {

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

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

			if (!fIsDisposed) {
				if (isDynamicLoad()) {
					fContentDescriptor.setPostBuffer(getPostBufferSize());
					fContentDescriptor.setPreBuffer(getPreBufferSize());
					fContentDescriptor.setNumLines(getNumberOfVisibleLines());

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

	private void getDynamicLoadFromPreference() {
		setDynamicLoad(DebugUIPlugin.getDefault().getPreferenceStore().getBoolean(IDebugPreferenceConstants.PREF_DYNAMIC_LOAD_MEM));
	}

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

	private int getPageSize() {
		return fPageSize;
	}

	private int getNumLinesToLoad() {
		int numberOfLines = -1;

		if (isDynamicLoad()) {
			numberOfLines = getNumberOfVisibleLines();
		} else {
			numberOfLines = getPageSize();
		}

		return numberOfLines;
	}

	private void setDynamicLoad(boolean load) {
		fContentDescriptor.setDynamicLoad(load);
	}

	private void handlePageStartAddressChanged(BigInteger address) {
		// do not handle if in dynamic mode
		if (isDynamicLoad()) {
			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 (isMemoryBlockBaseAddressChanged()) {
			return;
		}

		Object key = fTableViewer.getKey(0);
		if (key != null) {
			if (key.equals(address)) {
				return;
			}
		}

		BigInteger start = fContentDescriptor.getStartAddress();
		BigInteger end = fContentDescriptor.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()));
		}

		fContentDescriptor.setLoadAddress(address);
		final BigInteger finaladdress = address;
		Runnable runnable = () -> {
			if (fTableViewer.getTable().isDisposed()) {
				return;
			}

			fTableViewer.setTopIndex(finaladdress);
			refresh();
		};

		runOnUIThread(runnable);

		updateSyncPageStartAddress(address);
		updateSyncTopAddress(address);
	}

	private void handleDyanicLoadChanged() {

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

		updateDynamicLoadProperty();
		if (isDynamicLoad()) {
			refresh();
			fTableViewer.setTopIndex(pageStart);
		} else {
			handlePageStartAddressChanged(pageStart);
		}
	}

	@Override
	public void becomesHidden() {
		// creates new object for storing potential changes in sync properties
		fPendingSyncProperties = new PendingPropertyChanges();
		super.becomesHidden();

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

	@Override
	public void becomesVisible() {

		if (!fIsCreated) {
			// label can still be constructed even though the rendering has not
			// finished being
			// initialized
			updateRenderingLabel(true);
			super.becomesVisible();
			return;
		}

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

		super.becomesVisible();

		if (fPendingSyncProperties != null) {
			// deal with format
			boolean format = false;
			int rowSize = getBytesPerLine();
			if (fPendingSyncProperties.getRowSize() > 0) {
				format = true;
				rowSize = fPendingSyncProperties.getRowSize();
			}

			int colSize = getBytesPerColumn();
			if (fPendingSyncProperties.getColumnSize() > 0) {
				format = true;
				colSize = fPendingSyncProperties.getColumnSize();
			}

			if (format) {
				format(rowSize, colSize);
			}

			BigInteger selectedAddress = fPendingSyncProperties.getSelectedAddress();
			if (selectedAddress != null) {
				fTableViewer.setSelection(selectedAddress);
			}

			updateDynamicLoadProperty();

			if (isDynamicLoad()) {
				BigInteger topVisibleAddress = fPendingSyncProperties.getTopVisibleAddress();
				if (topVisibleAddress != null) {
					fContentDescriptor.setLoadAddress(topVisibleAddress);
					fTableViewer.setTopIndex(topVisibleAddress);
				}
			} else if (!(getMemoryBlock() instanceof IMemoryBlockExtension)) {
				BigInteger topVisibleAddress = fPendingSyncProperties.getTopVisibleAddress();
				if (topVisibleAddress != null) {
					fTableViewer.setTopIndex(topVisibleAddress);
				}
			} else {
				if (fPendingSyncProperties.getPageSize() > 0) {
					fPageSize = fPendingSyncProperties.getPageSize();
					fContentDescriptor.setNumLines(fPageSize);
				}

				BigInteger pageStartAddress = fPendingSyncProperties.getPageStartAddress();
				if (pageStartAddress != null) {
					fContentDescriptor.setLoadAddress(pageStartAddress);
				}

				fTableViewer.setTopIndex(pageStartAddress);
			}

			showTable();
			refresh();
		}

		updateRenderingLabel(true);

		Job job = new Job("becomesVisible") //$NON-NLS-1$
		{
			@Override
			protected IStatus run(IProgressMonitor monitor) {
				if (fIsDisposed) {
					return Status.OK_STATUS;
				}
				try {
					fContentDescriptor.updateContentBaseAddress();
				} catch (DebugException e) {
					showMessage(e.getMessage());
				}
				return Status.OK_STATUS;
			}
		};
		job.setSystem(true);
		job.schedule();

		// discard these properties
		fPendingSyncProperties = null;
	}

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

		Display.getDefault().asyncExec(() -> {
			int rowSize = getBytesPerLine();
			if (rowSize < newColumnSize) {
				rowSize = newColumnSize;
			}

			format(rowSize, newColumnSize);
		});
	}

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

		Display.getDefault().asyncExec(() -> {
			int colSize = getBytesPerColumn();
			if (newRowSize < colSize) {
				colSize = newRowSize;
			}

			format(newRowSize, colSize);
		});
	}

	/**
	 * update selected address in synchronizer if update is true.
	 *
	 * @param address the address to update
	 */
	private void updateSyncSelectedAddress(BigInteger address) {

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

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

		if (!fIsCreated) {
			return;
		}

		PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractAsyncTableRendering.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, AbstractAsyncTableRendering.PROPERTY_ROW_SIZE, null, Integer.valueOf(fBytePerLine));
		firePropertyChangedEvent(event);
	}

	/**
	 * update top visible address in synchronizer
	 *
	 * @param address the address to update
	 */
	private void updateSyncTopAddress(BigInteger address) {

		if (!fIsCreated) {
			return;
		}

		PropertyChangeEvent event = new PropertyChangeEvent(this, AbstractAsyncTableRendering.PROPERTY_TOP_ADDRESS, null, address);
		firePropertyChangedEvent(event);
	}

	private void updateSyncPageStartAddress(BigInteger address) {

		if (!fIsCreated) {
			return;
		}

		if (isMemoryBlockBaseAddressChanged()) {
			return;
		}

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

	/**
	 * 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);
	}

	/**
	 * 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);
		fTableViewer.getCursor().addMouseTrackListener(listener);
	}

	/**
	 * 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;
	}

	/**
	 * 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 given {@link Point} to find the {@link TableItem} for
	 * @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 (TableItem item : items) {
			if (item.getData() != null) {
				Point start = new Point(item.getBounds(0).x, item.getBounds(0).y);
				start = fTableViewer.getTable().toDisplay(start);
				Point end = new Point(start.x + item.getBounds(0).width, start.y + item.getBounds(0).height);

				if (start.y < point.y && point.y < end.y) {
					return item;
				}
			}
		}
		return null;
	}

	/**
	 * Method for figuring out which column the point is located.
	 *
	 * @param point the {@link Point} the get 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();

		TableItem item = null;
		for (int i = 0; i < fTableViewer.getTable().getItemCount(); i++) {
			item = fTableViewer.getTable().getItem(i);
			if (item.getData() != null) {
				break;
			}
		}

		if (item != null) {
			for (int i = 0; i < colCnt; i++) {
				Point start = new Point(item.getBounds(i).x, item.getBounds(i).y);
				start = fTableViewer.getTable().toDisplay(start);
				Point 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;
	}

	/**
	 * 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) {
			Object address = fTableViewer.getKey(fTableViewer.getTable().indexOf(item), col);
			if (address != null && address instanceof BigInteger) {
				Object data = item.getData();
				if (data instanceof MemorySegment) {
					MemorySegment line = (MemorySegment) data;

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

						String str = getToolTipText((BigInteger) address, bytes);

						if (str != null) {
							((Label) toolTipControl).setText(str);
						}
					} else {
						String str = getToolTipText((BigInteger) 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) {
		StringBuilder buf = new StringBuilder("0x"); //$NON-NLS-1$
		buf.append(address.toString(16).toUpperCase());

		return buf.toString();
	}

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

		IMemoryBlockTablePresentation presentation = getTablePresentationAdapter();
		if (presentation != null) {
			columnLabels = presentation.getColumnLabels(getMemoryBlock(), getBytesPerLine(), getBytesPerLine() / getBytesPerColumn());
		}

		// 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
				int addressableUnit = getAddressableUnitPerColumn();
				if (addressableUnit >= 4) {
					columns[i].setText(Integer.toHexString(j * addressableUnit).toUpperCase() + " - " + Integer.toHexString(j * addressableUnit + addressableUnit - 1).toUpperCase()); //$NON-NLS-1$
				} else {
					columns[i].setText(Integer.toHexString(j * addressableUnit).toUpperCase());
				}
				j++;
			}
		}
	}

	/**
	 *
	 * Return this rendering's viewer
	 *
	 * @return this rendering's viewer
	 */
	public StructuredViewer getViewer() {
		return fTableViewer;
	}

	private boolean isMemoryBlockBaseAddressChanged() {
		try {
			BigInteger address = getMemoryBlockBaseAddress();
			BigInteger oldBaseAddress = fContentDescriptor.getContentBaseAddress();
			if (!oldBaseAddress.equals(address)) {
				return true;
			}
		} catch (DebugException e) {
			// fail silently
		}
		return false;
	}

	/**
	 * @param topVisibleAddress the address to get the description for
	 */
	private void createContentDescriptor(final BigInteger topVisibleAddress) {
		fContentDescriptor = new TableRenderingContentDescriptor(AbstractAsyncTableRendering.this);
		fContentDescriptor.setPostBuffer(getPostBufferSize());
		fContentDescriptor.setPreBuffer(getPreBufferSize());
		fContentDescriptor.setLoadAddress(topVisibleAddress);
		try {
			fContentDescriptor.updateContentBaseAddress();

		} catch (DebugException e) {
			fError = true;
			showMessage(e.getMessage());
		}

		fContentDescriptor.setAddressableSize(getAddressableSize());

		try {
			int addressSize = 4;
			if (getMemoryBlock() instanceof IMemoryBlockExtension) {
				IMemoryBlockExtension extMb = (IMemoryBlockExtension) getMemoryBlock();
				addressSize = extMb.getAddressSize();

				if (addressSize <= 0) {
					DebugUIPlugin.logErrorMessage("Invalid address Size: " + addressSize); //$NON-NLS-1$
					addressSize = 4;
				}
				fContentDescriptor.setAddressSize(addressSize);
			}
			fContentDescriptor.setAddressSize(addressSize);
		} catch (DebugException e) {
			fError = true;
			showMessage(e.getMessage());
		} finally {
			if (fContentDescriptor.getAddressSize() <= 0) {
				fContentDescriptor.setAddressSize(4);
			}
		}
	}

	/**
	 * Return the number of lines to be bufferred before the top visible line of
	 * the memory rendering
	 *
	 * @return number of lines to be buffered before the top visible line in the
	 *         memory rendering
	 */
	private int getPreBufferSize() {
		if (fPreBufferSize < 0) {
			getPreBufferSizeFromPreference();
		}

		return fPreBufferSize;
	}

	/**
	 * Returns the number of lines to be bufferred after the last visible line
	 * in the memory rendering
	 *
	 * @return the number of lines to be bufferred after the last visible line
	 *         in the memory rendering
	 */
	private int getPostBufferSize() {
		if (fPostBufferSize < 0) {
			getPostBufferSizeFromPreference();
		}

		return fPostBufferSize;
	}

	private TableRenderingContentDescriptor getContentDescriptor() {
		return fContentDescriptor;
	}

	private void createGoToAddressComposite(Composite parent) {
		fGoToAddressComposite = new GoToAddressComposite();
		fGoToAddressComposite.createControl(parent);
		Button button = fGoToAddressComposite.getButton(IDialogConstants.OK_ID);
		if (button != null) {
			button.addSelectionListener(new SelectionAdapter() {

				@Override
				public void widgetSelected(SelectionEvent e) {
					doGoToAddress();
				}
			});

			button = fGoToAddressComposite.getButton(IDialogConstants.CANCEL_ID);
			if (button != null) {
				button.addSelectionListener(new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						hideGotoAddressComposite();
					}
				});
			}
		}

		fGoToAddressComposite.getExpressionWidget().addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				doGoToAddress();
			}
		});

		fGoToAddressComposite.getExpressionWidget().addKeyListener(new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent e) {
				if (e.keyCode == SWT.ESC) {
					hideGotoAddressComposite();
				}
				super.keyPressed(e);
			}
		});
	}

	private void showGoToAddressComposite() {

		String selectedStr = getSelectedAsString();
		Text text = fGoToAddressComposite.getExpressionWidget();
		text.setText(selectedStr);
		text.setSelection(0, text.getCharCount());

		double height = fGoToAddressComposite.getHeight();
		double canvasHeight = fSashForm.getParent().getClientArea().height;
		double tableHeight = canvasHeight - height;

		double tableWeight = (tableHeight / canvasHeight) * 100;
		double textWeight = (height / canvasHeight) * 100;
		fSashForm.setWeights(new int[] { (int) tableWeight, (int) textWeight });
		fSashForm.setMaximizedControl(null);

		fGoToAddressComposite.getExpressionWidget().setFocus();
	}

	private void hideGotoAddressComposite() {
		fSashForm.setMaximizedControl(fTableViewer.getControl());
		if (isActivated()) {
			fTableViewer.getControl().setFocus();
		}
	}

	/**
	 *
	 */
	private void doGoToAddress() {
		try {
			BigInteger address = fGoToAddressComposite.getGoToAddress(fContentDescriptor.getContentBaseAddress(), getSelectedAddress());
			fGoToAddressAction.doGoToAddress(address.toString(16));
			hideGotoAddressComposite();
		} catch (DebugException e1) {
			MemoryViewUtil.openError(DebugUIMessages.GoToAddressAction_Go_to_address_failed, DebugUIMessages.GoToAddressAction_Go_to_address_failed, e1);
		} catch (NumberFormatException e1) {
			MemoryViewUtil.openError(DebugUIMessages.GoToAddressAction_Go_to_address_failed, DebugUIMessages.GoToAddressAction_Address_is_invalid, e1);
		}
	}

	@Override
	public void activated() {
		super.activated();

		fActivated = true;
		IWorkbench workbench = PlatformUI.getWorkbench();
		ICommandService commandSupport = workbench.getAdapter(ICommandService.class);
		IContextService contextSupport = workbench.getAdapter(IContextService.class);

		if (commandSupport != null && contextSupport != null) {
			fContext.add(contextSupport.activateContext(ID_ASYNC_TABLE_RENDERING_CONTEXT));
			Command gotoCommand = commandSupport.getCommand(ID_GO_TO_ADDRESS_COMMAND);

			if (fGoToAddressHandler == null) {
				fGoToAddressHandler = new AbstractHandler() {
					@Override
					public Object execute(ExecutionEvent event) throws ExecutionException {
						if (fSashForm.getMaximizedControl() != null) {
							fGoToAddressAction.run();
						} else {
							hideGotoAddressComposite();
						}
						return null;
					}
				};
			}
			gotoCommand.setHandler(fGoToAddressHandler);

			// The page up and page down actions can only be activated if the
			// rendering
			// is in manual scrolling mode. We are unable to determine the
			// scrolling mode
			// until the content descriptor is created. When the rendering is
			// activated, the content
			// descriptor may not be created yet. In that case, we cannot
			// activate the shortcuts here.
			// We will activate the shortcut after the rendering is created.
			if (fContentDescriptor != null && !isDynamicLoad()) {
				activatePageActions();
			}
		}
	}

	private void activatePageActions() {
		IWorkbench workbench = PlatformUI.getWorkbench();
		ICommandService commandSupport = workbench.getAdapter(ICommandService.class);
		if (commandSupport != null) {
			Command nextPage = commandSupport.getCommand(ID_NEXT_PAGE_COMMAND);
			if (fNextPageHandler == null) {
				fNextPageHandler = new AbstractHandler() {

					@Override
					public Object execute(ExecutionEvent arg0) throws ExecutionException {
						fNextAction.run();
						return null;
					}
				};
			}
			nextPage.setHandler(fNextPageHandler);

			Command prevPage = commandSupport.getCommand(ID_PREV_PAGE_COMMAND);
			if (fPrevPageHandler == null) {
				fPrevPageHandler = new AbstractHandler() {

					@Override
					public Object execute(ExecutionEvent arg0) throws ExecutionException {
						fPrevAction.run();
						return null;
					}
				};
			}
			prevPage.setHandler(fPrevPageHandler);
		}
	}

	@Override
	public void deactivated() {

		fActivated = false;
		IWorkbench workbench = PlatformUI.getWorkbench();
		ICommandService commandSupport = workbench.getAdapter(ICommandService.class);
		IContextService contextSupport = workbench.getAdapter(IContextService.class);

		if (commandSupport != null && contextSupport != null) {
			// remove handler
			Command command = commandSupport.getCommand(ID_GO_TO_ADDRESS_COMMAND);
			command.setHandler(null);

			command = commandSupport.getCommand(ID_NEXT_PAGE_COMMAND);
			command.setHandler(null);

			command = commandSupport.getCommand(ID_PREV_PAGE_COMMAND);
			command.setHandler(null);

			if (fContext != null) {
				contextSupport.deactivateContexts(fContext);
			}
		}
		super.deactivated();
	}

	private boolean isActivated() {
		return fActivated;
	}

	/**
	 * 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);
}
