blob: c600a63b8b4aec1bc1b28d4027f209d3df8a64e0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2012 Wind River Systems 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.numberformat;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.IDsfStatusConstants;
import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor;
import org.eclipse.cdt.dsf.concurrent.RequestMonitor;
import org.eclipse.cdt.dsf.datamodel.DMContexts;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMContext;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.FormattedValueDMData;
import org.eclipse.cdt.dsf.debug.service.IFormattedValues.IFormattedDataDMContext;
import org.eclipse.cdt.dsf.debug.ui.viewmodel.IDebugVMConstants;
import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerDataRequestMonitor;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IPropertiesUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IPresentationContext;
import com.ibm.icu.text.MessageFormat;
/**
* A helper class for View Model Node implementations that support elements
* to be formatted using different number formats. The various static methods in
* this class handle populating the properties of an IPropertiesUpdate using data
* retrieved from a DSF service implementing {@link IFormattedValues} interface.
*
* @see org.eclipse.cdt.dsf.ui.viewmodel.properties.IElementPropertiesProvider
* @see org.eclipse.cdt.dsf.debug.service.IFormattedValues
*
* @since 2.0
*/
public class FormattedValueVMUtil {
/**
* Cache to avoid creating many duplicate strings of formats and properties.
*/
private static Map<String, Map<String, String>> fFormatProperties = Collections
.synchronizedMap(new TreeMap<String, Map<String, String>>());
/**
* Common map of user-readable labels for format IDs.
*/
private static Map<String, String> fFormatLabels = new HashMap<>(8);
static {
setFormatLabel(IFormattedValues.NATURAL_FORMAT,
MessagesForNumberFormat.FormattedValueVMUtil_Natural_format__label);
setFormatLabel(IFormattedValues.HEX_FORMAT, MessagesForNumberFormat.FormattedValueVMUtil_Hex_format__label);
setFormatLabel(IFormattedValues.DECIMAL_FORMAT,
MessagesForNumberFormat.FormattedValueVMUtil_Decimal_format__label);
setFormatLabel(IFormattedValues.OCTAL_FORMAT, MessagesForNumberFormat.FormattedValueVMUtil_Octal_format__label);
setFormatLabel(IFormattedValues.BINARY_FORMAT,
MessagesForNumberFormat.FormattedValueVMUtil_Binary_format__label);
setFormatLabel(IFormattedValues.STRING_FORMAT,
MessagesForNumberFormat.FormattedValueVMUtil_String_format__label);
}
/**
* Adds a user-readable label for a given format ID. If a given view model has a custom format ID, it can
* add its label to the map of format IDs using this method.
*
* @param formatId Format ID to set the label for.
* @param label User-readable label for a format.
*/
public static void setFormatLabel(String formatId, String label) {
fFormatLabels.put(formatId, label);
}
/**
* Returns a user readable label for a given format ID.
*/
public static String getFormatLabel(String formatId) {
String label = fFormatLabels.get(formatId);
if (label != null) {
return label;
} else {
return MessageFormat.format(MessagesForNumberFormat.FormattedValueVMUtil_Other_format__format_text,
new Object[] { formatId });
}
}
/**
* Returns an element property representing an element value in a given format.
* @deprecated Replaced by {@link #getPropertyForFormatId(String, String)}
*/
@Deprecated
public static String getPropertyForFormatId(String formatId) {
return getPropertyForFormatId(formatId, ""); //$NON-NLS-1$
}
/**
* Returns an element property representing an element value in a given format.
*
* @param Format ID to create the property for.
* @param prefix The prefix for the property that is used to distinguish
* it from other number format values in a given property map. May be
* <code>null</code> or an empty string if no prefix is used.
* @return The generated property name.
*
* @since 2.2
*/
public static String getPropertyForFormatId(String formatId, String prefix) {
if (formatId == null) {
return null;
}
if (prefix == null) {
prefix = ""; //$NON-NLS-1$
}
synchronized (fFormatProperties) {
Map<String, String> formatsMap = getFormatsMap(prefix);
String property = formatsMap.get(formatId);
if (property == null) {
property = (prefix + IDebugVMConstants.PROP_FORMATTED_VALUE_BASE + "." + formatId).intern(); //$NON-NLS-1$
formatsMap.put(formatId, property);
}
return property;
}
}
private static Map<String, String> getFormatsMap(String prefix) {
synchronized (fFormatProperties) {
Map<String, String> prefixMap = fFormatProperties.get(prefix);
if (prefixMap == null) {
prefixMap = new TreeMap<>();
fFormatProperties.put(prefix, prefixMap);
}
return prefixMap;
}
}
/**
* Returns a format ID based on the element property representing a
* formatted element value.
*
* @deprecated Replaced by {@link #getFormatFromProperty(String, String)}
*/
@Deprecated
public static String getFormatFromProperty(String property) {
return getFormatFromProperty(property, ""); //$NON-NLS-1$
}
/**
* Returns a format ID based on the element property representing a
* formatted element value. This method has an additional prefix parameter
* which is used when multiple number formats are stored in a single
* property map.
*
* @param property The property to extract the format from.
* @param prefix The prefix for the property that is used to distinguish
* it from other number format values in a given property map. May be
* <code>null</code> or an empty string if no prefix is used.
* @return The format ID.
*
* @throws IllegalArgumentException if the property is not a formatted value
* property.
*
* @since 2.2
*/
public static String getFormatFromProperty(String property, String prefix) {
if (prefix == null) {
prefix = ""; //$NON-NLS-1$
}
synchronized (fFormatProperties) {
Map<String, String> formatsMap = getFormatsMap(prefix);
for (Map.Entry<String, String> entry : formatsMap.entrySet()) {
if (entry.getValue().equals(property)) {
return entry.getKey();
}
}
if (!property.startsWith(prefix)
|| !property.startsWith(IDebugVMConstants.PROP_FORMATTED_VALUE_BASE, prefix.length())) {
throw new IllegalArgumentException(
"Property " + property + " is not a valid formatted value format property."); //$NON-NLS-1$//$NON-NLS-2$
}
String formatId = property
.substring(prefix.length() + IDebugVMConstants.PROP_FORMATTED_VALUE_BASE.length() + 1).intern();
formatsMap.put(formatId, property);
return formatId;
}
}
/**
* Returns the user-selected number format that is saved in the given
* presentation context.
*/
public static String getPreferredFormat(IPresentationContext context) {
Object prop = context.getProperty(IDebugVMConstants.PROP_FORMATTED_VALUE_FORMAT_PREFERENCE);
if (prop != null) {
return (String) prop;
}
return IFormattedValues.NATURAL_FORMAT;
}
/**
* This method fills in the formatted value properties in the given array
* of property update objects using data retrieved from the given
* formatted values service.
*
* @param updates The array of updates to fill in information to. This
* update is used to retrieve the data model context and to write the
* properties into. Implementation will not directly mark these updates
* complete, but contribute towards that end by marking [monitor] complete.
* @param service The service to be used to retrieve the values from.
* @param dmcType The class type of the data model context. Some updates
* can contain multiple formatted data data model contexts, and this
* method assures that there is no ambiguity in which context should be
* used.
* @param monitor Request monitor used to signal completion of work
*
* @deprecated This method has been replaced by the {@link FormattedValueRetriever}
* utility.
*/
@Deprecated
@ConfinedToDsfExecutor("service.getExecutor()")
public static void updateFormattedValues(final IPropertiesUpdate updates[], final IFormattedValues service,
final Class<? extends IFormattedDataDMContext> dmcType, final RequestMonitor monitor) {
// First retrieve the available formats for each update's element (if
// needed). Store the result in a map (for internal use) and in the
// update object (if requested). After that's done, call another method
// to retrieve the formatted values. Note that we use a synchronized map
// because it's updated by a request monitor with an ImmediateExecutor.
final Map<IPropertiesUpdate, String[]> availableFormats = Collections
.synchronizedMap(new HashMap<IPropertiesUpdate, String[]>(updates.length * 4 / 3));
final CountingRequestMonitor countingRm = new CountingRequestMonitor(service.getExecutor(), monitor) {
@Override
protected void handleCompleted() {
// Retrieve the formatted values now that we have the available formats (where needed).
// Note that we are passing off responsibility of our parent monitor
updateFormattedValuesWithAvailableFormats(updates, service, dmcType, availableFormats, monitor);
// Note: we must not call the update's done method
}
};
int count = 0;
// For each update, query the formats available for the update's
// element...but only if necessary. The available formats are necessary
// only if the update explicitly requests that information, or if the
// update is asking what the active format is or is asking for the value
// of the element in that format. The reason we need them in the last
// two cases is that we can't establish the 'active' format for an
// element without knowing its available formats. See
// updateFormattedValuesWithAvailableFormats(), as that's where we make
// that determination.
for (final IPropertiesUpdate update : updates) {
if ((!update.getProperties().contains(IDebugVMConstants.PROP_FORMATTED_VALUE_AVAILABLE_FORMATS)
&& !update.getProperties().contains(IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT)
&& !update.getProperties().contains(IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT_VALUE))) {
continue;
}
IFormattedDataDMContext dmc = null;
if (update.getElement() instanceof IDMVMContext) {
dmc = DMContexts.getAncestorOfType(((IDMVMContext) update.getElement()).getDMContext(), dmcType);
}
if (dmc == null) {
update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE,
"Update element did not contain a valid context: " + dmcType, null)); //$NON-NLS-1$
continue;
}
service.getAvailableFormats(dmc,
new ViewerDataRequestMonitor<String[]>(ImmediateExecutor.getInstance(), update) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
// Set the result (available formats) into the update object if it was requested
if (update.getProperties()
.contains(IDebugVMConstants.PROP_FORMATTED_VALUE_AVAILABLE_FORMATS)) {
update.setProperty(IDebugVMConstants.PROP_FORMATTED_VALUE_AVAILABLE_FORMATS,
getData());
}
// also add it to the map; we'll need to access it when querying the element's value.
availableFormats.put(update, getData());
} else {
update.setStatus(getStatus());
}
countingRm.done();
// Note we don't mark the update object done, and we
// avoid calling our base implementation so that it
// doesn't either. The completion of this request is
// just a step in servicing the update.
}
});
count++;
}
countingRm.setDoneCount(count);
}
/**
* @param updates
* the update objects to act on. Implementation will not directly
* mark these complete, but contribute towards that end by
* marking [monitor] complete.
* @param availableFormatsMap
* prior to calling this method, the caller queries (where
* necessary) the formats supported by the element in each
* update, and it puts that information in this map. If an entry
* in [updates] does not appear in this map, it means that its
* view-model element doesn't support any formats (very
* unlikely), or that the available formats aren't necessary to
* service the properties specified in the update
* @param monitor
* Request monitor used to signal completion of work
*/
@ConfinedToDsfExecutor("service.getExecutor()")
private static void updateFormattedValuesWithAvailableFormats(IPropertiesUpdate updates[], IFormattedValues service,
Class<? extends IFormattedDataDMContext> dmcType, Map<IPropertiesUpdate, String[]> availableFormatsMap,
final RequestMonitor monitor) {
// Use a single counting RM for all the requested formats for each update.
final CountingRequestMonitor countingRm = new CountingRequestMonitor(ImmediateExecutor.getInstance(), monitor);
int count = 0;
for (final IPropertiesUpdate update : updates) {
IFormattedDataDMContext dmc = null;
if (update.getElement() instanceof IDMVMContext) {
dmc = DMContexts.getAncestorOfType(((IDMVMContext) update.getElement()).getDMContext(), dmcType);
}
if (dmc == null) {
// The error status should already be set by the calling method.
continue;
}
// Determine the 'active' value format. It is the view preference if
// and only if the element supports it. Otherwise it is the first
// format supported by the element. If our caller didn't provide the
// available formats for an update (element), then it means the
// update doesn't contain any properties that requires us to
// determine the active format.
String[] availableFormats = availableFormatsMap.get(update);
String _activeFormat = null;
if (availableFormats != null && availableFormats.length != 0) {
_activeFormat = getPreferredFormat(update.getPresentationContext());
update.setProperty(IDebugVMConstants.PROP_FORMATTED_VALUE_FORMAT_PREFERENCE, _activeFormat);
if (!isFormatAvailable(_activeFormat, availableFormats)) {
_activeFormat = availableFormats[0];
}
}
final String activeFormat = _activeFormat; // null means we don't need to know what the active format is
if (update.getProperties().contains(IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT)) {
assert activeFormat != null
: "Our caller should have provided the available formats if this property was specified; given available formats, an 'active' nomination is guaranteed."; //$NON-NLS-1$
update.setProperty(IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT, activeFormat);
}
// Service the properties that ask for the value in a specific
// format. If the update request contains the property
// PROP_FORMATTED_VALUE_ACTIVE_FORMAT_VALUE, and the active format
// has not been explicitly requested, then we need an additional
// iteration to provide it.
boolean activeFormatValueRequested = false; // does the update object ask for PROP_FORMATTED_VALUE_ACTIVE_FORMAT_VALUE?
boolean activeFormatValueHandled = false; // have we come across a specific format request that is the active format?
if (update.getProperties().contains(IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT_VALUE)) {
assert activeFormat != null
: "Our caller should have provided the available formats if this property was specified; given available formats, an 'active' nomination is guaranteed."; //$NON-NLS-1$
activeFormatValueRequested = true; // we may end up making an additional run
}
for (Iterator<String> itr = update.getProperties().iterator(); itr.hasNext()
|| (activeFormatValueRequested && !activeFormatValueHandled);) {
String nextFormat;
if (itr.hasNext()) {
String propertyName = itr.next();
if (propertyName.startsWith(IDebugVMConstants.PROP_FORMATTED_VALUE_BASE)) {
nextFormat = FormattedValueVMUtil.getFormatFromProperty(propertyName);
if (nextFormat.equals(activeFormat)) {
activeFormatValueHandled = true;
}
// if we know the supported formats (we may not), then no-op if this format is unsupported
if (availableFormats != null && !isFormatAvailable(nextFormat, availableFormats)) {
continue;
}
} else {
continue;
}
} else {
// the additional iteration to handle the active format
nextFormat = activeFormat;
activeFormatValueHandled = true;
}
final boolean _activeFormatValueRequested = activeFormatValueRequested;
final FormattedValueDMContext formattedValueDmc = service.getFormattedValueContext(dmc, nextFormat);
service.getFormattedExpressionValue(formattedValueDmc,
// Here also use the ViewerDataRequestMonitor in order to propagate the update's cancel request.
// Use an immediate executor to avoid the possibility of a rejected execution exception.
new ViewerDataRequestMonitor<FormattedValueDMData>(ImmediateExecutor.getInstance(), update) {
@Override
protected void handleCompleted() {
if (isSuccess()) {
String format = formattedValueDmc.getFormatID();
update.setProperty(FormattedValueVMUtil.getPropertyForFormatId(format),
getData().getFormattedValue());
if (_activeFormatValueRequested && format.equals(activeFormat)) {
update.setProperty(IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT_VALUE,
getData().getFormattedValue());
}
} else {
update.setStatus(getStatus());
}
countingRm.done();
// Note: we must not call the update's done method
}
});
count++;
}
}
countingRm.setDoneCount(count);
}
private static boolean isFormatAvailable(String format, String[] availableFormats) {
for (String availableFormat : availableFormats) {
if (availableFormat.equals(format)) {
return true;
}
}
return false;
}
}