blob: d4a78f6a26aaca771c8f777ebd143d31decc0280 [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.List;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
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.DataDrivenCondition;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenCondition.ConditionOperator;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.fsm.model.DataDrivenCondition.TimeRangeOperator;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.XmlUtils;
import org.eclipse.tracecompass.tmf.analysis.xml.core.module.TmfXmlStrings;
import org.eclipse.tracecompass.tmf.analysis.xml.core.module.TmfXmlUtils;
import org.eclipse.tracecompass.tmf.core.timestamp.ITmfTimestamp;
import org.eclipse.tracecompass.tmf.core.timestamp.TmfTimestamp;
import org.w3c.dom.Element;
/**
* The base compilation unit for XML tests and conditions
*
* @author Geneviève Bastien
* @author Florian Wininger
*/
public abstract class TmfXmlConditionCu implements IDataDrivenCompilationUnit {
private static final TmfXmlConditionCu TRUE_CONDITION = new TmfXmlConditionCu() {
@Override
public DataDrivenCondition generate() {
return DataDrivenCondition.TRUE_CONDITION;
}
};
/** Compararison condition */
private static class TmfXmlCompareConditionCu extends TmfXmlConditionCu {
private final TmfXmlStateValueCu fFirstValue;
private final TmfXmlStateValueCu fSecondValue;
private final ConditionOperator fOperator;
TmfXmlCompareConditionCu(TmfXmlStateValueCu firstValue, TmfXmlStateValueCu secondValue, ConditionOperator conditionOperator) {
fFirstValue = firstValue;
fSecondValue = secondValue;
fOperator = conditionOperator;
}
@Override
public DataDrivenCondition generate() {
return new DataDrivenCondition.DataDrivenComparisonCondition(fFirstValue.generate(), fSecondValue.generate(), fOperator);
}
}
/** Compare time range */
private static class TmfXmlTimeRangeConditionCu extends TmfXmlConditionCu {
private final TimeRangeOperator fOperator;
private final long fBegin;
private final long fEnd;
TmfXmlTimeRangeConditionCu(TimeRangeOperator operator, long begin, long end) {
fOperator = operator;
fBegin = begin;
fEnd = end;
}
@Override
public DataDrivenCondition generate() {
return new DataDrivenCondition.DataDrivenTimeRangeCondition(fOperator, fBegin, fEnd);
}
}
/** Compare time range */
private static class TmfXmlElapsedTimeConditionCu extends TmfXmlConditionCu {
private final ConditionOperator fOperator;
private final String fReference;
private final long fValue;
TmfXmlElapsedTimeConditionCu(ConditionOperator operator, String reference, long end) {
fOperator = operator;
fReference = reference;
fValue = end;
}
@Override
public DataDrivenCondition generate() {
return new DataDrivenCondition.DataDrivenElapsedTimeCondition(fOperator, fReference, fValue);
}
}
/** NOT condition */
private static class TmfXmlNotConditionCu extends TmfXmlConditionCu {
private final TmfXmlConditionCu fCondition;
TmfXmlNotConditionCu(TmfXmlConditionCu compile) {
fCondition = compile;
}
@Override
public DataDrivenCondition generate() {
return new DataDrivenCondition.DataDrivenNotCondition(fCondition.generate());
}
}
/** Pattern matching condition */
static class TmfXmlRegexConditionCu extends TmfXmlConditionCu {
private final Pattern fPattern;
private final TmfXmlStateValueCu fValue;
TmfXmlRegexConditionCu(Pattern pattern, TmfXmlStateValueCu value) {
fPattern = pattern;
fValue = value;
}
@Override
public DataDrivenCondition generate() {
return new DataDrivenCondition.DataDrivenRegexCondition(fPattern, fValue.generate());
}
}
/** AND condition */
public static class TmfXmlAndConditionCu extends TmfXmlConditionCu {
private final List<TmfXmlConditionCu> fConditions;
TmfXmlAndConditionCu(List<TmfXmlConditionCu> childConditions) {
fConditions = childConditions;
}
@Override
public DataDrivenCondition generate() {
List<DataDrivenCondition> conditions = fConditions.stream()
.map(TmfXmlConditionCu::generate)
.collect(Collectors.toList());
return new DataDrivenCondition.DataDrivenAndCondition(conditions);
}
}
/** OR condition */
public static class TmfXmlOrConditionCu extends TmfXmlConditionCu {
private final List<TmfXmlConditionCu> fConditions;
TmfXmlOrConditionCu(List<TmfXmlConditionCu> childConditions) {
fConditions = childConditions;
}
@Override
public DataDrivenCondition generate() {
List<DataDrivenCondition> conditions = fConditions.stream()
.map(TmfXmlConditionCu::generate)
.collect(Collectors.toList());
return new DataDrivenCondition.DataDrivenOrCondition(conditions);
}
}
@Override
public abstract DataDrivenCondition generate();
/**
* Compile a test element, ie an element that contains a name
*
* TODO: Return the condition 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 condition
* @return The condition
*/
public static @Nullable TmfXmlConditionCu compileNamedCondition(AnalysisCompilationData analysisData, Element namedEl) {
String id = namedEl.getAttribute(TmfXmlStrings.ID);
List<@Nullable Element> childElements = XmlUtils.getChildElements(namedEl);
Element child = NonNullUtils.checkNotNull(childElements.get(0));
// Compile the child of the IF node
childElements = XmlUtils.getChildElements(child);
if (childElements.size() != 1) {
// TODO: Validation message here
Activator.logError("There should be only one element under this condition"); //$NON-NLS-1$
throw new NullPointerException("Can't compile the condition"); //$NON-NLS-1$
}
Element subCondition = Objects.requireNonNull(childElements.get(0));
TmfXmlConditionCu condition = compile(analysisData, subCondition);
if (condition == null) {
return null;
}
analysisData.addTest(id, condition);
return condition;
}
/**
* @param analysisData
* The analysis data already compiled
* @param conditionEl
* the XML element corresponding to the condition
* @return The condition compilation unit or <code>null</code> if there was
* compilation error
*/
public static @Nullable TmfXmlConditionCu compile(AnalysisCompilationData analysisData, Element conditionEl) {
switch (conditionEl.getNodeName()) {
case TmfXmlStrings.CONDITION:
return compileSingleCondition(analysisData, conditionEl);
case TmfXmlStrings.NOT: {
List<@Nullable Element> childElements = XmlUtils.getChildElements(conditionEl);
if (childElements.size() != 1) {
Activator.logError("Compiling condition: NOT condition must have 1 and only 1 child"); //$NON-NLS-1$
return null;
}
Element element = Objects.requireNonNull(childElements.get(0));
TmfXmlConditionCu compile = compile(analysisData, element);
return (compile == null ? null : new TmfXmlNotConditionCu(compile));
}
case TmfXmlStrings.AND: {
List<TmfXmlConditionCu> childConditions = getCompiledChildConditions(analysisData, conditionEl);
return (childConditions == null ? null : new TmfXmlAndConditionCu(childConditions));
}
case TmfXmlStrings.OR: {
List<TmfXmlConditionCu> childConditions = getCompiledChildConditions(analysisData, conditionEl);
return (childConditions == null ? null : new TmfXmlOrConditionCu(childConditions));
}
default:
Activator.logError("Xml condition: Unsupported condition type: " + conditionEl.getNodeName()); //$NON-NLS-1$
}
return null;
}
private static @Nullable List<TmfXmlConditionCu> getCompiledChildConditions(AnalysisCompilationData analysisData, Element conditionEl) {
List<@Nullable Element> childElements = XmlUtils.getChildElements(conditionEl);
if (childElements.isEmpty()) {
Activator.logError("Compiling condition: AND and OR condition must have at least 1 element"); //$NON-NLS-1$
return null;
}
List<TmfXmlConditionCu> childConditions = new ArrayList<>();
for (Element element : childElements) {
TmfXmlConditionCu condition = compile(analysisData, Objects.requireNonNull(element));
if (condition == null) {
return null;
}
childConditions.add(condition);
}
return childConditions;
}
private static @Nullable TmfXmlConditionCu compileSingleCondition(AnalysisCompilationData analysisData, Element conditionEl) {
// Is the condition a comparison condition?
if (conditionEl.getElementsByTagName(TmfXmlStrings.STATE_VALUE).getLength() > 0) {
return compileValueCondition(analysisData, conditionEl);
}
// Compile a time range condition
List<Element> childElements = TmfXmlUtils.getChildElements(conditionEl, TmfXmlStrings.TIME_RANGE);
if (childElements.size() == 1) {
return compileTimeRangeCondition(childElements.get(0));
}
// Compile an elapsed time condition
childElements = TmfXmlUtils.getChildElements(conditionEl, TmfXmlStrings.ELAPSED_TIME);
if (childElements.size() == 1) {
return compileElapsedTimeCondition(childElements.get(0));
}
return null;
}
private static @Nullable TmfXmlConditionCu compileElapsedTimeCondition(Element element) {
String unit = element.getAttribute(TmfXmlStrings.UNIT);
List<@Nullable Element> childElements = XmlUtils.getChildElements(element);
if (childElements.size() != 1) {
Activator.logError("Invalid timestampsChecker declaration in XML : Only one timing condition is allowed"); //$NON-NLS-1$
return null;
}
final Element firstElement = NonNullUtils.checkNotNull(childElements.get(0));
String type = firstElement.getNodeName();
ConditionOperator operator;
switch (type) {
case TmfXmlStrings.LESS:
operator = ConditionOperator.LT;
break;
case TmfXmlStrings.EQUAL:
operator = ConditionOperator.EQ;
break;
case TmfXmlStrings.MORE:
operator = ConditionOperator.GT;
break;
default:
Activator.logError("ElapsedTimeChecker: Invalid operator: " + type); //$NON-NLS-1$
return null;
}
final String reference = firstElement.getAttribute(TmfXmlStrings.SINCE);
final String valueStr = firstElement.getAttribute(TmfXmlStrings.VALUE);
try {
long value = valueToNanoseconds(Long.parseLong(valueStr), unit);
return new TmfXmlElapsedTimeConditionCu(operator, reference, value);
} catch (NumberFormatException e) {
Activator.logError("Invalid value for elapsed time: " + e.getMessage()); //$NON-NLS-1$
return null;
}
}
private static @Nullable TmfXmlConditionCu compileTimeRangeCondition(Element element) {
String unit = element.getAttribute(TmfXmlStrings.UNIT);
List<@Nullable Element> childElements = NonNullUtils.checkNotNull(XmlUtils.getChildElements(element));
if (childElements.size() != 1) {
Activator.logError("Invalid timestampsChecker declaration in XML : Only one timing condition is allowed"); //$NON-NLS-1$
return null;
}
final Element firstElement = NonNullUtils.checkNotNull(childElements.get(0));
TimeRangeOperator operator;
String type = firstElement.getNodeName();
switch (type) {
case TmfXmlStrings.IN:
operator = TimeRangeOperator.IN;
break;
case TmfXmlStrings.OUT:
operator = TimeRangeOperator.OUT;
break;
default:
Activator.logError("TimeRangeChecker: Invalid operator: " + type); //$NON-NLS-1$
return null;
}
final String beginStr = firstElement.getAttribute(TmfXmlStrings.BEGIN);
final String endStr = firstElement.getAttribute(TmfXmlStrings.END);
try {
long begin = valueToNanoseconds(Long.parseLong(beginStr), unit);
long end = valueToNanoseconds(Long.parseLong(endStr), unit);
return new TmfXmlTimeRangeConditionCu(operator, begin, end);
} catch (NumberFormatException e) {
Activator.logError("Invalid value for time range: " + e.getMessage()); //$NON-NLS-1$
return null;
}
}
/**
* Normalize the value into a nanosecond time value
*
* @param timestamp
* The timestamp value
* @param unit
* The initial unit of the timestamp
* @return The value of the timestamp in nanoseconds
*/
public static long valueToNanoseconds(long timestamp, String unit) {
switch (unit) {
case TmfXmlStrings.NS:
return timestamp;
case TmfXmlStrings.US:
return TmfTimestamp.create(timestamp, ITmfTimestamp.MICROSECOND_SCALE).toNanos();
case TmfXmlStrings.MS:
return TmfTimestamp.create(timestamp, ITmfTimestamp.MILLISECOND_SCALE).toNanos();
case TmfXmlStrings.S:
return TmfTimestamp.create(timestamp, ITmfTimestamp.SECOND_SCALE).toNanos();
default:
throw new IllegalArgumentException("The time unit is not yet supporting."); //$NON-NLS-1$
}
}
private static @Nullable TmfXmlConditionCu compileValueCondition(AnalysisCompilationData analysisData, Element conditionEl) {
ConditionOperator conditionOperator = getConditionOperator(conditionEl);
// Compile the case with 2 state values
List<Element> childElements = TmfXmlUtils.getChildElements(conditionEl, TmfXmlStrings.STATE_VALUE);
if (childElements.size() == 2) {
TmfXmlStateValueCu firstValue = TmfXmlStateValueCu.compileValue(analysisData, childElements.get(0));
TmfXmlStateValueCu secondValue = TmfXmlStateValueCu.compileValue(analysisData, childElements.get(1));
if (firstValue == null || secondValue == null) {
return null;
}
return new TmfXmlCompareConditionCu(firstValue, secondValue, conditionOperator);
}
// Compile the case with first element being a stateAttribute or event
// field
if (childElements.size() == 1) {
TmfXmlStateValueCu secondValue = TmfXmlStateValueCu.compileValue(analysisData, childElements.get(0));
TmfXmlStateValueCu firstValue = null;
List<Element> attributes = TmfXmlUtils.getChildElements(conditionEl, TmfXmlStrings.STATE_ATTRIBUTE);
if (!attributes.isEmpty()) {
// The first value is an array of state attributes
TmfXmlStateSystemPathCu path = TmfXmlStateSystemPathCu.compile(analysisData, attributes);
if (path == null) {
return null;
}
firstValue = TmfXmlStateValueCu.compileAsQuery(path);
} else {
// The first value is an event field
attributes = TmfXmlUtils.getChildElements(conditionEl, TmfXmlStrings.ELEMENT_FIELD);
if (attributes.size() != 1) {
Activator.logError("Condition: There should be either 2 state values or 1 attribute or field and 1 state value"); //$NON-NLS-1$
return null;
}
firstValue = TmfXmlStateValueCu.compileField(analysisData, attributes.get(0));
}
if (firstValue == null || secondValue == null) {
return null;
}
return new TmfXmlCompareConditionCu(firstValue, secondValue, conditionOperator);
}
return null;
}
private static DataDrivenCondition.ConditionOperator getConditionOperator(Element rootNode) {
String equationType = rootNode.getAttribute(TmfXmlStrings.OPERATOR);
switch (equationType) {
case TmfXmlStrings.EQ:
return ConditionOperator.EQ;
case TmfXmlStrings.NE:
return ConditionOperator.NE;
case TmfXmlStrings.GE:
return ConditionOperator.GE;
case TmfXmlStrings.GT:
return ConditionOperator.GT;
case TmfXmlStrings.LE:
return ConditionOperator.LE;
case TmfXmlStrings.LT:
return ConditionOperator.LT;
case TmfXmlStrings.NULL:
return ConditionOperator.EQ;
default:
throw new IllegalArgumentException("TmfXmlCondition: invalid comparison operator."); //$NON-NLS-1$
}
}
/**
* Create an OR condition from the list of conditions, or return the single
* condition if there is only one
*
* @param conditions
* The list of conditions to OR
* @return And OR condition, or the single condition
*/
public static TmfXmlConditionCu createOrCondition(List<TmfXmlConditionCu> conditions) {
if (conditions.isEmpty()) {
return TRUE_CONDITION;
}
return conditions.size() == 1 ? conditions.get(0) : new TmfXmlOrConditionCu(conditions);
}
/**
* Create an AND condition from the list of conditions, or return the single
* condition if there is only one
*
* @param conditions
* The list of conditions to AND
* @return And AND condition, or the single condition
*/
public static @Nullable TmfXmlConditionCu createAndCondition(List<TmfXmlConditionCu> conditions) {
if (conditions.isEmpty()) {
return TRUE_CONDITION;
}
return conditions.size() == 1 ? conditions.get(0) : new TmfXmlAndConditionCu(conditions);
}
}