CombiningEvaluator.java
package org.jsoup.select;
import org.jsoup.internal.StringUtil;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.LeafNode;
import org.jsoup.nodes.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
/**
* Base combining (and, or) evaluator.
*/
public abstract class CombiningEvaluator extends Evaluator {
final ArrayList<Evaluator> evaluators; // maintain original order so that #toString() is sensible
final List<Evaluator> sortedEvaluators; // cost ascending order
int num = 0;
int cost = 0;
boolean wantsNodes;
CombiningEvaluator(Collection<Evaluator> evaluators) {
this(new ArrayList<>(evaluators));
}
CombiningEvaluator(ArrayList<Evaluator> evaluators) {
this.evaluators = evaluators;
sortedEvaluators = new ArrayList<>(evaluators.size());
updateEvaluators();
}
public void add(Evaluator e) {
evaluators.add(e);
updateEvaluators();
}
@Override protected void reset() {
for (Evaluator evaluator : evaluators) {
evaluator.reset();
}
super.reset();
}
@Override protected int cost() {
return cost;
}
@Override
boolean wantsNodes() {
return wantsNodes;
}
void updateEvaluators() {
// used so we don't need to bash on size() for every match test
num = evaluators.size();
// sort the evaluators by lowest cost first, to optimize the evaluation order
cost = 0;
for (Evaluator evaluator : evaluators) {
cost += evaluator.cost();
}
sortedEvaluators.clear();
sortedEvaluators.addAll(evaluators);
sortedEvaluators.sort(Comparator.comparingInt(Evaluator::cost));
// any want nodes?
for (Evaluator evaluator : evaluators) {
if (evaluator.wantsNodes()) {
wantsNodes = true;
break;
}
}
}
public static final class And extends CombiningEvaluator {
/** Creates an evaluator that requires every supplied evaluator to match. */
public And(Collection<Evaluator> evaluators) {
super(evaluators);
}
And(ArrayList<Evaluator> evaluators) {
super(evaluators);
}
And(Evaluator... evaluators) {
this(Arrays.asList(evaluators));
}
@Override
public boolean matches(Element root, Element el) {
for (int i = 0; i < num; i++) {
Evaluator eval = sortedEvaluators.get(i);
if (!eval.matches(root, el))
return false;
}
return true;
}
@Override
public boolean matches(Element root, LeafNode leaf) {
for (int i = 0; i < num; i++) {
Evaluator eval = sortedEvaluators.get(i);
if (!eval.matches(root, leaf))
return false;
}
return true;
}
@Override boolean matchesRelative(Element root, Node node, Element anchor) {
for (int i = 0; i < num; i++) {
Evaluator eval = sortedEvaluators.get(i);
if (!eval.matchesRelative(root, node, anchor))
return false;
}
return true;
}
@Override
public String toString() {
return StringUtil.join(evaluators, "");
}
}
public static final class Or extends CombiningEvaluator {
/**
* Creates an evaluator that requires at least one supplied evaluator to match.
* @param evaluators alternative evaluators
*/
public Or(Collection<Evaluator> evaluators) {
super(evaluators);
}
Or(ArrayList<Evaluator> evaluators) {
super(evaluators);
}
Or(Evaluator... evaluators) { this(Arrays.asList(evaluators)); }
@Override
public boolean matches(Element root, Element element) {
for (int i = 0; i < num; i++) {
Evaluator eval = sortedEvaluators.get(i);
if (eval.matches(root, element))
return true;
}
return false;
}
@Override
public boolean matches(Element root, LeafNode leaf) {
for (int i = 0; i < num; i++) {
Evaluator eval = sortedEvaluators.get(i);
if (eval.matches(root, leaf))
return true;
}
return false;
}
@Override boolean matchesRelative(Element root, Node node, Element anchor) {
for (int i = 0; i < num; i++) {
Evaluator eval = sortedEvaluators.get(i);
if (eval.matchesRelative(root, node, anchor))
return true;
}
return false;
}
@Override
public String toString() {
return StringUtil.join(evaluators, ", ");
}
}
}