001package org.jsoup.select;
002
003import org.jsoup.helper.StringUtil;
004import org.jsoup.nodes.Element;
005
006import java.util.ArrayList;
007import java.util.Arrays;
008import java.util.Collection;
009
010/**
011 * Base combining (and, or) evaluator.
012 */
013abstract class CombiningEvaluator extends Evaluator {
014    final ArrayList<Evaluator> evaluators;
015    int num = 0;
016
017    CombiningEvaluator() {
018        super();
019        evaluators = new ArrayList<>();
020    }
021
022    CombiningEvaluator(Collection<Evaluator> evaluators) {
023        this();
024        this.evaluators.addAll(evaluators);
025        updateNumEvaluators();
026    }
027
028    Evaluator rightMostEvaluator() {
029        return num > 0 ? evaluators.get(num - 1) : null;
030    }
031    
032    void replaceRightMostEvaluator(Evaluator replacement) {
033        evaluators.set(num - 1, replacement);
034    }
035
036    void updateNumEvaluators() {
037        // used so we don't need to bash on size() for every match test
038        num = evaluators.size();
039    }
040
041    static final class And extends CombiningEvaluator {
042        And(Collection<Evaluator> evaluators) {
043            super(evaluators);
044        }
045
046        And(Evaluator... evaluators) {
047            this(Arrays.asList(evaluators));
048        }
049
050        @Override
051        public boolean matches(Element root, Element node) {
052            for (int i = 0; i < num; i++) {
053                Evaluator s = evaluators.get(i);
054                if (!s.matches(root, node))
055                    return false;
056            }
057            return true;
058        }
059
060        @Override
061        public String toString() {
062            return StringUtil.join(evaluators, " ");
063        }
064    }
065
066    static final class Or extends CombiningEvaluator {
067        /**
068         * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR.
069         * @param evaluators initial OR clause (these are wrapped into an AND evaluator).
070         */
071        Or(Collection<Evaluator> evaluators) {
072            super();
073            if (num > 1)
074                this.evaluators.add(new And(evaluators));
075            else // 0 or 1
076                this.evaluators.addAll(evaluators);
077            updateNumEvaluators();
078        }
079
080        Or(Evaluator... evaluators) { this(Arrays.asList(evaluators)); }
081
082        Or() {
083            super();
084        }
085
086        public void add(Evaluator e) {
087            evaluators.add(e);
088            updateNumEvaluators();
089        }
090
091        @Override
092        public boolean matches(Element root, Element node) {
093            for (int i = 0; i < num; i++) {
094                Evaluator s = evaluators.get(i);
095                if (s.matches(root, node))
096                    return true;
097            }
098            return false;
099        }
100
101        @Override
102        public String toString() {
103            return StringUtil.join(evaluators, ", ");
104        }
105    }
106}