Skip to main content

This CGIT instance is deprecated, and repositories have been moved to Gitlab or Github. See the repository descriptions for specific locations.

aboutsummaryrefslogtreecommitdiffstats
blob: 2b3136d199e00842f94b785fcfe39a046aa9475f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
/*******************************************************************************
 * Copyright (c) 2004, 2015 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;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;

import org.eclipse.core.commands.util.Tracing;
import org.eclipse.jface.util.Util;

/**
 * <p>
 * A resolution of bindings for a given state. To see if we already have a
 * cached binding set, just create one of these binding sets and then look it up
 * in a map. If it is not already there, then add it and set the cached binding
 * resolution.
 * </p>
 *
 * @since 3.1
 */
final class CachedBindingSet {

	/**
	 * A factor for computing the hash code for all cached binding sets.
	 */
	private static final int HASH_FACTOR = 89;

	/**
	 * The seed for the hash code for all cached binding sets.
	 */
	private static final int HASH_INITIAL = CachedBindingSet.class.getName()
			.hashCode();

	/**
	 * <p>
	 * A representation of the tree of active contexts at the time this cached
	 * binding set was computed. It is a map of context id (<code>String</code>)
	 * to context id (<code>String</code>). Each key represents one of the
	 * active contexts or one of its ancestors, while each value represents its
	 * parent. This is a way of perserving information about what the hierarchy
	 * looked like.
	 * </p>
	 * <p>
	 * This value will be <code>null</code> if the contexts were disregarded
	 * in the computation. It may also be empty. All of the keys are guaranteed
	 * to be non- <code>null</code>, but the values can be <code>null</code>
	 * (i.e., no parent).
	 * </p>
	 */
	private final Map activeContextTree;

	/**
	 * The map representing the resolved state of the bindings. This is a map of
	 * a trigger (<code>TriggerSequence</code>) to binding (<code>Binding</code>).
	 * This value may be <code>null</code> if it has not yet been initialized.
	 */
	private volatile Map bindingsByTrigger;

	/**
	 * A map of triggers to collections of bindings. If this binding set
	 * contains conflicts, they are logged here.
	 *
	 * @since 3.3
	 */
	private volatile Map conflictsByTrigger;

	/**
	 * 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;

	/**
	 * Whether <code>hashCode</code> still contains a valid value.
	 */
	private transient boolean hashCodeComputed = false;

	/**
	 * <p>
	 * The list of locales that were active at the time this binding set was
	 * computed. This list starts with the most specific representation of the
	 * locale, and moves to more general representations. For example, this
	 * array might look like ["en_US", "en", "", null].
	 * </p>
	 * <p>
	 * This value will never be <code>null</code>, and it will never be
	 * empty. It must contain at least one element, but its elements can be
	 * <code>null</code>.
	 * </p>
	 */
	private final String[] locales;

	/**
	 * <p>
	 * The list of platforms that were active at the time this binding set was
	 * computed. This list starts with the most specific representation of the
	 * platform, and moves to more general representations. For example, this
	 * array might look like ["gtk", "", null].
	 * </p>
	 * <p>
	 * This value will never be <code>null</code>, and it will never be
	 * empty. It must contain at least one element, but its elements can be
	 * <code>null</code>.
	 * </p>
	 */
	private final String[] platforms;

	/**
	 * A map of prefixes (<code>TriggerSequence</code>) to a map of
	 * available completions (possibly <code>null</code>, which means there
	 * is an exact match). The available completions is a map of trigger (<code>TriggerSequence</code>)
	 * to command identifier (<code>String</code>). This value is
	 * <code>null</code> if it has not yet been initialized.
	 */
	private volatile Map prefixTable;

	/**
	 * <p>
	 * The list of schemes that were active at the time this binding set was
	 * computed. This list starts with the active scheme, and then continues
	 * with all of its ancestors -- in order. For example, this might look like
	 * ["emacs", "default"].
	 * </p>
	 * <p>
	 * This value will never be <code>null</code>, and it will never be
	 * empty. It must contain at least one element. Its elements cannot be
	 * <code>null</code>.
	 * </p>
	 */
	private final String[] schemeIds;

