package edu.washington.escience.myria.expression;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import edu.washington.escience.myria.Type;
import edu.washington.escience.myria.expression.evaluate.ExpressionOperatorParameter;
/**
* @author dominik
*/
public abstract class NAryExpression extends ExpressionOperator {
/***/
private static final long serialVersionUID = 1L;
/**
* The children of this operator expression.
*/
@JsonProperty private final List<ExpressionOperator> children;
/**
* This is not really unused, it's used automagically by Jackson deserialization.
*/
protected NAryExpression() {
children = ImmutableList.of();
}
/**
* @param children the children.
*/
protected NAryExpression(final List<ExpressionOperator> children) {
this.children = ImmutableList.copyOf(children);
}
@Override
public List<ExpressionOperator> getChildren() {
return children;
}
/**
* Returns the function call string: child + functionName. E.g, for {@code substring(int beginIndex, int endIndex)},
* <code>functionName</code> is <code>".substring</code>.
*
* @param functionName the string representation of the Java function name.
* @param parameters parameters that are needed to determine the java string
* @return the Java string for this operator.
*/
protected final String getDotFunctionCallString(
final String functionName, final ExpressionOperatorParameter parameters) {
StringBuilder callString =
new StringBuilder(children.get(0).getJavaString(parameters))
.append(functionName)
.append("(");
Iterator<ExpressionOperator> it = children.iterator();
it.next(); // skip first child because it is what we call
if (it.hasNext()) {
callString.append(it.next().getJavaString(parameters));
while (it.hasNext()) {
callString.append(",");
callString.append(it.next().getJavaString(parameters));
}
}
callString.append(")");
return callString.toString();
}
/**
* A function that could be used as the default type checker for an expression where all operands must be numeric or
* have the same type.
*
* @param parameters parameters that are needed to determine the output type
* @return the default numeric type, based on the types of the children and Java type precedence.
*/
protected Type checkAndReturnDefaultType(final ExpressionOperatorParameter parameters) {
List<Type> types = Lists.newArrayListWithCapacity(getChildren().size());
for (ExpressionOperator child : getChildren()) {
types.add(child.getOutputType(parameters));
}
// if all types are the same, we can just return it
if (ImmutableSet.copyOf(types).size() == 1) {
return types.get(0);
}
// otherwise all types have to be numeric and we look for the dominating type
ImmutableList<Type> validTypes =
ImmutableList.of(Type.DOUBLE_TYPE, Type.FLOAT_TYPE, Type.LONG_TYPE, Type.INT_TYPE);
List<Integer> indexes = Lists.newArrayListWithCapacity(getChildren().size());
int childIdx = 0;
for (Type type : types) {
final int idx = validTypes.indexOf(type);
indexes.add(idx);
Preconditions.checkArgument(
idx != -1,
"%s cannot handle child [%s] of Type %s",
getClass().getSimpleName(),
getChild(childIdx),
type);
childIdx++;
}
return validTypes.get(Collections.min(indexes));
}
/**
* @param index the child to return
* @return Child at the index'th position
*/
protected ExpressionOperator getChild(final int index) {
return getChildren().get(index);
}
/**
* A function that could be used as the default hash code for an n-ary expression.
*
* @return a hash of (getClass().getCanonicalName(), children).
*/
protected final int defaultHashCode() {
return Objects.hash(getClass().getCanonicalName(), children);
}
@Override
public int hashCode() {
return defaultHashCode();
}
@Override
public boolean equals(final Object other) {
if (other == null || !getClass().equals(other.getClass())) {
return false;
}
NAryExpression otherExpr = (NAryExpression) other;
for (int i = 0; i < children.size(); i++) {
if (!Objects.equals(getChild(i), otherExpr.getChild(i))) {
return false;
}
}
return true;
}
}