Commit | Line | Data |
---|---|---|
3a5f73a1 JCK |
1 | /******************************************************************************* |
2 | * Copyright (c) 2016 Ecole Polytechnique de Montreal, Ericsson | |
3 | * | |
4 | * All rights reserved. This program and the accompanying materials are | |
5 | * made available under the terms of the Eclipse Public License v1.0 which | |
6 | * accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | ******************************************************************************/ | |
6eca054d | 9 | package org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model; |
3a5f73a1 JCK |
10 | |
11 | import java.util.ArrayList; | |
12 | import java.util.Collections; | |
13 | import java.util.HashMap; | |
14 | import java.util.Iterator; | |
15 | import java.util.List; | |
16 | import java.util.Map; | |
17 | ||
18 | import org.eclipse.jdt.annotation.NonNull; | |
19 | import org.eclipse.jdt.annotation.Nullable; | |
146e73f6 | 20 | import org.eclipse.osgi.util.NLS; |
3a5f73a1 | 21 | import org.eclipse.tracecompass.common.core.NonNullUtils; |
146e73f6 | 22 | import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.Activator; |
6eca054d GB |
23 | import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model.TmfXmlScenarioHistoryBuilder.ScenarioStatusType; |
24 | import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.IXmlStateSystemContainer; | |
25 | import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.stateprovider.TmfXmlStrings; | |
3a5f73a1 JCK |
26 | import org.eclipse.tracecompass.tmf.core.event.ITmfEvent; |
27 | import org.w3c.dom.Element; | |
28 | import org.w3c.dom.NodeList; | |
29 | ||
30 | import com.google.common.collect.ImmutableList; | |
31 | import com.google.common.collect.ImmutableMap; | |
32 | ||
33 | /** | |
34 | * This Class implements a state machine (FSM) tree in the XML-defined state | |
35 | * system. | |
36 | * | |
37 | * @author Jean-Christian Kouame | |
3a5f73a1 JCK |
38 | */ |
39 | public class TmfXmlFsm { | |
40 | ||
41 | private final Map<String, TmfXmlState> fStatesMap; | |
42 | private final List<TmfXmlScenario> fActiveScenariosList; | |
43 | private final List<TmfXmlBasicTransition> fPreconditions; | |
44 | private final String fId; | |
45 | private final ITmfXmlModelFactory fModelFactory; | |
46 | private final IXmlStateSystemContainer fContainer; | |
47 | private final String fFinalStateId; | |
48 | private final String fAbandonStateId; | |
49 | private final boolean fInstanceMultipleEnabled; | |
50 | private final String fInitialStateId; | |
51 | private int fTotalScenarios; | |
52 | ||
53 | /** | |
54 | * Factory to create a {@link TmfXmlFsm} | |
55 | * | |
56 | * @param modelFactory | |
57 | * The factory used to create XML model elements | |
58 | * @param node | |
59 | * The XML root of this fsm | |
60 | * @param container | |
61 | * The state system container this fsm belongs to | |
62 | * @return The new {@link TmfXmlFsm} | |
63 | */ | |
64 | public static TmfXmlFsm create(ITmfXmlModelFactory modelFactory, Element node, IXmlStateSystemContainer container) { | |
65 | String id = node.getAttribute(TmfXmlStrings.ID); | |
66 | boolean instanceMultipleEnabled = node.getAttribute(TmfXmlStrings.MULTIPLE).isEmpty() ? true : Boolean.parseBoolean(node.getAttribute(TmfXmlStrings.MULTIPLE)); | |
67 | final List<@NonNull TmfXmlBasicTransition> preconditions = new ArrayList<>(); | |
68 | ||
69 | // Get the preconditions | |
70 | NodeList nodesPreconditions = node.getElementsByTagName(TmfXmlStrings.PRECONDITION); | |
71 | for (int i = 0; i < nodesPreconditions.getLength(); i++) { | |
72 | preconditions.add(new TmfXmlBasicTransition(((Element) NonNullUtils.checkNotNull(nodesPreconditions.item(i))))); | |
73 | } | |
74 | ||
75 | // Get the initial state and the preconditions | |
76 | String initialState = node.getAttribute(TmfXmlStrings.INITIAL); | |
77 | if (initialState.isEmpty()) { | |
78 | NodeList nodesInitialState = node.getElementsByTagName(TmfXmlStrings.INITIAL); | |
79 | if (nodesInitialState.getLength() == 1) { | |
80 | NodeList nodesTransition = ((Element) nodesInitialState.item(0)).getElementsByTagName(TmfXmlStrings.TRANSITION); | |
81 | if (nodesInitialState.getLength() != 1) { | |
82 | throw new IllegalArgumentException("initial state : there should be one and only one initial state."); //$NON-NLS-1$ | |
83 | } | |
84 | initialState = ((Element) nodesTransition.item(0)).getAttribute(TmfXmlStrings.TARGET); | |
85 | } | |
86 | } | |
87 | ||
88 | Map<@NonNull String, @NonNull TmfXmlState> statesMap = new HashMap<>(); | |
89 | // Get the FSM states | |
90 | NodeList nodesState = node.getElementsByTagName(TmfXmlStrings.STATE); | |
91 | for (int i = 0; i < nodesState.getLength(); i++) { | |
92 | Element element = (Element) NonNullUtils.checkNotNull(nodesState.item(i)); | |
93 | TmfXmlState state = modelFactory.createState(element, container, null); | |
94 | statesMap.put(state.getId(), state); | |
95 | ||
96 | // If the initial state was not already set, we use the first state | |
97 | // declared in the fsm description as initial state | |
98 | if (initialState.isEmpty()) { | |
99 | initialState = state.getId(); | |
100 | } | |
101 | } | |
102 | ||
103 | if (initialState.isEmpty()) { | |
104 | throw new IllegalStateException("No initial state has been declared in fsm " + id); //$NON-NLS-1$ | |
105 | } | |
106 | ||
107 | // Get the FSM final state | |
108 | String finalStateId = TmfXmlStrings.NULL; | |
109 | NodeList nodesFinalState = node.getElementsByTagName(TmfXmlStrings.FINAL); | |
110 | if (nodesFinalState.getLength() == 1) { | |
111 | final Element finalElement = NonNullUtils.checkNotNull((Element) nodesFinalState.item(0)); | |
112 | finalStateId = finalElement.getAttribute(TmfXmlStrings.ID); | |
113 | if (!finalStateId.isEmpty()) { | |
114 | TmfXmlState finalState = modelFactory.createState(finalElement, container, null); | |
115 | statesMap.put(finalState.getId(), finalState); | |
116 | } | |
117 | } | |
118 | ||
119 | // Get the FSM abandon state | |
120 | String abandonStateId = TmfXmlStrings.NULL; | |
121 | NodeList nodesAbandonState = node.getElementsByTagName(TmfXmlStrings.ABANDON_STATE); | |
122 | if (nodesAbandonState.getLength() == 1) { | |
123 | final Element abandonElement = NonNullUtils.checkNotNull((Element) nodesAbandonState.item(0)); | |
124 | abandonStateId = abandonElement.getAttribute(TmfXmlStrings.ID); | |
125 | if (!abandonStateId.isEmpty()) { | |
126 | TmfXmlState abandonState = modelFactory.createState(abandonElement, container, null); | |
127 | statesMap.put(abandonState.getId(), abandonState); | |
128 | } | |
129 | } | |
130 | return new TmfXmlFsm(modelFactory, container, id, instanceMultipleEnabled, initialState, finalStateId, abandonStateId, preconditions, statesMap); | |
131 | } | |
132 | ||
133 | private TmfXmlFsm(ITmfXmlModelFactory modelFactory, IXmlStateSystemContainer container, String id, boolean multiple, | |
134 | String initialState, String finalState, String abandonState, List<TmfXmlBasicTransition> preconditions, | |
135 | Map<String, TmfXmlState> states) { | |
136 | fModelFactory = modelFactory; | |
137 | fTotalScenarios = 0; | |
138 | fContainer = container; | |
139 | fId = id; | |
140 | fInstanceMultipleEnabled = multiple; | |
141 | fInitialStateId = initialState; | |
142 | fFinalStateId = finalState; | |
143 | fAbandonStateId = abandonState; | |
144 | fPreconditions = ImmutableList.copyOf(preconditions); | |
145 | fStatesMap = ImmutableMap.copyOf(states); | |
146 | fActiveScenariosList = new ArrayList<>(); | |
147 | } | |
148 | ||
149 | /** | |
150 | * Get the fsm ID | |
151 | * | |
152 | * @return the id of this fsm | |
153 | */ | |
154 | public String getId() { | |
155 | return fId; | |
156 | } | |
157 | ||
158 | /** | |
159 | * Get the initial state ID of this fsm | |
160 | * | |
161 | * @return the id of the initial state of this finite state machine | |
162 | */ | |
163 | public String getInitialStateId() { | |
164 | return fInitialStateId; | |
165 | } | |
166 | ||
167 | /** | |
168 | * Get the final state ID of this fsm | |
169 | * | |
170 | * @return the id of the final state of this finite state machine | |
171 | */ | |
172 | public String getFinalStateId() { | |
173 | return fFinalStateId; | |
174 | } | |
175 | ||
176 | /** | |
177 | * Get the abandon state ID fo this fsm | |
178 | * | |
179 | * @return the id of the abandon state of this finite state machine | |
180 | */ | |
181 | public String getAbandonStateId() { | |
182 | return fAbandonStateId; | |
183 | } | |
184 | ||
185 | /** | |
186 | * Get the states table of this fsm in map | |
187 | * | |
188 | * @return The map containing all state definition for this fsm | |
189 | */ | |
190 | public Map<String, TmfXmlState> getStatesMap() { | |
191 | return Collections.unmodifiableMap(fStatesMap); | |
192 | } | |
193 | ||
194 | /** | |
195 | * Process the active event and determine the next step of this fsm | |
196 | * | |
197 | * @param event | |
198 | * The event to process | |
199 | * @param tests | |
200 | * The list of possible transitions of the state machine | |
201 | * @param scenarioInfo | |
202 | * The active scenario details. | |
203 | * @return A pair containing the next state of the state machine and the | |
204 | * actions to execute | |
205 | */ | |
206 | public @Nullable TmfXmlStateTransition next(ITmfEvent event, Map<String, TmfXmlTransitionValidator> tests, TmfXmlScenarioInfo scenarioInfo) { | |
207 | boolean matched = false; | |
208 | TmfXmlStateTransition stateTransition = null; | |
146e73f6 GB |
209 | TmfXmlState state = fStatesMap.get(scenarioInfo.getActiveState()); |
210 | if (state == null) { | |
211 | /** FIXME: This logging should be replaced by something the user will see, this is XML debugging information! */ | |
212 | Activator.logError(NLS.bind(Messages.TmfXmlFsm_StateUndefined, scenarioInfo.getActiveState(), getId())); | |
213 | return null; | |
214 | } | |
3a5f73a1 JCK |
215 | for (int i = 0; i < state.getTransitionList().size() && !matched; i++) { |
216 | stateTransition = state.getTransitionList().get(i); | |
217 | matched = stateTransition.test(event, scenarioInfo, tests); | |
218 | } | |
219 | return matched ? stateTransition : null; | |
220 | } | |
221 | ||
222 | ||
223 | ||
224 | /** | |
225 | * Validate the preconditions of this fsm. If not validate, the fsm will | |
226 | * skip the active event. | |
227 | * | |
228 | * @param event | |
229 | * The current event | |
230 | * @param tests | |
231 | * The transition inputs | |
232 | * @return True if one of the precondition is validated, false otherwise | |
233 | */ | |
234 | private boolean validatePreconditions(ITmfEvent event, Map<String, TmfXmlTransitionValidator> tests) { | |
235 | if (fPreconditions.isEmpty()) { | |
236 | return true; | |
237 | } | |
238 | for (TmfXmlBasicTransition precondition : fPreconditions) { | |
239 | if (precondition.test(event, null, tests)) { | |
240 | return true; | |
241 | } | |
242 | } | |
243 | return false; | |
244 | } | |
245 | ||
246 | /** | |
247 | * Handle the current event | |
248 | * | |
249 | * @param event | |
250 | * The current event | |
251 | * @param transitionMap | |
252 | * The transitions of the pattern | |
253 | */ | |
254 | public void handleEvent(ITmfEvent event, Map<String, TmfXmlTransitionValidator> transitionMap) { | |
255 | if (!validatePreconditions(event, transitionMap)) { | |
256 | return; | |
257 | } | |
258 | for (Iterator<TmfXmlScenario> currentItr = fActiveScenariosList.iterator(); currentItr.hasNext();) { | |
259 | TmfXmlScenario scenario = currentItr.next(); | |
260 | // Remove inactive scenarios or handle the active ones. | |
261 | if (!scenario.isActive()) { | |
262 | currentItr.remove(); | |
263 | } else { | |
264 | handleScenario(scenario, event); | |
265 | } | |
266 | } | |
267 | } | |
268 | ||
269 | /** | |
270 | * Abandon all ongoing scenarios | |
271 | */ | |
272 | public void dispose() { | |
273 | for (TmfXmlScenario scenario : fActiveScenariosList) { | |
274 | if (scenario.isActive()) { | |
275 | scenario.cancel(); | |
276 | } | |
277 | } | |
278 | } | |
279 | ||
280 | private static void handleScenario(TmfXmlScenario scenario, ITmfEvent event) { | |
281 | if (scenario.isActive()) { | |
282 | scenario.handleEvent(event); | |
283 | } | |
284 | } | |
285 | ||
286 | /** | |
287 | * Create a new scenario of this fsm | |
288 | * | |
289 | * @param event | |
290 | * The current event, null if not | |
291 | * @param eventHandler | |
292 | * The event handler this fsm belongs | |
293 | * @param force | |
294 | * True to force the creation of the scenario, false otherwise | |
295 | */ | |
296 | public synchronized void createScenario(@Nullable ITmfEvent event, TmfXmlPatternEventHandler eventHandler, boolean force) { | |
297 | if (force || isNewScenarioAllowed()) { | |
298 | TmfXmlScenario scenario = new TmfXmlScenario(event, eventHandler, fId, fContainer, fModelFactory); | |
299 | fTotalScenarios++; | |
300 | fActiveScenariosList.add(scenario); | |
301 | } | |
302 | } | |
303 | ||
304 | /** | |
305 | * Check if we have the right to create a new scenario. A new scenario could | |
306 | * be created if it is not the first scenario of an FSM and the FSM is not a | |
307 | * singleton and the status of the last created scenario is not PENDING. | |
308 | * | |
309 | * @return True if the start of a new scenario is allowed, false otherwise | |
310 | */ | |
311 | public synchronized boolean isNewScenarioAllowed() { | |
ce1ac9ef GB |
312 | return fTotalScenarios > 0 |
313 | && !fActiveScenariosList.get(fActiveScenariosList.size() - 1).getScenarioInfos().getStatus().equals(ScenarioStatusType.PENDING) | |
314 | && fInstanceMultipleEnabled; | |
3a5f73a1 JCK |
315 | } |
316 | } |