1 /*******************************************************************************
2  * Copyright (c) 2005, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 
15 package org.eclipse.jface.commands;
16 
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.Map;
20 import java.util.Set;
21 
22 import org.eclipse.core.commands.IStateListener;
23 import org.eclipse.core.commands.State;
24 import org.eclipse.jface.menus.IMenuStateIds;
25 
26 /**
27  * <p>
28  * A piece of boolean state grouped with other boolean states. Of these states,
29  * only one may have a value of {@link Boolean#TRUE} at any given point in time.
30  * The values of all other states must be {@link Boolean#FALSE}.
31  * </p>
32  * <p>
33  * If this state is registered using {@link IMenuStateIds#STYLE}, then it will
34  * control the presentation of the command if displayed in the menus, tool bars
35  * or status line.
36  * </p>
37  * <p>
38  * Clients may instantiate or extend this interface.
39  * </p>
40  *
41  * @since 3.2
42  */
43 public class RadioState extends ToggleState {
44 
45 	/**
46 	 * The manager of radio groups within the application. This ensures that
47 	 * only one member of a radio group is active at any one time, and tracks
48 	 * group memberships.
49 	 */
50 	private static final class RadioStateManager {
51 
52 		/**
53 		 * A group of radio states with the same identifier.
54 		 */
55 		private static final class RadioGroup implements IStateListener {
56 
57 			/**
58 			 * The active state. If there is no active state, then this value is
59 			 * <code>null</code>.
60 			 */
61 			private RadioState active = null;
62 
63 			/**
64 			 * The current members in this group. If there are no members, then
65 			 * this value is <code>nlistenerull</code>.
66 			 */
67 			private Set<RadioState> members = null;
68 
69 			/**
70 			 * Activates a member. This checks to see if there are any other
71 			 * active members. If there are, they are deactivated.
72 			 *
73 			 * @param state
74 			 *            The state that should become active; must not be
75 			 *            <code>null</code>.
76 			 */
activateMember(final RadioState state)77 			private final void activateMember(final RadioState state) {
78 				if (active != null && active != state) {
79 					active.setValue(Boolean.FALSE);
80 				}
81 				active = state;
82 			}
83 
84 			/**
85 			 * Adds a member to this radio group. If the state being added is
86 			 * active, then it replaces the currently active group member as
87 			 * the active state.
88 			 *
89 			 * @param state
90 			 *            The state to add; must not be <code>null</code>.
91 			 */
addMember(final RadioState state)92 			private final void addMember(final RadioState state) {
93 				if (members == null) {
94 					members = new HashSet<>(5);
95 				}
96 
97 				members.add(state);
98 				state.addListener(this);
99 
100 				final Object value = state.getValue();
101 				if (value instanceof Boolean) {
102 					if (((Boolean) value).booleanValue()) {
103 						activateMember(state);
104 					}
105 				}
106 			}
107 
108 			@Override
handleStateChange(final State state, final Object oldValue)109 			public final void handleStateChange(final State state,
110 					final Object oldValue) {
111 				final Object newValue = state.getValue();
112 				if (newValue instanceof Boolean) {
113 					if (((Boolean) newValue).booleanValue()) {
114 						activateMember((RadioState) state);
115 					}
116 				}
117 			}
118 
119 			/**
120 			 * Removes a member from this radio group. If the state was the
121 			 * active state, then there will be no active state.
122 			 *
123 			 * @param state
124 			 *            The state to remove; must not be <code>null</code>.
125 			 */
removeMember(final RadioState state)126 			private final void removeMember(final RadioState state) {
127 				state.removeListener(this);
128 				if (active == state) {
129 					active = null;
130 				}
131 
132 				if (members == null) {
133 					return;
134 				}
135 				members.remove(state);
136 			}
137 		}
138 
139 		/**
140 		 * The map of radio states indexed by identifier (<code>String</code>).
141 		 * The radio states is either a single <code>RadioState</code> instance
142 		 * or a <code>Collection</code> of <code>RadioState</code> instances.
143 		 */
144 		private static Map<String, RadioGroup> radioStatesById = null;
145 
146 		/**
147 		 * Activates a particular state within a given group.
148 		 *
149 		 * @param identifier
150 		 *            The identifier of the group to which the state belongs;
151 		 *            must not be <code>null</code>.
152 		 * @param state
153 		 *            The state to activate; must not be <code>null</code>.
154 		 */
activateGroup(final String identifier, final RadioState state)155 		private static final void activateGroup(final String identifier,
156 				final RadioState state) {
157 			if (radioStatesById == null) {
158 				return;
159 			}
160 
161 			final RadioGroup radioGroup = radioStatesById.get(identifier);
162 			if (radioGroup != null) {
163 				radioGroup.activateMember(state);
164 			}
165 		}
166 
167 		/**
168 		 * Registers a piece of state with the radio manager.
169 		 *
170 		 * @param identifier
171 		 *            The identifier of the radio group; must not be
172 		 *            <code>null</code>.
173 		 * @param state
174 		 *            The state to register; must not be <code>null</code>.
175 		 */
registerState(final String identifier, final RadioState state)176 		private static final void registerState(final String identifier,
177 				final RadioState state) {
178 			if (radioStatesById == null) {
179 				radioStatesById = new HashMap<>();
180 			}
181 
182 			RadioGroup radioGroup = radioStatesById.get(identifier);
183 			if (radioGroup == null) {
184 				radioGroup = new RadioGroup();
185 				radioStatesById.put(identifier, radioGroup);
186 			}
187 			radioGroup.addMember(state);
188 		}
189 
190 		/**
191 		 * Unregisters a piece of state from the radio manager.
192 		 *
193 		 * @param identifier
194 		 *            The identifier of the radio group; must not be
195 		 *            <code>null</code>.
196 		 * @param state
197 		 *            The state to unregister; must not be <code>null</code>.
198 		 */
unregisterState(final String identifier, final RadioState state)199 		private static final void unregisterState(final String identifier,
200 				final RadioState state) {
201 			if (radioStatesById == null) {
202 				return;
203 			}
204 
205 			final RadioGroup radioGroup = radioStatesById.get(identifier);
206 			if (radioGroup != null) {
207 				radioGroup.removeMember(state);
208 			}
209 		}
210 	}
211 
212 	/**
213 	 * The identifier of the radio group to which this state belongs. This value
214 	 * may be <code>null</code> if this state doesn't really belong to a group
215 	 * (yet).
216 	 */
217 	private String radioGroupIdentifier = null;
218 
219 	/**
220 	 * Unregisters this state from the manager, which detaches the listeners.
221 	 */
222 	@Override
dispose()223 	public void dispose() {
224 		setRadioGroupIdentifier(null);
225 	}
226 
227 	/**
228 	 * Sets the identifier of the radio group for this piece of state. If the
229 	 * identifier is cleared, then the state is unregistered.
230 	 *
231 	 * @param identifier
232 	 *            The identifier of the radio group for this state; may be
233 	 *            <code>null</code> if the identifier is being cleared.
234 	 *
235 	 */
setRadioGroupIdentifier(final String identifier)236 	public final void setRadioGroupIdentifier(final String identifier) {
237 		if (identifier == null) {
238 			RadioStateManager.unregisterState(radioGroupIdentifier, this);
239 			radioGroupIdentifier = null;
240 		} else {
241 			radioGroupIdentifier = identifier;
242 			RadioStateManager.registerState(identifier, this);
243 		}
244 	}
245 
246 	/**
247 	 * Sets the value for this object. This notifies the radio state manager of
248 	 * the change.
249 	 *
250 	 * @param value
251 	 *            The new value; should be a <code>Boolean</code>.
252 	 */
253 	@Override
setValue(final Object value)254 	public void setValue(final Object value) {
255 		if (!(value instanceof Boolean)) {
256 			throw new IllegalArgumentException(
257 					"RadioState takes a Boolean as a value"); //$NON-NLS-1$
258 		}
259 
260 		if (((Boolean) value).booleanValue() && (radioGroupIdentifier != null)) {
261 			RadioStateManager.activateGroup(radioGroupIdentifier, this);
262 		}
263 
264 		super.setValue(value);
265 	}
266 }
267