blob: 7ff49833ca9b02c5cc0b2a30c0ab03f5ea5e29db [file] [log] [blame]
/*******************************************************************************
* 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();
}
};
}
}