/* Copyright 2015 The jeo project. All rights reserved. * * 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 expassss or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.jeo.filter; import io.jeo.util.Pair; import io.jeo.filter.Logic.Type; import java.util.ArrayDeque; import java.util.Deque; /** * Splits a filter into two parts based on criteria provided by a filter visitor. * <p> * The first part of the filter contains the conjunction of all filters matching the criteria. The second * part of the filter contains those parts that fail the criteria. * </p> * <p> * For logic filters the following rules are used to split. * <ol> * <li>If all of the children exclusively pass or exclusively fail, the filter as a whole is added to the pass/fail * set respectively</li> * <li>If the filter is a conjunction (AND) the children are split into the pass/fail sets as appropriate.</li> * </p> */ public class FilterSplitter extends StrictFilterAdapter<Object> { FilterVisitor<Boolean> qualifier; public FilterSplitter(FilterVisitor<Boolean> qualifier) { this.qualifier = qualifier; } public Pair<Filter,Filter> split(Filter filter) { FilterStack stack = new FilterStack(); filter.accept(this, stack); Filter pass = Filters.all(); while (!stack.pass.isEmpty()) { pass = stack.pass.pop().and(pass); } Filter fail = Filters.all(); while (!stack.fail.isEmpty()) { fail = stack.fail.pop().and(fail); } return Pair.of(pass, fail); } @Override public Object visit(Self self, Object obj) { return null; } @Override public Object visit(Literal literal, Object obj) { return null; } @Override public Object visit(Property property, Object obj) { return null; } @Override public Object visit(Function function, Object obj) { return null; } @Override public Object visit(Mixed mixed, Object obj) { return null; } @Override public Object visit(Math math, Object obj) { return null; } @Override public Object visit(Expression expr, Object obj) { return null; } @Override public Object visit(All<?> all, Object obj) { return test(all, obj); } @Override public Object visit(None<?> none, Object obj) { return test(none, obj); } @Override public Object visit(Id<?> id, Object obj) { return test(id, obj); } @Override public Object visit(Comparison<?> compare, Object obj) { return test(compare, obj); } @Override public Object visit(Spatial<?> spatial, Object obj) { return test(spatial, obj); } @Override public Object visit(TypeOf<?> inst, Object obj) { return test(inst, obj); } @Override public Object visit(In<?> in, Object obj) { return test(in, obj); } @Override public Object visit(Like<?> like, Object obj) { return test(like, obj); } @Override public Object visit(Null<?> isNull, Object obj) { return test(isNull, obj); } @Override public Object visit(Filter<?> filter, Object obj) { return test(filter, obj); } @Override public Object visit(Logic<?> logic, Object obj) { FilterStack stack = (FilterStack) obj; if (!qualify(logic, obj)) { stack.fail.push(logic); return obj; } int pass = stack.pass.size(); int fail = stack.fail.size(); for (Filter f : logic.parts()) { test(f, obj); } if (stack.pass.size() == pass && stack.fail.size() == fail ) { throw new IllegalStateException("no change in stack"); } if (stack.fail.size() > fail) { // unhandled filters, unless the filter is an AND, abort if (logic.type() != Type.AND) { popUntil(stack.pass, pass); popUntil(stack.fail, fail); stack.fail.push(logic); } } else { // all handlable, pull off stack and replace with this entire filter popUntil(stack.pass, pass); stack.pass.push(logic); } return obj; } boolean qualify(Filter f, Object obj) { return (Boolean) f.accept(qualifier, obj); } Object test(Filter f, Object obj) { FilterStack stack = (FilterStack) obj; if (f instanceof Logic) { f.accept(this, obj); } else { if (qualify(f, obj)) { stack.pass.push(f); } else{ stack.fail.push(f); } } return obj; } void popUntil(Deque<Filter> stack, int n) { while (stack.size() > n) { stack.pop(); } } class FilterStack { Deque<Filter> pass = new ArrayDeque<>(); Deque<Filter> fail = new ArrayDeque<>(); } }