	/**
	 * The map representing the resolved state of the bindings. This is a map of
	 * a command id (<code>String</code>) to triggers (<code>Collection</code>
	 * of <code>TriggerSequence</code>). This value may be <code>null</code>
	 * if it has not yet been initialized.
	 */
	private volatile Map triggersByCommandId;

	/**
	 * Constructs a new instance of <code>CachedBindingSet</code>.
	 *
	 * @param activeContextTree
	 *            The set of context identifiers that were active when this
	 *            binding set was calculated; may be empty. If it is
	 *            <code>null</code>, then the contexts were disregarded in
	 *            the computation. This is a map of context id (
	 *            <code>String</code>) to parent context id (
	 *            <code>String</code>). This is a way of caching the look of
	 *            the context tree at the time the binding set was computed.
	 * @param locales
	 *            The locales that were active when this binding set was
	 *            calculated. The first element is the currently active locale,
	 *            and it is followed by increasingly more general locales. This
	 *            must not be <code>null</code> and must contain at least one
	 *            element. The elements can be <code>null</code>, though.
	 * @param platforms
	 *            The platform that were active when this binding set was
	 *            calculated. The first element is the currently active
	 *            platform, and it is followed by increasingly more general
	 *            platforms. This must not be <code>null</code> and must
	 *            contain at least one element. The elements can be
	 *            <code>null</code>, though.
	 * @param schemeIds
	 *            The scheme that was active when this binding set was
	 *            calculated, followed by its ancestors. This may be
	 *            <code>null</code or empty. The
	 *            elements cannot be <code>null</code>.
	 */
	CachedBindingSet(final Map activeContextTree, final String[] locales,
			final String[] platforms, final String[] schemeIds) {
		if (locales == null) {
			throw new NullPointerException("The locales cannot be null."); //$NON-NLS-1$
		}

		if (locales.length == 0) {
			throw new NullPointerException("The locales cannot be empty."); //$NON-NLS-1$
		}

		if (platforms == null) {
			throw new NullPointerException("The platforms cannot be null."); //$NON-NLS-1$
		}

		if (platforms.length == 0) {
			throw new NullPointerException("The platforms cannot be empty."); //$NON-NLS-1$
		}

		this.activeContextTree = activeContextTree;
		this.locales = locales;
		this.platforms = platforms;
		this.schemeIds = schemeIds;
	}

	/**
	 * Compares this binding set with another object. The objects will be equal
	 * if they are both instance of <code>CachedBindingSet</code> and have
	 * equivalent values for all of their properties.
	 *
	 * @param object
	 *            The object with which to compare; may be <code>null</code>.
	 * @return <code>true</code> if they are both instances of
	 *         <code>CachedBindingSet</code> and have the same values for all
	 *         of their properties; <code>false</code> otherwise.
	 */
	@Override
	public final boolean equals(final Object object) {
		if (!(object instanceof CachedBindingSet)) {
			return false;
		}

		final CachedBindingSet other = (CachedBindingSet) object;

		if (!Objects.equals(activeContextTree, other.activeContextTree)) {
			return false;
		}
		if (!Arrays.equals(locales, other.locales)) {
			return false;
		}
		if (!Arrays.equals(platforms, other.platforms)) {
			return false;
		}
		return Arrays.equals(schemeIds, other.schemeIds);
	}

	/**
	 * Returns the map of command identifiers indexed by trigger sequence.
	 *
	 * @return A map of triggers (<code>TriggerSequence</code>) to bindings (<code>Binding</code>).
	 *         This value may be <code>null</code> if this was not yet
	 *         initialized.
	 */
	final Map getBindingsByTrigger() {
		return bindingsByTrigger;
	}

	/**
	 * Returns a map of conflicts for this set of contexts.
	 *
	 * @return A map of trigger to a collection of Bindings. May be
	 *         <code>null</code>.
	 * @since 3.3
	 */
	final Map getConflictsByTrigger() {
		return conflictsByTrigger;
	}

	/**
	 * Returns the map of prefixes to a map of trigger sequence to command
	 * identifiers.
	 *
	 * @return A map of prefixes (<code>TriggerSequence</code>) to a map of
	 *         available completions (possibly <code>null</code>, which means
	 *         there is an exact match). The available completions is a map of
	 *         trigger (<code>TriggerSequence</code>) to command identifier (<code>String</code>).
	 *         This value may be <code>null</code> if it has not yet been
	 *         initialized.
	 */
	final Map getPrefixTable() {
		return prefixTable;
	}

