1 /* 2 * Copyright (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.css; 27 28 import com.sun.javafx.css.Combinator; 29 import com.sun.javafx.css.PseudoClassState; 30 31 import java.io.DataInputStream; 32 import java.io.DataOutputStream; 33 import java.io.IOException; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 import java.util.Set; 38 39 40 /** 41 * A compound selector which behaves according to the CSS standard. The selector is 42 * composed of one or more <code>Selectors</code>, along with an array of 43 * <code>CompoundSelectorRelationships</code> indicating the required relationship at each 44 * stage. There must be exactly one less <code>Combinator</code> than 45 * there are selectors. 46 * <p> 47 * For example, the parameters <code>[selector1, selector2, selector3]</code> 48 * and <code>[Combinator.CHILD, Combinator.DESCENDANT]</code> will match 49 * a component when all of the following conditions hold: 50 * <ol> 51 * <li>The component itself is matched by selector3 52 * <li>The component has an ancestor which is matched by selector2 53 * <li>The ancestor matched in step 2 is a direct CHILD of a component 54 * matched by selector1 55 * </ol> 56 * In other words, the compound selector specified above is (in CSS syntax) 57 * <code>selector1 > selector2 selector3</code>. The greater-than (>) 58 * between selector1 and selector2 specifies a direct CHILD, whereas the 59 * whitespace between selector2 and selector3 corresponds to 60 * <code>Combinator.DESCENDANT</code>. 61 * 62 * @since 9 63 */ 64 final public class CompoundSelector extends Selector { 65 66 private final List<SimpleSelector> selectors; 67 /** 68 * The selectors that make up this compound selector 69 * @return Immutable List<SimpleSelector> 70 */ 71 public List<SimpleSelector> getSelectors() { 72 return selectors; 73 } 74 75 private final List<Combinator> relationships; 76 // /** 77 // * The relationships between the selectors 78 // * @return Immutable List<Combinator> 79 // */ 80 // public List<Combinator> getRelationships() { 81 // return relationships; 82 // } 83 84 /** 85 * Creates a <code>CompoundSelector</code> from a list of selectors and a 86 * list of <code>Combinator</code> relationships. There must be exactly one 87 * less <code>Combinator</code> than there are selectors. 88 */ 89 CompoundSelector(List<SimpleSelector> selectors, List<Combinator> relationships) { 90 this.selectors = 91 (selectors != null) 92 ? Collections.unmodifiableList(selectors) 93 : Collections.EMPTY_LIST; 94 this.relationships = 95 (relationships != null) 96 ? Collections.unmodifiableList(relationships) 97 : Collections.EMPTY_LIST; 98 } 99 100 private CompoundSelector() { 101 this(null, null); 102 } 103 104 105 @Override public Match createMatch() { 106 final PseudoClassState allPseudoClasses = new PseudoClassState(); 107 int idCount = 0; 108 int styleClassCount = 0; 109 110 for(int n=0, nMax=selectors.size(); n<nMax; n++) { 111 Selector sel = selectors.get(n); 112 Match match = sel.createMatch(); 113 allPseudoClasses.addAll(match.pseudoClasses); 114 idCount += match.idCount; 115 styleClassCount += match.styleClassCount; 116 } 117 118 return new Match(this, allPseudoClasses, idCount, styleClassCount); 119 } 120 121 @Override public boolean applies(final Styleable styleable) { 122 return applies(styleable, selectors.size()-1, null, 0); 123 } 124 125 @Override public boolean applies(final Styleable styleable, Set<PseudoClass>[] triggerStates, int depth) { 126 127 assert (triggerStates == null || depth < triggerStates.length); 128 if (triggerStates != null && triggerStates.length <= depth) { 129 return false; 130 } 131 132 // 133 // We only care about pseudo-class if the selector applies. But in 134 // the case of a compound selector, we don't know whether it applies 135 // until all the selectors have been checked (in the worse case). So 136 // the setting of pseudo-class has to be deferred until we know 137 // that this compound selector applies. So we'll send a new 138 // PseudoClassSet[] and if the compound selector applies, 139 // just copy the state back. 140 // 141 final Set<PseudoClass>[] tempStates = triggerStates != null 142 ? new PseudoClassState[triggerStates.length] : null; 143 144 final boolean applies = applies(styleable, selectors.size()-1, tempStates, depth); 145 146 if (applies && tempStates != null) { 147 148 for(int n=0; n<triggerStates.length; n++) { 149 150 final Set<PseudoClass> pseudoClassOut = triggerStates[n]; 151 final Set<PseudoClass> pseudoClassIn = tempStates[n]; 152 153 if (pseudoClassOut != null) { 154 pseudoClassOut.addAll(pseudoClassIn); 155 } else { 156 triggerStates[n] = pseudoClassIn; 157 } 158 159 } 160 } 161 return applies; 162 } 163 164 private boolean applies(final Styleable styleable, final int index, Set<PseudoClass>[] triggerStates, int depth) { 165 // If the index is < 0 then we know we don't apply 166 if (index < 0) return false; 167 168 // Simply check the selector associated with this index and see if it 169 // applies to the Node 170 if (! selectors.get(index).applies(styleable, triggerStates, depth)) return false; 171 172 // If there are no more selectors to check (ie: index == 0) then we 173 // know we know we apply 174 if (index == 0) return true; 175 176 // We have not yet checked all the selectors in this CompoundSelector, 177 // so now we need to find the next parent and try again. If the 178 // relationship between this selector and its ancestor selector is 179 // "CHILD" then it is required that the parent scenegraph node match 180 // the ancestor selector. Otherwise, we just walk up the scenegraph 181 // until we find an ancestor node that matches the selector. If we 182 // manage to walk all the way to the top without having satisfied all 183 // of the selectors, then we know it doesn't apply. 184 final Combinator relationship = relationships.get(index-1); 185 if (relationship == Combinator.CHILD) { 186 final Styleable parent = styleable.getStyleableParent(); 187 if (parent == null) return false; 188 // If this call succeeds, then all preceding selectors will have 189 // matched due to the recursive nature of the call 190 return applies(parent, index - 1, triggerStates, ++depth); 191 } else { 192 Styleable parent = styleable.getStyleableParent(); 193 while (parent != null) { 194 boolean answer = applies(parent, index - 1, triggerStates, ++depth); 195 // If a call to stateMatches succeeded, then we know that 196 // all preceding selectors will have also matched. 197 if (answer) return true; 198 // Otherwise we need to get the next parent and try again 199 parent = parent.getStyleableParent(); 200 } 201 } 202 return false; 203 } 204 205 @Override public boolean stateMatches(final Styleable styleable, Set<PseudoClass> states) { 206 return stateMatches(styleable, states, selectors.size()-1); 207 } 208 209 private boolean stateMatches(Styleable styleable, Set<PseudoClass> states, int index) { 210 // If the index is < 0 then we know we don't match 211 if (index < 0) return false; 212 213 // Simply check the selector associated with this index and see if it 214 // matches the Node and states provided. 215 if (! selectors.get(index).stateMatches(styleable, states)) return false; 216 217 // If there are no more selectors to match (ie: index == 0) then we 218 // know we have successfully matched 219 if (index == 0) return true; 220 221 // We have not yet checked all the selectors in this CompoundSelector, 222 // so now we need to find the next parent and try again. If the 223 // relationship between this selector and its ancestor selector is 224 // "CHILD" then it is required that the parent scenegraph node match 225 // the ancestor selector. Otherwise, we just walk up the scenegraph 226 // until we find an ancestor node that matches the selector. If we 227 // manage to walk all the way to the top without having satisfied all 228 // of the selectors, then we know it doesn't match. 229 final Combinator relationship = relationships.get(index - 1); 230 if (relationship == Combinator.CHILD) { 231 final Styleable parent = styleable.getStyleableParent(); 232 if (parent == null) return false; 233 if (selectors.get(index-1).applies(parent)) { 234 // If this call succeeds, then all preceding selectors will have 235 // matched due to the recursive nature of the call 236 Set<PseudoClass> parentStates = parent.getPseudoClassStates(); 237 return stateMatches(parent, parentStates, index - 1); 238 } 239 } else { 240 Styleable parent = styleable.getStyleableParent(); 241 while (parent != null) { 242 if (selectors.get(index-1).applies(parent)) { 243 Set<PseudoClass> parentStates = parent.getPseudoClassStates(); 244 return stateMatches(parent, parentStates, index - 1); 245 } 246 // Otherwise we need to get the next parent and try again 247 parent = parent.getStyleableParent(); 248 } 249 } 250 251 return false; 252 } 253 254 private int hash = -1; 255 256 /* Hash code is used in Style's hash code and Style's hash 257 code is used by StyleHelper */ 258 @Override public int hashCode() { 259 if (hash == -1) { 260 for (int i = 0, max=selectors.size(); i<max; i++) 261 hash = 31 * (hash + selectors.get(i).hashCode()); 262 for (int i = 0, max=relationships.size(); i<max; i++) 263 hash = 31 * (hash + relationships.get(i).hashCode()); 264 } 265 return hash; 266 } 267 268 @Override public boolean equals(Object obj) { 269 if (obj == null) { 270 return false; 271 } 272 if (getClass() != obj.getClass()) { 273 return false; 274 } 275 final CompoundSelector other = (CompoundSelector) obj; 276 if (other.selectors.size() != selectors.size()) return false; 277 // Avoid ArrayList equals since it uses enhanced for loop 278 for (int i = 0, max=selectors.size(); i<max; i++) { 279 if (!other.selectors.get(i).equals(selectors.get(i))) return false; 280 } 281 // Avoid ArrayList equals since it uses enhanced for loop 282 if (other.relationships.size() != relationships.size()) return false; 283 for (int i = 0, max=relationships.size(); i<max; i++) { 284 if (!other.relationships.get(i).equals(relationships.get(i))) return false; 285 } 286 return true; 287 } 288 289 @Override public String toString() { 290 StringBuilder sbuf = new StringBuilder(); 291 sbuf.append(selectors.get(0)); 292 for(int n=1; n<selectors.size(); n++) { 293 sbuf.append(relationships.get(n-1)); 294 sbuf.append(selectors.get(n)); 295 } 296 return sbuf.toString(); 297 } 298 299 @Override protected final void writeBinary(final DataOutputStream os, final StyleConverter.StringStore stringStore) 300 throws IOException 301 { 302 super.writeBinary(os, stringStore); 303 os.writeShort(selectors.size()); 304 for (int n=0; n< selectors.size(); n++) selectors.get(n).writeBinary(os,stringStore); 305 os.writeShort(relationships.size()); 306 for (int n=0; n< relationships.size(); n++) os.writeByte(relationships.get(n).ordinal()); 307 } 308 309 static CompoundSelector readBinary(int bssVersion, final DataInputStream is, final String[] strings) 310 throws IOException 311 { 312 313 final int nSelectors = is.readShort(); 314 final List<SimpleSelector> selectors = new ArrayList<SimpleSelector>(); 315 for (int n=0; n<nSelectors; n++) { 316 selectors.add((SimpleSelector)Selector.readBinary(bssVersion, is,strings)); 317 } 318 319 final int nRelationships = is.readShort(); 320 321 final List<Combinator> relationships = new ArrayList<Combinator>(); 322 for (int n=0; n<nRelationships; n++) { 323 final int ordinal = is.readByte(); 324 if (ordinal == Combinator.CHILD.ordinal()) 325 relationships.add(Combinator.CHILD); 326 else if (ordinal == Combinator.DESCENDANT.ordinal()) 327 relationships.add(Combinator.DESCENDANT); 328 else { 329 assert false : "error deserializing CompoundSelector: Combinator = " + ordinal; 330 relationships.add(Combinator.DESCENDANT); 331 } 332 } 333 return new CompoundSelector(selectors, relationships); 334 } 335 }