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