| /******************************************************************************* |
| * Copyright (c) 2005, 2018 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.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.util.IPropertyChangeListener; |
| import org.eclipse.jface.util.PropertyChangeEvent; |
| import org.eclipse.ui.IMemento; |
| import org.eclipse.ui.commands.ICommandService; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| |
| /** |
| * <p> |
| * A manager for items parsed from the preference store. This attaches a |
| * listener to the registry after the first read, and monitors the preference |
| * 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 PreferencePersistence extends RegistryPersistence { |
| |
| /** |
| * 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 IMemento elementToAdd, final IMemento[][] indexedArray, |
| final int index, final int currentCount) { |
| final IMemento[] elements; |
| if (currentCount == 0) { |
| elements = new IMemento[1]; |
| indexedArray[index] = elements; |
| } else if (currentCount >= indexedArray[index].length) { |
| final IMemento[] copy = new IMemento[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 message to log; must not be <code>null</code>. |
| */ |
| protected static final void addWarning(final List<IStatus> warningsToLog, final String message) { |
| addWarning(warningsToLog, message, 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 message to log; must not 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 String id) { |
| addWarning(warningsToLog, message, 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 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 String id, |
| final String extraAttributeName, final String extraAttributeValue) { |
| String statusMessage = message; |
| if (id != null) { |
| statusMessage = statusMessage + ": id='" + id + '\''; //$NON-NLS-1$ |
| } |
| if (extraAttributeName != null) { |
| if (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); |
| } |
| |
| /** |
| * Reads a boolean attribute from a memnto. |
| * |
| * @param memento The memento 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 IMemento memento, final String attribute, |
| final boolean defaultValue) { |
| final String value = memento.getString(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 a memento. This converts zero-length strings |
| * into <code>null</code>. |
| * |
| * @param memento The memento 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 IMemento memento, final String attribute) { |
| String value = memento.getString(attribute); |
| if ((value != null) && (value.isEmpty())) { |
| value = null; |
| } |
| |
| return value; |
| } |
| |
| /** |
| * Reads the parameterized command from a parent memento. 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 memento The memento 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 IMemento memento, |
| final ICommandService commandService, final List<IStatus> warningsToLog, final String message, |
| final String id) { |
| final String commandId = readRequired(memento, ATT_COMMAND_ID, warningsToLog, message, id); |
| if (commandId == null) { |
| return null; |
| } |
| |
| final Command command = commandService.getCommand(commandId); |
| return readParameters(memento, warningsToLog, command); |
| } |
| |
| /** |
| * Reads the parameters from a parent memento. 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 memento The memento 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 memento; <code>null</code> if |
| * none can be found. |
| */ |
| protected static final ParameterizedCommand readParameters(final IMemento memento, |
| final List<IStatus> warningsToLog, final Command command) { |
| final IMemento[] parameterMementos = memento.getChildren(TAG_PARAMETER); |
| if ((parameterMementos == null) || (parameterMementos.length == 0)) { |
| return new ParameterizedCommand(command, null); |
| } |
| |
| final Collection<Parameterization> parameters = new ArrayList<>(); |
| for (final IMemento parameterMemento : parameterMementos) { |
| // Read out the id. |
| final String id = parameterMemento.getString(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$ |
| continue; |
| } |
| |
| // Find the parameter on the command. |
| IParameter parameter = null; |
| try { |
| final IParameter[] commandParameters = command.getParameters(); |
| if (commandParameters != 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", id); //$NON-NLS-1$ |
| continue; |
| } |
| |
| // Read out the value. |
| final String value = parameterMemento.getString(ATT_VALUE); |
| if ((value == null) || (value.isEmpty())) { |
| // The name should never be null. This is invalid. |
| addWarning(warningsToLog, "Parameters need a value", id); //$NON-NLS-1$ |
| 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 memento. |
| * |
| * @param memento The memento 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 IMemento memento, final String attribute, |
| final List<IStatus> warningsToLog, final String message) { |
| return readRequired(memento, attribute, warningsToLog, message, null); |
| } |
| |
| /** |
| * Reads a required attribute from the memento. This logs the identifier of the |
| * item if this required element cannot be found. |
| * |
| * @param memento The memento 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 IMemento memento, final String attribute, |
| final List<IStatus> warningsToLog, final String message, final String id) { |
| final String value = memento.getString(attribute); |
| if ((value == null) || (value.isEmpty())) { |
| addWarning(warningsToLog, message, id); |
| return null; |
| } |
| |
| return value; |
| } |
| |
| /** |
| * Whether the preference and registry change listeners have been attached yet. |
| */ |
| protected boolean preferenceListenerAttached = false; |
| |
| /** |
| * The registry change listener for this class. |
| */ |
| private final IPropertyChangeListener preferenceChangeListener; |
| |
| /** |
| * Detaches the preference change listener from the registry. |
| */ |
| @Override |
| public final void dispose() { |
| super.dispose(); |
| |
| final IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore(); |
| store.removePropertyChangeListener(preferenceChangeListener); |
| } |
| |
| /** |
| * Checks whether the preference change could affect this persistence class. |
| * |
| * @param event The event indicating the preference 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 PropertyChangeEvent event); |
| |
| /** |
| * Reads the various elements from the registry. Subclasses should extend, but |
| * must not override. |
| */ |
| @Override |
| protected void read() { |
| super.read(); |
| |
| if (!preferenceListenerAttached) { |
| final IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore(); |
| store.addPropertyChangeListener(preferenceChangeListener); |
| } |
| } |
| |
| /** |
| * Constructs a new instance of {@link PreferencePersistence}. A preference |
| * change listener is created. |
| */ |
| protected PreferencePersistence() { |
| super(); |
| |
| preferenceChangeListener = event -> { |
| if (isChangeImportant(event)) { |
| read(); |
| } |
| }; |
| } |
| } |