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 javafx.geometry.NodeOrientation; 29 import javafx.scene.Node; 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.Iterator; 37 import java.util.List; 38 import java.util.Set; 39 40 import com.sun.javafx.css.PseudoClassState; 41 import com.sun.javafx.css.StyleClassSet; 42 43 import static javafx.geometry.NodeOrientation.INHERIT; 44 import static javafx.geometry.NodeOrientation.LEFT_TO_RIGHT; 45 import static javafx.geometry.NodeOrientation.RIGHT_TO_LEFT; 46 47 /** 48 * A simple selector which behaves according to the CSS standard. 49 * 50 * @since 9 51 */ 52 final public class SimpleSelector extends Selector { 53 54 /** 55 * If specified in the CSS file, the name of the java class to which 56 * this selector is applied. For example, if the CSS file had: 57 * <code><pre> 58 * Rectangle { } 59 * </pre></code> 60 * then name would be "Rectangle". 61 */ 62 final private String name; 63 /** 64 * @return The name of the java class to which this selector is applied, or *. 65 */ 66 public String getName() { 67 return name; 68 } 69 70 /** 71 * @return Immutable List<String> of style-classes of the selector 72 */ 73 public List<String> getStyleClasses() { 74 75 final List<String> names = new ArrayList<String>(); 76 77 Iterator<StyleClass> iter = styleClassSet.iterator(); 78 while (iter.hasNext()) { 79 names.add(iter.next().getStyleClassName()); 80 } 81 82 return Collections.unmodifiableList(names); 83 } 84 85 public Set<StyleClass> getStyleClassSet() { 86 return styleClassSet; 87 } 88 89 /** styleClasses converted to a set of bit masks */ 90 final private StyleClassSet styleClassSet; 91 92 final private String id; 93 /* 94 * @return The value of the selector id, which may be an empty string. 95 */ 96 public String getId() { 97 return id; 98 } 99 100 // a mask of bits corresponding to the pseudoclasses 101 final private PseudoClassState pseudoClassState; 102 103 Set<PseudoClass> getPseudoClassStates() { 104 return pseudoClassState; 105 } 106 107 /** 108 * @return Immutable List<String> of pseudo-classes of the selector 109 */ 110 List<String> getPseudoclasses() { 111 112 final List<String> names = new ArrayList<String>(); 113 114 Iterator<PseudoClass> iter = pseudoClassState.iterator(); 115 while (iter.hasNext()) { 116 names.add(iter.next().getPseudoClassName()); 117 } 118 119 if (nodeOrientation == RIGHT_TO_LEFT) { 120 names.add("dir(rtl)"); 121 } else if (nodeOrientation == LEFT_TO_RIGHT) { 122 names.add("dir(ltr)"); 123 } 124 125 return Collections.unmodifiableList(names); 126 } 127 128 // true if name is not a wildcard 129 final private boolean matchOnName; 130 131 // true if id given 132 final private boolean matchOnId; 133 134 // true if style class given 135 final private boolean matchOnStyleClass; 136 137 // dir(ltr) or dir(rtl), otherwise inherit 138 final private NodeOrientation nodeOrientation; 139 140 // Used in Match. If nodeOrientation is ltr or rtl, 141 // then count it as a pseudoclass 142 public NodeOrientation getNodeOrientation() { 143 return nodeOrientation; 144 } 145 146 // TODO: The parser passes styleClasses as a List. Should be array? 147 SimpleSelector(final String name, final List<String> styleClasses, 148 final List<String> pseudoClasses, final String id) 149 { 150 this.name = name == null ? "*" : name; 151 // if name is not null and not empty or wildcard, 152 // then match needs to check name 153 this.matchOnName = (name != null && !("".equals(name)) && !("*".equals(name))); 154 155 this.styleClassSet = new StyleClassSet(); 156 157 int nMax = styleClasses != null ? styleClasses.size() : 0; 158 for(int n=0; n<nMax; n++) { 159 160 final String styleClassName = styleClasses.get(n); 161 if (styleClassName == null || styleClassName.isEmpty()) continue; 162 163 final StyleClass styleClass = StyleClassSet.getStyleClass(styleClassName); 164 this.styleClassSet.add(styleClass); 165 } 166 167 this.matchOnStyleClass = (this.styleClassSet.size() > 0); 168 169 this.pseudoClassState = new PseudoClassState(); 170 171 nMax = pseudoClasses != null ? pseudoClasses.size() : 0; 172 173 NodeOrientation dir = NodeOrientation.INHERIT; 174 for(int n=0; n<nMax; n++) { 175 176 final String pclass = pseudoClasses.get(n); 177 if (pclass == null || pclass.isEmpty()) continue; 178 179 // TODO: This is not how we should handle functional pseudo-classes in the long-run! 180 if ("dir(".regionMatches(true, 0, pclass, 0, 4)) { 181 final boolean rtl = "dir(rtl)".equalsIgnoreCase(pclass); 182 dir = rtl ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; 183 continue; 184 } 185 186 final PseudoClass pseudoClass = PseudoClassState.getPseudoClass(pclass); 187 this.pseudoClassState.add(pseudoClass); 188 } 189 190 this.nodeOrientation = dir; 191 this.id = id == null ? "" : id; 192 // if id is not null and not empty, then match needs to check id 193 this.matchOnId = (id != null && !("".equals(id))); 194 195 } 196 197 @Override public Match createMatch() { 198 final int idCount = (matchOnId) ? 1 : 0; 199 int styleClassCount = styleClassSet.size(); 200 return new Match(this, pseudoClassState, idCount, styleClassCount); 201 } 202 203 @Override public boolean applies(Styleable styleable) { 204 205 // handle functional pseudo-class :dir() 206 // INHERIT applies to both :dir(rtl) and :dir(ltr) 207 if (nodeOrientation != INHERIT && styleable instanceof Node) { 208 final Node node = (Node)styleable; 209 final NodeOrientation orientation = node.getNodeOrientation(); 210 211 if (orientation == INHERIT 212 ? node.getEffectiveNodeOrientation() != nodeOrientation 213 : orientation != nodeOrientation) 214 { 215 return false; 216 } 217 } 218 219 // if the selector has an id, 220 // then bail if it doesn't match the node's id 221 // (do this first since it is potentially the cheapest check) 222 if (matchOnId) { 223 final String otherId = styleable.getId(); 224 final boolean idMatch = id.equals(otherId); 225 if (!idMatch) return false; 226 } 227 228 // If name is not a wildcard, 229 // then bail if it doesn't match the node's class name 230 // if not wildcard, then match name with node's class name 231 if (matchOnName) { 232 final String otherName = styleable.getTypeSelector(); 233 final boolean classMatch = this.name.equals(otherName); 234 if (!classMatch) return false; 235 } 236 237 if (matchOnStyleClass) { 238 239 final StyleClassSet otherStyleClassSet = new StyleClassSet(); 240 final List<String> styleClasses = styleable.getStyleClass(); 241 for(int n=0, nMax = styleClasses.size(); n<nMax; n++) { 242 243 final String styleClassName = styleClasses.get(n); 244 if (styleClassName == null || styleClassName.isEmpty()) continue; 245 246 final StyleClass styleClass = StyleClassSet.getStyleClass(styleClassName); 247 otherStyleClassSet.add(styleClass); 248 } 249 250 boolean styleClassMatch = matchStyleClasses(otherStyleClassSet); 251 if (!styleClassMatch) return false; 252 } 253 254 return true; 255 } 256 257 @Override public boolean applies(Styleable styleable, Set<PseudoClass>[] pseudoClasses, int depth) { 258 259 260 final boolean applies = applies(styleable); 261 262 // 263 // We only need the pseudo-classes if the selector applies to the node. 264 // 265 if (applies && pseudoClasses != null && depth < pseudoClasses.length) { 266 267 if (pseudoClasses[depth] == null) { 268 pseudoClasses[depth] = new PseudoClassState(); 269 } 270 271 pseudoClasses[depth].addAll(pseudoClassState); 272 273 } 274 return applies; 275 } 276 277 @Override public boolean stateMatches(final Styleable styleable, Set<PseudoClass> states) { 278 // [foo bar] matches [foo bar bang], 279 // but [foo bar bang] doesn't match [foo bar] 280 return states != null ? states.containsAll(pseudoClassState) : false; 281 } 282 283 // Are the Selector's style classes a subset of the Node's style classes? 284 // 285 // http://www.w3.org/TR/css3-selectors/#class-html 286 // The following selector matches any P element whose class attribute has been 287 // assigned a list of whitespace-separated values that includes both 288 // pastoral and marine: 289 // 290 // p.pastoral.marine { color: green } 291 // 292 // This selector matches when class="pastoral blue aqua marine" but does not 293 // match for class="pastoral blue". 294 private boolean matchStyleClasses(StyleClassSet otherStyleClasses) { 295 return otherStyleClasses.containsAll(styleClassSet); 296 } 297 298 @Override public boolean equals(Object obj) { 299 if (obj == null) { 300 return false; 301 } 302 if (getClass() != obj.getClass()) { 303 return false; 304 } 305 final SimpleSelector other = (SimpleSelector) obj; 306 if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { 307 return false; 308 } 309 if ((this.id == null) ? (other.id != null) : !this.id.equals(other.id)) { 310 return false; 311 } 312 if (this.styleClassSet.equals(other.styleClassSet) == false) { 313 return false; 314 } 315 if (this.pseudoClassState.equals(other.pseudoClassState) == false) { 316 return false; 317 } 318 319 return true; 320 } 321 322 /* Hash code is used in Style's hash code and Style's hash 323 code is used by StyleHelper */ 324 @Override public int hashCode() { 325 int hash = 7; 326 hash = 31 * (hash + name.hashCode()); 327 hash = 31 * (hash + styleClassSet.hashCode()); 328 hash = 31 * (hash + styleClassSet.hashCode()); 329 hash = (id != null) ? 31 * (hash + id.hashCode()) : 0; 330 hash = 31 * (hash + pseudoClassState.hashCode()); 331 return hash; 332 } 333 334 /** Converts this object to a string. */ 335 @Override public String toString() { 336 337 StringBuilder sbuf = new StringBuilder(); 338 if (name != null && name.isEmpty() == false) sbuf.append(name); 339 else sbuf.append("*"); 340 Iterator<StyleClass> iter1 = styleClassSet.iterator(); 341 while(iter1.hasNext()) { 342 final StyleClass styleClass = iter1.next(); 343 sbuf.append('.').append(styleClass.getStyleClassName()); 344 } 345 if (id != null && id.isEmpty() == false) { 346 sbuf.append('#'); 347 sbuf.append(id); 348 } 349 Iterator<PseudoClass> iter2 = pseudoClassState.iterator(); 350 while(iter2.hasNext()) { 351 final PseudoClass pseudoClass = iter2.next(); 352 sbuf.append(':').append(pseudoClass.getPseudoClassName()); 353 } 354 355 return sbuf.toString(); 356 } 357 358 @Override protected final void writeBinary(final DataOutputStream os, final StyleConverter.StringStore stringStore) 359 throws IOException 360 { 361 super.writeBinary(os, stringStore); 362 os.writeShort(stringStore.addString(name)); 363 os.writeShort(styleClassSet.size()); 364 Iterator<StyleClass> iter1 = styleClassSet.iterator(); 365 while(iter1.hasNext()) { 366 final StyleClass sc = iter1.next(); 367 os.writeShort(stringStore.addString(sc.getStyleClassName())); 368 } 369 os.writeShort(stringStore.addString(id)); 370 int pclassSize = pseudoClassState.size() 371 + (nodeOrientation == RIGHT_TO_LEFT || nodeOrientation == LEFT_TO_RIGHT ? 1 : 0); 372 os.writeShort(pclassSize); 373 Iterator<PseudoClass> iter2 = pseudoClassState.iterator(); 374 while(iter2.hasNext()) { 375 final PseudoClass pc = iter2.next(); 376 os.writeShort(stringStore.addString(pc.getPseudoClassName())); 377 } 378 if (nodeOrientation == RIGHT_TO_LEFT) { 379 os.writeShort(stringStore.addString("dir(rtl)")); 380 } else if (nodeOrientation == LEFT_TO_RIGHT) { 381 os.writeShort(stringStore.addString("dir(ltr)")); 382 } 383 } 384 385 static SimpleSelector readBinary(int bssVersion, final DataInputStream is, final String[] strings) 386 throws IOException 387 { 388 final String name = strings[is.readShort()]; 389 final int nStyleClasses = is.readShort(); 390 final List<String> styleClasses = new ArrayList<String>(); 391 for (int n=0; n < nStyleClasses; n++) { 392 styleClasses.add(strings[is.readShort()]); 393 } 394 final String id = strings[is.readShort()]; 395 final int nPseudoclasses = is.readShort(); 396 final List<String> pseudoclasses = new ArrayList<String>(); 397 for(int n=0; n < nPseudoclasses; n++) { 398 pseudoclasses.add(strings[is.readShort()]); 399 } 400 return new SimpleSelector(name, styleClasses, pseudoclasses, id); 401 } 402 }