001: /*******************************************************************************
002: * Copyright (c) 2005, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.services;
011:
012: import java.util.ArrayList;
013: import java.util.Collection;
014: import java.util.List;
015:
016: import org.eclipse.core.commands.Command;
017: import org.eclipse.core.commands.IParameter;
018: import org.eclipse.core.commands.Parameterization;
019: import org.eclipse.core.commands.ParameterizedCommand;
020: import org.eclipse.core.commands.common.NotDefinedException;
021: import org.eclipse.core.expressions.ElementHandler;
022: import org.eclipse.core.expressions.EvaluationResult;
023: import org.eclipse.core.expressions.Expression;
024: import org.eclipse.core.expressions.ExpressionConverter;
025: import org.eclipse.core.expressions.IEvaluationContext;
026: import org.eclipse.core.runtime.CoreException;
027: import org.eclipse.core.runtime.IConfigurationElement;
028: import org.eclipse.core.runtime.IExtensionRegistry;
029: import org.eclipse.core.runtime.IRegistryChangeEvent;
030: import org.eclipse.core.runtime.IRegistryChangeListener;
031: import org.eclipse.core.runtime.IStatus;
032: import org.eclipse.core.runtime.MultiStatus;
033: import org.eclipse.core.runtime.Platform;
034: import org.eclipse.core.runtime.Status;
035: import org.eclipse.swt.widgets.Display;
036: import org.eclipse.ui.commands.ICommandService;
037: import org.eclipse.ui.internal.WorkbenchPlugin;
038: import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
039: import org.eclipse.ui.internal.util.Util;
040: import org.eclipse.ui.services.IDisposable;
041:
042: /**
043: * <p>
044: * A manager for items parsed from the registry. This attaches a listener to the
045: * registry after the first read, and monitors the registry for changes from
046: * that point on. When {@link #dispose()} is called, the listener is detached.
047: * </p>
048: * <p>
049: * This class is only intended for internal use within the
050: * <code>org.eclipse.ui.workbench</code> plug-in.
051: * </p>
052: *
053: * @since 3.2
054: */
055: public abstract class RegistryPersistence implements IDisposable,
056: IWorkbenchRegistryConstants {
057:
058: /**
059: * The expression to return when there is an error. Never <code>null</code>.
060: */
061: protected static final Expression ERROR_EXPRESSION = new Expression() {
062: public final EvaluationResult evaluate(
063: final IEvaluationContext context) {
064: return null;
065: }
066: };
067:
068: /**
069: * Inserts the given element into the indexed two-dimensional array in the
070: * array at the index. The array is grown as necessary.
071: *
072: * @param elementToAdd
073: * The element to add to the indexed array; may be
074: * <code>null</code>
075: * @param indexedArray
076: * The two-dimensional array that is indexed by element type;
077: * must not be <code>null</code>.
078: * @param index
079: * The index at which the element should be added; must be a
080: * valid index.
081: * @param currentCount
082: * The current number of items in the array at the index.
083: */
084: protected static final void addElementToIndexedArray(
085: final IConfigurationElement elementToAdd,
086: final IConfigurationElement[][] indexedArray,
087: final int index, final int currentCount) {
088: final IConfigurationElement[] elements;
089: if (currentCount == 0) {
090: elements = new IConfigurationElement[1];
091: indexedArray[index] = elements;
092: } else {
093: if (currentCount >= indexedArray[index].length) {
094: final IConfigurationElement[] copy = new IConfigurationElement[indexedArray[index].length * 2];
095: System.arraycopy(indexedArray[index], 0, copy, 0,
096: currentCount);
097: elements = copy;
098: indexedArray[index] = elements;
099: } else {
100: elements = indexedArray[index];
101: }
102: }
103: elements[currentCount] = elementToAdd;
104: }
105:
106: /**
107: * Adds a warning to be logged at some later point in time.
108: *
109: * @param warningsToLog
110: * The collection of warnings to be logged; must not be
111: * <code>null</code>.
112: * @param message
113: * The mesaage to log; must not be <code>null</code>.
114: * @param element
115: * The element from which the warning originates; may be
116: * <code>null</code>.
117: */
118: protected static final void addWarning(final List warningsToLog,
119: final String message, final IConfigurationElement element) {
120: addWarning(warningsToLog, message, element, null, null, null);
121: }
122:
123: /**
124: * Adds a warning to be logged at some later point in time. This logs the
125: * identifier of the item.
126: *
127: * @param warningsToLog
128: * The collection of warnings to be logged; must not be
129: * <code>null</code>.
130: * @param message
131: * The mesaage to log; must not be <code>null</code>.
132: * @param element
133: * The element from which the warning originates; may be
134: * <code>null</code>.
135: * @param id
136: * The identifier of the item for which a warning is being
137: * logged; may be <code>null</code>.
138: */
139: protected static final void addWarning(final List warningsToLog,
140: final String message, final IConfigurationElement element,
141: final String id) {
142: addWarning(warningsToLog, message, element, id, null, null);
143: }
144:
145: /**
146: * Adds a warning to be logged at some later point in time. This logs the
147: * identifier of the item, as well as an extra attribute.
148: *
149: * @param warningsToLog
150: * The collection of warnings to be logged; must not be
151: * <code>null</code>.
152: * @param message
153: * The mesaage to log; must not be <code>null</code>.
154: * @param element
155: * The element from which the warning originates; may be
156: * <code>null</code>.
157: * @param id
158: * The identifier of the item for which a warning is being
159: * logged; may be <code>null</code>.
160: * @param extraAttributeName
161: * The name of extra attribute to be logged; may be
162: * <code>null</code>.
163: * @param extraAttributeValue
164: * The value of the extra attribute to be logged; may be
165: * <code>null</code>.
166: */
167: protected static final void addWarning(final List warningsToLog,
168: final String message, final IConfigurationElement element,
169: final String id, final String extraAttributeName,
170: final String extraAttributeValue) {
171: String statusMessage = message;
172: if (element != null) {
173: statusMessage = statusMessage
174: + ": plug-in='" + element.getNamespace() + '\''; //$NON-NLS-1$
175: }
176: if (id != null) {
177: if (element != null) {
178: statusMessage = statusMessage + ',';
179: } else {
180: statusMessage = statusMessage + ':';
181: }
182: statusMessage = statusMessage + " id='" + id + '\''; //$NON-NLS-1$
183: }
184: if (extraAttributeName != null) {
185: if ((element != null) || (id != null)) {
186: statusMessage = statusMessage + ',';
187: } else {
188: statusMessage = statusMessage + ':';
189: }
190: statusMessage = statusMessage + ' ' + extraAttributeName
191: + "='" //$NON-NLS-1$
192: + extraAttributeValue + '\'';
193: }
194:
195: final IStatus status = new Status(IStatus.WARNING,
196: WorkbenchPlugin.PI_WORKBENCH, 0, statusMessage, null);
197: warningsToLog.add(status);
198: }
199:
200: /**
201: * Checks that the class attribute or element exists for this element. This
202: * is used for executable extensions that are being read in.
203: *
204: * @param configurationElement
205: * The configuration element which should contain a class
206: * attribute or a class child element; must not be
207: * <code>null</code>.
208: * @param warningsToLog
209: * The list of warnings to be logged; never <code>null</code>.
210: * @param message
211: * The message to log if something goes wrong; may be
212: * <code>null</code>.
213: * @param id
214: * The identifier of the handle object; may be <code>null</code>.
215: * @return <code>true</code> if the class attribute or element exists;
216: * <code>false</code> otherwise.
217: */
218: protected static final boolean checkClass(
219: final IConfigurationElement configurationElement,
220: final List warningsToLog, final String message,
221: final String id) {
222: // Check to see if we have a handler class.
223: if ((configurationElement.getAttribute(ATT_CLASS) == null)
224: && (configurationElement.getChildren(TAG_CLASS).length == 0)) {
225: addWarning(warningsToLog, message, configurationElement, id);
226: return false;
227: }
228:
229: return true;
230: }
231:
232: /**
233: * Checks to see whether the configuration element represents a pulldown
234: * action. This involves reading the <code>style</code> and
235: * <code>pulldown</code> attributes.
236: *
237: * @param element
238: * The element to check; must not be <code>null</code>.
239: * @return <code>true</code> if the element is a pulldown action;
240: * <code>false</code> otherwise.
241: */
242: protected static final boolean isPulldown(
243: final IConfigurationElement element) {
244: final String style = readOptional(element, ATT_STYLE);
245: final boolean pulldown = readBoolean(element, ATT_PULLDOWN,
246: false);
247: return (pulldown || STYLE_PULLDOWN.equals(style));
248: }
249:
250: /**
251: * Logs any warnings in <code>warningsToLog</code>.
252: *
253: * @param warningsToLog
254: * The warnings to log; may be <code>null</code>.
255: * @param message
256: * The message to include in the log entry; must not be
257: * <code>null</code>.
258: */
259: protected static final void logWarnings(final List warningsToLog,
260: final String message) {
261: // If there were any warnings, then log them now.
262: if ((warningsToLog != null) && (!warningsToLog.isEmpty())) {
263: final IStatus status = new MultiStatus(
264: WorkbenchPlugin.PI_WORKBENCH,
265: 0,
266: (IStatus[]) warningsToLog
267: .toArray(new IStatus[warningsToLog.size()]),
268: message, null);
269: WorkbenchPlugin.log(status);
270: }
271: }
272:
273: /**
274: * Reads a boolean attribute from an element.
275: *
276: * @param configurationElement
277: * The configuration element from which to read the attribute;
278: * must not be <code>null</code>.
279: * @param attribute
280: * The attribute to read; must not be <code>null</code>.
281: * @param defaultValue
282: * The default boolean value.
283: * @return The attribute's value; may be <code>null</code> if none.
284: */
285: protected static final boolean readBoolean(
286: final IConfigurationElement configurationElement,
287: final String attribute, final boolean defaultValue) {
288: final String value = configurationElement
289: .getAttribute(attribute);
290: if (value == null) {
291: return defaultValue;
292: }
293:
294: if (defaultValue) {
295: return !value.equalsIgnoreCase("false"); //$NON-NLS-1$
296: }
297:
298: return value.equalsIgnoreCase("true"); //$NON-NLS-1$
299: }
300:
301: /**
302: * Reads an optional attribute from an element. This converts zero-length
303: * strings into <code>null</code>.
304: *
305: * @param configurationElement
306: * The configuration element from which to read the attribute;
307: * must not be <code>null</code>.
308: * @param attribute
309: * The attribute to read; must not be <code>null</code>.
310: * @return The attribute's value; may be <code>null</code> if none.
311: */
312: protected static final String readOptional(
313: final IConfigurationElement configurationElement,
314: final String attribute) {
315: String value = configurationElement.getAttribute(attribute);
316: if ((value != null) && (value.length() == 0)) {
317: value = null;
318: }
319:
320: return value;
321: }
322:
323: /**
324: * Reads the parameterized command from a parent configuration element. This
325: * is used to read the parameter sub-elements from a key element, as well as
326: * the command id. Each parameter is guaranteed to be valid. If invalid
327: * parameters are found, then a warning status will be appended to the
328: * <code>warningsToLog</code> list. The command id is required, or a
329: * warning will be logged.
330: *
331: * @param configurationElement
332: * The configuration element from which the parameters should be
333: * read; must not be <code>null</code>.
334: * @param commandService
335: * The service providing commands for the workbench; must not be
336: * <code>null</code>.
337: * @param warningsToLog
338: * The list of warnings found during parsing. Warnings found will
339: * parsing the parameters will be appended to this list. This
340: * value must not be <code>null</code>.
341: * @param message
342: * The message to print if the command identifier is not present;
343: * must not be <code>null</code>.
344: * @return The array of parameters found for this configuration element;
345: * <code>null</code> if none can be found.
346: */
347: protected static final ParameterizedCommand readParameterizedCommand(
348: final IConfigurationElement configurationElement,
349: final ICommandService commandService,
350: final List warningsToLog, final String message,
351: final String id) {
352: final String commandId = readRequired(configurationElement,
353: ATT_COMMAND_ID, warningsToLog, message, id);
354: if (commandId == null) {
355: return null;
356: }
357:
358: final Command command = commandService.getCommand(commandId);
359: final ParameterizedCommand parameterizedCommand = readParameters(
360: configurationElement, warningsToLog, command);
361:
362: return parameterizedCommand;
363: }
364:
365: /**
366: * Reads the parameters from a parent configuration element. This is used to
367: * read the parameter sub-elements from a key element. Each parameter is
368: * guaranteed to be valid. If invalid parameters are found, then a warning
369: * status will be appended to the <code>warningsToLog</code> list.
370: *
371: * @param configurationElement
372: * The configuration element from which the parameters should be
373: * read; must not be <code>null</code>.
374: * @param warningsToLog
375: * The list of warnings found during parsing. Warnings found will
376: * parsing the parameters will be appended to this list. This
377: * value must not be <code>null</code>.
378: * @param command
379: * The command around which the parameterization should be
380: * created; must not be <code>null</code>.
381: * @return The array of parameters found for this configuration element;
382: * <code>null</code> if none can be found.
383: */
384: protected static final ParameterizedCommand readParameters(
385: final IConfigurationElement configurationElement,
386: final List warningsToLog, final Command command) {
387: final IConfigurationElement[] parameterElements = configurationElement
388: .getChildren(TAG_PARAMETER);
389: if ((parameterElements == null)
390: || (parameterElements.length == 0)) {
391: return new ParameterizedCommand(command, null);
392: }
393:
394: final Collection parameters = new ArrayList();
395: for (int i = 0; i < parameterElements.length; i++) {
396: final IConfigurationElement parameterElement = parameterElements[i];
397:
398: // Read out the id.
399: final String id = parameterElement.getAttribute(ATT_ID);
400: if ((id == null) || (id.length() == 0)) {
401: // The name should never be null. This is invalid.
402: addWarning(warningsToLog, "Parameters need a name", //$NON-NLS-1$
403: configurationElement);
404: continue;
405: }
406:
407: // Find the parameter on the command.
408: IParameter parameter = null;
409: try {
410: final IParameter[] commandParameters = command
411: .getParameters();
412: if (parameters != null) {
413: for (int j = 0; j < commandParameters.length; j++) {
414: final IParameter currentParameter = commandParameters[j];
415: if (Util.equals(currentParameter.getId(), id)) {
416: parameter = currentParameter;
417: break;
418: }
419: }
420:
421: }
422: } catch (final NotDefinedException e) {
423: // This should not happen.
424: }
425: if (parameter == null) {
426: // The name should never be null. This is invalid.
427: addWarning(warningsToLog,
428: "Could not find a matching parameter", //$NON-NLS-1$
429: configurationElement, id);
430: continue;
431: }
432:
433: // Read out the value.
434: final String value = parameterElement
435: .getAttribute(ATT_VALUE);
436: if ((value == null) || (value.length() == 0)) {
437: // The name should never be null. This is invalid.
438: addWarning(warningsToLog, "Parameters need a value", //$NON-NLS-1$
439: configurationElement, id);
440: continue;
441: }
442:
443: parameters.add(new Parameterization(parameter, value));
444: }
445:
446: if (parameters.isEmpty()) {
447: return new ParameterizedCommand(command, null);
448: }
449:
450: return new ParameterizedCommand(
451: command,
452: (Parameterization[]) parameters
453: .toArray(new Parameterization[parameters.size()]));
454: }
455:
456: /**
457: * Reads a required attribute from the configuration element.
458: *
459: * @param configurationElement
460: * The configuration element from which to read; must not be
461: * <code>null</code>.
462: * @param attribute
463: * The attribute to read; must not be <code>null</code>.
464: * @param warningsToLog
465: * The list of warnings; must not be <code>null</code>.
466: * @param message
467: * The warning message to use if the attribute is missing; must
468: * not be <code>null</code>.
469: * @return The required attribute; may be <code>null</code> if missing.
470: */
471: protected static final String readRequired(
472: final IConfigurationElement configurationElement,
473: final String attribute, final List warningsToLog,
474: final String message) {
475: return readRequired(configurationElement, attribute,
476: warningsToLog, message, null);
477: }
478:
479: /**
480: * Reads a required attribute from the configuration element. This logs the
481: * identifier of the item if this required element cannot be found.
482: *
483: * @param configurationElement
484: * The configuration element from which to read; must not be
485: * <code>null</code>.
486: * @param attribute
487: * The attribute to read; must not be <code>null</code>.
488: * @param warningsToLog
489: * The list of warnings; must not be <code>null</code>.
490: * @param message
491: * The warning message to use if the attribute is missing; must
492: * not be <code>null</code>.
493: * @param id
494: * The identifier of the element for which this is a required
495: * attribute; may be <code>null</code>.
496: * @return The required attribute; may be <code>null</code> if missing.
497: */
498: protected static final String readRequired(
499: final IConfigurationElement configurationElement,
500: final String attribute, final List warningsToLog,
501: final String message, final String id) {
502: final String value = configurationElement
503: .getAttribute(attribute);
504: if ((value == null) || (value.length() == 0)) {
505: addWarning(warningsToLog, message, configurationElement, id);
506: return null;
507: }
508:
509: return value;
510: }
511:
512: /**
513: * Reads a <code>when</code> child element from the given configuration
514: * element. Warnings will be appended to <code>warningsToLog</code>.
515: *
516: * @param parentElement
517: * The configuration element which might have a <code>when</code>
518: * element as a child; never <code>null</code>.
519: * @param whenElementName
520: * The name of the when element to find; never <code>null</code>.
521: * @param id
522: * The identifier of the menu element whose <code>when</code>
523: * expression is being read; never <code>null</code>.
524: * @param warningsToLog
525: * The list of warnings while parsing the extension point; never
526: * <code>null</code>.
527: * @return The <code>when</code> expression for the
528: * <code>configurationElement</code>, if any; otherwise,
529: * <code>null</code>.
530: */
531: protected static final Expression readWhenElement(
532: final IConfigurationElement parentElement,
533: final String whenElementName, final String id,
534: final List warningsToLog) {
535: // Check to see if we have an when expression.
536: final IConfigurationElement[] whenElements = parentElement
537: .getChildren(whenElementName);
538: Expression whenExpression = null;
539: if (whenElements.length > 0) {
540: // Check if we have too many when elements.
541: if (whenElements.length > 1) {
542: // There should only be one when element
543: addWarning(
544: warningsToLog,
545: "There should only be one when element", parentElement, //$NON-NLS-1$
546: id, "whenElementName", //$NON-NLS-1$
547: whenElementName);
548: return ERROR_EXPRESSION;
549: }
550:
551: final IConfigurationElement whenElement = whenElements[0];
552: final IConfigurationElement[] expressionElements = whenElement
553: .getChildren();
554: if (expressionElements.length > 0) {
555: // Check if we have too many expression elements
556: if (expressionElements.length > 1) {
557: // There should only be one expression element
558: addWarning(
559: warningsToLog,
560: "There should only be one expression element", parentElement, //$NON-NLS-1$
561: id, "whenElementName", //$NON-NLS-1$
562: whenElementName);
563: return ERROR_EXPRESSION;
564: }
565:
566: // Convert the activeWhen element into an expression.
567: final ElementHandler elementHandler = ElementHandler
568: .getDefault();
569: final ExpressionConverter converter = ExpressionConverter
570: .getDefault();
571: final IConfigurationElement expressionElement = expressionElements[0];
572: try {
573: whenExpression = elementHandler.create(converter,
574: expressionElement);
575: } catch (final CoreException e) {
576: // There when expression could not be created.
577: addWarning(
578: warningsToLog,
579: "Problem creating when element", //$NON-NLS-1$
580: parentElement, id,
581: "whenElementName", whenElementName); //$NON-NLS-1$
582: return ERROR_EXPRESSION;
583: }
584: }
585: }
586:
587: return whenExpression;
588: }
589:
590: /**
591: * The registry change listener for this class.
592: */
593: private final IRegistryChangeListener registryChangeListener;
594:
595: /**
596: * Whether the preference and registry change listeners have been attached
597: * yet.
598: */
599: protected boolean registryListenerAttached = false;
600:
601: /**
602: * Constructs a new instance of {@link RegistryPersistence}. A registry
603: * change listener is created.
604: */
605: protected RegistryPersistence() {
606: registryChangeListener = new IRegistryChangeListener() {
607: public final void registryChanged(
608: final IRegistryChangeEvent event) {
609: if (isChangeImportant(event)) {
610: Display.getDefault().asyncExec(new Runnable() {
611: public final void run() {
612: read();
613: }
614: });
615: }
616: }
617: };
618: }
619:
620: /**
621: * Detaches the registry change listener from the registry.
622: */
623: public void dispose() {
624: final IExtensionRegistry registry = Platform
625: .getExtensionRegistry();
626: registry.removeRegistryChangeListener(registryChangeListener);
627: registryListenerAttached = false;
628: }
629:
630: /**
631: * Checks whether the registry change could affect this persistence class.
632: *
633: * @param event
634: * The event indicating the registry change; must not be
635: * <code>null</code>.
636: * @return <code>true</code> if the persistence instance is affected by
637: * this change; <code>false</code> otherwise.
638: */
639: protected abstract boolean isChangeImportant(
640: final IRegistryChangeEvent event);
641:
642: /**
643: * Reads the various elements from the registry. Subclasses should extend,
644: * but must not override.
645: */
646: protected void read() {
647: if (!registryListenerAttached) {
648: final IExtensionRegistry registry = Platform
649: .getExtensionRegistry();
650: registry.addRegistryChangeListener(registryChangeListener);
651: registryListenerAttached = true;
652: }
653: }
654: }
|