/******************************************************************************* * Copyright (c) 2000, 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.ui.actions.SimpleWildcardTester; import org.eclipse.ui.internal.ActionExpression; import org.eclipse.ui.internal.registry.IWorkbenchRegistryConstants; import org.eclipse.ui.internal.util.Util; import org.eclipse.ui.model.IWorkbenchAdapter; import org.osgi.framework.Bundle; /** * Determines the enablement status given a selection. This calculation is done * based on the definition of the <code>enablesFor</code> attribute, * <code>enablement</code> element, and the <code>selection</code> element * found in the <code>IConfigurationElement</code> provided. * <p> * This class can be instantiated by clients. It is not intended to be extended. * </p> * * @since 3.0 * * Note: The dependency on org.eclipse.jface.text for ITextSelection must be * severed It may be possible to do with IActionFilter generic workbench * registers IActionFilter for "size" property against IStructuredSelection * workbench text registers IActionFilter for "size" property against * ITextSelection code here: sel.getAdapter(IActionFilter.class) As an interim * solution, use reflection to access selections implementing ITextSelection */ public final class SelectionEnabler { /* package */static class SelectionClass { public String className; public String nameFilter; public boolean recursive; } public static final int ANY_NUMBER = -2; /** * The constant integer hash code value meaning the hash code has not yet * been computed. */ private static final int HASH_CODE_NOT_COMPUTED = -1; /** * A factor for computing the hash code for all schemes. */ private static final int HASH_FACTOR = 89; /** * The seed for the hash code for all schemes. */ private static final int HASH_INITIAL = SelectionEnabler.class.getName() .hashCode(); /** * Cached value of <code>org.eclipse.jface.text.ITextSelection.class</code>; * <code>null</code> if not initialized or not present. */ private static Class iTextSelectionClass = null; /** * Hard-wired id of the JFace text plug-in (not on pre-req chain). */ private static final String JFACE_TEXT_PLUG_IN = "org.eclipse.jface.text"; //$NON-NLS-1$ public static final int MULTIPLE = -5; public static final int NONE = -4; public static final int NONE_OR_ONE = -3; public static final int ONE_OR_MORE = -1; /** * Hard-wired fully qualified name of the text selection class (not on * pre-req chain). */ private static final String TEXT_SELECTION_CLASS = "org.eclipse.jface.text.ITextSelection"; //$NON-NLS-1$ /** * Indicates whether the JFace text plug-in is even around. Without the * JFace text plug-in, text selections are moot. */ private static boolean textSelectionPossible = true; public static final int UNKNOWN = 0; /** * Returns <code>ITextSelection.class</code> or <code>null</code> if the * class is not available. * * @return <code>ITextSelection.class</code> or <code>null</code> if * class not available * @since 3.0 */ private static Class getTextSelectionClass() { if (iTextSelectionClass != null) { // tried before and succeeded return iTextSelectionClass; } if (!textSelectionPossible) { // tried before and failed return null; } // JFace text plug-in is not on prereq chain of generic wb plug-in // hence: ITextSelection.class won't compile // and Class.forName("org.eclipse.jface.text.ITextSelection") won't find // it need to be trickier... Bundle bundle = Platform.getBundle(JFACE_TEXT_PLUG_IN); if (bundle == null || bundle.getState() == Bundle.UNINSTALLED) { // JFace text plug-in is not around, or has already // been removed, assume that it will never be around textSelectionPossible = false; return null; } // plug-in is around // it's not our job to activate the plug-in if (bundle.getState() == Bundle.INSTALLED) { // assume it might come alive later textSelectionPossible = true; return null; } try { Class c = bundle.loadClass(TEXT_SELECTION_CLASS); // remember for next time iTextSelectionClass = c; return iTextSelectionClass; } catch (ClassNotFoundException e) { // unable to load ITextSelection - sounds pretty serious // treat as if JFace text plug-in were unavailable textSelectionPossible = false; return null; } } /** * Verifies that the given name matches the given wildcard filter. Returns * true if it does. * * @param name * @param filter * @return <code>true</code> if there is a match */ public static boolean verifyNameMatch(String name, String filter) { return SimpleWildcardTester.testWildcardIgnoreCase(filter, name); } private List classes = new ArrayList(); private ActionExpression enablementExpression; /** * The hash code for this object. This value is computed lazily, and marked * as invalid when one of the values on which it is based changes. */ private transient int hashCode = HASH_CODE_NOT_COMPUTED; private int mode = UNKNOWN; /** * Create a new instance of the receiver. * * @param configElement */ public SelectionEnabler(IConfigurationElement configElement) { super(); if (configElement == null) { throw new IllegalArgumentException(); } parseClasses(configElement); } public final boolean equals(final Object object) { if (object instanceof SelectionEnabler) { final SelectionEnabler that = (SelectionEnabler) object; return Util.equals(this.classes, that.classes) && Util.equals(this.enablementExpression, that.enablementExpression) && Util.equals(this.mode, that.mode); } return false; } /** * Computes the hash code for this object based on the id. * * @return The hash code for this object. */ public final int hashCode() { if (hashCode == HASH_CODE_NOT_COMPUTED) { hashCode = HASH_INITIAL * HASH_FACTOR + Util.hashCode(classes); hashCode = hashCode * HASH_FACTOR + Util.hashCode(enablementExpression); hashCode = hashCode * HASH_FACTOR + Util.hashCode(mode); if (hashCode == HASH_CODE_NOT_COMPUTED) { hashCode++; } } return hashCode; } /** * Returns true if given structured selection matches the conditions * specified in the registry for this action. */ private boolean isEnabledFor(ISelection sel) { Object obj = sel; int count = sel.isEmpty() ? 0 : 1; if (verifySelectionCount(count) == false) { return false; } // Compare selection to enablement expression. if (enablementExpression != null) { return enablementExpression.isEnabledFor(obj); } // Compare selection to class requirements. if (classes.isEmpty()) { return true; } if (obj instanceof IAdaptable) { IAdaptable element = (IAdaptable) obj; if (verifyElement(element) == false) { return false; } } else { return false; } return true; } /** * Returns true if given text selection matches the conditions specified in * the registry for this action. */ private boolean isEnabledFor(ISelection sel, int count) { if (verifySelectionCount(count) == false) { return false; } // Compare selection to enablement expression. if (enablementExpression != null) { return enablementExpression.isEnabledFor(sel); } // Compare selection to class requirements. if (classes.isEmpty()) { return true; } for (int i = 0; i < classes.size(); i++) { SelectionClass sc = (SelectionClass) classes.get(i); if (verifyClass(sel, sc.className)) { return true; } } return false; } /** * Returns true if given structured selection matches the conditions * specified in the registry for this action. */ private boolean isEnabledFor(IStructuredSelection ssel) { int count = ssel.size(); if (verifySelectionCount(count) == false) { return false; } // Compare selection to enablement expression. if (enablementExpression != null) { return enablementExpression.isEnabledFor(ssel); } // Compare selection to class requirements. if (classes.isEmpty()) { return true; } for (Iterator elements = ssel.iterator(); elements.hasNext();) { Object obj = elements.next(); if (obj instanceof IAdaptable) { IAdaptable element = (IAdaptable) obj; if (verifyElement(element) == false) { return false; } } else { return false; } } return true; } /** * Check if the receiver is enabled for the given selection. * * @param selection * @return <code>true</code> if the given selection matches the conditions * specified in <code>IConfirgurationElement</code>. */ public boolean isEnabledForSelection(ISelection selection) { // Optimize it. if (mode == UNKNOWN) { return false; } // Handle undefined selections. if (selection == null) { selection = StructuredSelection.EMPTY; } // According to the dictionary, a selection is "one that // is selected", or "a collection of selected things". // In reflection of this, we deal with one or a collection. // special case: structured selections if (selection instanceof IStructuredSelection) { return isEnabledFor((IStructuredSelection) selection); } // special case: text selections // Code should read // if (selection instanceof ITextSelection) { // int count = ((ITextSelection) selection).getLength(); // return isEnabledFor(selection, count); // } // use Java reflection to avoid dependence of org.eclipse.jface.text // which is in an optional part of the generic workbench Class tselClass = getTextSelectionClass(); if (tselClass != null && tselClass.isInstance(selection)) { try { Method m = tselClass.getDeclaredMethod( "getLength", new Class[0]); //$NON-NLS-1$ Object r = m.invoke(selection, new Object[0]); if (r instanceof Integer) { return isEnabledFor(selection, ((Integer) r).intValue()); } // should not happen - but enable if it does return true; } catch (NoSuchMethodException e) { // should not happen - fall through if it does } catch (IllegalAccessException e) { // should not happen - fall through if it does } catch (InvocationTargetException e) { // should not happen - fall through if it does } } // all other cases return isEnabledFor(selection); } /** * Parses registry element to extract mode and selection elements that will * be used for verification. */ private void parseClasses(IConfigurationElement config) { // Get enables for. String enablesFor = config .getAttribute(IWorkbenchRegistryConstants.ATT_ENABLES_FOR); if (enablesFor == null) { enablesFor = "*"; //$NON-NLS-1$ } if (enablesFor.equals("*")) { //$NON-NLS-1$ mode = ANY_NUMBER; } else if (enablesFor.equals("?")) { //$NON-NLS-1$ mode = NONE_OR_ONE; } else if (enablesFor.equals("!")) { //$NON-NLS-1$ mode = NONE; } else if (enablesFor.equals("+")) { //$NON-NLS-1$ mode = ONE_OR_MORE; } else if (enablesFor.equals("multiple") //$NON-NLS-1$ || enablesFor.equals("2+")) { //$NON-NLS-1$ mode = MULTIPLE; } else { try { mode = Integer.parseInt(enablesFor); } catch (NumberFormatException e) { mode = UNKNOWN; } } // Get enablement block. IConfigurationElement[] children = config .getChildren(IWorkbenchRegistryConstants.TAG_ENABLEMENT); if (children.length > 0) { enablementExpression = new ActionExpression(children[0]); return; } // Get selection block. children = config .getChildren(IWorkbenchRegistryConstants.TAG_SELECTION); if (children.length > 0) { classes = new ArrayList(); for (int i = 0; i < children.length; i++) { IConfigurationElement sel = children[i]; String cname = sel .getAttribute(IWorkbenchRegistryConstants.ATT_CLASS); String name = sel .getAttribute(IWorkbenchRegistryConstants.ATT_NAME); SelectionClass sclass = new SelectionClass(); sclass.className = cname; sclass.nameFilter = name; classes.add(sclass); } } } /** * Verifies if the element is an instance of a class with a given class * name. If direct match fails, implementing interfaces will be tested, then * recursively all superclasses and their interfaces. */ private boolean verifyClass(Object element, String className) { Class eclass = element.getClass(); Class clazz = eclass; boolean match = false; while (clazz != null) { // test the class itself if (clazz.getName().equals(className)) { match = true; break; } // test all the interfaces it implements Class[] interfaces = clazz.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { if (interfaces[i].getName().equals(className)) { match = true; break; } } if (match == true) { break; } // get the superclass clazz = clazz.getSuperclass(); } return match; } /** * Verifies if the given element matches one of the selection requirements. * Element must at least pass the type test, and optionally wildcard name * match. */ private boolean verifyElement(IAdaptable element) { if (classes.isEmpty()) { return true; } for (int i = 0; i < classes.size(); i++) { SelectionClass sc = (SelectionClass) classes.get(i); if (verifyClass(element, sc.className) == false) { continue; } if (sc.nameFilter == null) { return true; } IWorkbenchAdapter de = (IWorkbenchAdapter) Util.getAdapter(element, IWorkbenchAdapter.class); if ((de != null) && verifyNameMatch(de.getLabel(element), sc.nameFilter)) { return true; } } return false; } /** * Compare selection count with requirements. */ private boolean verifySelectionCount(int count) { if (count > 0 && mode == NONE) { return false; } if (count == 0 && mode == ONE_OR_MORE) { return false; } if (count > 1 && mode == NONE_OR_ONE) { return false; } if (count < 2 && mode == MULTIPLE) { return false; } if (mode > 0 && count != mode) { return false; } return true; } }