001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 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;
011:
012: import java.lang.reflect.InvocationTargetException;
013: import java.lang.reflect.Method;
014: import java.util.ArrayList;
015: import java.util.Iterator;
016: import java.util.List;
017:
018: import org.eclipse.core.runtime.IAdaptable;
019: import org.eclipse.core.runtime.IConfigurationElement;
020: import org.eclipse.core.runtime.Platform;
021: import org.eclipse.jface.viewers.ISelection;
022: import org.eclipse.jface.viewers.IStructuredSelection;
023: import org.eclipse.jface.viewers.StructuredSelection;
024: import org.eclipse.ui.actions.SimpleWildcardTester;
025: import org.eclipse.ui.internal.ActionExpression;
026: import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants;
027: import org.eclipse.ui.internal.util.Util;
028: import org.eclipse.ui.model.IWorkbenchAdapter;
029: import org.osgi.framework.Bundle;
030:
031: /**
032: * Determines the enablement status given a selection. This calculation is done
033: * based on the definition of the <code>enablesFor</code> attribute,
034: * <code>enablement</code> element, and the <code>selection</code> element
035: * found in the <code>IConfigurationElement</code> provided.
036: * <p>
037: * This class can be instantiated by clients. It is not intended to be extended.
038: * </p>
039: *
040: * @since 3.0
041: *
042: * Note: The dependency on org.eclipse.jface.text for ITextSelection must be
043: * severed It may be possible to do with IActionFilter generic workbench
044: * registers IActionFilter for "size" property against IStructuredSelection
045: * workbench text registers IActionFilter for "size" property against
046: * ITextSelection code here: sel.getAdapter(IActionFilter.class) As an interim
047: * solution, use reflection to access selections implementing ITextSelection
048: */
049: public final class SelectionEnabler {
050:
051: /* package */static class SelectionClass {
052: public String className;
053:
054: public String nameFilter;
055:
056: public boolean recursive;
057: }
058:
059: public static final int ANY_NUMBER = -2;
060:
061: /**
062: * The constant integer hash code value meaning the hash code has not yet
063: * been computed.
064: */
065: private static final int HASH_CODE_NOT_COMPUTED = -1;
066:
067: /**
068: * A factor for computing the hash code for all schemes.
069: */
070: private static final int HASH_FACTOR = 89;
071:
072: /**
073: * The seed for the hash code for all schemes.
074: */
075: private static final int HASH_INITIAL = SelectionEnabler.class
076: .getName().hashCode();
077:
078: /**
079: * Cached value of <code>org.eclipse.jface.text.ITextSelection.class</code>;
080: * <code>null</code> if not initialized or not present.
081: */
082: private static Class iTextSelectionClass = null;
083:
084: /**
085: * Hard-wired id of the JFace text plug-in (not on pre-req chain).
086: */
087: private static final String JFACE_TEXT_PLUG_IN = "org.eclipse.jface.text"; //$NON-NLS-1$
088:
089: public static final int MULTIPLE = -5;
090:
091: public static final int NONE = -4;
092:
093: public static final int NONE_OR_ONE = -3;
094:
095: public static final int ONE_OR_MORE = -1;
096:
097: /**
098: * Hard-wired fully qualified name of the text selection class (not on
099: * pre-req chain).
100: */
101: private static final String TEXT_SELECTION_CLASS = "org.eclipse.jface.text.ITextSelection"; //$NON-NLS-1$
102:
103: /**
104: * Indicates whether the JFace text plug-in is even around. Without the
105: * JFace text plug-in, text selections are moot.
106: */
107: private static boolean textSelectionPossible = true;
108:
109: public static final int UNKNOWN = 0;
110:
111: /**
112: * Returns <code>ITextSelection.class</code> or <code>null</code> if the
113: * class is not available.
114: *
115: * @return <code>ITextSelection.class</code> or <code>null</code> if
116: * class not available
117: * @since 3.0
118: */
119: private static Class getTextSelectionClass() {
120: if (iTextSelectionClass != null) {
121: // tried before and succeeded
122: return iTextSelectionClass;
123: }
124: if (!textSelectionPossible) {
125: // tried before and failed
126: return null;
127: }
128:
129: // JFace text plug-in is not on prereq chain of generic wb plug-in
130: // hence: ITextSelection.class won't compile
131: // and Class.forName("org.eclipse.jface.text.ITextSelection") won't find
132: // it need to be trickier...
133: Bundle bundle = Platform.getBundle(JFACE_TEXT_PLUG_IN);
134: if (bundle == null || bundle.getState() == Bundle.UNINSTALLED) {
135: // JFace text plug-in is not around, or has already
136: // been removed, assume that it will never be around
137: textSelectionPossible = false;
138: return null;
139: }
140:
141: // plug-in is around
142: // it's not our job to activate the plug-in
143: if (bundle.getState() == Bundle.INSTALLED) {
144: // assume it might come alive later
145: textSelectionPossible = true;
146: return null;
147: }
148:
149: try {
150: Class c = bundle.loadClass(TEXT_SELECTION_CLASS);
151: // remember for next time
152: iTextSelectionClass = c;
153: return iTextSelectionClass;
154: } catch (ClassNotFoundException e) {
155: // unable to load ITextSelection - sounds pretty serious
156: // treat as if JFace text plug-in were unavailable
157: textSelectionPossible = false;
158: return null;
159: }
160: }
161:
162: /**
163: * Verifies that the given name matches the given wildcard filter. Returns
164: * true if it does.
165: *
166: * @param name
167: * @param filter
168: * @return <code>true</code> if there is a match
169: */
170: public static boolean verifyNameMatch(String name, String filter) {
171: return SimpleWildcardTester
172: .testWildcardIgnoreCase(filter, name);
173: }
174:
175: private List classes = new ArrayList();
176:
177: private ActionExpression enablementExpression;
178:
179: /**
180: * The hash code for this object. This value is computed lazily, and marked
181: * as invalid when one of the values on which it is based changes.
182: */
183: private transient int hashCode = HASH_CODE_NOT_COMPUTED;
184:
185: private int mode = UNKNOWN;
186:
187: /**
188: * Create a new instance of the receiver.
189: *
190: * @param configElement
191: */
192: public SelectionEnabler(IConfigurationElement configElement) {
193: super ();
194: if (configElement == null) {
195: throw new IllegalArgumentException();
196: }
197: parseClasses(configElement);
198: }
199:
200: public final boolean equals(final Object object) {
201: if (object instanceof SelectionEnabler) {
202: final SelectionEnabler that = (SelectionEnabler) object;
203: return Util.equals(this .classes, that.classes)
204: && Util.equals(this .enablementExpression,
205: that.enablementExpression)
206: && Util.equals(this .mode, that.mode);
207: }
208:
209: return false;
210: }
211:
212: /**
213: * Computes the hash code for this object based on the id.
214: *
215: * @return The hash code for this object.
216: */
217: public final int hashCode() {
218: if (hashCode == HASH_CODE_NOT_COMPUTED) {
219: hashCode = HASH_INITIAL * HASH_FACTOR
220: + Util.hashCode(classes);
221: hashCode = hashCode * HASH_FACTOR
222: + Util.hashCode(enablementExpression);
223: hashCode = hashCode * HASH_FACTOR + Util.hashCode(mode);
224: if (hashCode == HASH_CODE_NOT_COMPUTED) {
225: hashCode++;
226: }
227: }
228: return hashCode;
229: }
230:
231: /**
232: * Returns true if given structured selection matches the conditions
233: * specified in the registry for this action.
234: */
235: private boolean isEnabledFor(ISelection sel) {
236: Object obj = sel;
237: int count = sel.isEmpty() ? 0 : 1;
238:
239: if (verifySelectionCount(count) == false) {
240: return false;
241: }
242:
243: // Compare selection to enablement expression.
244: if (enablementExpression != null) {
245: return enablementExpression.isEnabledFor(obj);
246: }
247:
248: // Compare selection to class requirements.
249: if (classes.isEmpty()) {
250: return true;
251: }
252: if (obj instanceof IAdaptable) {
253: IAdaptable element = (IAdaptable) obj;
254: if (verifyElement(element) == false) {
255: return false;
256: }
257: } else {
258: return false;
259: }
260:
261: return true;
262: }
263:
264: /**
265: * Returns true if given text selection matches the conditions specified in
266: * the registry for this action.
267: */
268: private boolean isEnabledFor(ISelection sel, int count) {
269: if (verifySelectionCount(count) == false) {
270: return false;
271: }
272:
273: // Compare selection to enablement expression.
274: if (enablementExpression != null) {
275: return enablementExpression.isEnabledFor(sel);
276: }
277:
278: // Compare selection to class requirements.
279: if (classes.isEmpty()) {
280: return true;
281: }
282: for (int i = 0; i < classes.size(); i++) {
283: SelectionClass sc = (SelectionClass) classes.get(i);
284: if (verifyClass(sel, sc.className)) {
285: return true;
286: }
287: }
288: return false;
289: }
290:
291: /**
292: * Returns true if given structured selection matches the conditions
293: * specified in the registry for this action.
294: */
295: private boolean isEnabledFor(IStructuredSelection ssel) {
296: int count = ssel.size();
297:
298: if (verifySelectionCount(count) == false) {
299: return false;
300: }
301:
302: // Compare selection to enablement expression.
303: if (enablementExpression != null) {
304: return enablementExpression.isEnabledFor(ssel);
305: }
306:
307: // Compare selection to class requirements.
308: if (classes.isEmpty()) {
309: return true;
310: }
311: for (Iterator elements = ssel.iterator(); elements.hasNext();) {
312: Object obj = elements.next();
313: if (obj instanceof IAdaptable) {
314: IAdaptable element = (IAdaptable) obj;
315: if (verifyElement(element) == false) {
316: return false;
317: }
318: } else {
319: return false;
320: }
321: }
322:
323: return true;
324: }
325:
326: /**
327: * Check if the receiver is enabled for the given selection.
328: *
329: * @param selection
330: * @return <code>true</code> if the given selection matches the conditions
331: * specified in <code>IConfirgurationElement</code>.
332: */
333: public boolean isEnabledForSelection(ISelection selection) {
334: // Optimize it.
335: if (mode == UNKNOWN) {
336: return false;
337: }
338:
339: // Handle undefined selections.
340: if (selection == null) {
341: selection = StructuredSelection.EMPTY;
342: }
343:
344: // According to the dictionary, a selection is "one that
345: // is selected", or "a collection of selected things".
346: // In reflection of this, we deal with one or a collection.
347:
348: // special case: structured selections
349: if (selection instanceof IStructuredSelection) {
350: return isEnabledFor((IStructuredSelection) selection);
351: }
352:
353: // special case: text selections
354: // Code should read
355: // if (selection instanceof ITextSelection) {
356: // int count = ((ITextSelection) selection).getLength();
357: // return isEnabledFor(selection, count);
358: // }
359: // use Java reflection to avoid dependence of org.eclipse.jface.text
360: // which is in an optional part of the generic workbench
361: Class tselClass = getTextSelectionClass();
362: if (tselClass != null && tselClass.isInstance(selection)) {
363: try {
364: Method m = tselClass.getDeclaredMethod(
365: "getLength", new Class[0]); //$NON-NLS-1$
366: Object r = m.invoke(selection, new Object[0]);
367: if (r instanceof Integer) {
368: return isEnabledFor(selection, ((Integer) r)
369: .intValue());
370: }
371: // should not happen - but enable if it does
372: return true;
373: } catch (NoSuchMethodException e) {
374: // should not happen - fall through if it does
375: } catch (IllegalAccessException e) {
376: // should not happen - fall through if it does
377: } catch (InvocationTargetException e) {
378: // should not happen - fall through if it does
379: }
380: }
381:
382: // all other cases
383: return isEnabledFor(selection);
384: }
385:
386: /**
387: * Parses registry element to extract mode and selection elements that will
388: * be used for verification.
389: */
390: private void parseClasses(IConfigurationElement config) {
391: // Get enables for.
392: String enablesFor = config
393: .getAttribute(IWorkbenchRegistryConstants.ATT_ENABLES_FOR);
394: if (enablesFor == null) {
395: enablesFor = "*"; //$NON-NLS-1$
396: }
397: if (enablesFor.equals("*")) { //$NON-NLS-1$
398: mode = ANY_NUMBER;
399: } else if (enablesFor.equals("?")) { //$NON-NLS-1$
400: mode = NONE_OR_ONE;
401: } else if (enablesFor.equals("!")) { //$NON-NLS-1$
402: mode = NONE;
403: } else if (enablesFor.equals("+")) { //$NON-NLS-1$
404: mode = ONE_OR_MORE;
405: } else if (enablesFor.equals("multiple") //$NON-NLS-1$
406: || enablesFor.equals("2+")) { //$NON-NLS-1$
407: mode = MULTIPLE;
408: } else {
409: try {
410: mode = Integer.parseInt(enablesFor);
411: } catch (NumberFormatException e) {
412: mode = UNKNOWN;
413: }
414: }
415:
416: // Get enablement block.
417: IConfigurationElement[] children = config
418: .getChildren(IWorkbenchRegistryConstants.TAG_ENABLEMENT);
419: if (children.length > 0) {
420: enablementExpression = new ActionExpression(children[0]);
421: return;
422: }
423:
424: // Get selection block.
425: children = config
426: .getChildren(IWorkbenchRegistryConstants.TAG_SELECTION);
427: if (children.length > 0) {
428: classes = new ArrayList();
429: for (int i = 0; i < children.length; i++) {
430: IConfigurationElement sel = children[i];
431: String cname = sel
432: .getAttribute(IWorkbenchRegistryConstants.ATT_CLASS);
433: String name = sel
434: .getAttribute(IWorkbenchRegistryConstants.ATT_NAME);
435: SelectionClass sclass = new SelectionClass();
436: sclass.className = cname;
437: sclass.nameFilter = name;
438: classes.add(sclass);
439: }
440: }
441: }
442:
443: /**
444: * Verifies if the element is an instance of a class with a given class
445: * name. If direct match fails, implementing interfaces will be tested, then
446: * recursively all superclasses and their interfaces.
447: */
448: private boolean verifyClass(Object element, String className) {
449: Class eclass = element.getClass();
450: Class clazz = eclass;
451: boolean match = false;
452: while (clazz != null) {
453: // test the class itself
454: if (clazz.getName().equals(className)) {
455: match = true;
456: break;
457: }
458: // test all the interfaces it implements
459: Class[] interfaces = clazz.getInterfaces();
460: for (int i = 0; i < interfaces.length; i++) {
461: if (interfaces[i].getName().equals(className)) {
462: match = true;
463: break;
464: }
465: }
466: if (match == true) {
467: break;
468: }
469: // get the superclass
470: clazz = clazz.getSuperclass();
471: }
472: return match;
473: }
474:
475: /**
476: * Verifies if the given element matches one of the selection requirements.
477: * Element must at least pass the type test, and optionally wildcard name
478: * match.
479: */
480: private boolean verifyElement(IAdaptable element) {
481: if (classes.isEmpty()) {
482: return true;
483: }
484: for (int i = 0; i < classes.size(); i++) {
485: SelectionClass sc = (SelectionClass) classes.get(i);
486: if (verifyClass(element, sc.className) == false) {
487: continue;
488: }
489: if (sc.nameFilter == null) {
490: return true;
491: }
492: IWorkbenchAdapter de = (IWorkbenchAdapter) Util.getAdapter(
493: element, IWorkbenchAdapter.class);
494: if ((de != null)
495: && verifyNameMatch(de.getLabel(element),
496: sc.nameFilter)) {
497: return true;
498: }
499: }
500: return false;
501: }
502:
503: /**
504: * Compare selection count with requirements.
505: */
506: private boolean verifySelectionCount(int count) {
507: if (count > 0 && mode == NONE) {
508: return false;
509: }
510: if (count == 0 && mode == ONE_OR_MORE) {
511: return false;
512: }
513: if (count > 1 && mode == NONE_OR_ONE) {
514: return false;
515: }
516: if (count < 2 && mode == MULTIPLE) {
517: return false;
518: }
519: if (mode > 0 && count != mode) {
520: return false;
521: }
522: return true;
523: }
524: }
|