/*******************************************************************************
* Copyright (c) 2016 Ericsson AB and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ericsson - initial API and implementation
* *******************************************************************************/
package org.eclipse.cdt.debug.ui.memory.traditional;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.cdt.debug.core.model.IMemoryBlockAddressInfoRetrieval;
import org.eclipse.cdt.debug.core.model.IMemoryBlockAddressInfoRetrieval.EventType;
import org.eclipse.cdt.debug.core.model.IMemoryBlockAddressInfoRetrieval.IAddressInfoUpdateListener;
import org.eclipse.cdt.debug.core.model.IMemoryBlockAddressInfoRetrieval.IGetMemoryBlockAddressInfoReq;
import org.eclipse.cdt.debug.core.model.IMemoryBlockAddressInfoRetrieval.IMemoryBlockAddressInfoItem;
import org.eclipse.cdt.debug.internal.core.CRequest;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.debug.ui.DebugUITools;
import org.eclipse.debug.ui.contexts.DebugContextEvent;
import org.eclipse.debug.ui.contexts.IDebugContextListener;
import org.eclipse.debug.ui.contexts.IDebugContextService;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPartSite;
/**
* @since 1.4
*/
public class RenderingAddressInfo extends Rendering
implements IDebugContextListener, IAddressInfoUpdateListener {
private final TraditionalRendering fParent;
/**
* Simple tracker of selected context, to reduce the number of asynchronous calls to resolve the
* information items related to a selected context
*/
private volatile Object fSelectedContext;
private IMemoryBlockAddressInfoRetrieval fAddressInfoRetrieval = null;
/**
* This maintains the full set of information items retrieved for the currently selected context. This is
* updated each time a context selection change is detected
*/
private volatile IMemoryBlockAddressInfoItem[] fAddressInfoItems;
private final AddressInfoTypeMap fAddressInfoTypeStatusMap = new AddressInfoTypeMap();
public RenderingAddressInfo(Composite parent, TraditionalRendering renderingParent) {
super(parent, renderingParent);
fParent = renderingParent;
// Register as Debug context listener
IWorkbenchPartSite site = fParent.getMemoryRenderingContainer().getMemoryRenderingSite().getSite();
DebugUITools.addPartDebugContextListener(site, this);
IDebugContextService contextService = DebugUITools.getDebugContextManager()
.getContextService(site.getWorkbenchWindow());
resolveAddressInfoForCurrentSelection(contextService);
}
/**
* Keeps a map from information type to its state and to a corresponding Action instance
* needed to update the actual state from UI interactions
*/
class AddressInfoTypeMap extends HashMap<String, Boolean> {
private static final long serialVersionUID = 1L;
private final Map<String, Action> fTypeToActionMap = new HashMap<>();
public Action getAction(final String infoType) {
if (!containsKey(infoType)) {
if (fTypeToActionMap.containsKey(infoType)) {
// The key status has been removed, clean the action map
fTypeToActionMap.remove(infoType);
}
return null;
}
Action action = fTypeToActionMap.get(infoType);
if (action != null) {
return action;
} else {
action = new Action(infoType, IAction.AS_CHECK_BOX) {
@Override
public void run() {
put(infoType, Boolean.valueOf(isChecked()));
redrawPanes();
}
};
action.setChecked(get(infoType));
fTypeToActionMap.put(infoType, action);
}
return action;
}
@Override
public void clear() {
fTypeToActionMap.clear();
super.clear();
}
}
@Override
void resolveAddressInfoForCurrentSelection() {
IWorkbenchPartSite site = fParent.getMemoryRenderingContainer().getMemoryRenderingSite().getSite();
IDebugContextService contextService = DebugUITools.getDebugContextManager()
.getContextService(site.getWorkbenchWindow());
resolveAddressInfoForCurrentSelection(contextService);
}
public void dispose() {
fSelectedContext = null;
fMapStartAddrToInfoItems.clear();
fAddressInfoTypeStatusMap.clear();
fAddressInfoItems = null;
IWorkbenchPartSite site = fParent.getMemoryRenderingContainer().getMemoryRenderingSite().getSite();
DebugUITools.removePartDebugContextListener(site, this);
if (fAddressInfoRetrieval != null) {
fAddressInfoRetrieval.removeAddressInfoUpdateListener(this);
}
super.dispose();
}
private class GetMemoryBlockAddressInfoReq extends CRequest implements IGetMemoryBlockAddressInfoReq {
private Map<String, IMemoryBlockAddressInfoItem[]> fInfoTypeToItems = Collections
.synchronizedMap(new HashMap<String, IMemoryBlockAddressInfoItem[]>());
private final Object fContext;
GetMemoryBlockAddressInfoReq(Object context) {
fContext = context;
}
@Override
public IMemoryBlockAddressInfoItem[] getAddressInfoItems(String type) {
return fInfoTypeToItems.get(type);
}
@Override
public void setAddressInfoItems(String type, IMemoryBlockAddressInfoItem[] items) {
fInfoTypeToItems.put(type, items);
}
public Object getContext() {
return fContext;
}
@Override
public String[] getAddressInfoItemTypes() {
return fInfoTypeToItems.keySet().toArray(new String[fInfoTypeToItems.size()]);
}
@Override
public IMemoryBlockAddressInfoItem[] getAllAddressInfoItems() {
// concatenate the different type of items received into a single list
List<IMemoryBlockAddressInfoItem> allItemsList = new ArrayList<>();
// For each set of items
for (IMemoryBlockAddressInfoItem[] partialItems : fInfoTypeToItems.values()) {
if (partialItems != null && partialItems.length > 0) {
allItemsList.addAll(Arrays.asList(partialItems));
}
}
return allItemsList.toArray(new IMemoryBlockAddressInfoItem[allItemsList.size()]);
}
}
/**
* @since 1.4
*/
@Override
public void debugContextChanged(DebugContextEvent event) {
if ((event.getFlags() & DebugContextEvent.ACTIVATED) > 0) {
// Resolve selection
ISelection selection = event.getContext();
if (!(selection instanceof IStructuredSelection)) {
return;
}
Object elem = ((IStructuredSelection) selection).getFirstElement();
handleDebugContextChanged(elem);
}
}
private void resolveAddressInfoForCurrentSelection(IDebugContextService contextService) {
IWorkbenchPartSite site = fParent.getMemoryRenderingContainer().getMemoryRenderingSite().getSite();
// Check current selection
ISelection selection = contextService.getActiveContext(site.getId(),
((IViewSite) site).getSecondaryId());
if (selection instanceof StructuredSelection) {
handleDebugContextChanged(((StructuredSelection) selection).getFirstElement());
}
}
private void handleDebugContextChanged(final Object context) {
if (isDisposed() || context == null || !fParent.isShowCrossRefInfoGlobalPref()) {
// Invalid context or user has chosen not to see additional address information
return;
}
if (context instanceof IAdaptable) {
IAdaptable adaptable = (IAdaptable) context;
final IMemoryBlockAddressInfoRetrieval addrInfo = ((IMemoryBlockAddressInfoRetrieval) adaptable
.getAdapter(IMemoryBlockAddressInfoRetrieval.class));
if (addrInfo == null) {
// No information retrieval available
return;
}
// Save the selected context to later help us determine if the selection has really changed
fSelectedContext = context;
final Display display = getDisplay();
addrInfo.getMemoryBlockAddressInfo(context, getMemoryBlock(),
new GetMemoryBlockAddressInfoReq(context) {
@Override
public void done() {
// If the context is still valid
if (getContext().equals(fSelectedContext)) {
final IMemoryBlockAddressInfoItem[] addressInfoItems = getAllAddressInfoItems();
if (fParent.isShowCrossRefInfoGlobalPref()) {
final String[] types = getAddressInfoItemTypes();
if (!display.isDisposed()) {
display.asyncExec(new Runnable() {
@Override
public void run() {
for (String type : types) {
if (!fAddressInfoTypeStatusMap.containsKey(type)) {
fAddressInfoTypeStatusMap.put(type, Boolean.TRUE);
}
}
// The selection has changed, so our Address information may no longer be valid
fAddressInfoItems = addressInfoItems;
fMapStartAddrToInfoItems.clear();
if (fBinaryPane.isVisible()) {
redrawPanes();
}
refreshUpdateListener(addrInfo);
}
});
}
}
}
}
private void refreshUpdateListener(final IMemoryBlockAddressInfoRetrieval addrInfo) {
if (fAddressInfoRetrieval == null) {
// One retrieval per session,
// Register this rendering to listen for info updates
fAddressInfoRetrieval = addrInfo;
addrInfo.addAddressInfoUpdateListener(RenderingAddressInfo.this);
}
}
});
}
}
/**
* @return Return the view port end address, if the DataPane displays information with a single height per
* row i.e. single height is used when no additional address information is available for any of
* the addresses in the view port
*/
private BigInteger getViewportEndAddressSingleHeight() {
int cellHeight = fBinaryPane.getCellTextHeight() + (getCellPadding() * 2);
int rowCount = getBounds().height / cellHeight;
BigInteger endAddress = fViewportAddress
.add(BigInteger.valueOf(this.getBytesPerRow() * rowCount / getAddressableSize()));
return endAddress;
}
private boolean isWithinRange(BigInteger item, BigInteger start, BigInteger end) {
if (item.compareTo(start) > -1 && item.compareTo(end) < 1) {
return true;
}
return false;
}
private String[] orderTypesAscending(Set<String> items) {
List<String> collection = new ArrayList<String>(items);
Collections.sort(collection);
return collection.toArray(new String[collection.size()]);
}
@Override
protected void redrawPanes() {
if (!isDisposed() && this.isVisible()) {
// Refresh address information visible in the current viewport
getVisibleValueToAddressInfoItems();
}
super.redrawPanes();
}
@Override
public void handleAddressInfoUpdate(EventType type, Object update) {
fAddressInfoItems = null;
resolveAddressInfoForCurrentSelection();
}
@Override
Map<BigInteger, List<IMemoryBlockAddressInfoItem>> getVisibleValueToAddressInfoItems() {
IMemoryBlockAddressInfoItem[] items = fAddressInfoItems;
if (items == null || !fParent.isShowCrossRefInfoGlobalPref()) {
fMapStartAddrToInfoItems.clear();
return fMapStartAddrToInfoItems;
}
if (getRadix() != RADIX_HEX && getRadix() != RADIX_BINARY) {
// If not using Hex or Binary radix, we can not accurately determine the location of cross
// reference information
// unless the cell size matches the addressable size of the target system
if (fParent.getAddressableSize() != getBytesPerColumn()) {
fMapStartAddrToInfoItems.clear();
return fMapStartAddrToInfoItems;
}
}
Map<BigInteger, List<IMemoryBlockAddressInfoItem>> allValuesMap = new HashMap<>(items.length);
// This local variable will hold the same values as the instance variable fMapAddressToInfoItems, and
// be used as
// return value. The reason for the duplication is to prevent concurrent access exceptions
Map<BigInteger, List<IMemoryBlockAddressInfoItem>> filteredValuesMap = new HashMap<>(items.length);
synchronized (fMapStartAddrToInfoItems) {
// Refreshing the Address to InfoItem data map
fMapStartAddrToInfoItems.clear();
BigInteger startAddress = getViewportStartAddress();
// Get the endAddress considering a page that uses single height,
// Note: The UI may some times present rows with double height even if the user does not see items
// with additional info, the reason is that the second part of a view port page may contain all
// the items with info.
// if we were to use and endAddress for a page that uses double height, but end up not having
// items with additional information, then it would need to switch to single height to compact the
// information in the view but since an endAddress for double height was used it will not consider
// half of the items for additional information, so marking info. would not be shown.
BigInteger endAddress = getViewportEndAddressSingleHeight();
for (IMemoryBlockAddressInfoItem item : items) {
// Skip information types not wanted by the user
String itemType = item.getInfoType();
if (!fAddressInfoTypeStatusMap.containsKey(itemType)
|| fAddressInfoTypeStatusMap.get(itemType).equals(Boolean.FALSE)) {
continue;
}
List<IMemoryBlockAddressInfoItem> containers = allValuesMap.get(item.getAddress());
if (containers == null) {
containers = new ArrayList<>();
allValuesMap.put(item.getAddress(), containers);
}
containers.add(item);
// If any address within the item width is within the visible range we want it in the filtered
// result
BigInteger itemStart = item.getAddress();
BigInteger itemEnd = item.getAddress().add(item.getRangeInAddressableUnits());
boolean itemStartIsInRange = isWithinRange(itemStart, startAddress, endAddress);
boolean itemEndIsInRange = isWithinRange(itemEnd, startAddress, endAddress);
boolean itemSpansOverVisibleRange = isWithinRange(startAddress, itemStart, itemEnd)
&& isWithinRange(endAddress, itemStart, itemEnd);
if (itemStartIsInRange || itemEndIsInRange || itemSpansOverVisibleRange) {
fMapStartAddrToInfoItems.put(item.getAddress(), allValuesMap.get(item.getAddress()));
filteredValuesMap.put(item.getAddress(), allValuesMap.get(item.getAddress()));
}
}
}
return filteredValuesMap;
}
@Override
String buildAddressInfoString(BigInteger cellAddress, String separator, boolean addTypeHeaders) {
List<IMemoryBlockAddressInfoItem> infoItems = fMapStartAddrToInfoItems.get(cellAddress);
if (infoItems == null || infoItems.size() < 1) {
// No information to display
return "";
}
// The container string builder for all types
StringBuilder sb = new StringBuilder();
Map<String, StringBuilder> infoTypeToStringBuilder = new HashMap<>();
for (int i = 0; i < infoItems.size(); i++) {
String infoType = infoItems.get(i).getInfoType();
// Resolve string builder for this info type
StringBuilder typeBuilder = infoTypeToStringBuilder.get(infoType);
if (typeBuilder == null) {
// Create a String builder per information type
if (addTypeHeaders) {
typeBuilder = new StringBuilder(infoType).append(":").append(separator);
} else {
typeBuilder = new StringBuilder();
}
infoTypeToStringBuilder.put(infoType, typeBuilder);
}
// append the new item information to the string builder associated to its type
typeBuilder.append(infoItems.get(i).getLabel()).append(separator);
}
// Present the group of items sorted by type name
String[] sortedTypes = orderTypesAscending(infoTypeToStringBuilder.keySet());
// Consolidate the String builders per type into a single one
int i = 0;
for (String type : sortedTypes) {
StringBuilder builder = infoTypeToStringBuilder.get(type);
String text = builder.toString();
text = text.substring(0, text.length() - 1);
sb.append(text);
if (i < infoTypeToStringBuilder.keySet().size() - 1) {
sb.append(separator);
}
i++;
}
return sb.toString();
}
@Override
boolean hasAddressInfo(BigInteger cellAddress) {
return fMapStartAddrToInfoItems.keySet().contains(cellAddress);
}
/**
* Indicates if additional address information is available to display in the current visible range
*/
@Override
boolean hasVisibleRangeInfo() {
return (fBinaryPane.fPaneVisible && fMapStartAddrToInfoItems.size() > 0);
}
@Override
public Action[] getDynamicActions() {
List<Action> actionList = new ArrayList<Action>(fAddressInfoTypeStatusMap.size());
if (getPaneVisible(Rendering.PANE_BINARY)) {
for (final String infoType : fAddressInfoTypeStatusMap.keySet()) {
Action action = fAddressInfoTypeStatusMap.getAction(infoType);
if (action != null) {
actionList.add(action);
}
}
}
return actionList.toArray(new Action[actionList.size()]);
}
}