/******************************************************************************* * Copyright (c) 2005, 2007 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui.internal.services; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.IParameter; import org.eclipse.core.commands.Parameterization; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.commands.common.NotDefinedException; import org.eclipse.core.expressions.ElementHandler; import org.eclipse.core.expressions.EvaluationResult; import org.eclipse.core.expressions.Expression; import org.eclipse.core.expressions.ExpressionConverter; import org.eclipse.core.expressions.IEvaluationContext; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IRegistryChangeEvent; import org.eclipse.core.runtime.IRegistryChangeListener; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants; import org.eclipse.ui.internal.util.Util; import org.eclipse.ui.services.IDisposable; /** * <p> * A manager for items parsed from the registry. This attaches a listener to the * registry after the first read, and monitors the registry for changes from * that point on. When {@link #dispose()} is called, the listener is detached. * </p> * <p> * This class is only intended for internal use within the * <code>org.eclipse.ui.workbench</code> plug-in. * </p> * * @since 3.2 */ public abstract class RegistryPersistence implements IDisposable, IWorkbenchRegistryConstants { /** * The expression to return when there is an error. Never <code>null</code>. */ protected static final Expression ERROR_EXPRESSION = new Expression() { public final EvaluationResult evaluate(final IEvaluationContext context) { return null; } }; /** * Inserts the given element into the indexed two-dimensional array in the * array at the index. The array is grown as necessary. * * @param elementToAdd * The element to add to the indexed array; may be * <code>null</code> * @param indexedArray * The two-dimensional array that is indexed by element type; * must not be <code>null</code>. * @param index * The index at which the element should be added; must be a * valid index. * @param currentCount * The current number of items in the array at the index. */ protected static final void addElementToIndexedArray( final IConfigurationElement elementToAdd, final IConfigurationElement[][] indexedArray, final int index, final int currentCount) { final IConfigurationElement[] elements; if (currentCount == 0) { elements = new IConfigurationElement[1]; indexedArray[index] = elements; } else { if (currentCount >= indexedArray[index].length) { final IConfigurationElement[] copy = new IConfigurationElement[indexedArray[index].length * 2]; System.arraycopy(indexedArray[index], 0, copy, 0, currentCount); elements = copy; indexedArray[index] = elements; } else { elements = indexedArray[index]; } } elements[currentCount] = elementToAdd; } /** * Adds a warning to be logged at some later point in time. * * @param warningsToLog * The collection of warnings to be logged; must not be * <code>null</code>. * @param message * The mesaage to log; must not be <code>null</code>. * @param element * The element from which the warning originates; may be * <code>null</code>. */ protected static final void addWarning(final List warningsToLog, final String message, final IConfigurationElement element) { addWarning(warningsToLog, message, element, null, null, null); } /** * Adds a warning to be logged at some later point in time. This logs the * identifier of the item. * * @param warningsToLog * The collection of warnings to be logged; must not be * <code>null</code>. * @param message * The mesaage to log; must not be <code>null</code>. * @param element * The element from which the warning originates; may be * <code>null</code>. * @param id * The identifier of the item for which a warning is being * logged; may be <code>null</code>. */ protected static final void addWarning(final List warningsToLog, final String message, final IConfigurationElement element, final String id) { addWarning(warningsToLog, message, element, id, null, null); } /** * Adds a warning to be logged at some later point in time. This logs the * identifier of the item, as well as an extra attribute. * * @param warningsToLog * The collection of warnings to be logged; must not be * <code>null</code>. * @param message * The mesaage to log; must not be <code>null</code>. * @param element * The element from which the warning originates; may be * <code>null</code>. * @param id * The identifier of the item for which a warning is being * logged; may be <code>null</code>. * @param extraAttributeName * The name of extra attribute to be logged; may be * <code>null</code>. * @param extraAttributeValue * The value of the extra attribute to be logged; may be * <code>null</code>. */ protected static final void addWarning(final List warningsToLog, final String message, final IConfigurationElement element, final String id, final String extraAttributeName, final String extraAttributeValue) { String statusMessage = message; if (element != null) { statusMessage = statusMessage + ": plug-in='" + element.getNamespace() + '\''; //$NON-NLS-1$ } if (id != null) { if (element != null) { statusMessage = statusMessage + ','; } else { statusMessage = statusMessage + ':'; } statusMessage = statusMessage + " id='" + id + '\''; //$NON-NLS-1$ } if (extraAttributeName != null) { if ((element != null) || (id != null)) { statusMessage = statusMessage + ','; } else { statusMessage = statusMessage + ':'; } statusMessage = statusMessage + ' ' + extraAttributeName + "='" //$NON-NLS-1$ + extraAttributeValue + '\''; } final IStatus status = new Status(IStatus.WARNING, WorkbenchPlugin.PI_WORKBENCH, 0, statusMessage, null); warningsToLog.add(status); } /** * Checks that the class attribute or element exists for this element. This * is used for executable extensions that are being read in. * * @param configurationElement * The configuration element which should contain a class * attribute or a class child element; must not be * <code>null</code>. * @param warningsToLog * The list of warnings to be logged; never <code>null</code>. * @param message * The message to log if something goes wrong; may be * <code>null</code>. * @param id * The identifier of the handle object; may be <code>null</code>. * @return <code>true</code> if the class attribute or element exists; * <code>false</code> otherwise. */ protected static final boolean checkClass( final IConfigurationElement configurationElement, final List warningsToLog, final String message, final String id) { // Check to see if we have a handler class. if ((configurationElement.getAttribute(ATT_CLASS) == null) && (configurationElement.getChildren(TAG_CLASS).length == 0)) { addWarning(warningsToLog, message, configurationElement, id); return false; } return true; } /** * Checks to see whether the configuration element represents a pulldown * action. This involves reading the <code>style</code> and * <code>pulldown</code> attributes. * * @param element * The element to check; must not be <code>null</code>. * @return <code>true</code> if the element is a pulldown action; * <code>false</code> otherwise. */ protected static final boolean isPulldown( final IConfigurationElement element) { final String style = readOptional(element, ATT_STYLE); final boolean pulldown = readBoolean(element, ATT_PULLDOWN, false); return (pulldown || STYLE_PULLDOWN.equals(style)); } /** * Logs any warnings in <code>warningsToLog</code>. * * @param warningsToLog * The warnings to log; may be <code>null</code>. * @param message * The message to include in the log entry; must not be * <code>null</code>. */ protected static final void logWarnings(final List warningsToLog, final String message) { // If there were any warnings, then log them now. if ((warningsToLog != null) && (!warningsToLog.isEmpty())) { final IStatus status = new MultiStatus( WorkbenchPlugin.PI_WORKBENCH, 0, (IStatus[]) warningsToLog .toArray(new IStatus[warningsToLog.size()]), message, null); WorkbenchPlugin.log(status); } } /** * Reads a boolean attribute from an element. * * @param configurationElement * The configuration element from which to read the attribute; * must not be <code>null</code>. * @param attribute * The attribute to read; must not be <code>null</code>. * @param defaultValue * The default boolean value. * @return The attribute's value; may be <code>null</code> if none. */ protected static final boolean readBoolean( final IConfigurationElement configurationElement, final String attribute, final boolean defaultValue) { final String value = configurationElement.getAttribute(attribute); if (value == null) { return defaultValue; } if (defaultValue) { return !value.equalsIgnoreCase("false"); //$NON-NLS-1$ } return value.equalsIgnoreCase("true"); //$NON-NLS-1$ } /** * Reads an optional attribute from an element. This converts zero-length * strings into <code>null</code>. * * @param configurationElement * The configuration element from which to read the attribute; * must not be <code>null</code>. * @param attribute * The attribute to read; must not be <code>null</code>. * @return The attribute's value; may be <code>null</code> if none. */ protected static final String readOptional( final IConfigurationElement configurationElement, final String attribute) { String value = configurationElement.getAttribute(attribute); if ((value != null) && (value.length() == 0)) { value = null; } return value; } /** * Reads the parameterized command from a parent configuration element. This * is used to read the parameter sub-elements from a key element, as well as * the command id. Each parameter is guaranteed to be valid. If invalid * parameters are found, then a warning status will be appended to the * <code>warningsToLog</code> list. The command id is required, or a * warning will be logged. * * @param configurationElement * The configuration element from which the parameters should be * read; must not be <code>null</code>. * @param commandService * The service providing commands for the workbench; must not be * <code>null</code>. * @param warningsToLog * The list of warnings found during parsing. Warnings found will * parsing the parameters will be appended to this list. This * value must not be <code>null</code>. * @param message * The message to print if the command identifier is not present; * must not be <code>null</code>. * @return The array of parameters found for this configuration element; * <code>null</code> if none can be found. */ protected static final ParameterizedCommand readParameterizedCommand( final IConfigurationElement configurationElement, final ICommandService commandService, final List warningsToLog, final String message, final String id) { final String commandId = readRequired(configurationElement, ATT_COMMAND_ID, warningsToLog, message, id); if (commandId == null) { return null; } final Command command = commandService.getCommand(commandId); final ParameterizedCommand parameterizedCommand = readParameters( configurationElement, warningsToLog, command); return parameterizedCommand; } /** * Reads the parameters from a parent configuration element. This is used to * read the parameter sub-elements from a key element. Each parameter is * guaranteed to be valid. If invalid parameters are found, then a warning * status will be appended to the <code>warningsToLog</code> list. * * @param configurationElement * The configuration element from which the parameters should be * read; must not be <code>null</code>. * @param warningsToLog * The list of warnings found during parsing. Warnings found will * parsing the parameters will be appended to this list. This * value must not be <code>null</code>. * @param command * The command around which the parameterization should be * created; must not be <code>null</code>. * @return The array of parameters found for this configuration element; * <code>null</code> if none can be found. */ protected static final ParameterizedCommand readParameters( final IConfigurationElement configurationElement, final List warningsToLog, final Command command) { final IConfigurationElement[] parameterElements = configurationElement .getChildren(TAG_PARAMETER); if ((parameterElements == null) || (parameterElements.length == 0)) { return new ParameterizedCommand(command, null); } final Collection parameters = new ArrayList(); for (int i = 0; i < parameterElements.length; i++) { final IConfigurationElement parameterElement = parameterElements[i]; // Read out the id. final String id = parameterElement.getAttribute(ATT_ID); if ((id == null) || (id.length() == 0)) { // The name should never be null. This is invalid. addWarning(warningsToLog, "Parameters need a name", //$NON-NLS-1$ configurationElement); continue; } // Find the parameter on the command. IParameter parameter = null; try { final IParameter[] commandParameters = command.getParameters(); if (parameters != null) { for (int j = 0; j < commandParameters.length; j++) { final IParameter currentParameter = commandParameters[j]; if (Util.equals(currentParameter.getId(), id)) { parameter = currentParameter; break; } } } } catch (final NotDefinedException e) { // This should not happen. } if (parameter == null) { // The name should never be null. This is invalid. addWarning(warningsToLog, "Could not find a matching parameter", //$NON-NLS-1$ configurationElement, id); continue; } // Read out the value. final String value = parameterElement.getAttribute(ATT_VALUE); if ((value == null) || (value.length() == 0)) { // The name should never be null. This is invalid. addWarning(warningsToLog, "Parameters need a value", //$NON-NLS-1$ configurationElement, id); continue; } parameters.add(new Parameterization(parameter, value)); } if (parameters.isEmpty()) { return new ParameterizedCommand(command, null); } return new ParameterizedCommand(command, (Parameterization[]) parameters .toArray(new Parameterization[parameters.size()])); } /** * Reads a required attribute from the configuration element. * * @param configurationElement * The configuration element from which to read; must not be * <code>null</code>. * @param attribute * The attribute to read; must not be <code>null</code>. * @param warningsToLog * The list of warnings; must not be <code>null</code>. * @param message * The warning message to use if the attribute is missing; must * not be <code>null</code>. * @return The required attribute; may be <code>null</code> if missing. */ protected static final String readRequired( final IConfigurationElement configurationElement, final String attribute, final List warningsToLog, final String message) { return readRequired(configurationElement, attribute, warningsToLog, message, null); } /** * Reads a required attribute from the configuration element. This logs the * identifier of the item if this required element cannot be found. * * @param configurationElement * The configuration element from which to read; must not be * <code>null</code>. * @param attribute * The attribute to read; must not be <code>null</code>. * @param warningsToLog * The list of warnings; must not be <code>null</code>. * @param message * The warning message to use if the attribute is missing; must * not be <code>null</code>. * @param id * The identifier of the element for which this is a required * attribute; may be <code>null</code>. * @return The required attribute; may be <code>null</code> if missing. */ protected static final String readRequired( final IConfigurationElement configurationElement, final String attribute, final List warningsToLog, final String message, final String id) { final String value = configurationElement.getAttribute(attribute); if ((value == null) || (value.length() == 0)) { addWarning(warningsToLog, message, configurationElement, id); return null; } return value; } /** * Reads a <code>when</code> child element from the given configuration * element. Warnings will be appended to <code>warningsToLog</code>. * * @param parentElement * The configuration element which might have a <code>when</code> * element as a child; never <code>null</code>. * @param whenElementName * The name of the when element to find; never <code>null</code>. * @param id * The identifier of the menu element whose <code>when</code> * expression is being read; never <code>null</code>. * @param warningsToLog * The list of warnings while parsing the extension point; never * <code>null</code>. * @return The <code>when</code> expression for the * <code>configurationElement</code>, if any; otherwise, * <code>null</code>. */ protected static final Expression readWhenElement( final IConfigurationElement parentElement, final String whenElementName, final String id, final List warningsToLog) { // Check to see if we have an when expression. final IConfigurationElement[] whenElements = parentElement .getChildren(whenElementName); Expression whenExpression = null; if (whenElements.length > 0) { // Check if we have too many when elements. if (whenElements.length > 1) { // There should only be one when element addWarning(warningsToLog, "There should only be one when element", parentElement, //$NON-NLS-1$ id, "whenElementName", //$NON-NLS-1$ whenElementName); return ERROR_EXPRESSION; } final IConfigurationElement whenElement = whenElements[0]; final IConfigurationElement[] expressionElements = whenElement .getChildren(); if (expressionElements.length > 0) { // Check if we have too many expression elements if (expressionElements.length > 1) { // There should only be one expression element addWarning( warningsToLog, "There should only be one expression element", parentElement, //$NON-NLS-1$ id, "whenElementName", //$NON-NLS-1$ whenElementName); return ERROR_EXPRESSION; } // Convert the activeWhen element into an expression. final ElementHandler elementHandler = ElementHandler .getDefault(); final ExpressionConverter converter = ExpressionConverter .getDefault(); final IConfigurationElement expressionElement = expressionElements[0]; try { whenExpression = elementHandler.create(converter, expressionElement); } catch (final CoreException e) { // There when expression could not be created. addWarning( warningsToLog, "Problem creating when element", //$NON-NLS-1$ parentElement, id, "whenElementName", whenElementName); //$NON-NLS-1$ return ERROR_EXPRESSION; } } } return whenExpression; } /** * The registry change listener for this class. */ private final IRegistryChangeListener registryChangeListener; /** * Whether the preference and registry change listeners have been attached * yet. */ protected boolean registryListenerAttached = false; /** * Constructs a new instance of {@link RegistryPersistence}. A registry * change listener is created. */ protected RegistryPersistence() { registryChangeListener = new IRegistryChangeListener() { public final void registryChanged(final IRegistryChangeEvent event) { if (isChangeImportant(event)) { Display.getDefault().asyncExec(new Runnable() { public final void run() { read(); } }); } } }; } /** * Detaches the registry change listener from the registry. */ public void dispose() { final IExtensionRegistry registry = Platform.getExtensionRegistry(); registry.removeRegistryChangeListener(registryChangeListener); registryListenerAttached = false; } /** * Checks whether the registry change could affect this persistence class. * * @param event * The event indicating the registry change; must not be * <code>null</code>. * @return <code>true</code> if the persistence instance is affected by * this change; <code>false</code> otherwise. */ protected abstract boolean isChangeImportant( final IRegistryChangeEvent event); /** * Reads the various elements from the registry. Subclasses should extend, * but must not override. */ protected void read() { if (!registryListenerAttached) { final IExtensionRegistry registry = Platform.getExtensionRegistry(); registry.addRegistryChangeListener(registryChangeListener); registryListenerAttached = true; } } }