/*******************************************************************************
 * Copyright (c) 2009, 2011 Wind River Systems 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:
 *     Wind River Systems - initial API and implementation
 *     Winnie Lai (Texas Instruments) - Individual Element Number Format (Bug 202556)
 *******************************************************************************/
package org.eclipse.cdt.dsf.debug.ui.viewmodel.numberformat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.RejectedExecutionException;

import org.eclipse.cdt.dsf.concurrent.ConfinedToDsfExecutor;
import org.eclipse.cdt.dsf.concurrent.CountingRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DataRequestMonitor;
import org.eclipse.cdt.dsf.concurrent.DsfRunnable;
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.service.DsfServices;
import org.eclipse.cdt.dsf.service.DsfSession;
import org.eclipse.cdt.dsf.ui.concurrent.ViewerDataRequestMonitor;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMNode;
import org.eclipse.cdt.dsf.ui.viewmodel.IVMProvider;
import org.eclipse.cdt.dsf.ui.viewmodel.datamodel.IDMVMContext;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IPropertiesUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.PropertiesUpdateStatus;
import org.eclipse.cdt.dsf.ui.viewmodel.update.ICacheEntry;
import org.eclipse.cdt.dsf.ui.viewmodel.update.ICachingVMProviderExtension2;
import org.eclipse.cdt.dsf.ui.viewmodel.update.IVMUpdatePolicyExtension;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.util.tracker.ServiceTracker;

/**
 * A helper class for View Model Node implementations that support elements 
 * to be formatted using different number formats.  This object can be 
 * instantiated by a VM node to retrieve formatted values from a given service
 * using given DMC type.  
 * <p>
 * Note: This class is a replacement for the {@link FormattedValueVMUtil#updateFormattedValues(IPropertiesUpdate[], IFormattedValues, Class, RequestMonitor)}
 * static method.  This new implementation retrieves cached values if they are
 * available in the VM Cache. 
 * </p>
 * 
 * @see FormattedValueVMUtil
 * @see org.eclipse.cdt.dsf.ui.viewmodel.properties.IElementPropertiesProvider
 * @see org.eclipse.cdt.dsf.debug.service.IFormattedValues
 * 
 * @since 2.0
 */
public class FormattedValueRetriever {

    private final IVMNode fNode;
    private final ICachingVMProviderExtension2 fCache;
    private final IElementFormatProvider fElementFormatProvider;
    private final ServiceTracker fServiceTracker;
    private final Class<? extends IFormattedDataDMContext> fDmcType;
    private final String fPropertyPrefix;
    
    private final String PROP_AVAILABLE_FORMATS;
    private final String PROP_ACTIVE_FORMAT;
    private final String PROP_ACTIVE_FORMAT_VALUE;
    private final String PROP_BASE;

    public FormattedValueRetriever(IVMNode node, DsfSession session, Class<?> serviceClass, Class<? extends IFormattedDataDMContext> dmcType) {
        this(node, createFilter(session, serviceClass), dmcType, null);
    }
    
    public FormattedValueRetriever(IVMNode node, DsfSession session, Class<?> serviceClass, Class<? extends IFormattedDataDMContext> dmcType, String propertyPrefix) {
        this(node, createFilter(session, serviceClass), dmcType, propertyPrefix);
    }

    public FormattedValueRetriever(IVMNode node, Filter filter, Class<? extends IFormattedDataDMContext> dmcType, String propertyPrefix) {
        fNode = node;
        fCache = (ICachingVMProviderExtension2)node.getVMProvider();
        IVMProvider vmprovider = fNode.getVMProvider();
        fElementFormatProvider = vmprovider instanceof IElementFormatProvider ? (IElementFormatProvider) vmprovider : null;
        fServiceTracker = new ServiceTracker(DsfUIPlugin.getBundleContext(), filter, null);
        fServiceTracker.open();
        fDmcType = dmcType;
        if (propertyPrefix == null) {
            propertyPrefix = ""; //$NON-NLS-1$
        }
        fPropertyPrefix = propertyPrefix;
        PROP_AVAILABLE_FORMATS = (fPropertyPrefix + IDebugVMConstants.PROP_FORMATTED_VALUE_AVAILABLE_FORMATS).intern();
        PROP_ACTIVE_FORMAT = (fPropertyPrefix + IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT).intern();
        PROP_ACTIVE_FORMAT_VALUE = (fPropertyPrefix + IDebugVMConstants.PROP_FORMATTED_VALUE_ACTIVE_FORMAT_VALUE).intern();
        PROP_BASE = (fPropertyPrefix + IDebugVMConstants.PROP_FORMATTED_VALUE_BASE).intern();
    }
    
