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