/*
* Data Hub Service (DHuS) - For Space data distribution.
* Copyright (C) 2016 GAEL Systems
*
* This file is part of DHuS software sources.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.gael.dhus.olingo.v1;
import fr.gael.dhus.olingo.v1.visitor.ExecutableExpressionTree;
import fr.gael.dhus.olingo.v1.visitor.functors.Transformers;
import fr.gael.dhus.util.functional.ComparatorTransformer;
import fr.gael.dhus.util.functional.tuple.Duo;
import java.util.Comparator;
import java.util.List;
import org.apache.commons.collections4.Factory;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ConstantFactory;
import org.apache.olingo.odata2.api.edm.EdmException;
import org.apache.olingo.odata2.api.edm.EdmLiteral;
import org.apache.olingo.odata2.api.edm.EdmLiteralKind;
import org.apache.olingo.odata2.api.edm.EdmTyped;
import org.apache.olingo.odata2.api.uri.expression.BinaryExpression;
import org.apache.olingo.odata2.api.uri.expression.BinaryOperator;
import org.apache.olingo.odata2.api.uri.expression.ExpressionVisitor;
import org.apache.olingo.odata2.api.uri.expression.FilterExpression;
import org.apache.olingo.odata2.api.uri.expression.LiteralExpression;
import org.apache.olingo.odata2.api.uri.expression.MemberExpression;
import org.apache.olingo.odata2.api.uri.expression.MethodExpression;
import org.apache.olingo.odata2.api.uri.expression.MethodOperator;
import org.apache.olingo.odata2.api.uri.expression.OrderByExpression;
import org.apache.olingo.odata2.api.uri.expression.OrderExpression;
import org.apache.olingo.odata2.api.uri.expression.PropertyExpression;
import org.apache.olingo.odata2.api.uri.expression.SortOrder;
import org.apache.olingo.odata2.api.uri.expression.UnaryExpression;
import org.apache.olingo.odata2.api.uri.expression.UnaryOperator;
/**
* An Expression visitor that produces a functional expression tree able to validate entries
* from any kind of collection.
* <p>Subclasses must implement
* {@link #visitProperty(PropertyExpression, String, EdmTyped)} and
* {@link #visitOrder(OrderExpression, Object, SortOrder).
* <p><strong>THIS CLASS IS NOT SUITABLE FOR FILTERING OF DATABASE DATA!</strong>, please use the
* {@link OlingoSQLVisitor} instead.
*/
public abstract class FunctionalVisitor implements ExpressionVisitor
{
// $filter
@Override
public Object visitFilterExpression(FilterExpression fe, String filter, Object exp)
{
// Exp is a Node<?, Boolean>, returns an ExecutableExpressionTree.
ExecutableExpressionTree.Node node = ExecutableExpressionTree.Node.class.cast(exp);
return new ExecutableExpressionTree(node);
}
@Override
public Object visitBinary(BinaryExpression be, BinaryOperator op, Object left, Object right)
{
// `left` and `right` are instances of Node<?> (Expression Tree)
// Returns a BiTransformer (functional Java) as a Node<?> (Expression Tree)
Transformer res;
switch(op)
{
case EQ: res = Transformers.<Object>eq(); break;
case NE: res = Transformers.<Object>ne(); break;
case AND: res = Transformers.and(); break;
case OR: res = Transformers.or(); break;
case GE: res = Transformers.ge(); break;
case GT: res = Transformers.gt(); break;
case LE: res = Transformers.le(); break;
case LT: res = Transformers.lt(); break;
case ADD: res = Transformers.add(); break;
case SUB: res = Transformers.sub(); break;
case MUL: res = Transformers.mul(); break;
case DIV: res = Transformers.div(); break;
case MODULO: res = Transformers.mod(); break;
default:
throw new UnsupportedOperationException("Unsupported operator: " + op.toUriLiteral());
}
ExecutableExpressionTree.Node nleft = ExecutableExpressionTree.Node.class.cast(left);
ExecutableExpressionTree.Node nright = ExecutableExpressionTree.Node.class.cast(right);
return ExecutableExpressionTree.Node.createNode(res, nleft, nright);
}
@Override
public Object visitLiteral(LiteralExpression literal, EdmLiteral edm_literal)
{
try
{
// A literal is a Provider<?> (Functional Java)
// Returns a Node<?> (Expression Tree)
Object o = edm_literal.getType().valueOfString(
edm_literal.getLiteral(),
EdmLiteralKind.DEFAULT,
null,
edm_literal.getType().getDefaultType());
return ExecutableExpressionTree.Node.createLeave(ConstantFactory.constantFactory(o));
}
catch (EdmException ex)
{
throw new RuntimeException(ex);
}
}
@Override
public Object visitMethod(MethodExpression me, MethodOperator method, List<Object> params)
{
if (params.size() == 1)
{
Transformer trans = null;
switch(method)
{
case TOLOWER: trans = Transformers.tolower(); break;
case TOUPPER: trans = Transformers.toupper(); break;
case TRIM: trans = Transformers.trim(); break;
case LENGTH: trans = Transformers.length(); break;
case YEAR: trans = Transformers.year(); break;
case MONTH: trans = Transformers.month(); break;
case DAY: trans = Transformers.day(); break;
case HOUR: trans = Transformers.hour(); break;
case MINUTE: trans = Transformers.minute(); break;
case SECOND: trans = Transformers.second(); break;
case ROUND: trans = Transformers.round(); break;
case FLOOR: trans = Transformers.floor(); break;
case CEILING: trans = Transformers.ceiling(); break;
default:
throw new UnsupportedOperationException("Unsupported method: " + method.toUriLiteral());
}
ExecutableExpressionTree.Node param =
ExecutableExpressionTree.Node.class.cast(params.get(0));
return ExecutableExpressionTree.Node.createNode(trans, param);
}
else if (params.size() == 2)
{
Transformer bi_trans = null;
switch(method)
{
case ENDSWITH: bi_trans = Transformers.endswith(); break;
case INDEXOF: bi_trans = Transformers.indexof(); break;
case STARTSWITH: bi_trans = Transformers.startswith(); break;
case SUBSTRING: bi_trans = Transformers.substring(); break;
case SUBSTRINGOF: bi_trans = Transformers.substringof(); break;
case CONCAT: bi_trans = Transformers.concat(); break;
default:
throw new UnsupportedOperationException("Unsupported method: " + method.toUriLiteral());
}
ExecutableExpressionTree.Node param1 =
ExecutableExpressionTree.Node.class.cast(params.get(0));
ExecutableExpressionTree.Node param2 =
ExecutableExpressionTree.Node.class.cast(params.get(1));
return ExecutableExpressionTree.Node.createNode(bi_trans, param1, param2);
}
else if (params.size() == 3 && method == MethodOperator.SUBSTRING)
{
Transformer bi_trans = Transformers.substring2();
ExecutableExpressionTree.Node str =
ExecutableExpressionTree.Node.class.cast(params.get(0));
final ExecutableExpressionTree.Node intsupp1 =
ExecutableExpressionTree.Node.class.cast(params.get(1));
final ExecutableExpressionTree.Node intsupp2 =
ExecutableExpressionTree.Node.class.cast(params.get(2));
// Merges 2 nodes in One, return an array instead.
ExecutableExpressionTree.Node n = new ExecutableExpressionTree.Node()
{
@Override
public Factory exec(Object element)
{
Integer[] res = new Integer[2];
res[0] = (Integer)intsupp1.exec(element);
res[1] = (Integer)intsupp2.exec(element);
return ConstantFactory.constantFactory(res);
}
};
return ExecutableExpressionTree.Node.createNode(bi_trans, str, n);
}
return null;
}
@Override
public Object visitMember(MemberExpression me, Object path, Object property)
{
// Not used
return property;
}
@Override
public Object visitUnary(UnaryExpression ue, UnaryOperator operator, Object operand)
{
// operand is a Node<?> (Expression Tree)
// Returns an instance of Transformer<?> (functional java) as a Node<?> (Expression Tree)
Transformer trans;
switch (operator)
{
case NOT: trans = Transformers.not(); break;
case MINUS: trans = Transformers.minus(); break;
default:
throw new UnsupportedOperationException("Unsupported operator: " + operator.toUriLiteral());
}
ExecutableExpressionTree.Node param = ExecutableExpressionTree.Node.class.cast(operand);
return ExecutableExpressionTree.Node.createNode(trans, param);
}
/**
* Called for each property in the $filter option.
* An implementation MUST return an {@link ExecutableExpressionTree.Node}.
* @param pe PropertyExpression.
* @param uri_literal name of the property.
* @param prop Property.
* @return an {@link ExecutableExpressionTree.Node}.
*/
@Override
public abstract Object visitProperty(PropertyExpression pe, String uri_literal, EdmTyped prop);
// $orderby
@Override
public Object visitOrderByExpression(OrderByExpression orderby, String exp, List<Object> orders)
{
final ExecutableExpressionTree.Node node = (ExecutableExpressionTree.Node) (orders.get(0));
return new Comparator()
{
@Override
public int compare(Object o1, Object o2)
{
return (Integer) node.exec(new Duo<>(o1, o2));
}
};
}
/**
* Called for each fields in the $orderby option.
* returns a {@linkplain ExecutableExpressionTree.Node#createDuoNode(Transformer,
* ExecutableExpressionTree.Node, ExecutableExpressionTree.Node) DuoNode}
* {@link ExecutableExpressionTree.Node}
* @param oe OrderExpression
* @param filter an {@link ExecutableExpressionTree.Node}.
* @param sort_order `asc` or `desc`.
* @return an {@link ExecutableExpressionTree.Node}
*/
@Override
public Object visitOrder(OrderExpression oe, Object filter, SortOrder sort_order)
{
ExecutableExpressionTree.Node param = ExecutableExpressionTree.Node.class.cast(filter);
Transformer cmp = new ComparatorTransformer(sort_order == SortOrder.desc);
return ExecutableExpressionTree.Node.createDuoNode(cmp, param, param);
}
}