/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.pig.newplan.logical.rules;
import java.util.List;
import java.util.Collection;
import java.util.Set;
import java.util.HashSet;
import org.apache.pig.impl.logicalLayer.FrontendException;
import org.apache.pig.impl.util.Utils;
import org.apache.pig.newplan.logical.expression.*;
import org.apache.pig.newplan.Operator;
import org.apache.pig.newplan.OperatorPlan;
import org.apache.pig.newplan.PlanWalker;
import org.apache.pig.newplan.PlanVisitor;
import org.apache.pig.impl.util.Pair;
/**
* A NOT conversion visitor that will traverse the expression tree in a depth-first
* manner with post-order handling. A status of negativity for a NOT expression is
* recorded in the depth-first traversal before subtree traversal and reversed after
* traversing the subtree. All "reversible" expressions is replaced by its negated
* counterpart for negative negativity. Currently equality op, and its
* non-equality counter part, all range comparisons, logical AND and OR are reversible.
*
* Notably missing is the "is null" for lack of a "is not null" base expression;
* UDF and regex are not reversed too for the same sake.
*
*/
class NOTConversionVisitor extends LogicalExpressionVisitor {
boolean not = false;
NOTConversionVisitor(OperatorPlan plan) throws FrontendException {
super(plan, new NotConversionWalker(plan));
}
public void flip() {
not = !not;
}
private void reset(Operator newOp, Operator oldOp)
throws FrontendException {
List<Operator> p = plan.getPredecessors(oldOp);
if (p != null) {
Operator[] preds = p.toArray(new Operator[0]);
for (Operator pred : preds) {
Pair<Integer, Integer> pos = plan.disconnect(pred, oldOp);
plan.connect(pred, pos.first, newOp, pos.second);
}
}
List<Operator> s = plan.getSuccessors(oldOp);
if (s != null) {
Operator[] sucs = s.toArray(new Operator[0]);
for (Operator suc : sucs) {
plan.disconnect(oldOp, suc);
}
}
plan.remove(oldOp);
}
private void insert(Operator before, Operator after)
throws FrontendException {
// assume after is already connected to before
List<Operator> p = plan.getPredecessors(after);
if (p != null) {
Operator[] preds = p.toArray(new Operator[0]);
for (Operator pred : preds) {
if (pred != before) {
Pair<Integer, Integer> pos = plan.disconnect(pred, after);
plan.connect(pred, pos.first, before, pos.second);
}
}
}
}
private void remove(Operator op) throws FrontendException {
List<Operator> p = plan.getPredecessors(op);
if (p != null) {
Operator[] preds = p.toArray(new Operator[0]);
for (Operator pred : preds) {
Pair<Integer, Integer> pos = plan.disconnect(pred, op);
List<Operator> s = plan.getSuccessors(op);
if (s != null) {
Operator[] sucs = s.toArray(new Operator[0]);
for (int i = 0; i < sucs.length; i++)
plan.connect(pred, pos.first+i, sucs[i], pos.second+i);
}
}
}
List<Operator> s = plan.getSuccessors(op);
if (s != null) {
Operator[] sucs = s.toArray(new Operator[0]);
for (Operator suc : sucs) {
plan.disconnect(op, suc);
}
}
plan.remove(op);
}
@Override
public void visit(NotExpression not) throws FrontendException {
remove(not);
}
@Override
public void visit(AndExpression andExpr) throws FrontendException {
if (not) {
// Application of DeMorgan's Law to AND
LogicalExpression newExp = new OrExpression(plan,
andExpr.getLhs(), andExpr.getRhs());
reset(newExp, andExpr);
}
}
@Override
public void visit(OrExpression orExpr) throws FrontendException {
if (not) {
// Application of DeMorgan's Law to OR
LogicalExpression newExp = new AndExpression(plan,
orExpr.getLhs(), orExpr.getRhs());
reset(newExp, orExpr);
}
}
@Override
public void visit(EqualExpression equal) throws FrontendException {
if (not) {
// Application of DeMorgan's Law to OR
LogicalExpression newExp = new NotEqualExpression(plan,
equal.getLhs(), equal.getRhs());
reset(newExp, equal);
}
}
@Override
public void visit(NotEqualExpression op) throws FrontendException {
if (not) {
// Application of DeMorgan's Law to OR
LogicalExpression newExp = new EqualExpression(plan,
op.getLhs(), op.getRhs());
reset(newExp, op);
}
}
@Override
public void visit(IsNullExpression op) throws FrontendException {
if (not) {
LogicalExpression newExp = new NotExpression(plan, op);
insert(newExp, op);
}
}
@Override
public void visit(RegexExpression op) throws FrontendException {
if (not) {
LogicalExpression newExp = new NotExpression(plan, op);
insert(newExp, op);
}
}
@Override
public void visit(UserFuncExpression op) throws FrontendException {
if (not) {
LogicalExpression newExp = new NotExpression(plan, op);
insert(newExp, op);
}
}
@Override
public void visit(GreaterThanExpression greaterThanExpression)
throws FrontendException {
if (not) {
reset(new LessThanEqualExpression(plan,
greaterThanExpression.getLhs(),
greaterThanExpression.getRhs()),
greaterThanExpression);
}
}
@Override
public void visit(
GreaterThanEqualExpression greaterThanEqualExpression)
throws FrontendException {
if (not) {
reset(new LessThanExpression(plan,
greaterThanEqualExpression.getLhs(),
greaterThanEqualExpression.getRhs()),
greaterThanEqualExpression);
}
}
@Override
public void visit(LessThanExpression lessThanExpression)
throws FrontendException {
if (not) {
reset(new GreaterThanEqualExpression(plan,
lessThanExpression.getLhs(),
lessThanExpression.getRhs()),
lessThanExpression);
}
}
@Override
public void visit(LessThanEqualExpression lessThanEqualExpression)
throws FrontendException {
if (not) {
reset(new GreaterThanExpression(plan,
lessThanEqualExpression.getLhs(),
lessThanEqualExpression.getRhs()),
lessThanEqualExpression);
}
}
private static class NotConversionWalker extends PlanWalker {
public NotConversionWalker(OperatorPlan plan) {
super(plan);
}
@Override
public PlanWalker spawnChildWalker(OperatorPlan plan) {
return new NotConversionWalker(plan);
}
/**
* Begin traversing the graph.
*
* @param visitor
* Visitor this walker is being used by.
* @throws FrontendException
* if an error is encountered while walking.
*/
@Override
public void walk(PlanVisitor visitor) throws FrontendException {
List<Operator> roots = plan.getSources();
Set<Operator> seen = new HashSet<Operator>();
depthFirst(null, roots, seen, visitor);
}
private void depthFirst(Operator node,
Collection<Operator> successors,
Set<Operator> seen, PlanVisitor visitor)
throws FrontendException {
if (successors == null) return;
Operator[] sucs = successors.toArray(new Operator[0]);
for (Operator suc : sucs) {
if (seen.add(suc)) {
if (suc instanceof NotExpression)
((NOTConversionVisitor) visitor).flip();
if(suc instanceof AndExpression
|| suc instanceof NotExpression
|| suc instanceof OrExpression
){
//visit successors of suc only if they are the boolean operators
// the NOT conversion should be propagated only for
// their successors
Collection<Operator> newSuccessors = Utils.mergeCollection(plan.getSuccessors(suc), plan.getSoftLinkSuccessors(suc));
depthFirst(suc, newSuccessors, seen, visitor);
}
suc.accept(visitor);
if (suc instanceof NotExpression)
((NOTConversionVisitor) visitor).flip();
}
}
}
}
}