    /**
     * Creates an OSGI service filter for the given service type in a given 
     * DSF session.
     */
    private static Filter createFilter(DsfSession session, Class<?> serviceClass) {
        try {
            return DsfUIPlugin.getBundleContext().createFilter( DsfServices.createServiceFilter(serviceClass, session.getId()) );
        } catch (InvalidSyntaxException e) {
            throw new RuntimeException("Unable to create service filter for " + serviceClass, e); //$NON-NLS-1$
        }        
    }
    
    public void dispose() {
        fServiceTracker.close();
    }
    
    /**
     * 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.  
     * <p>
     * Note: The node parameter must return a <code>ICachingVMProviderExtension2</code>
     * through its {@link IVMNode#getVMProvider()} method.
     * 
     * @param node This method also takes an <code>IVMNode</code> parameter 
     * which allows for retrieving the format value data from the View Model 
     * cache.  If the needed value property is cached already, the cached 
     * value will be used otherwise the properties will be retrieved from the 
     * 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 rm Request monitor used to signal completion of work
     * 
     * @since 2.2
     */
    @ConfinedToDsfExecutor("node.getExecutor()")
    public void update(final IPropertiesUpdate updates[], final RequestMonitor rm) 
    {
    	retreiveElementActiveFormat(updates, new DataRequestMonitor<Map<IPropertiesUpdate, String>>(ImmediateExecutor.getInstance(), rm) {
    		@Override
    		protected void handleCompleted() {
    			final Map<IPropertiesUpdate, String> elementFormatMap = getData();
    	        final Map<IPropertiesUpdate, String[]> cachedAvailableFormatsMap = calcCachedAvailableFormatsMap(updates);
    	        if ((cachedAvailableFormatsMap != null && cachedAvailableFormatsMap.size() == updates.length)) {
    	            // All updates were satisfied by the cache.
    	            doUpdateWithAvailableFormats(updates, cachedAvailableFormatsMap, elementFormatMap, rm);
    	        } else {
    	            final IFormattedValues service = (IFormattedValues)fServiceTracker.getService();
    	            if (service == null) {
    	                rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Service not available " + fServiceTracker, null)); //$NON-NLS-1$
    	                rm.done();
    	                return;
    	            }
    	            try {
    	                service.getExecutor().execute(new DsfRunnable() {
    	                    @Override
							public void run() {
    	                        retrieveAvailableFormats(
    	                            calcOutstandingAvailableFormatsUpdates(updates, cachedAvailableFormatsMap), 
    	                            new DataRequestMonitor<Map<IPropertiesUpdate, String[]>>(fNode.getVMProvider().getExecutor(), rm) {
    	                                @Override
    	                                protected void handleSuccess() {
    	                                    Map<IPropertiesUpdate, String[]> availableFormatsMap;
    	                                    if (cachedAvailableFormatsMap != null) {
    	                                        availableFormatsMap = cachedAvailableFormatsMap;
    	                                        availableFormatsMap.putAll(getData());
    	                                    } else {
    	                                        availableFormatsMap = getData();
    	                                    }
    	                                    // Retrieve the formatted values now that we have the available formats (where needed).
    	                                    // Note that we are passing off responsibility of our parent monitor  
    	                                    doUpdateWithAvailableFormats(updates, availableFormatsMap, elementFormatMap, rm); 
    	                                }
    	                            });
    	                    }
    	                });
    	            } catch (RejectedExecutionException e) {
    	                rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Service executor shut down " + service.getExecutor(), e)); //$NON-NLS-1$
    	                rm.done();
    	            }
    	        }
    		}
    	});
    }
    
