001package org.jsoup.select;
002
003import org.jsoup.internal.StringUtil;
004import org.jsoup.nodes.Element;
005import org.jsoup.nodes.LeafNode;
006import org.jsoup.nodes.Node;
007
008import java.util.ArrayList;
009import java.util.Arrays;
010import java.util.Collection;
011import java.util.Comparator;
012import java.util.List;
013
014/**
015 * Base combining (and, or) evaluator.
016 */
017public abstract class CombiningEvaluator extends Evaluator {
018    final ArrayList<Evaluator> evaluators; // maintain original order so that #toString() is sensible
019    final List<Evaluator> sortedEvaluators; // cost ascending order
020    int num = 0;
021    int cost = 0;
022    boolean wantsNodes;
023
024    CombiningEvaluator(Collection<Evaluator> evaluators) {
025        this(new ArrayList<>(evaluators));
026    }
027
028    CombiningEvaluator(ArrayList<Evaluator> evaluators) {
029        this.evaluators = evaluators;
030        sortedEvaluators = new ArrayList<>(evaluators.size());
031        updateEvaluators();
032    }
033
034    public void add(Evaluator e) {
035        evaluators.add(e);
036        updateEvaluators();
037    }
038
039    @Override protected void reset() {
040        for (Evaluator evaluator : evaluators) {
041            evaluator.reset();
042        }
043        super.reset();
044    }
045
046    @Override protected int cost() {
047        return cost;
048    }
049
050    @Override
051    boolean wantsNodes() {
052        return wantsNodes;
053    }
054
055    void updateEvaluators() {
056        // used so we don't need to bash on size() for every match test
057        num = evaluators.size();
058
059        // sort the evaluators by lowest cost first, to optimize the evaluation order
060        cost = 0;
061        for (Evaluator evaluator : evaluators) {
062            cost += evaluator.cost();
063        }
064        sortedEvaluators.clear();
065        sortedEvaluators.addAll(evaluators);
066        sortedEvaluators.sort(Comparator.comparingInt(Evaluator::cost));
067
068        // any want nodes?
069        for (Evaluator evaluator : evaluators) {
070            if (evaluator.wantsNodes()) {
071                wantsNodes = true;
072                break;
073            }
074        }
075    }
076
077    public static final class And extends CombiningEvaluator {
078        /** Creates an evaluator that requires every supplied evaluator to match. */
079        public And(Collection<Evaluator> evaluators) {
080            super(evaluators);
081        }
082
083        And(ArrayList<Evaluator> evaluators) {
084            super(evaluators);
085        }
086
087        And(Evaluator... evaluators) {
088            this(Arrays.asList(evaluators));
089        }
090
091        @Override
092        public boolean matches(Element root, Element el) {
093            for (int i = 0; i < num; i++) {
094                Evaluator eval = sortedEvaluators.get(i);
095                if (!eval.matches(root, el))
096                    return false;
097            }
098            return true;
099        }
100
101        @Override
102        public boolean matches(Element root, LeafNode leaf) {
103            for (int i = 0; i < num; i++) {
104                Evaluator eval = sortedEvaluators.get(i);
105                if (!eval.matches(root, leaf))
106                    return false;
107            }
108            return true;
109        }
110
111        @Override boolean matchesRelative(Element root, Node node, Element anchor) {
112            for (int i = 0; i < num; i++) {
113                Evaluator eval = sortedEvaluators.get(i);
114                if (!eval.matchesRelative(root, node, anchor))
115                    return false;
116            }
117            return true;
118        }
119
120        @Override
121        public String toString() {
122            return StringUtil.join(evaluators, "");
123        }
124    }
125
126    public static final class Or extends CombiningEvaluator {
127        /**
128         * Creates an evaluator that requires at least one supplied evaluator to match.
129         * @param evaluators alternative evaluators
130         */
131        public Or(Collection<Evaluator> evaluators) {
132            super(evaluators);
133        }
134
135        Or(ArrayList<Evaluator> evaluators) {
136            super(evaluators);
137        }
138
139        Or(Evaluator... evaluators) { this(Arrays.asList(evaluators)); }
140
141        @Override
142        public boolean matches(Element root, Element element) {
143            for (int i = 0; i < num; i++) {
144                Evaluator eval = sortedEvaluators.get(i);
145                if (eval.matches(root, element))
146                    return true;
147            }
148            return false;
149        }
150
151        @Override
152        public boolean matches(Element root, LeafNode leaf) {
153            for (int i = 0; i < num; i++) {
154                Evaluator eval = sortedEvaluators.get(i);
155                if (eval.matches(root, leaf))
156                    return true;
157            }
158            return false;
159        }
160
161        @Override boolean matchesRelative(Element root, Node node, Element anchor) {
162            for (int i = 0; i < num; i++) {
163                Evaluator eval = sortedEvaluators.get(i);
164                if (eval.matchesRelative(root, node, anchor))
165                    return true;
166            }
167            return false;
168        }
169
170        @Override
171        public String toString() {
172            return StringUtil.join(evaluators, ", ");
173        }
174    }
175}