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