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}