blob: 52d087a81623fab2d5b085782f5be0385c4ba4ed [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 É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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.common.core.NonNullUtils;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.Activator;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenAction;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionConditional;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionList;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionSegment;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionStateChange;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenActionStateChange.StackAction;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenCondition;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenStateSystemPath;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.values.DataDrivenValue;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.values.DataDrivenValueConstant;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.XmlUtils;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
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;
/**
* The compilation unit for an XML state change element
*
* FIXME: When porting the XML pattern to these classes, see if state change can
* be an action
*
* @author Geneviève Bastien
* @author Florian Wininger
*/
public abstract class TmfXmlActionCu implements IDataDrivenCompilationUnit {
/** A state change assigning a value to a path in the state system */
private static class TmfXmlStateChangeAssignationCu extends TmfXmlActionCu {
private TmfXmlStateSystemPathCu fPath;
private TmfXmlStateValueCu fRightOperand;
private boolean fIncrement;
private boolean fUpdate;
private StackAction fStackAction;
private @Nullable TmfXmlStateValueCu fFutureTime;
public TmfXmlStateChangeAssignationCu(TmfXmlStateSystemPathCu path,
TmfXmlStateValueCu rightOperandCu, boolean increment, boolean update, StackAction stackAction, @Nullable TmfXmlStateValueCu futureTime) {
fPath = path;
fRightOperand = rightOperandCu;
fIncrement = increment;
fUpdate = update;
fStackAction = stackAction;
fFutureTime = futureTime;
}
@Override
public DataDrivenAction generate() {
DataDrivenStateSystemPath path = fPath.generate();
DataDrivenValue rightOperand = fRightOperand.generate();
DataDrivenValue futureTime = null;
if (fFutureTime != null) {
futureTime = fFutureTime.generate();
}
return new DataDrivenActionStateChange(path, rightOperand, fIncrement, fUpdate, fStackAction, futureTime);
}
}
/** Conditional state change */
private static class TmfXmlConditionalStateChangeCu extends TmfXmlActionCu {
private final TmfXmlConditionCu fCondition;
private final TmfXmlActionCu fThen;
private final @Nullable TmfXmlActionCu fElse;
public TmfXmlConditionalStateChangeCu(TmfXmlConditionCu condition, TmfXmlActionCu thenChange, @Nullable TmfXmlActionCu elseChange) {
fCondition = condition;
fThen = thenChange;
fElse = elseChange;
}
@Override
public DataDrivenAction generate() {
DataDrivenCondition condition = fCondition.generate();
DataDrivenAction thenChange = fThen.generate();
DataDrivenAction elseChange = (fElse != null ? fElse.generate() : null);
return new DataDrivenActionConditional(condition, thenChange, elseChange);
}
}
/** Action list action */
private static class TmfXmlActionListCu extends TmfXmlActionCu {
private final List<TmfXmlActionCu> fActions;
public TmfXmlActionListCu(List<TmfXmlActionCu> actions) {
fActions = actions;
}
@Override
public DataDrivenAction generate() {
List<DataDrivenAction> actions = fActions.stream()
.map(TmfXmlActionCu::generate)
.collect(Collectors.toList());
return new DataDrivenActionList(actions);
}
}
/** No action */
private static final TmfXmlActionCu NO_ACTION_CU = new TmfXmlActionCu() {
@Override
public DataDrivenAction generate() {
return DataDrivenAction.NO_ACTION;
}
};
/** Segment creation action */
private static class TmfXmlActionSegmentCu extends TmfXmlActionCu {
private final TmfXmlStateValueCu fType;
private final @Nullable TmfXmlStateValueCu fStart;
private final @Nullable TmfXmlStateValueCu fDuration;
private final @Nullable TmfXmlStateValueCu fEnd;
private final Map<String, TmfXmlStateValueCu> fFieldMap;
public TmfXmlActionSegmentCu(TmfXmlStateValueCu segmentTypeValue, @Nullable TmfXmlStateValueCu beginTimeValue, @Nullable TmfXmlStateValueCu durationValue, @Nullable TmfXmlStateValueCu endTimeValue, Map<String, TmfXmlStateValueCu> fieldMap) {
fType = segmentTypeValue;
fStart = beginTimeValue;
fDuration = durationValue;
fEnd = endTimeValue;
fFieldMap = fieldMap;
}
@Override
public DataDrivenAction generate() {
DataDrivenValue type = fType.generate();
DataDrivenValue start = fStart != null ? fStart.generate() : null;
DataDrivenValue end = fEnd != null ? fEnd.generate() : null;
DataDrivenValue duration = fDuration != null ? fDuration.generate() : null;
Map<String, DataDrivenValue> fields = new HashMap<>();
for (Entry<String, TmfXmlStateValueCu> fieldEntry : fFieldMap.entrySet()) {
fields.put(fieldEntry.getKey(), fieldEntry.getValue().generate());
}
return new DataDrivenActionSegment(type, start, duration, end, fields);
}
}
@Override
public abstract DataDrivenAction generate();
/**
* Compile a state change XML element
*
* @param analysisData
* The analysis data already compiled
* @param stateChange
* The state change XML element to compile
* @return The state change compilation unit or <code>null</code> if there was a
* compilation error
*/
public static @Nullable TmfXmlActionCu compile(AnalysisCompilationData analysisData, Element stateChange) {
/*
* child nodes is either a list of TmfXmlStateAttributes and TmfXmlStateValues,
* or an if-then-else series of nodes.
*/
List<Element> childElements = TmfXmlUtils.getChildElements(stateChange, TmfXmlStrings.IF);
if (childElements.size() == 1) {
return compileConditionalChange(analysisData, stateChange, childElements.get(0));
} else if (childElements.size() > 1) {
// TODO: Validation message here
Activator.logError("Conditional State Change: There should be only 1 if node"); //$NON-NLS-1$
return null;
}
return compileAssignationChange(analysisData, stateChange);
}
/** Compile a conditional state change */
private static @Nullable TmfXmlActionCu compileConditionalChange(AnalysisCompilationData analysisData, Element stateChange, Element ifNode) {
// Compile the child of the IF node
List<@Nullable Element> childElements = XmlUtils.getChildElements(ifNode);
if (childElements.size() != 1) {
// TODO: Validation message here
Activator.logError("There should be only one element under this condition"); //$NON-NLS-1$
return null;
}
Element subCondition = Objects.requireNonNull(childElements.get(0));
TmfXmlConditionCu condition = TmfXmlConditionCu.compile(analysisData, subCondition);
if (condition == null) {
return null;
}
// Compile the then element
List<Element> thenElements = TmfXmlUtils.getChildElements(stateChange, TmfXmlStrings.THEN);
if (thenElements.size() != 1) {
// TODO: Validation message here
Activator.logError("Conditional State Change: There should be 1 and only 1 then element"); //$NON-NLS-1$
return null;
}
TmfXmlActionCu thenChange = compile(analysisData, thenElements.get(0));
if (thenChange == null) {
return null;
}
// Compile the else element
List<Element> elseElements = TmfXmlUtils.getChildElements(stateChange, TmfXmlStrings.ELSE);
if (elseElements.isEmpty()) {
return new TmfXmlConditionalStateChangeCu(condition, thenChange, null);
}
if (thenElements.size() != 1) {
// TODO: Validation message here
Activator.logError("Conditional State Change: There should be at most 1 else element"); //$NON-NLS-1$
return null;
}
TmfXmlActionCu elseChange = compile(analysisData, elseElements.get(0));
if (elseChange == null) {
return null;
}
return new TmfXmlConditionalStateChangeCu(condition, thenChange, elseChange);
}
/** Compile an assignation state change */
private static @Nullable TmfXmlActionCu compileAssignationChange(AnalysisCompilationData analysisContent, Element stateChange) {
List<@NonNull Element> leftOperands = TmfXmlUtils.getChildElements(stateChange, TmfXmlStrings.STATE_ATTRIBUTE);
List<@NonNull Element> rightOperands = TmfXmlUtils.getChildElements(stateChange, TmfXmlStrings.STATE_VALUE);
List<@NonNull Element> futureTimes = TmfXmlUtils.getChildElements(stateChange, TmfXmlStrings.FUTURE_TIME);
if (rightOperands.size() != 1) {
// TODO: Validation message here
Activator.logError("There should only be one state Value in this state change"); //$NON-NLS-1$
}
Element rightOperand = rightOperands.get(0);
TmfXmlStateSystemPathCu path = TmfXmlStateSystemPathCu.compile(analysisContent, leftOperands);
if (path == null) {
return null;
}
if (futureTimes.size() > 1) {
// TODO: Validation message here
Activator.logError("There should at most one future time for this state change"); //$NON-NLS-1$
}
Element futureTime = futureTimes.isEmpty() ? null : futureTimes.get(0);
TmfXmlStateValueCu futureTimeCu = null;
if (futureTime != null) {
futureTimeCu = TmfXmlStateValueCu.compileValue(analysisContent, futureTime);
if (futureTimeCu == null) {
return null;
}
}
TmfXmlStateValueCu rightOperandCu = TmfXmlStateValueCu.compileValue(analysisContent, rightOperand);
if (rightOperandCu == null) {
return null;
}
/* Check if there is an increment for the value */
boolean increment = Boolean.parseBoolean(rightOperand.getAttribute(TmfXmlStrings.INCREMENT));
/* Check if this value is an update of the ongoing state */
boolean update = Boolean.parseBoolean(rightOperand.getAttribute(TmfXmlStrings.UPDATE));
/*
* Stack Actions : allow to define a stack with PUSH/POP/PEEK methods
*/
String stack = rightOperand.getAttribute(TmfXmlStrings.ATTRIBUTE_STACK);
StackAction stackAction = DataDrivenActionStateChange.StackAction.getTypeFromString(stack);
// Extra attribute validation
// Update and increment are not valid for stack actions
if (update && stackAction != StackAction.NONE) {
Activator.logError("State change: Update cannot be done with stack action " + stackAction); //$NON-NLS-1$
return null;
}
if (increment && stackAction != StackAction.NONE) {
Activator.logError("State change: Increment cannot be done with stack action " + stackAction); //$NON-NLS-1$
return null;
}
return new TmfXmlStateChangeAssignationCu(path, rightOperandCu, increment, update, stackAction, futureTimeCu);
}
/**
* Compile a named action element, ie an action that contains an ID
*
* TODO: Return the action itself instead of the string when everything has
* moved to new code path
*
* @param analysisData
* The analysis data already compiled
* @param namedEl
* the XML element corresponding to the action
* @return The action, or <code>null</code> if there was compilation
* errors
*/
public static @Nullable TmfXmlActionCu compileNamedAction(AnalysisCompilationData analysisData, Element namedEl) {
String actionId = namedEl.getAttribute(TmfXmlStrings.ID);
if (actionId.isEmpty()) {
// TODO: Validation message here
Activator.logError("The action should have an ID attribute"); //$NON-NLS-1$
return null;
}
List<@Nullable Element> childElements = XmlUtils.getChildElements(namedEl);
List<TmfXmlActionCu> actionList = new ArrayList<>();
for (Element child : childElements) {
final @NonNull Element nonNullChild = NonNullUtils.checkNotNull(child);
switch (nonNullChild.getNodeName()) {
case TmfXmlStrings.STATE_CHANGE:
{
TmfXmlActionCu action = compile(analysisData, nonNullChild);
if (action == null) {
return null;
}
actionList.add(action);
}
break;
case TmfXmlStrings.FSM_SCHEDULE_ACTION:
// Do not do anything, this action is not supported yet
// TODO: Validation message here
Activator.logWarning("Action " + TmfXmlStrings.FSM_SCHEDULE_ACTION + " is not supported yet, it will have no effect"); //$NON-NLS-1$ //$NON-NLS-2$
break;
case TmfXmlStrings.SEGMENT:
{
TmfXmlActionCu action = compileSegmentAction(analysisData, nonNullChild);
if (action == null) {
return null;
}
actionList.add(action);
}
break;
case TmfXmlStrings.ACTION:
{
// Compile the subaction
TmfXmlActionCu subAction = compileNamedAction(analysisData, nonNullChild);
if (subAction == null) {
return null;
}
actionList.add(subAction);
}
break;
default:
// TODO: Validation message here
Activator.logError("Invalid action type : " + nonNullChild.getNodeName()); //$NON-NLS-1$
}
}
TmfXmlActionCu actionCu = createActionList(actionList);
analysisData.addAction(actionId, actionCu);
return actionCu;
}
private static @Nullable TmfXmlActionCu compileSegmentAction(AnalysisCompilationData analysisData, Element node) {
// Compile the segment type
List<@NonNull Element> segmentType = TmfXmlUtils.getChildElements(node, TmfXmlStrings.SEGMENT_TYPE);
if (segmentType.size() != 1) {
// TODO: Validation message here
Activator.logWarning("Segment action: There should be one and only one " + TmfXmlStrings.SEGMENT_TYPE + " element."); //$NON-NLS-1$ //$NON-NLS-2$
return null;
}
Element typeElement = Objects.requireNonNull(segmentType.get(0));
String segmentName = typeElement.getAttribute(TmfXmlStrings.SEGMENT_NAME);
TmfXmlStateValueCu segmentTypeValue = null;
if (!segmentName.isEmpty()) {
// Constant name for the segment type
segmentTypeValue = new TmfXmlStateValueCu(() -> new DataDrivenValueConstant(null, ITmfStateValue.Type.NULL, segmentName));
} else {
List<Element> nameElements = TmfXmlUtils.getChildElements(typeElement, TmfXmlStrings.SEGMENT_NAME);
if (nameElements.size() != 1) {
// TODO: Validation message here
Activator.logError("Segment type: You need to either give a segName attribute or define one and only one <segName> element with child <stateValue> to get the type name"); //$NON-NLS-1$
return null;
}
Element segNameElement = Objects.requireNonNull(nameElements.get(0));
List<Element> nameValueElements = TmfXmlUtils.getChildElements(segNameElement, TmfXmlStrings.STATE_VALUE);
if (nameValueElements.size() != 1) {
// TODO: Validation message here
Activator.logError("Segment type: the <segName> element should have one and only one <stateValue> child"); //$NON-NLS-1$
return null;
}
Element segNameValueElement = Objects.requireNonNull(nameValueElements.get(0));
segmentTypeValue = TmfXmlStateValueCu.compileValue(analysisData, segNameValueElement);
}
if (segmentTypeValue == null) {
return null;
}
// Compile the optional time element
TmfXmlStateValueCu beginTimeValue = null;
TmfXmlStateValueCu durationValue = null;
TmfXmlStateValueCu endTimeValue = null;
List<Element> timeElements = TmfXmlUtils.getChildElements(node, TmfXmlStrings.SEGMENT_TIME);
if (timeElements.size() > 1) {
// TODO: Validation message here
Activator.logWarning("Segment time: there should be only one <segTime> element"); //$NON-NLS-1$
}
if (timeElements.size() > 0) {
Element segTimeElement = Objects.requireNonNull(timeElements.get(0));
List<Element> beginElements = TmfXmlUtils.getChildElements(segTimeElement, TmfXmlStrings.BEGIN);
if (beginElements.isEmpty()) {
// TODO: Validation message here
Activator.logError("Segment time: there should be one <begin> element to describe segment start time"); //$NON-NLS-1$
return null;
}
if (beginElements.size() > 1) {
// TODO: Validation message here
Activator.logWarning("Segment time: there should be only one <begin> element"); //$NON-NLS-1$
}
beginTimeValue = TmfXmlStateValueCu.compileValue(analysisData, Objects.requireNonNull(beginElements.get(0)));
if (beginTimeValue == null) {
return null;
}
beginTimeValue = new TmfXmlStateValueCu(new TmfXmlStateValueCu.ValueWrapperGenerator(beginTimeValue, ITmfStateValue.Type.LONG));
// Validate end time or duration
List<Element> durationElements = TmfXmlUtils.getChildElements(segTimeElement, TmfXmlStrings.DURATION);
List<Element> endElements = TmfXmlUtils.getChildElements(segTimeElement, TmfXmlStrings.END);
if (durationElements.isEmpty() && endElements.isEmpty()) {
// TODO: Validation message here
Activator.logError("Segment time: there should be either a <duration> or <end> element"); //$NON-NLS-1$
return null;
}
if (!durationElements.isEmpty() && !endElements.isEmpty()) {
// TODO: Validation message here
Activator.logError("Segment time: only one of <duration> or <end> shoud be present"); //$NON-NLS-1$
return null;
}
if (durationElements.size() > 1) {
// TODO: Validation message here
Activator.logWarning("Segment time: there should be only one <duration> element"); //$NON-NLS-1$
}
if (endElements.size() > 1) {
// TODO: Validation message here
Activator.logWarning("Segment time: there should be only one <end> element"); //$NON-NLS-1$
}
if (!durationElements.isEmpty()) {
durationValue = TmfXmlStateValueCu.compileValue(analysisData, Objects.requireNonNull(durationElements.get(0)));
if (durationValue == null) {
return null;
}
durationValue = new TmfXmlStateValueCu(new TmfXmlStateValueCu.ValueWrapperGenerator(durationValue, ITmfStateValue.Type.LONG));
} else {
endTimeValue = TmfXmlStateValueCu.compileValue(analysisData, Objects.requireNonNull(endElements.get(0)));
if (endTimeValue == null) {
return null;
}
endTimeValue = new TmfXmlStateValueCu(new TmfXmlStateValueCu.ValueWrapperGenerator(endTimeValue, ITmfStateValue.Type.LONG));
}
}
// Compile the segment fields
List<Element> contentElements = TmfXmlUtils.getChildElements(node, TmfXmlStrings.SEGMENT_CONTENT);
Map<String, TmfXmlStateValueCu> fieldMap = new HashMap<>();
for (Element contentEl : contentElements) {
List<Element> fieldElements = TmfXmlUtils.getChildElements(contentEl, TmfXmlStrings.SEGMENT_FIELD);
for (Element fieldEl : fieldElements) {
String name = fieldEl.getAttribute(TmfXmlStrings.NAME);
if (name.isEmpty()) {
// TODO: Validation message here
Activator.logError("Segment field does not have a name"); //$NON-NLS-1$
return null;
}
if (fieldMap.containsKey(name)) {
// TODO: Validation message here
Activator.logError("Redefinition of field " + name); //$NON-NLS-1$
return null;
}
TmfXmlStateValueCu fieldCu = TmfXmlStateValueCu.compileSegmentField(analysisData, fieldEl);
if (fieldCu == null) {
return null;
}
fieldMap.put(name, fieldCu);
}
}
return new TmfXmlActionSegmentCu(segmentTypeValue, beginTimeValue, durationValue, endTimeValue, fieldMap);
}
/**
* Create an action from a list
*
* @param actions
* The list of actions
* @return The new action
*/
public static TmfXmlActionCu createActionList(List<TmfXmlActionCu> actions) {
if (actions.size() == 0) {
// There are unsupported actions, so the action list may be empty
return NO_ACTION_CU;
}
return actions.size() == 1 ? actions.get(0) : new TmfXmlActionListCu(actions);
}
}