/*******************************************************************************
* Copyright (c) 2014, 2015 Ecole Polytechnique de Montreal and others
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Florian Wininger - Initial API and implementation
* Naser Ezzati - Add the comparison operators
* Patrick Tasse - Add message to exceptions
* Jean-Christian Kouame - Add comparison between two state values
******************************************************************************/
package org.eclipse.tracecompass.internal.tmf.analysis.xml.core.model;
import java.util.ArrayList;
import java.util.List;
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.module.IXmlStateSystemContainer;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.module.XmlUtils;
import org.eclipse.tracecompass.internal.tmf.analysis.xml.core.stateprovider.TmfXmlStrings;
import org.eclipse.tracecompass.statesystem.core.ITmfStateSystem;
import org.eclipse.tracecompass.statesystem.core.exceptions.AttributeNotFoundException;
import org.eclipse.tracecompass.statesystem.core.statevalue.ITmfStateValue;
import org.eclipse.tracecompass.tmf.core.event.ITmfEvent;
import org.w3c.dom.Element;
/**
* This Class implement a condition tree in the XML-defined state system.
*
* <pre>
* example:
* <and>
* <condition>
* <stateAttribute type="location" value="CurrentThread" />
* <stateAttribute type="constant" value="System_call" />
* <stateValue type="null" />
* </condition>
* <condition>
* <stateValue type="long" value="2" />
* <stateValue type="long" value="5" />
* </condition>
* </and>
* </pre>
*
* @author Florian Wininger
*/
public class TmfXmlCondition implements ITmfXmlCondition {
private final List<TmfXmlCondition> fConditions = new ArrayList<>();
private final List<ITmfXmlStateValue> fStateValues;
private final LogicalOperator fOperator;
private final IXmlStateSystemContainer fContainer;
private final ConditionOperator fConditionOperator;
private ConditionType fType;
private @Nullable TmfXmlTimestampCondition fTimeCondition;
private enum LogicalOperator {
NONE,
NOT,
AND,
OR
}
private enum ConditionOperator {
NONE,
EQ,
NE,
GE,
GT,
LE,
LT
}
// TODO The XmlCondition needs to be split into several classes of condition
// instead of using an enum
private enum ConditionType {
DATA,
TIME,
NONE
}
/**
* Factory to create {@link TmfXmlCondition}
*
* @param modelFactory
* The factory used to create XML model elements
* @param node
* The XML root of this condition
* @param container
* The state system container this condition belongs to
* @return The new {@link TmfXmlCondition}
*/
public static TmfXmlCondition create(ITmfXmlModelFactory modelFactory, Element node, IXmlStateSystemContainer container) {
Element rootNode = node;
/* Process the conditions: in each case, only process Element nodes */
List<@Nullable Element> childElements = XmlUtils.getChildElements(rootNode);
/*
* If the node is an if, take the child as the root condition
*
* FIXME: Maybe the caller should do this instead.
*/
if (node.getNodeName().equals(TmfXmlStrings.IF)) {
if (childElements.isEmpty()) {
throw new IllegalArgumentException("TmfXmlCondition constructor: IF node with no child element"); //$NON-NLS-1$
}
rootNode = NonNullUtils.checkNotNull(childElements.get(0));
childElements = XmlUtils.getChildElements(rootNode);
}
List<@NonNull TmfXmlCondition> conditions = new ArrayList<>();
switch (rootNode.getNodeName()) {
case TmfXmlStrings.CONDITION:
return createPatternCondition(modelFactory, container, rootNode, childElements);
case TmfXmlStrings.NOT:
return createMultipleCondition(modelFactory, container, childElements, LogicalOperator.NOT, conditions);
case TmfXmlStrings.AND:
return createMultipleCondition(modelFactory, container, childElements, LogicalOperator.AND, conditions);
case TmfXmlStrings.OR:
return createMultipleCondition(modelFactory, container, childElements, LogicalOperator.OR, conditions);
default:
throw new IllegalArgumentException("TmfXmlCondition constructor: XML node " + rootNode.getNodeName() + " is of the wrong type"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
private static TmfXmlCondition createPatternCondition(ITmfXmlModelFactory modelFactory, IXmlStateSystemContainer container, Element rootNode, List<@Nullable Element> childElements) {
ArrayList<ITmfXmlStateValue> stateValues;
ConditionOperator conditionOperator;
TmfXmlTimestampCondition timeCondition = null;
int size = rootNode.getElementsByTagName(TmfXmlStrings.STATE_VALUE).getLength();
if (size != 0) {
stateValues = new ArrayList<>(size);
if (size == 1) {
conditionOperator = getConditionOperator(rootNode);
getStateValuesForXmlCondition(modelFactory, NonNullUtils.checkNotNull(childElements), stateValues, container);
} else {
// No need to test if the childElements size is actually 2.
// The XSD validation do this check already.
conditionOperator = ConditionOperator.EQ;
stateValues.add(modelFactory.createStateValue(NonNullUtils.checkNotNull(childElements.get(0)), container, new ArrayList<ITmfXmlStateAttribute>()));
stateValues.add(modelFactory.createStateValue(NonNullUtils.checkNotNull(childElements.get(1)), container, new ArrayList<ITmfXmlStateAttribute>()));
}
return new TmfXmlCondition(ConditionType.DATA, stateValues, LogicalOperator.NONE, conditionOperator, null, new ArrayList<>(), container);
}
final Element firstElement = NonNullUtils.checkNotNull(childElements.get(0));
timeCondition = modelFactory.createTimestampsCondition(firstElement, container);
return new TmfXmlCondition(ConditionType.TIME, new ArrayList<>(), LogicalOperator.NONE, ConditionOperator.EQ, timeCondition, new ArrayList<>(), container);
}
private static TmfXmlCondition createMultipleCondition(ITmfXmlModelFactory modelFactory, IXmlStateSystemContainer container, List<@Nullable Element> childElements, LogicalOperator op,
List<@NonNull TmfXmlCondition> conditions) {
for (Element condition : childElements) {
if (condition == null) {
continue;
}
conditions.add(modelFactory.createCondition(condition, container));
}
return new TmfXmlCondition(ConditionType.NONE, new ArrayList<>(), op, ConditionOperator.NONE, null, conditions, container);
}
private TmfXmlCondition(ConditionType type, ArrayList<@NonNull ITmfXmlStateValue> stateValues, LogicalOperator operator, ConditionOperator conditionOperator, @Nullable TmfXmlTimestampCondition timeCondition, List<@NonNull TmfXmlCondition> conditions,
IXmlStateSystemContainer container) {
fType = type;
fStateValues = stateValues;
fOperator = operator;
fTimeCondition = timeCondition;
fContainer = container;
fConditions.addAll(conditions);
fConditionOperator = conditionOperator;
}
private static void getStateValuesForXmlCondition(ITmfXmlModelFactory modelFactory, List<@Nullable Element> childElements, List<ITmfXmlStateValue> stateValues, IXmlStateSystemContainer container) {
Element stateValueElement = NonNullUtils.checkNotNull(childElements.remove(childElements.size() - 1));
/*
* A state value is either preceded by an eventField or a number of
* state attributes
*/
final Element firstElement = NonNullUtils.checkNotNull(childElements.get(0));
if (childElements.size() == 1 && firstElement.getNodeName().equals(TmfXmlStrings.ELEMENT_FIELD)) {
String attribute = firstElement.getAttribute(TmfXmlStrings.NAME);
stateValues.add(modelFactory.createStateValue(stateValueElement, container, attribute));
} else {
List<ITmfXmlStateAttribute> attributes = new ArrayList<>();
for (Element element : childElements) {
if (element == null) {
throw new NullPointerException("There should be at list one element"); //$NON-NLS-1$
}
if (!element.getNodeName().equals(TmfXmlStrings.STATE_ATTRIBUTE)) {
throw new IllegalArgumentException("TmfXmlCondition: a condition either has a eventField element or a number of TmfXmlStateAttribute elements before the state value"); //$NON-NLS-1$
}
ITmfXmlStateAttribute attribute = modelFactory.createStateAttribute(element, container);
attributes.add(attribute);
}
stateValues.add(modelFactory.createStateValue(stateValueElement, container, attributes));
}
}
private static 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$
}
}
@Override
public boolean test(ITmfEvent event, @Nullable TmfXmlScenarioInfo scenarioInfo) {
ITmfStateSystem ss = fContainer.getStateSystem();
if (fType == ConditionType.DATA) {
try {
return testForEvent(event, NonNullUtils.checkNotNull(ss), scenarioInfo);
} catch (AttributeNotFoundException e) {
Activator.logError("Attribute not found", e); //$NON-NLS-1$
return false;
}
} else if (fType == ConditionType.TIME) {
if (fTimeCondition != null) {
return fTimeCondition.test(event, scenarioInfo);
}
} else if (!fConditions.isEmpty()) {
/* Verify a condition tree */
switch (fOperator) {
case AND:
for (ITmfXmlCondition childCondition : fConditions) {
if (!childCondition.test(event, scenarioInfo)) {
return false;
}
}
return true;
case NONE:
break;
case NOT:
return !fConditions.get(0).test(event, scenarioInfo);
case OR:
for (ITmfXmlCondition childCondition : fConditions) {
if (childCondition.test(event, scenarioInfo)) {
return true;
}
}
return false;
default:
break;
}
}
return true;
}
private boolean testForEvent(ITmfEvent event, ITmfStateSystem ss, @Nullable TmfXmlScenarioInfo scenarioInfo) throws AttributeNotFoundException {
/*
* The condition is either the equality check of a state value or a
* boolean operation on other conditions
*/
if (fStateValues.size() == 1) {
ITmfXmlStateValue filter = fStateValues.get(0);
int quark = IXmlStateSystemContainer.ROOT_QUARK;
for (ITmfXmlStateAttribute attribute : filter.getAttributes()) {
quark = attribute.getAttributeQuark(event, quark, scenarioInfo);
/*
* When verifying a condition, the state attribute must exist,
* if it does not, the query is not valid, we stop the condition
* check
*/
if (quark == IXmlStateSystemContainer.ERROR_QUARK) {
throw new AttributeNotFoundException(ss.getSSID() + " Attribute:" + attribute); //$NON-NLS-1$
}
}
/*
* The actual value: it can be either queried in the state system or
* found in the event
*/
ITmfStateValue valueState = (quark != IXmlStateSystemContainer.ROOT_QUARK) ? ss.queryOngoingState(quark) : filter.getEventFieldValue(event);
/* Get the value to compare to from the XML file */
ITmfStateValue valueXML;
valueXML = filter.getValue(event, scenarioInfo);
return compare(valueState, valueXML, fConditionOperator);
}
/* Get the two values needed for the comparison */
ITmfStateValue valuesXML1 = fStateValues.get(0).getValue(event, scenarioInfo);
ITmfStateValue valuesXML2 = fStateValues.get(1).getValue(event, scenarioInfo);
return valuesXML1.equals(valuesXML2);
}
@Override
public String toString() {
StringBuilder output = new StringBuilder("TmfXmlCondition: "); //$NON-NLS-1$
if (fOperator != LogicalOperator.NONE) {
output.append(fOperator).append(" on ").append(fConditions); //$NON-NLS-1$
} else {
output.append(fConditionOperator).append(" {").append(fStateValues.get(0)); //$NON-NLS-1$
if (fStateValues.size() == 2) {
output.append(", ").append(fStateValues.get(1)); //$NON-NLS-1$
}
output.append("}"); //$NON-NLS-1$
}
return output.toString();
}
/**
* Compare two ITmfStateValues based on the given comparison operator
*
* @param source
* the state value to compare to
* @param dest
* the state value to be compared with
* @param comparisonOperator
* the operator to compare the inputs
* @return the boolean result of the comparison
*/
public boolean compare(ITmfStateValue source, ITmfStateValue dest, ConditionOperator comparisonOperator) {
switch (comparisonOperator) {
// TODO The comparison operator should have a compareHelper that calls compare
case EQ:
return (source.compareTo(dest) == 0);
case NE:
return (source.compareTo(dest) != 0);
case GE:
return (source.compareTo(dest) >= 0);
case GT:
return (source.compareTo(dest) > 0);
case LE:
return (source.compareTo(dest) <= 0);
case LT:
return (source.compareTo(dest) < 0);
case NONE:
default:
throw new IllegalArgumentException("TmfXmlCondition: invalid comparison operator."); //$NON-NLS-1$
}
}
}