    private void retreiveElementActiveFormat(final IPropertiesUpdate updates[], final DataRequestMonitor<Map<IPropertiesUpdate, String>> rm) {
    	if (fElementFormatProvider == null) {
    		rm.setData(new HashMap<IPropertiesUpdate, String>(0));
    		rm.done();
    		return;
    	}
        Map<IPropertiesUpdate, String> cachedMap = null;
        HashSet<IPropertiesUpdate> outstanding = null;
    	for (IPropertiesUpdate update : updates) {
    		if (isElementFormatPropertyNeeded(update) == false) {
    			continue;
    		}
    		String active = null;
    		ICacheEntry cacheEntry = fCache.getCacheEntry(fNode, update.getViewerInput(), update.getElementPath());
            if (cacheEntry != null && cacheEntry.getProperties() != null) {
                active = (String) cacheEntry.getProperties().get(PROP_ACTIVE_FORMAT);
            }
            if (active != null) {
                if (cachedMap == null) {  
                	cachedMap = new HashMap<IPropertiesUpdate, String>(updates.length * 4/3);
                }
                cachedMap.put(update, active);
            } else {
            	if (outstanding == null) {
            		outstanding = new HashSet<IPropertiesUpdate>(updates.length * 4/3);
            	}
            	outstanding.add(update);
            }
    	}
    	if (outstanding == null || outstanding.size() == 0) {
    		rm.setData(cachedMap == null ? new HashMap<IPropertiesUpdate, String>(0) : cachedMap);
    		rm.done();
    		return;
    	}
        if (cachedMap == null) {  
        	cachedMap = new HashMap<IPropertiesUpdate, String>(updates.length * 4/3);
        }
        final Map<IPropertiesUpdate, String> elementFormatMap = Collections.synchronizedMap(cachedMap);
        rm.setData(elementFormatMap);
        final CountingRequestMonitor countingRm = new CountingRequestMonitor(ImmediateExecutor.getInstance(), rm); 
        int count = 0;
        for (final IPropertiesUpdate update : outstanding) {
        	fElementFormatProvider.getActiveFormat(update.getPresentationContext(), fNode, update.getViewerInput(), update.getElementPath(),
       			new ViewerDataRequestMonitor<String>(ImmediateExecutor.getInstance(), update) {
       				@Override
       				protected void handleCompleted() {
       					if (isSuccess()) {
       						String active = this.getData();
       						if (update.getProperties().contains(PROP_ACTIVE_FORMAT)) {
       							update.setProperty(PROP_ACTIVE_FORMAT, active);
       						}
       						elementFormatMap.put(update, active);
       					}
       					countingRm.done();
       				}
       			});
            count++;
        }
        countingRm.setDoneCount(count);
        
    }
    
    /**
     * Retrieves the <code>PROP_FORMATTED_VALUE_AVAILABLE_FORMATS</code> 
     * property for each update and returns it in a map.  The returned 
     * map may be <code>null</code> if no cache data is available. 
     * 
     * @since 2.2
     */
    private Map<IPropertiesUpdate, String[]> calcCachedAvailableFormatsMap(IPropertiesUpdate updates[]) {
        Map<IPropertiesUpdate, String[]> cachedAvailableFormatsMap = null; // delay creating map till needed
        for (IPropertiesUpdate update : updates) {
            ICacheEntry cacheEntry = fCache.getCacheEntry(fNode, update.getViewerInput(), update.getElementPath());
            if (cacheEntry != null && cacheEntry.getProperties() != null) {
                String[] availableFormats = (String[])
                    cacheEntry.getProperties().get(PROP_AVAILABLE_FORMATS);
                // Add the cached entry to the cached map even if its null.  This will help keep track
                // of whether we need to call the service for data.
                if (availableFormats != null || !isAvailableFormatsPropertyNeeded(update)) {
                    if (cachedAvailableFormatsMap == null) {  
                        cachedAvailableFormatsMap = new HashMap<IPropertiesUpdate, String[]>(updates.length * 4/3);
                    }
                    cachedAvailableFormatsMap.put(update, availableFormats);
                    continue;
                }
            }
        }
        return cachedAvailableFormatsMap;
    }
    
    /**
     * Generates a list of updates which still need the 
     * <code>PROP_FORMATTED_VALUE_AVAILABLE_FORMATS</code> property.
     * 
     * @since 2.2
     */
    private List<IPropertiesUpdate> calcOutstandingAvailableFormatsUpdates(IPropertiesUpdate[] updates, Map<IPropertiesUpdate, String[]> cachedAvailableFormatsMap) {
        if (cachedAvailableFormatsMap != null) {
            List<IPropertiesUpdate> outstandingUpdates = new ArrayList<IPropertiesUpdate>(updates.length - cachedAvailableFormatsMap.size());
            for (IPropertiesUpdate update : updates) {
                if (!cachedAvailableFormatsMap.containsKey(update)) {
                    outstandingUpdates.add(update);
                }
            }
            return outstandingUpdates;
        } else {
            return Arrays.asList(updates);
        }
    }
    
