/*******************************************************************************
* Copyright 2011 The Regents of the University of California
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.ohmage.conditionevaluator;
import org.andwellness.config.grammar.syntaxtree.NodeOptional;
import org.andwellness.config.grammar.syntaxtree.NodeSequence;
import org.andwellness.config.grammar.syntaxtree.NodeToken;
import org.andwellness.config.grammar.syntaxtree.condition;
import org.andwellness.config.grammar.syntaxtree.conjunction;
import org.andwellness.config.grammar.syntaxtree.expr;
import org.andwellness.config.grammar.syntaxtree.id;
import org.andwellness.config.grammar.syntaxtree.sentence;
import org.andwellness.config.grammar.syntaxtree.sentence_prime;
import org.andwellness.config.grammar.syntaxtree.start;
import org.andwellness.config.grammar.syntaxtree.value;
import org.andwellness.config.grammar.visitor.GJDepthFirst;
import org.ohmage.conditionevaluator.comparator.DataPointComparator;
import org.ohmage.conditionevaluator.comparator.DataPointComparatorFactory;
import org.ohmage.logprobe.Log;
import java.util.List;
/**
* Check to see if the condition outputs to true or false, based on previous responses.
* This should be extending a custom class as we want different return values depending
* on visitor, but GJDepthFirst plus wonky casting works well enough.
*
* The basic operation is as follows: For every expression, this visitor will lookup
* the 'id' in the current id list, grab the 'value', then use the DataPointComparator
* to compare the 'id' to the 'value' based on the 'condition'. This gets passed up
* to the overall sentence, which will combine all the expressions for a final Boolean
* value.
*
* @author jhicks
*
* @param <R> Must be a Boolean or a String, depending on the visitor.
* @param <A> Must be a Boolean, only used to pass from sentence to sentence_prime
*/
public class ConditionDepthFirst<R, A> extends GJDepthFirst<R, A> {
private static final String TAG = "ConditionDepthFirst";
// Holds the List of current ID/value pairs
private final List<DataPoint> _currentIdList;
//private static Logger _logger = Logger.getLogger(ConditionDepthFirst.class);
/**
* Used to set the currentIdList for the visitors to access
*
* @param currentIdList The current List of ID/Value pairs
*/
public ConditionDepthFirst(List<DataPoint> currentIdList) {
_currentIdList = currentIdList;
}
/**
* Return the String of the NodeToken.
*
* @return A String representing the token's value.
*/
@Override
@SuppressWarnings("unchecked")
public R visit(NodeToken n, A argu) {
return (R) n.toString();
}
/**
* f0 -> sentence()
* f1 -> <EOF>
*
* @return The validity of the sentence.
*/
@Override
public R visit(start n, A argu) {
R _ret=null;
// Return the evaluation of the overall sentence
_ret = n.f0.accept(this, argu);
n.f1.accept(this, argu);
return _ret;
}
/**
* f0 -> expr() sentence_prime()
* | "(" sentence() ")" sentence_prime()
*
* A bit more complicated because the grammar was transformed from
* left recursive to right recursive, adding a "sentence_prime" condition
* First evaluate the expr or sentence depending on which f0 we have.
* Then pass this to the sentence_prime and return the eval of the sentence_prime.
*
* @return The validity of the sentence_prime
*/
@Override
@SuppressWarnings("unchecked")
public R visit(sentence n, A argu) {
R _ret=null;
// Pull out the node sequence which will be one of
// expr() sentence_prime() or "(" sentence() ")" sentence_prime()
NodeSequence nodeSequence = (NodeSequence) n.f0.choice;
// Check to see which choice we have
// f0 -> expr() sentence_prime()
if (n.f0.which == 0) {
// Separate out the nodes
expr expressionNode = (expr) nodeSequence.elementAt(0);
sentence_prime sentencePrimeNode = (sentence_prime) nodeSequence.elementAt(1);
// First eval the expr
Boolean exprBool = (Boolean) expressionNode.accept(this, argu);
// Now eval the sentence prime, passing in the result from the expr
_ret = sentencePrimeNode.accept(this, (A) exprBool);
}
// f0 -> "(" sentence() ")" sentence_prime()
else if (n.f0.which == 1) {
// Separate out the meaningful nodes and evaluate
sentence sentenceNode = (sentence) nodeSequence.elementAt(1);
sentence_prime sentencePrimeNode = (sentence_prime) nodeSequence.elementAt(3);
Boolean sentenceBool = (Boolean) sentenceNode.accept(this, argu);
_ret = sentencePrimeNode.accept(this, (A) sentenceBool);
}
return _ret;
}
/**
* f0 -> ( conjunction() sentence() sentence_prime() )?
*
* The sentence_prime visitor makes the least intuitive sense. There are two cases
* because of the ?:
* 1) The base case is the sentence_prime is nullified and does not exist.
* Return the input Boolean argument as the sentence_prime has no effect.
* 2) The sentence_prime exists. Notice that a sentence_prime is just a conjunction
* with a sentence, the left side of the conjunction does not exist. The Boolean
* argument is the left side so what we evaluate is 'argu conjunction sentence', which
* is then passed recursively into the sentence_prime.
*
* @param argu A Boolean that represents the left side of the conjunction
* @return A Boolean that represents the valisity of this sentence_prime
*/
@Override
@SuppressWarnings("unchecked")
public R visit(sentence_prime n, A argu) {
R _ret=null;
// Pull out the initial value (the one before the first conjunction)
Boolean initialValue = (Boolean) argu;
// The NodeOptional will tell us if this node exists
NodeOptional nodeOptional = n.f0;
// If this is a nullified sentence_prime, just return the initial value back
// (The base case of the recursiveness)
if (!nodeOptional.present()) {
_ret = (R) initialValue;
}
// Evaluate the initial value conjoined with the sentence, send in
// as the new initial value for the new sentence prime, and return
// what that sentence_prime gives us back
else {
// The boolean to pass to the sentence prime
Boolean toPassToSentencePrime;
// Since we know the node exists, grab it
NodeSequence nodeSequence = (NodeSequence) nodeOptional.node;
// Pull out the conjunction value
String conjValue = (String) nodeSequence.elementAt(0).accept(this, argu);
Boolean sentValue; // Don't evaluate now, use shortcutting if appropriate
sentence_prime sentencePrime = (sentence_prime) nodeSequence.elementAt(2);
if ("and".equals(conjValue)) {
// Use shortcutting, don't evaluate the sentence if the initialValue is false
// because false 'and'ed with anything is false
if (initialValue.booleanValue() == false) {
toPassToSentencePrime = new Boolean(false);
}
// Now we have to evaluate the other side of the 'and'
else {
sentValue = (Boolean) nodeSequence.elementAt(1).accept(this, argu);
// Both sides of the and must be true to return true
if (sentValue.booleanValue() == true) {
toPassToSentencePrime = new Boolean(true);
}
else {
toPassToSentencePrime = new Boolean(false);
}
}
}
else if ("or".equals(conjValue)) {
// If the first side of the 'or' is true, we don't need to evaluate the second
if (initialValue.booleanValue() == true) {
toPassToSentencePrime = new Boolean(true);
}
// Evaluate the other side of the 'or'
else {
sentValue = (Boolean) nodeSequence.elementAt(1).accept(this, argu);
// Since the left side of the 'or' was false, this must be true to return true
if (sentValue.booleanValue() == true) {
toPassToSentencePrime = new Boolean(true);
}
else {
toPassToSentencePrime = new Boolean(false);
}
}
}
else {
// this should never happen?
throw new IllegalArgumentException("Conjunction neither and nor or.");
}
// Now pass this recursively to the sentence_prime to see if there is anything else to do
_ret = sentencePrime.accept(this, (A) toPassToSentencePrime);
}
return _ret;
}
/**
* f0 -> id()
* f1 -> condition()
* f2 -> value()
*
* A bit of logic is needed here but still fairly straight forward. First, lookup the
* id in the List<DataPoint> argu. If the ID does not exist or is of value "NOT_SHOWN",
* return false. If the ID is of value "SKIPPED", only return true if the condition is "=="
* and the nodeValue is "SKIPPED". Else compare the nodeValue to the value found in the
* id list.
*
* @return A Boolean representing the validity of this expression.
*/
@Override
@SuppressWarnings("unchecked")
public R visit(expr n, A argu) {
R _ret=null;
String nodeId = (String) n.f0.accept(this, argu);
String nodeCondition = (String) n.f1.accept(this, argu);
String nodeValue = (String) n.f2.accept(this, argu);
// Lookup the nodeID in the List of IDs with responses.
DataPoint dataPointForComparison = new DataPoint(nodeId);
int nodeIdLocation = _currentIdList.indexOf(dataPointForComparison);
// If we can't find the nodeId, assume this expression is false
if (nodeIdLocation == -1) {
_ret = (R) new Boolean(false);
/*if (ConditionDepthFirst._logger.isDebugEnabled()) {
ConditionDepthFirst._logger.debug("Could not find node id " + nodeId);
}*/
}
// If we find the ID, evaluation the expression
else {
boolean result;
DataPoint dataPoint = _currentIdList.get(nodeIdLocation);
// Grab a DataPointComparator to compare the DataPoint to the value
DataPointComparator dataPointComparator =
DataPointComparatorFactory.createDataPointComparator(dataPoint.getPromptType());
result = dataPointComparator.compare(dataPoint, nodeValue, nodeCondition);
_ret = (R) new Boolean(result);
}
/*if (ConditionDepthFirst._logger.isDebugEnabled()) {
ConditionDepthFirst._logger.debug("Evaluated " + nodeId + " " + nodeCondition +
" " + nodeValue + " and got " + ((Boolean) _ret).toString());
}*/
Log.v(TAG, "Evaluated " + nodeId + " " + nodeCondition + " " + nodeValue + " and got " + ((Boolean) _ret).toString());
return _ret;
}
/**
* f0 -> <TEXT>
*/
@Override
public R visit(id n, A argu) {
R _ret=null;
_ret = n.f0.accept(this, argu);
return _ret;
}
/**
* f0 -> "=="
* | "!="
* | "<"
* | ">"
* | "<="
* | ">="
*/
@Override
public R visit(condition n, A argu) {
R _ret=null;
_ret = n.f0.accept(this, argu);
return _ret;
}
/**
* f0 -> <TEXT>
*/
@Override
public R visit(value n, A argu) {
R _ret=null;
_ret = n.f0.accept(this, argu);
return _ret;
}
/**
* f0 -> "and"
* | "or"
*/
@Override
public R visit(conjunction n, A argu) {
R _ret=null;
_ret = n.f0.accept(this, argu);
return _ret;
}
}