/******************************************************************************* * Copyright (c) 2007, 2009 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 *******************************************************************************/ package org.eclipse.cdt.dsf.ui.viewmodel.properties; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.cdt.dsf.concurrent.ImmediateExecutor; import org.eclipse.cdt.dsf.concurrent.ThreadSafe; import org.eclipse.cdt.dsf.internal.ui.DsfUIPlugin; import org.eclipse.cdt.dsf.ui.concurrent.ViewerDataRequestMonitor; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider; import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate; /** * A configurable label provider which uses element's property label provider * to set element's label attributes. * <p> * When this provider is registered for an element it calculates the properties * that need to be retrieved based on view's active columns, and then it calls the * element's property provider to retrieve those properties. After the property * values are retrieved, they are processed in order to produce correct label text, * images, fonts, and colors, for the given element. * * @since 2.0 - Renamed from PropertyBasedLabelProvider */ @ThreadSafe public class PropertiesBasedLabelProvider implements IElementLabelProvider { public static final String ID_COLUMN_NO_COLUMNS = "ID_COLUMN_NO_COLUMNS"; //$NON-NLS-1$ /** * Attribute information for each column by column ID. */ private Map<String, LabelColumnInfo> fColumnInfos = Collections.synchronizedMap(new HashMap<String,LabelColumnInfo>()); private IPropertiesUpdateListener[] fListeners = new IPropertiesUpdateListener[0]; /** * Standard constructor. A property based label constructor does not * initialize column attribute information {@link #setColumnInfo(String, LabelColumnInfo)} * must be called to configure each column. */ public PropertiesBasedLabelProvider() { } /** * Sets the given column info object for the given column ID. This column * info will be used to generate the label when the given column is visibile. * * @param columnId Column ID that the given column info is being registered for. * @param info Column 'info' object containing column attributes. * @return The previous column info object configured for this ID. */ public LabelColumnInfo setColumnInfo(String columnId, LabelColumnInfo info) { LabelColumnInfo oldInfo = fColumnInfos.put(columnId, info); return oldInfo; } /** * Returns the given column info object for the given column ID. * @param columnId Column ID to retrieve the column info for. * * @param columnId Column ID that the given column info is being registered for. * @@return Column 'info' object containing column attributes. */ public LabelColumnInfo getColumnInfo(String columnId) { return fColumnInfos.get(columnId); } /** * Adds a listener for properties updates generated by this label provider. * * @since 2.2 */ public void addPropertiesUpdateListener(IPropertiesUpdateListener listener) { synchronized(this) { if (!Arrays.asList(fListeners).contains(listener)) { IPropertiesUpdateListener[] newListeners = new IPropertiesUpdateListener[fListeners.length + 1]; System.arraycopy(fListeners, 0, newListeners, 0, fListeners.length); newListeners[fListeners.length] = listener; fListeners = newListeners; } } } /** * Removes a listener for properties updates generated by this label provider. * * @since 2.2 */ public void removePropertiesUpdateListener(IPropertiesUpdateListener listener) { synchronized(this) { int listenerIdx = Arrays.asList(fListeners).indexOf(listener); if (listenerIdx != -1) { IPropertiesUpdateListener[] newListeners = new IPropertiesUpdateListener[fListeners.length - 1]; System.arraycopy(fListeners, 0, newListeners, 0, listenerIdx); System.arraycopy(fListeners, listenerIdx + 1, newListeners, listenerIdx, newListeners.length - listenerIdx); fListeners = newListeners; } } } /** * In addition to guarantees on [labelUpdates] declared by * {@link IElementLabelProvider}, we further require/assume that all the * model elements referenced by [labelUpdates] adapt to the same * {@link IElementPropertiesProvider}. * * @see org.eclipse.debug.internal.ui.viewers.model.provisional.IElementLabelProvider#update(org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate[]) */ public void update(final ILabelUpdate[] labelUpdates) { IElementPropertiesProvider propertiesProvider = getElementPropertiesProvider(labelUpdates[0].getElement()); if (propertiesProvider == null) { for (ILabelUpdate update : labelUpdates) { update.setStatus(new Status(IStatus.ERROR, DsfUIPlugin.PLUGIN_ID, "Properties-based label provider " + this + " failed to generate a label, no properties provider registered for element: " + labelUpdates[0].getElement())); //$NON-NLS-1$ //$NON-NLS-2$ update.done(); } return; } // We are guaranteed that all the provided updates are for the same // presentation context. Thus we can safely assume they request the same // columns String[] columnIds = labelUpdates[0].getColumnIds(); Set<String> propertyNames = calcPropertyNamesForColumns(columnIds); // Call the properties provider. Create a request monitor for each label update. // We can use an immediate executor for the request monitor because the label provider // is thread safe. final IPropertiesUpdate[] propertiesUpdates = new IPropertiesUpdate[labelUpdates.length]; for (int i = 0; i < labelUpdates.length; i++) { final int idx = i; propertiesUpdates[idx] = new VMPropertiesUpdate( propertyNames, labelUpdates[idx], new ViewerDataRequestMonitor<Map<String, Object>>(ImmediateExecutor.getInstance(), labelUpdates[idx]) { @Override protected void handleCompleted() { notifyPropertiesUpdateCompleted(propertiesUpdates[idx]); updateLabel(labelUpdates[idx], getStatus(), getData()); } }); } notifyPropertiesUpdatesStarted(propertiesUpdates); propertiesProvider.update(propertiesUpdates); } private void notifyPropertiesUpdatesStarted(IPropertiesUpdate[] updates) { IPropertiesUpdateListener[] listeners = null; synchronized(this) { listeners = fListeners; } for (IPropertiesUpdateListener listener : listeners) { listener.propertiesUpdatesStarted(updates); } } private void notifyPropertiesUpdateCompleted(IPropertiesUpdate update) { IPropertiesUpdateListener[] listeners = null; synchronized(this) { listeners = fListeners; } for (IPropertiesUpdateListener listener : listeners) { listener.propertiesUpdateCompleted(update); } } /** * Calculates the names of properties that have to be retrieved from the property * provider to generate the labels for given columns. * @param columnIds Column IDs to check. * @return Array of property names. */ private Set<String> calcPropertyNamesForColumns(String[] columnIds) { Set<String> propertyNames = new HashSet<String>(); if (columnIds == null) { LabelColumnInfo columnInfo = getColumnInfo(ID_COLUMN_NO_COLUMNS); if (columnInfo != null) { for (String propertyName : columnInfo.getPropertyNames()) { propertyNames.add(propertyName); } } } else { for (String columnId : columnIds) { LabelColumnInfo info = getColumnInfo(columnId); if (info != null) { String[] infoPropertyNames = info.getPropertyNames(); for (int i = 0; i < infoPropertyNames.length; i++) { propertyNames.add(infoPropertyNames[i]); } } } } return propertyNames; } /** * Updates the label information based on given map of properties. * * @param update Label update to write to. * @param status Result of the properties update * @param properties Properties retrieved from the element properties provider. * * @since 2.0 */ protected void updateLabel(ILabelUpdate update, IStatus status, Map<String, Object> properties) { if (update.getColumnIds() == null) { LabelColumnInfo info = getColumnInfo(ID_COLUMN_NO_COLUMNS); if (info != null) { info.updateColumn(update, 0, status, properties); } } else { String[] columnIds = update.getColumnIds(); for (int i = 0; i < columnIds.length; i++) { LabelColumnInfo info = getColumnInfo(columnIds[i]); if (info != null) { info.updateColumn(update, i, status, properties); } } } update.done(); } private IElementPropertiesProvider getElementPropertiesProvider(Object element) { if (element instanceof IAdaptable) { return (IElementPropertiesProvider)((IAdaptable)element).getAdapter(IElementPropertiesProvider.class); } return null; } }