blob: e8bc5c72946f6fb30784fb0194b4386c1cb94425 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 École Polytechnique de Montréal
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.compile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.Activator;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenFsm;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenFsmSimpleState;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenFsmState;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenFsmStateTransition;
import org.eclipse.tracecompass.tmf.analysis.xml.core.module.TmfXmlStrings;
import org.eclipse.tracecompass.tmf.analysis.xml.core.module.TmfXmlUtils;
import org.w3c.dom.Element;
/**
* A compilation unit for XML finite state machine
*
* @author Geneviève Bastien
* @author Jean-Christian Kouamé
*/
public abstract class TmfXmlFsmStateCu implements IDataDrivenCompilationUnit {
/**
* Represents an FSM
*/
public static class TmfXmlFsmCu extends TmfXmlFsmStateCu {
private final TmfXmlFsmStateCu fInitialState;
private final Collection<TmfXmlFsmSimpleStateCu> fStates;
private final TmfXmlConditionCu fPreCondition;
private final boolean fConsuming;
private final boolean fMultipleInstances;
/**
* Constructor
*
* @param id
* The ID of this FSM
* @param initialStateCu
* The initial state compilation unit
* @param states
* The list of states
* @param consuming
* Whether this fsm is consuming (ie, once a transition is
* taken in one scenario, it won't be taken on others)
* @param instanceMultipleEnabled
* Whether multiple scenarios of this FSM can be run in
* parallel
* @param preCondition
* The preconditions for this FSM
*/
private TmfXmlFsmCu(String id, TmfXmlFsmStateCu initialStateCu, Collection<TmfXmlFsmSimpleStateCu> states, boolean consuming, boolean instanceMultipleEnabled, TmfXmlConditionCu preCondition) {
super(id);
fInitialState = initialStateCu;
fStates = states;
fPreCondition = preCondition;
fConsuming = consuming;
fMultipleInstances = instanceMultipleEnabled;
}
@Override
public DataDrivenFsm generate() {
Map<String, DataDrivenFsmSimpleState> states = fStates.stream()
.map(TmfXmlFsmSimpleStateCu::generate)
.collect(Collectors.toMap(DataDrivenFsmState::getId, state -> state));
return new DataDrivenFsm(getId(), fInitialState.generate(), states, fPreCondition.generate(), fConsuming, fMultipleInstances);
}
}
/**
* Represent a final state, without transitions
*/
private static class TmfXmlFsmFinalStateCu extends TmfXmlFsmSimpleStateCu {
public TmfXmlFsmFinalStateCu(String id) {
super(id, Collections.emptyList(), Collections.emptyList());
}
@Override
public DataDrivenFsmSimpleState generate() {
return DataDrivenFsmSimpleState.createFinalState(getId());
}
}
/**
* Package-private class so other compilation units can see it
*/
static class TmfXmlFsmSimpleStateCu extends TmfXmlFsmStateCu {
private final List<TmfXmlFsmStateTransitionCu> fTransitions = new ArrayList<>();
private final TmfXmlActionCu fOnEntryActions;
private final TmfXmlActionCu fOnExitActions;
private TmfXmlFsmSimpleStateCu(String id, List<TmfXmlActionCu> onEntryActions, List<TmfXmlActionCu> onExitActions) {
super(id);
fOnEntryActions = TmfXmlActionCu.createActionList(onEntryActions);
fOnExitActions = TmfXmlActionCu.createActionList(onExitActions);
}
private void addTransitions(List<TmfXmlFsmStateTransitionCu> transitions) {
fTransitions.addAll(transitions);
}
@Override
public DataDrivenFsmSimpleState generate() {
List<DataDrivenFsmStateTransition> transitions = fTransitions.stream()
.map(TmfXmlFsmStateTransitionCu::generate)
.collect(Collectors.toList());
return new DataDrivenFsmSimpleState(getId(), transitions, fOnEntryActions.generate(), fOnExitActions.generate());
}
}
private final String fId;
private TmfXmlFsmStateCu(String id) {
fId = id;
}
@Override
public abstract DataDrivenFsmState generate();
/**
* Get the ID of this FSM state
*
* @return The ID of the FSM state
*/
protected String getId() {
return fId;
}
/**
* Compile a finite state machine from an XML element
*
* FIXME: Return the FSM directly when legacy code is gone
*
* @param analysisData
* The analysis data already compiled
* @param element
* the XML element corresponding to the fsm
* @return The FSM
*/
public static @Nullable TmfXmlFsmCu compileFsm(AnalysisCompilationData analysisData, Element element) {
String id = element.getAttribute(TmfXmlStrings.ID);
if (id.isEmpty()) {
// TODO: Validation message here
Activator.logError("FSM: The FSM should have a non-empty 'id' parameter"); //$NON-NLS-1$
return null;
}
String consumingStr = element.getAttribute(TmfXmlStrings.CONSUMING);
String multipleInstancesStr = element.getAttribute(TmfXmlStrings.MULTIPLE);
boolean consuming = consumingStr.isEmpty() ? true : Boolean.parseBoolean(consumingStr);
boolean instanceMultipleEnabled = multipleInstancesStr.isEmpty() ? true : Boolean.parseBoolean(multipleInstancesStr);
Map<String, TmfXmlFsmSimpleStateCu> states = new HashMap<>();
// Create the FSM states, without transitions yet
// FSMs can be cyclic, so we need all states to be able to create the
// transitions
List<Element> stateElements = TmfXmlUtils.getChildElements(element, TmfXmlStrings.STATE);
TmfXmlFsmStateCu firstState = null;
for (Element stateElement : stateElements) {
TmfXmlFsmStateCu state = compileSimpleState(analysisData, stateElement, states, false);
if (state == null) {
return null;
}
// Save the first state in case there is no initial state
if (firstState == null) {
firstState = state;
}
}
// Get the final state
List<Element> finalElements = TmfXmlUtils.getChildElements(element, TmfXmlStrings.FINAL);
if (!finalElements.isEmpty()) {
if (finalElements.size() > 1) {
// TODO: Validation message here
Activator.logWarning("Fsm " + id + ": there should be only one final state"); //$NON-NLS-1$ //$NON-NLS-2$
}
TmfXmlFsmSimpleStateCu state = compileSimpleState(analysisData, finalElements.get(0), states, true);
if (state == null) {
return null;
}
}
// Compile the initial state
String initialStr = element.getAttribute(TmfXmlStrings.INITIAL);
List<Element> nodesInitialElement = TmfXmlUtils.getChildElements(element, TmfXmlStrings.INITIAL);
List<Element> nodesInitialStateElement = TmfXmlUtils.getChildElements(element, TmfXmlStrings.INITIAL_STATE);
TmfXmlFsmStateCu initialStateCu = null;
if (!nodesInitialStateElement.isEmpty()) {
if (!initialStr.isEmpty() || !nodesInitialElement.isEmpty()) {
// TODO: Validation message here
Activator.logWarning("Fsm " + id + ": the 'initial' attribute was set or an <initial> element was defined. Only one of the 3 should be used. The " + TmfXmlStrings.INITIAL_STATE + " element will have precedence"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
initialStateCu = compileInitialState(analysisData, nodesInitialStateElement.get(0), states);
if (initialStateCu == null) {
return null;
}
} else if (initialStr.isEmpty() && nodesInitialElement.isEmpty()) {
// Take the first state as initial state
if (firstState == null) {
// TODO: Validation message here
Activator.logError("FSM " + id + ": No state was defined."); //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
initialStateCu = firstState;
} else {
if (!initialStr.isEmpty() && !nodesInitialElement.isEmpty()) {
// TODO: Validation message here
Activator.logWarning("Fsm " + id + ": Both 'initial' attribute and <initial> element were declared. Only the 'initial' attribute will be used"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (!initialStr.isEmpty()) {
// Take the initial attribute as initial state, but make sure
// the state exists
initialStateCu = states.get(initialStr);
if (initialStateCu == null) {
// TODO: Validation message here
Activator.logError("FSM " + id + ": Undefined initial state " + initialStr); //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
} else {
// The initial element is the equivalent of the initial
// attribute except the starting state is the name of the target
List<Element> transitionElements = TmfXmlUtils.getChildElements(nodesInitialElement.get(0), TmfXmlStrings.TRANSITION);
if (transitionElements.isEmpty()) {
// TODO: Validation message here
Activator.logError("FSM " + id + ": No transition defined for 'initial' element"); //$NON-NLS-1$ //$NON-NLS-2$
return null;
} else if (transitionElements.size() > 1) {
// TODO: Validation message here
Activator.logWarning("FSM " + id + ": Too many transitions defined for 'initial' element. Only 1 needed"); //$NON-NLS-1$ //$NON-NLS-2$
}
initialStateCu = TmfXmlFsmStateTransitionCu.compileInitialTransition(analysisData, transitionElements.get(0), states);
if (initialStateCu == null) {
return null;
}
}
}
// For each state, compile the transitions
for (Element stateElement : stateElements) {
TmfXmlFsmSimpleStateCu simpleState = getSimpleState(stateElement, states);
List<TmfXmlFsmStateTransitionCu> transitions = compileTransitionElements(analysisData, stateElement, states);
if (transitions == null) {
return null;
}
if (transitions.isEmpty()) {
// TODO: Validation message here
Activator.logWarning("Fsm " + id + ": a state was defined without transition. You may get stuck there"); //$NON-NLS-1$ //$NON-NLS-2$
}
simpleState.addTransitions(transitions);
}
// Compile the preconditions
List<Element> preCondElements = TmfXmlUtils.getChildElements(element, TmfXmlStrings.PRECONDITION);
List<TmfXmlConditionCu> preConditions = new ArrayList<>();
for (Element preCondElement : preCondElements) {
TmfXmlConditionCu preCond = TmfXmlFsmStateTransitionCu.compileAsCondition(analysisData, preCondElement);
if (preCond == null) {
return null;
}
preConditions.add(preCond);
}
TmfXmlConditionCu preCondition = TmfXmlConditionCu.createOrCondition(preConditions);
TmfXmlFsmCu fsm = new TmfXmlFsmCu(id, initialStateCu, states.values(), consuming, instanceMultipleEnabled, preCondition);
analysisData.addFsm(id, fsm);
return fsm;
}
private static @Nullable List<TmfXmlFsmStateTransitionCu> compileTransitionElements(AnalysisCompilationData analysisData, Element element, Map<String, TmfXmlFsmSimpleStateCu> states) {
List<Element> childElements = TmfXmlUtils.getChildElements(element, TmfXmlStrings.TRANSITION);
List<TmfXmlFsmStateTransitionCu> transitions = new ArrayList<>();
for (Element childElement : childElements) {
TmfXmlFsmStateTransitionCu transition = TmfXmlFsmStateTransitionCu.compile(analysisData, childElement, states);
if (transition == null) {
return null;
}
transitions.add(transition);
}
return transitions;
}
private static @Nullable TmfXmlFsmSimpleStateCu compileInitialState(AnalysisCompilationData analysisData, Element element, Map<String, TmfXmlFsmSimpleStateCu> states) {
List<TmfXmlFsmStateTransitionCu> transitions = compileTransitionElements(analysisData, element, states);
if (transitions == null) {
return null;
}
TmfXmlFsmSimpleStateCu initialState = new TmfXmlFsmSimpleStateCu(TmfXmlStrings.INITIAL_STATE, Collections.emptyList(), Collections.emptyList());
initialState.addTransitions(transitions);
return initialState;
}
private static @Nullable TmfXmlFsmSimpleStateCu compileSimpleState(AnalysisCompilationData analysisData, Element element, Map<String, TmfXmlFsmSimpleStateCu> states, boolean isFinalState) {
String stateId = element.getAttribute(TmfXmlStrings.ID);
if (stateId.isEmpty()) {
// TODO: Validation message here
Activator.logError("FSM State: The state should have a non-empty 'id' parameter"); //$NON-NLS-1$
return null;
}
TmfXmlFsmStateCu currentStateCu = states.get(stateId);
if (currentStateCu != null) {
// TODO: Validation message here
Activator.logError("FSM State: Redefinition of state " + stateId); //$NON-NLS-1$
return null;
}
// Get the onEntry and onExit actions
List<Element> elements = TmfXmlUtils.getChildElements(element, TmfXmlStrings.ONENTRY);
List<TmfXmlActionCu> onEntryActions = Collections.emptyList();
if (!elements.isEmpty()) {
onEntryActions = getActionList(analysisData, elements.get(0));
if (onEntryActions == null) {
return null;
}
}
elements = TmfXmlUtils.getChildElements(element, TmfXmlStrings.ONEXIT);
List<TmfXmlActionCu> onExitActions = Collections.emptyList();
if (!elements.isEmpty()) {
onExitActions = getActionList(analysisData, elements.get(0));
if (onExitActions == null) {
return null;
}
}
TmfXmlFsmSimpleStateCu stateCu;
if (isFinalState) {
if (!onExitActions.isEmpty() || !onEntryActions.isEmpty()) {
// TODO: Validation message here
Activator.logWarning("FSM State: Final state should not have any onEntry and onExit actions"); //$NON-NLS-1$
}
stateCu = new TmfXmlFsmFinalStateCu(stateId);
} else {
stateCu = new TmfXmlFsmSimpleStateCu(stateId, onEntryActions, onExitActions);
}
states.put(stateId, stateCu);
return stateCu;
}
private static @Nullable List<TmfXmlActionCu> getActionList(AnalysisCompilationData analysisData, Element element) {
List<TmfXmlActionCu> actions = new ArrayList<>();
String onEntryActionStr = element.getAttribute(TmfXmlStrings.ACTION);
if (onEntryActionStr.isEmpty()) {
return actions;
}
@NonNull String[] actionIds = onEntryActionStr.split(TmfXmlStrings.AND_SEPARATOR);
for (String actionId : actionIds) {
TmfXmlActionCu action = analysisData.getAction(actionId);
if (action == null) {
// TODO: Validation message here
Activator.logError("FSM State: Undefined action " + actionId); //$NON-NLS-1$
return null;
}
actions.add(action);
}
return actions;
}
private static TmfXmlFsmSimpleStateCu getSimpleState(Element stateElement, Map<String, TmfXmlFsmSimpleStateCu> states) {
String stateId = stateElement.getAttribute(TmfXmlStrings.ID);
TmfXmlFsmStateCu state = states.get(stateId);
if (!(state instanceof TmfXmlFsmSimpleStateCu)) {
throw new NullPointerException("The requested state is not of the right type: " + stateId); //$NON-NLS-1$
}
return (TmfXmlFsmSimpleStateCu) state;
}
}