	/**
	 * Returns the map of triggers indexed by command identifiers.
	 *
	 * @return A map of command identifiers (<code>String</code>) to
	 *         triggers (<code>Collection</code> of
	 *         <code>TriggerSequence</code>). This value may be
	 *         <code>null</code> if this was not yet initialized.
	 */
	final Map getTriggersByCommandId() {
		return triggersByCommandId;
	}

	/**
	 * Computes the hash code for this cached binding set. The hash code is
	 * based only on the immutable values. This allows the set to be created and
	 * checked for in a hashed collection <em>before</em> doing any
	 * computation.
	 *
	 * @return The hash code for this cached binding set.
	 */
	@Override
	public final int hashCode() {
		if (!hashCodeComputed) {
			hashCode = HASH_INITIAL;
			hashCode = hashCode * HASH_FACTOR
					+ Util.hashCode(activeContextTree);
			hashCode = hashCode * HASH_FACTOR + Util.hashCode(locales);
			hashCode = hashCode * HASH_FACTOR + Util.hashCode(platforms);
			hashCode = hashCode * HASH_FACTOR + Util.hashCode(schemeIds);
			hashCodeComputed = true;
		}

		return hashCode;
	}

	/**
	 * Sets the map of command identifiers indexed by trigger.
	 *
	 * @param commandIdsByTrigger
	 *            The map to set; must not be <code>null</code>. This is a
	 *            map of triggers (<code>TriggerSequence</code>) to binding (<code>Binding</code>).
	 */
	final void setBindingsByTrigger(final Map commandIdsByTrigger) {
		if (commandIdsByTrigger == null) {
			throw new NullPointerException(
					"Cannot set a null binding resolution"); //$NON-NLS-1$
		}

		this.bindingsByTrigger = commandIdsByTrigger;
	}

	/**
	 * Sets the map of conflicting bindings by trigger.
	 *
	 * @param conflicts
	 *            The map to set; must not be <code>null</code>.
	 * @since 3.3
	 */
	final void setConflictsByTrigger(final Map conflicts) {
		if (conflicts == null) {
			throw new NullPointerException(
					"Cannot set a null binding conflicts"); //$NON-NLS-1$
		}
		conflictsByTrigger = conflicts;
	}

	/**
	 * Sets the map of prefixes to a map of trigger sequence to command
	 * identifiers.
	 *
	 * @param prefixTable
	 *            A map of prefixes (<code>TriggerSequence</code>) to a map
	 *            of available completions (possibly <code>null</code>, which
	 *            means there is an exact match). The available completions is a
	 *            map of trigger (<code>TriggerSequence</code>) to command
	 *            identifier (<code>String</code>). Must not be
	 *            <code>null</code>.
	 */
	final void setPrefixTable(final Map prefixTable) {
		if (prefixTable == null) {
			this.prefixTable = Collections.EMPTY_MAP;
			if (BindingManager.DEBUG) {
				Tracing.printTrace("BINDINGS", "Cannot set a null prefix table, set to EMPTY_MAP"); //$NON-NLS-1$ //$NON-NLS-2$
			}
			return;
		}

		this.prefixTable = prefixTable;
	}

	/**
	 * Sets the map of triggers indexed by command identifiers.
	 *
	 * @param triggersByCommandId
	 *            The map to set; must not be <code>null</code>. This is a
	 *            map of command identifiers (<code>String</code>) to
	 *            triggers (<code>Collection</code> of
	 *            <code>TriggerSequence</code>).
	 */
	final void setTriggersByCommandId(final Map triggersByCommandId) {
		if (triggersByCommandId == null) {
			throw new NullPointerException(
					"Cannot set a null binding resolution"); //$NON-NLS-1$
		}

		this.triggersByCommandId = triggersByCommandId;
	}

	/**
	 * @return true if all the required maps are computed and non null
	 */
	final boolean isInitialized() {
		return bindingsByTrigger != null && triggersByCommandId != null && conflictsByTrigger != null
				&& prefixTable != null;
	}
}

Back to the top