xref: /aosp_15_r20/external/jsoup/src/main/java/org/jsoup/select/CombiningEvaluator.java (revision 6da8f8c4bc310ad659121b84dd089062417a2ce2)
1 package org.jsoup.select;
2 
3 import org.jsoup.internal.StringUtil;
4 import org.jsoup.nodes.Element;
5 import org.jspecify.annotations.Nullable;
6 
7 import java.util.ArrayList;
8 import java.util.Arrays;
9 import java.util.Collection;
10 import java.util.Collections;
11 import java.util.Comparator;
12 
13 /**
14  * Base combining (and, or) evaluator.
15  */
16 public abstract class CombiningEvaluator extends Evaluator {
17     final ArrayList<Evaluator> evaluators; // maintain original order so that #toString() is sensible
18     final ArrayList<Evaluator> sortedEvaluators; // cost ascending order
19     int num = 0;
20     int cost = 0;
21 
CombiningEvaluator()22     CombiningEvaluator() {
23         super();
24         evaluators = new ArrayList<>();
25         sortedEvaluators = new ArrayList<>();
26     }
27 
CombiningEvaluator(Collection<Evaluator> evaluators)28     CombiningEvaluator(Collection<Evaluator> evaluators) {
29         this();
30         this.evaluators.addAll(evaluators);
31         updateEvaluators();
32     }
33 
reset()34     @Override protected void reset() {
35         for (Evaluator evaluator : evaluators) {
36             evaluator.reset();
37         }
38         super.reset();
39     }
40 
cost()41     @Override protected int cost() {
42         return cost;
43     }
44 
rightMostEvaluator()45     @Nullable Evaluator rightMostEvaluator() {
46         return num > 0 ? evaluators.get(num - 1) : null;
47     }
48 
replaceRightMostEvaluator(Evaluator replacement)49     void replaceRightMostEvaluator(Evaluator replacement) {
50         evaluators.set(num - 1, replacement);
51         updateEvaluators();
52     }
53 
updateEvaluators()54     void updateEvaluators() {
55         // used so we don't need to bash on size() for every match test
56         num = evaluators.size();
57 
58         // sort the evaluators by lowest cost first, to optimize the evaluation order
59         cost = 0;
60         for (Evaluator evaluator : evaluators) {
61             cost += evaluator.cost();
62         }
63         sortedEvaluators.clear();
64         sortedEvaluators.addAll(evaluators);
65         Collections.sort(sortedEvaluators, costComparator);
66     }
67 
68     private static final Comparator<Evaluator> costComparator = (o1, o2) -> o1.cost() - o2.cost();
69     // ^ comparingInt, sortedEvaluators.sort not available in targeted version
70 
71     public static final class And extends CombiningEvaluator {
And(Collection<Evaluator> evaluators)72         And(Collection<Evaluator> evaluators) {
73             super(evaluators);
74         }
75 
And(Evaluator... evaluators)76         And(Evaluator... evaluators) {
77             this(Arrays.asList(evaluators));
78         }
79 
80         @Override
matches(Element root, Element element)81         public boolean matches(Element root, Element element) {
82             for (int i = 0; i < num; i++) {
83                 Evaluator s = sortedEvaluators.get(i);
84                 if (!s.matches(root, element))
85                     return false;
86             }
87             return true;
88         }
89 
90         @Override
toString()91         public String toString() {
92             return StringUtil.join(evaluators, "");
93         }
94     }
95 
96     public static final class Or extends CombiningEvaluator {
97         /**
98          * Create a new Or evaluator. The initial evaluators are ANDed together and used as the first clause of the OR.
99          * @param evaluators initial OR clause (these are wrapped into an AND evaluator).
100          */
Or(Collection<Evaluator> evaluators)101         Or(Collection<Evaluator> evaluators) {
102             super();
103             if (num > 1)
104                 this.evaluators.add(new And(evaluators));
105             else // 0 or 1
106                 this.evaluators.addAll(evaluators);
107             updateEvaluators();
108         }
109 
Or(Evaluator... evaluators)110         Or(Evaluator... evaluators) { this(Arrays.asList(evaluators)); }
111 
Or()112         Or() {
113             super();
114         }
115 
add(Evaluator e)116         public void add(Evaluator e) {
117             evaluators.add(e);
118             updateEvaluators();
119         }
120 
121         @Override
matches(Element root, Element node)122         public boolean matches(Element root, Element node) {
123             for (int i = 0; i < num; i++) {
124                 Evaluator s = sortedEvaluators.get(i);
125                 if (s.matches(root, node))
126                     return true;
127             }
128             return false;
129         }
130 
131         @Override
toString()132         public String toString() {
133             return StringUtil.join(evaluators, ", ");
134         }
135     }
136 }
137