| /******************************************************************************* |
| * Copyright (c) 2005, 2015 IBM Corporation 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: |
| * 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 java.util.Objects; |
| 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.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() { |
| @Override |
| 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<IStatus> 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<IStatus> 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 message 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<IStatus> 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.getContributor().getName() + '\''; //$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<IStatus> 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<IStatus> 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, |
| 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.isEmpty())) { |
| 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<IStatus> 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); |
| return readParameters(configurationElement, warningsToLog, command); |
| } |
| |
| /** |
| * 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<IStatus> warningsToLog, final Command command) { |
| final IConfigurationElement[] parameterElements = configurationElement.getChildren(TAG_PARAMETER); |
| if ((parameterElements == null) || (parameterElements.length == 0)) { |
| return new ParameterizedCommand(command, null); |
| } |
| |
| final Collection<Parameterization> parameters = new ArrayList<>(); |
| for (final IConfigurationElement parameterElement : parameterElements) { |
| // Read out the id. |
| final String id = parameterElement.getAttribute(ATT_ID); |
| if ((id == null) || (id.isEmpty())) { |
| // 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 (final IParameter currentParameter : commandParameters) { |
| if (Objects.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.isEmpty())) { |
| // 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, |
| 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<IStatus> 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<IStatus> warningsToLog, final String message, final String id) { |
| final String value = configurationElement.getAttribute(attribute); |
| if ((value == null) || (value.isEmpty())) { |
| 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<IStatus> 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 = event -> { |
| if (isChangeImportant(event)) { |
| Display.getDefault().asyncExec(this::read); |
| } |
| }; |
| } |
| |
| /** |
| * Detaches the registry change listener from the registry. |
| */ |
| @Override |
| 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; |
| } |
| } |
| } |