1 /*******************************************************************************
2 * Copyright (c) 2016 Ecole Polytechnique de Montreal, Ericsson
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
.internal
.tmf
.analysis
.xml
.core
.model
;
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
;
18 import org
.eclipse
.jdt
.annotation
.NonNull
;
19 import org
.eclipse
.jdt
.annotation
.Nullable
;
20 import org
.eclipse
.osgi
.util
.NLS
;
21 import org
.eclipse
.tracecompass
.common
.core
.NonNullUtils
;
22 import org
.eclipse
.tracecompass
.internal
.tmf
.analysis
.xml
.core
.Activator
;
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
;
26 import org
.eclipse
.tracecompass
.tmf
.core
.event
.ITmfEvent
;
27 import org
.w3c
.dom
.Element
;
28 import org
.w3c
.dom
.NodeList
;
30 import com
.google
.common
.collect
.ImmutableList
;
31 import com
.google
.common
.collect
.ImmutableMap
;
34 * This Class implements a state machine (FSM) tree in the XML-defined state
37 * @author Jean-Christian Kouame
39 public class TmfXmlFsm
{
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 final boolean fConsuming
;
52 private boolean fEventConsumed
;
53 private int fTotalScenarios
;
56 * Factory to create a {@link TmfXmlFsm}
59 * The factory used to create XML model elements
61 * The XML root of this fsm
63 * The state system container this fsm belongs to
64 * @return The new {@link TmfXmlFsm}
66 public static TmfXmlFsm
create(ITmfXmlModelFactory modelFactory
, Element node
, IXmlStateSystemContainer container
) {
67 String id
= node
.getAttribute(TmfXmlStrings
.ID
);
68 boolean consuming
= node
.getAttribute(TmfXmlStrings
.CONSUMING
).isEmpty() ?
true : Boolean
.parseBoolean(node
.getAttribute(TmfXmlStrings
.CONSUMING
));
69 boolean instanceMultipleEnabled
= node
.getAttribute(TmfXmlStrings
.MULTIPLE
).isEmpty() ?
true : Boolean
.parseBoolean(node
.getAttribute(TmfXmlStrings
.MULTIPLE
));
70 final List
<@NonNull TmfXmlBasicTransition
> preconditions
= new ArrayList
<>();
72 // Get the preconditions
73 NodeList nodesPreconditions
= node
.getElementsByTagName(TmfXmlStrings
.PRECONDITION
);
74 for (int i
= 0; i
< nodesPreconditions
.getLength(); i
++) {
75 preconditions
.add(new TmfXmlBasicTransition(((Element
) NonNullUtils
.checkNotNull(nodesPreconditions
.item(i
)))));
78 // Get the initial state and the preconditions
79 String initialState
= node
.getAttribute(TmfXmlStrings
.INITIAL
);
80 if (initialState
.isEmpty()) {
81 NodeList nodesInitialState
= node
.getElementsByTagName(TmfXmlStrings
.INITIAL
);
82 if (nodesInitialState
.getLength() == 1) {
83 NodeList nodesTransition
= ((Element
) nodesInitialState
.item(0)).getElementsByTagName(TmfXmlStrings
.TRANSITION
);
84 if (nodesInitialState
.getLength() != 1) {
85 throw new IllegalArgumentException("initial state : there should be one and only one initial state."); //$NON-NLS-1$
87 initialState
= ((Element
) nodesTransition
.item(0)).getAttribute(TmfXmlStrings
.TARGET
);
91 Map
<@NonNull String
, @NonNull TmfXmlState
> statesMap
= new HashMap
<>();
93 NodeList nodesState
= node
.getElementsByTagName(TmfXmlStrings
.STATE
);
94 for (int i
= 0; i
< nodesState
.getLength(); i
++) {
95 Element element
= (Element
) NonNullUtils
.checkNotNull(nodesState
.item(i
));
96 TmfXmlState state
= modelFactory
.createState(element
, container
, null);
97 statesMap
.put(state
.getId(), state
);
99 // If the initial state was not already set, we use the first state
100 // declared in the fsm description as initial state
101 if (initialState
.isEmpty()) {
102 initialState
= state
.getId();
106 if (initialState
.isEmpty()) {
107 throw new IllegalStateException("No initial state has been declared in fsm " + id
); //$NON-NLS-1$
110 // Get the FSM final state
111 String finalStateId
= TmfXmlStrings
.NULL
;
112 NodeList nodesFinalState
= node
.getElementsByTagName(TmfXmlStrings
.FINAL
);
113 if (nodesFinalState
.getLength() == 1) {
114 final Element finalElement
= NonNullUtils
.checkNotNull((Element
) nodesFinalState
.item(0));
115 finalStateId
= finalElement
.getAttribute(TmfXmlStrings
.ID
);
116 if (!finalStateId
.isEmpty()) {
117 TmfXmlState finalState
= modelFactory
.createState(finalElement
, container
, null);
118 statesMap
.put(finalState
.getId(), finalState
);
122 // Get the FSM abandon state
123 String abandonStateId
= TmfXmlStrings
.NULL
;
124 NodeList nodesAbandonState
= node
.getElementsByTagName(TmfXmlStrings
.ABANDON_STATE
);
125 if (nodesAbandonState
.getLength() == 1) {
126 final Element abandonElement
= NonNullUtils
.checkNotNull((Element
) nodesAbandonState
.item(0));
127 abandonStateId
= abandonElement
.getAttribute(TmfXmlStrings
.ID
);
128 if (!abandonStateId
.isEmpty()) {
129 TmfXmlState abandonState
= modelFactory
.createState(abandonElement
, container
, null);
130 statesMap
.put(abandonState
.getId(), abandonState
);
133 return new TmfXmlFsm(modelFactory
, container
, id
, consuming
, instanceMultipleEnabled
, initialState
, finalStateId
, abandonStateId
, preconditions
, statesMap
);
136 private TmfXmlFsm(ITmfXmlModelFactory modelFactory
, IXmlStateSystemContainer container
, String id
, boolean consuming
,
137 boolean multiple
, String initialState
, String finalState
, String abandonState
, List
<TmfXmlBasicTransition
> preconditions
,
138 Map
<String
, TmfXmlState
> states
) {
139 fModelFactory
= modelFactory
;
141 fContainer
= container
;
143 fConsuming
= consuming
;
144 fInstanceMultipleEnabled
= multiple
;
145 fInitialStateId
= initialState
;
146 fFinalStateId
= finalState
;
147 fAbandonStateId
= abandonState
;
148 fPreconditions
= ImmutableList
.copyOf(preconditions
);
149 fStatesMap
= ImmutableMap
.copyOf(states
);
150 fActiveScenariosList
= new ArrayList
<>();
156 * @return the id of this fsm
158 public String
getId() {
163 * Get the initial state ID of this fsm
165 * @return the id of the initial state of this finite state machine
167 public String
getInitialStateId() {
168 return fInitialStateId
;
172 * Get the final state ID of this fsm
174 * @return the id of the final state of this finite state machine
176 public String
getFinalStateId() {
177 return fFinalStateId
;
181 * Get the abandon state ID fo this fsm
183 * @return the id of the abandon state of this finite state machine
185 public String
getAbandonStateId() {
186 return fAbandonStateId
;
190 * Get the states table of this fsm in map
192 * @return The map containing all state definition for this fsm
194 public Map
<String
, TmfXmlState
> getStatesMap() {
195 return Collections
.unmodifiableMap(fStatesMap
);
199 * Set whether the ongoing was consumed by a scenario or not
201 * @param eventConsumed
204 public void setEventConsumed(boolean eventConsumed
) {
205 fEventConsumed
= eventConsumed
;
208 private boolean isEventConsumed() {
209 return fEventConsumed
;
213 * Process the active event and determine the next step of this fsm
216 * The event to process
218 * The list of possible transitions of the state machine
219 * @param scenarioInfo
220 * The active scenario details.
221 * @return A pair containing the next state of the state machine and the
224 public @Nullable TmfXmlStateTransition
next(ITmfEvent event
, Map
<String
, TmfXmlTransitionValidator
> tests
, TmfXmlScenarioInfo scenarioInfo
) {
225 boolean matched
= false;
226 TmfXmlStateTransition stateTransition
= null;
227 TmfXmlState state
= fStatesMap
.get(scenarioInfo
.getActiveState());
229 /** FIXME: This logging should be replaced by something the user will see, this is XML debugging information! */
230 Activator
.logError(NLS
.bind(Messages
.TmfXmlFsm_StateUndefined
, scenarioInfo
.getActiveState(), getId()));
233 for (int i
= 0; i
< state
.getTransitionList().size() && !matched
; i
++) {
234 stateTransition
= state
.getTransitionList().get(i
);
235 matched
= stateTransition
.test(event
, scenarioInfo
, tests
);
237 return matched ? stateTransition
: null;
243 * Validate the preconditions of this fsm. If not validate, the fsm will
244 * skip the active event.
249 * The transition inputs
250 * @return True if one of the precondition is validated, false otherwise
252 private boolean validatePreconditions(ITmfEvent event
, Map
<String
, TmfXmlTransitionValidator
> tests
) {
253 if (fPreconditions
.isEmpty()) {
256 for (TmfXmlBasicTransition precondition
: fPreconditions
) {
257 if (precondition
.test(event
, null, tests
)) {
265 * Handle the current event
269 * @param transitionMap
270 * The transitions of the pattern
272 public void handleEvent(ITmfEvent event
, Map
<String
, TmfXmlTransitionValidator
> transitionMap
) {
273 setEventConsumed(false);
274 if (!validatePreconditions(event
, transitionMap
)) {
277 for (Iterator
<TmfXmlScenario
> currentItr
= fActiveScenariosList
.iterator(); currentItr
.hasNext();) {
278 TmfXmlScenario scenario
= currentItr
.next();
279 // Remove inactive scenarios or handle the active ones.
280 if (!scenario
.isActive()) {
283 handleScenario(scenario
, event
);
284 if (fConsuming
&& isEventConsumed()) {
292 * Abandon all ongoing scenarios
294 public void dispose() {
295 for (TmfXmlScenario scenario
: fActiveScenariosList
) {
296 if (scenario
.isActive()) {
302 private static void handleScenario(TmfXmlScenario scenario
, ITmfEvent event
) {
303 if (scenario
.isActive()) {
304 scenario
.handleEvent(event
);
309 * Create a new scenario of this fsm
312 * The current event, null if not
313 * @param eventHandler
314 * The event handler this fsm belongs
316 * True to force the creation of the scenario, false otherwise
318 public synchronized void createScenario(@Nullable ITmfEvent event
, TmfXmlPatternEventHandler eventHandler
, boolean force
) {
319 if (force
|| isNewScenarioAllowed()) {
320 TmfXmlScenario scenario
= new TmfXmlScenario(event
, eventHandler
, fId
, fContainer
, fModelFactory
);
322 fActiveScenariosList
.add(scenario
);
327 * Check if we have the right to create a new scenario. A new scenario could
328 * be created if it is not the first scenario of an FSM and the FSM is not a
329 * singleton and the status of the last created scenario is not PENDING.
331 * @return True if the start of a new scenario is allowed, false otherwise
333 public synchronized boolean isNewScenarioAllowed() {
334 return fTotalScenarios
> 0
335 && !fActiveScenariosList
.get(fActiveScenariosList
.size() - 1).getScenarioInfos().getStatus().equals(ScenarioStatusType
.PENDING
)
336 && fInstanceMultipleEnabled
;