    /**
     * Method to retrieve available formats for each update's element (if
     * needed). The result is returned in a map and in the
     * update object (if requested). 
     * <p>
     * Note that we use a synchronized map because it's updated by a request 
     * monitor with an ImmediateExecutor.
     * 
     * @since 2.2
     */
    @ConfinedToDsfExecutor("service.getExecutor()")
    private void retrieveAvailableFormats(
        final List<IPropertiesUpdate> updates, 
        final DataRequestMonitor<Map<IPropertiesUpdate, String[]>> rm) 
    {
        IFormattedValues service = (IFormattedValues)fServiceTracker.getService();
        assert service.getExecutor().isInExecutorThread();

        final Map<IPropertiesUpdate, String[]> availableFormats = Collections.synchronizedMap(new HashMap<IPropertiesUpdate, String[]>(updates.size() * 4/3));
        rm.setData(availableFormats);
        final CountingRequestMonitor countingRm = new CountingRequestMonitor(service.getExecutor(), rm); 
        int count = 0;
        
        for (final IPropertiesUpdate update : updates) {

            if (!isAvailableFormatsPropertyNeeded(update)) {
                continue;
            }
            
            IFormattedDataDMContext dmc = getFormattedDataDMContext(update);
            if (dmc == null) {
                continue;
            } 
            
            service.getAvailableFormats(
                dmc, 
                new ViewerDataRequestMonitor<String[]>(ImmediateExecutor.getInstance(), update) {
                    /**
                     * 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.
                     */
                    @Override
                    protected void handleCompleted() {
                        if (isSuccess()) {
                            // Set the result (available formats) into the update object if it was requested 
                            if (update.getProperties().contains(PROP_AVAILABLE_FORMATS)) {
                                update.setProperty(PROP_AVAILABLE_FORMATS, getData());
                            }
                            
                            if (getData().length != 0) { 
                                // 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(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "No number formats available for " + update.getElement(), null)); //$NON-NLS-1$
                            }           
                        } else {
                            update.setStatus(getStatus()); 
                        }
                        countingRm.done();
                    }
                });
            count++;
        }
        countingRm.setDoneCount(count);
        
    }
    
