/******************************************************************************* * Copyright (c) 2004, 2017 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.jface.bindings.keys; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import org.eclipse.jface.bindings.TriggerSequence; import org.eclipse.jface.bindings.keys.formatting.KeyFormatterFactory; import org.eclipse.jface.util.Util; /** * <p> * A <code>KeySequence</code> is defined as a list of zero or more * <code>KeyStrokes</code>, with the stipulation that all * <code>KeyStroke</code> objects must be complete, save for the last one, * whose completeness is optional. A <code>KeySequence</code> is said to be * complete if all of its <code>KeyStroke</code> objects are complete. * </p> * <p> * All <code>KeySequence</code> objects have a formal string representation * available via the <code>toString()</code> method. There are a number of * methods to get instances of <code>KeySequence</code> objects, including one * which can parse this formal string representation. * </p> * <p> * All <code>KeySequence</code> objects, via the <code>format()</code> * method, provide a version of their formal string representation translated by * platform and locale, suitable for display to a user. * </p> * <p> * <code>KeySequence</code> objects are immutable. Clients are not permitted * to extend this class. * </p> * * @since 3.1 */ public final class KeySequence extends TriggerSequence implements Comparable { /** * An empty key sequence instance for use by everyone. */ private final static KeySequence EMPTY_KEY_SEQUENCE = new KeySequence( new KeyStroke[0]); /** * The delimiter between multiple key strokes in a single key sequence -- * expressed in the formal key stroke grammar. This is not to be displayed * to the user. It is only intended as an internal representation. */ public final static String KEY_STROKE_DELIMITER = "\u0020"; //$NON-NLS-1$ /** * The set of delimiters for <code>KeyStroke</code> objects allowed during * parsing of the formal string representation. */ public final static String KEY_STROKE_DELIMITERS = KEY_STROKE_DELIMITER + "\b\r\u007F\u001B\f\n\0\t\u000B"; //$NON-NLS-1$ /** * Gets an instance of <code>KeySequence</code>. * * @return a key sequence. This key sequence will have no key strokes. * Guaranteed not to be <code>null</code>. */ public static final KeySequence getInstance() { return EMPTY_KEY_SEQUENCE; } /** * Creates an instance of <code>KeySequence</code> given a key sequence * and a key stroke. * * @param keySequence * a key sequence. Must not be <code>null</code>. * @param keyStroke * a key stroke. Must not be <code>null</code>. * @return a key sequence that is equal to the given key sequence with the * given key stroke appended to the end. Guaranteed not to be * <code>null</code>. */ public static final KeySequence getInstance(final KeySequence keySequence, final KeyStroke keyStroke) { if (keySequence == null || keyStroke == null) { throw new NullPointerException(); } final KeyStroke[] oldKeyStrokes = keySequence.getKeyStrokes(); final int oldKeyStrokeLength = oldKeyStrokes.length; final KeyStroke[] newKeyStrokes = new KeyStroke[oldKeyStrokeLength + 1]; System .arraycopy(oldKeyStrokes, 0, newKeyStrokes, 0, oldKeyStrokeLength); newKeyStrokes[oldKeyStrokeLength] = keyStroke; return new KeySequence(newKeyStrokes); } /** * Creates an instance of <code>KeySequence</code> given a single key * stroke. * * @param keyStroke * a single key stroke. Must not be <code>null</code>. * @return a key sequence. Guaranteed not to be <code>null</code>. */ public static final KeySequence getInstance(final KeyStroke keyStroke) { return new KeySequence(new KeyStroke[] { keyStroke }); } /** * Creates an instance of <code>KeySequence</code> given an array of key * strokes. * * @param keyStrokes * the array of key strokes. This array may be empty, but it must * not be <code>null</code>. This array must not contain * <code>null</code> elements. * @return a key sequence. Guaranteed not to be <code>null</code>. */ public static final KeySequence getInstance(final KeyStroke[] keyStrokes) { return new KeySequence(keyStrokes); } /** * Creates an instance of <code>KeySequence</code> given a list of key * strokes. * * @param keyStrokes * the list of key strokes. This list may be empty, but it must * not be <code>null</code>. If this list is not empty, it * must only contain instances of <code>KeyStroke</code>. * @return a key sequence. Guaranteed not to be <code>null</code>. */ public static final KeySequence getInstance(final List<KeyStroke> keyStrokes) { return new KeySequence(keyStrokes.toArray(new KeyStroke[keyStrokes.size()])); } /** * Creates an instance of <code>KeySequence</code> by parsing a given * formal string representation. * * @param string * the formal string representation to parse. * @return a key sequence. Guaranteed not to be <code>null</code>. * @throws ParseException * if the given formal string representation could not be parsed * to a valid key sequence. */ public static final KeySequence getInstance(final String string) throws ParseException { if (string == null) { throw new NullPointerException(); } final List<KeyStroke> keyStrokes = new ArrayList<>(); final StringTokenizer stringTokenizer = new StringTokenizer(string, KEY_STROKE_DELIMITERS); try { while (stringTokenizer.hasMoreTokens()) { keyStrokes.add(KeyStroke.getInstance(stringTokenizer.nextToken())); } final KeyStroke[] keyStrokeArray = keyStrokes.toArray(new KeyStroke[keyStrokes.size()]); return new KeySequence(keyStrokeArray); } catch (final IllegalArgumentException e) { throw new ParseException( "Could not construct key sequence with these key strokes: " //$NON-NLS-1$ + keyStrokes); } catch (final NullPointerException e) { throw new ParseException( "Could not construct key sequence with these key strokes: " //$NON-NLS-1$ + keyStrokes); } } /** * Constructs an instance of <code>KeySequence</code> given a list of key * strokes. * * @param keyStrokes * the list of key strokes. This list may be empty, but it must * not be <code>null</code>. If this list is not empty, it * must only contain instances of <code>KeyStroke</code>. */ protected KeySequence(final KeyStroke[] keyStrokes) { super(keyStrokes); for (int i = 0; i < triggers.length - 1; i++) { KeyStroke keyStroke = (KeyStroke) triggers[i]; if (!keyStroke.isComplete()) { throw new IllegalArgumentException(); } } } @Override public final int compareTo(final Object object) { final KeySequence castedObject = (KeySequence) object; return Util.compare(triggers, castedObject.triggers); } /** * Formats this key sequence into the current default look. * * @return A string representation for this key sequence using the default * look; never <code>null</code>. */ @Override public final String format() { return KeyFormatterFactory.getDefault().format(this); } /** * Returns the list of key strokes for this key sequence. * * @return the list of key strokes keys. This list may be empty, but is * guaranteed not to be <code>null</code>. If this list is not * empty, it is guaranteed to only contain instances of * <code>KeyStroke</code>. */ public final KeyStroke[] getKeyStrokes() { final int triggerLength = triggers.length; final KeyStroke[] keyStrokes = new KeyStroke[triggerLength]; System.arraycopy(triggers, 0, keyStrokes, 0, triggerLength); return keyStrokes; } @Override public final TriggerSequence[] getPrefixes() { final int numberOfPrefixes = triggers.length; final TriggerSequence[] prefixes = new TriggerSequence[numberOfPrefixes]; prefixes[0] = KeySequence.getInstance(); for (int i = 0; i < numberOfPrefixes - 1; i++) { final KeyStroke[] prefixKeyStrokes = new KeyStroke[i + 1]; System.arraycopy(triggers, 0, prefixKeyStrokes, 0, i + 1); prefixes[i + 1] = KeySequence.getInstance(prefixKeyStrokes); } return prefixes; } /** * Returns whether or not this key sequence is complete. Key sequences are * complete iff all of their key strokes are complete. * * @return <code>true</code>, iff the key sequence is complete. */ public final boolean isComplete() { final int triggersLength = triggers.length; for (int i = 0; i < triggersLength; i++) { if (!((KeyStroke) triggers[i]).isComplete()) { return false; } } return true; } /** * Returns the formal string representation for this key sequence. * * @return The formal string representation for this key sequence. * Guaranteed not to be <code>null</code>. * @see java.lang.Object#toString() */ @Override public final String toString() { return KeyFormatterFactory.getFormalKeyFormatter().format(this); } }