    /**
     * This method continues retrieving formatted value properties.  It is 
     * called once the available formats are calculated for each requested 
     * update.
     * 
     * @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
     * 
     * @since 2.2
     */
    @ConfinedToDsfExecutor("fNode.getExecutor()")
    private void doUpdateWithAvailableFormats(
        IPropertiesUpdate updates[],
        final Map<IPropertiesUpdate, String[]> availableFormatsMap,
        final Map<IPropertiesUpdate, String> elementFormatMap,
        final RequestMonitor rm)
    {
        final List<IPropertiesUpdate> outstandingUpdates = new ArrayList<IPropertiesUpdate>(updates.length); 
        final Map<IPropertiesUpdate, List<String>> requestedFormatsMap = new HashMap<IPropertiesUpdate, List<String>>(updates.length * 4 / 3);
        final Map<IPropertiesUpdate, String> activeFormatsMap = new HashMap<IPropertiesUpdate, String>(updates.length * 4 / 3);
        
        for (final IPropertiesUpdate update : updates) {
            String preferredFormat = FormattedValueVMUtil.getPreferredFormat(update.getPresentationContext());
            if (update.getProperties().contains(IDebugVMConstants.PROP_FORMATTED_VALUE_FORMAT_PREFERENCE)) {
                update.setProperty(IDebugVMConstants.PROP_FORMATTED_VALUE_FORMAT_PREFERENCE, preferredFormat);
            }

            final String activeFormat = calcActiveFormat(update, preferredFormat, availableFormatsMap, elementFormatMap);
            
            if (update.getProperties().contains(PROP_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(PROP_ACTIVE_FORMAT, activeFormat);
            }
            List<String> requestedFormats = calcRequestedFormats(update, activeFormat, availableFormatsMap.get(update));

            ICacheEntry cacheEntry = fCache.getCacheEntry(fNode, update.getViewerInput(), update.getElementPath());
            if (cacheEntry != null && cacheEntry.getProperties() != null) {
                IVMUpdatePolicyExtension updatePolicy = getVMUpdatePolicyExtension();
                Iterator<String> itr = requestedFormats.iterator();
                while (itr.hasNext()) {
                    String format = itr.next();
                    String formatProperty = FormattedValueVMUtil.getPropertyForFormatId(format, fPropertyPrefix);
                    Object value = cacheEntry.getProperties().get(formatProperty);
                    if (value != null || !canUpdateProperty(cacheEntry, updatePolicy, formatProperty)) {
                        itr.remove();
                        setUpdateFormatProperty(update, activeFormat, format, value);
                    }
                }
            }
            
            if (!requestedFormats.isEmpty()) {
                outstandingUpdates.add(update);
                requestedFormatsMap.put(update, requestedFormats);
                activeFormatsMap.put(update, activeFormat);
            } 
        }
        final IFormattedValues service = (IFormattedValues)fServiceTracker.getService();
        if (service == null) {
            rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Service not available " + fServiceTracker, null)); //$NON-NLS-1$
            rm.done();
            return;
        }
        try {
            service.getExecutor().execute(new DsfRunnable() {
                @Override
				public void run() {
                    doUpdateWithRequestedFormats(outstandingUpdates, requestedFormatsMap, activeFormatsMap, rm);
                }
            });
        } catch (RejectedExecutionException e) {
            rm.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, IDsfStatusConstants.REQUEST_FAILED, "Service executor shut down " + service.getExecutor(), e)); //$NON-NLS-1$
            rm.done();
        }
    }

    private IVMUpdatePolicyExtension getVMUpdatePolicyExtension() {
        if( fCache.getActiveUpdatePolicy() instanceof IVMUpdatePolicyExtension) {
            return (IVMUpdatePolicyExtension)fCache.getActiveUpdatePolicy();
        }
        return null;
    }
    
    private static boolean canUpdateProperty(ICacheEntry entry, IVMUpdatePolicyExtension updatePolicy, String property) {
        return !entry.isDirty() || (updatePolicy != null && updatePolicy.canUpdateDirtyProperty(entry, property));
    }
    
    /**
     * Retrieves the specified formatted values from the service. 
     * 
     * @param requestedFormatsMap Map containing the formats to be retrieved 
     * and filled in for each given update.
     * @param activeFormatsMap Map containing the active format for each given
     * update.  The active format value needs to be set in the update using the 
     * special property <code>PROP_FORMATTED_VALUE_ACTIVE_FORMAT_VALUE</code>.
     * 
     * @since 2.2
     */
    @ConfinedToDsfExecutor("service.getExecutor()")
    private void doUpdateWithRequestedFormats(
        List<IPropertiesUpdate> updates,
        final Map<IPropertiesUpdate, List<String>> requestedFormatsMap,
        final Map<IPropertiesUpdate, String> activeFormatsMap,
        final RequestMonitor monitor)
    { 
        IFormattedValues service = (IFormattedValues)fServiceTracker.getService();
        assert service.getExecutor().isInExecutorThread();

        // 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 = getFormattedDataDMContext(update);
            if (dmc == null) {
                continue;
            } 

            List<String> requestedFormats = requestedFormatsMap.get(update);
            for (String requestedFormat : requestedFormats) { 
                final FormattedValueDMContext formattedValueDmc = service.getFormattedValueContext(dmc, requestedFormat);
                service.getFormattedExpressionValue(
                    formattedValueDmc, 
                    // Here also use the ViewerDataRequestMonitor in order to propagate the update's cancel request. 
                    // However, when operation is complete, call the counting RM's done().
                    // 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()) {
                                setUpdateFormatProperty(
                                    update, 
                                    activeFormatsMap.get(update), 
                                    formattedValueDmc.getFormatID(), 
                                    getData().getFormattedValue()); 
                            } else {
                                update.setStatus(getStatus());
                            }
                            // Note: we must not call the update's done method, instead call counting RM done.
                            countingRm.done();
                            
                        };
                    });
                count++;
            }
        }
        countingRm.setDoneCount(count);
    }

    /**
     * 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.  
     * <p>
     * Note: If the availableFormatsMap doesn't contain the available formats  
     * for the given update, it means the update doesn't request any properties 
     * which requires the active format to be calculated.
     * 
     * @param update Properties update to calculate the active format for.  
     * @param availableFormatsMap The map of available formats.
     * @param elementFormatMap The map of element active format.
     * @return The active format, or null if active format not requested in 
     * update.
     */
    private String calcActiveFormat(IPropertiesUpdate update, String preferredFormat, Map<IPropertiesUpdate, String[]> availableFormatsMap,
    		Map<IPropertiesUpdate, String> elementFormatMap) {
        String[] availableFormats = availableFormatsMap.get(update);
        if (availableFormats != null && availableFormats.length != 0) {
        	String elementFormat = elementFormatMap.get(update);
            if (elementFormat != null && isFormatAvailable(elementFormat, availableFormats)) {
            	return elementFormat;
            }
            if (isFormatAvailable(preferredFormat, availableFormats)) {
                return preferredFormat;
            } else {
                return availableFormats[0];
            }
        }
        return null;  // null means we don't need to know what the active format is
    }

    /**
     * Returns <code>true</code> if the given availableFormats array contains 
     * the given format. 
     */
    private boolean isFormatAvailable(String format, String[] availableFormats) {
        for (String availableFormat : availableFormats) {
            if (availableFormat.equals(format)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 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.
     */ 
    private List<String> calcRequestedFormats(IPropertiesUpdate update, String activeFormat, String[] availableFormats) {
        List<String> requestedFormats = new ArrayList<String>(10); 
        
        boolean activeFormatValueHandled = false;   // have we come across a specific format request that is the active format?

        for (Iterator<String> itr = update.getProperties().iterator(); itr.hasNext() || (activeFormat != null && !activeFormatValueHandled);) {
            String nextFormat;
            if (itr.hasNext()) {
                String propertyName = itr.next();
                if (propertyName.startsWith(PROP_BASE)) {
                    nextFormat = FormattedValueVMUtil.getFormatFromProperty(propertyName, fPropertyPrefix);
                    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;
            }
            requestedFormats.add(nextFormat);
        }
        return requestedFormats;
    }
    
    /**
     * Writes the given formatted property value into the update.  It also 
     * writes the active format property if needed.
     * <p>
     * If the given property value is null, this method writes an error status 
     * instead.
     */
    private void setUpdateFormatProperty(IPropertiesUpdate update, String activeFormat, String format, Object value) {
        String formatProperty = FormattedValueVMUtil.getPropertyForFormatId(format, fPropertyPrefix); 
        if (value != null) {
            update.setProperty(formatProperty, value); 
            if (update.getProperties().contains(PROP_ACTIVE_FORMAT_VALUE) &&
                format.equals(activeFormat)) 
            {
                update.setProperty(PROP_ACTIVE_FORMAT_VALUE, value);
            }
        } else {
            IStatus staleDataStatus = DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_STATE, "Cache contains stale data. Refresh view.", null );//$NON-NLS-1$
            if (update.getProperties().contains(PROP_ACTIVE_FORMAT_VALUE) &&
                format.equals(activeFormat)) 
            {
                PropertiesUpdateStatus.getPropertiesStatus(update).setStatus(
                    new String[] { PROP_ACTIVE_FORMAT_VALUE, formatProperty },
                    staleDataStatus);
            } else {
                PropertiesUpdateStatus.getPropertiesStatus(update).setStatus(formatProperty, staleDataStatus);                
            }
        }
    }

    /**
     * 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.
     * @param update
     * @return
     */
    private boolean isAvailableFormatsPropertyNeeded(IPropertiesUpdate update) {
        return update.getProperties().contains(PROP_AVAILABLE_FORMATS) || 
               update.getProperties().contains(PROP_ACTIVE_FORMAT) ||
               update.getProperties().contains(PROP_ACTIVE_FORMAT_VALUE);
    }
    
    /**
     * For each update, query the active format for the update's
     * element...but only if necessary. It is necessary only if the
     * update is asking what the active format is or is asking for the value
     * of the element in that format.
     * @param update
     * @return true if needed
     */
    private boolean isElementFormatPropertyNeeded(IPropertiesUpdate update) {
    	if (fElementFormatProvider == null)
    		return false;
        return update.getProperties().contains(PROP_ACTIVE_FORMAT) ||
               update.getProperties().contains(PROP_ACTIVE_FORMAT_VALUE);
    }
    
    /**
     * Extracts the formatted data DMC from the update.  If update doesn't 
     * contain DMC-based elemtn, it writes an error to the update and returns 
     * <code>null</code>. 
     */
    private IFormattedDataDMContext getFormattedDataDMContext(IPropertiesUpdate update) 
    {
        IFormattedDataDMContext dmc = null;
        if (update.getElement() instanceof IDMVMContext) {
            dmc = DMContexts.getAncestorOfType(((IDMVMContext)update.getElement()).getDMContext(), fDmcType);
        }
        
        if (dmc == null) {
            update.setStatus(DsfUIPlugin.newErrorStatus(IDsfStatusConstants.INVALID_HANDLE, "Update element did not contain a valid context: " + fDmcType, null)); //$NON-NLS-1$
        } 
        return dmc;
    }
}
