1 /* 2 * Copyright (c) 2010, 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 com.sun.javafx.css; 27 28 import javafx.css.CompoundSelector; 29 import javafx.css.Selector; 30 import javafx.css.SimpleSelector; 31 import javafx.css.StyleClass; 32 33 import java.util.ArrayList; 34 import java.util.Collections; 35 import java.util.Comparator; 36 import java.util.HashMap; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Set; 40 41 /** 42 * Code to partition selectors into a tree-like structure for faster matching. 43 */ 44 public final class SelectorPartitioning { 45 46 /** package accessible */ 47 public SelectorPartitioning() {} 48 49 /* 50 * Wrapper so that we can have Map<ParitionKey, Partition> even though 51 * the innards of the key might be a String or long[] 52 */ 53 private final static class PartitionKey<K> { 54 55 private final K key; 56 57 private PartitionKey(K key) { 58 this.key = key; 59 } 60 61 @Override 62 public boolean equals(Object obj) { 63 if (obj == null) { 64 return false; 65 } 66 if (getClass() != obj.getClass()) { 67 return false; 68 } 69 final PartitionKey<K> other = (PartitionKey<K>) obj; 70 if (this.key != other.key && (this.key == null || !this.key.equals(other.key))) { 71 return false; 72 } 73 return true; 74 } 75 76 @Override 77 public int hashCode() { 78 int hash = 7; 79 hash = 71 * hash + (this.key != null ? this.key.hashCode() : 0); 80 return hash; 81 } 82 83 } 84 85 /** 86 * A Partition corresponds to a selector type, id or styleclass. For any 87 * given id (for example) there will be one Partition held in the 88 * corresponding map (idMap, for example). Each Partition has Slots which 89 * define a path from one Partition to the next. For example, if we have 90 * A.b#c, there will be a Partition for each of A, .b and #c. The partition 91 * for #c will have a Slot pointing to A. Likewise, A will have a Slot 92 * corresponding to .b. Each Slot is capable of pointing to more than one 93 * Partition. If another selector A.#c.z were partitioned, then the Slot 94 * for A in Partition #c would now have Slots for both .b and .z. 95 * <p> 96 * Rules are added to the last Slot or to the Partition. If there is a 97 * selector #c { -fx-fill: red; }, then the selector will be added to the 98 * Partition for #c. If the selector were for A.b#c, then selector would be added 99 * to the slot for '.b' which is in the slot for A in partion #c. 100 * <p> 101 * When Node is matched, it picks up the Selectors from the Partition and Slot 102 * as the graph is traversed. 103 */ 104 private static final class Partition { 105 106 private final PartitionKey key; 107 private final Map<PartitionKey, Slot> slots; 108 private List<Selector> selectors; 109 110 private Partition(PartitionKey key) { 111 this.key = key; 112 slots = new HashMap<PartitionKey,Slot>(); 113 } 114 115 private void addSelector(Selector pair) { 116 if (selectors == null) { 117 selectors = new ArrayList<Selector>(); 118 } 119 selectors.add(pair); 120 } 121 122 /** 123 * This routine finds the slot corresponding to the PartitionKey, 124 * creating a Partition and Slot if necessary. 125 */ 126 private Slot partition(PartitionKey id, Map<PartitionKey, Partition> map) { 127 128 Slot slot = slots.get(id); 129 if (slot == null) { 130 Partition partition = getPartition(id,map); 131 slot = new Slot(partition); 132 slots.put(id, slot); 133 } 134 return slot; 135 } 136 137 } 138 139 /** 140 * A Slot is pointer to the next piece of the selector. 141 */ 142 private static final class Slot { 143 144 145 // The Partition to which this Slot belongs 146 private final Partition partition; 147 148 // The other Slots to which this Slot refers 149 private final Map<PartitionKey, Slot> referents; 150 151 // Selectors that match the path to this slot 152 private List<Selector> selectors; 153 154 private Slot(Partition partition) { 155 this.partition = partition; 156 this.referents = new HashMap<PartitionKey, Slot>(); 157 } 158 159 private void addSelector(Selector pair) { 160 if (selectors == null) { 161 selectors = new ArrayList<Selector>(); 162 } 163 selectors.add(pair); 164 } 165 166 /** 167 * This routine finds the slot corresponding to the PartitionKey, 168 * creating a Partition and Slot if necessary. 169 */ 170 private Slot partition(PartitionKey id, Map<PartitionKey, Partition> map) { 171 Slot slot = referents.get(id); 172 if (slot == null) { 173 174 Partition p = getPartition(id, map); 175 slot = new Slot(p); 176 referents.put(id, slot); 177 178 } 179 return slot; 180 } 181 182 } 183 184 /* A Map for selectors that have an id */ 185 private final Map<PartitionKey, Partition> idMap = new HashMap<PartitionKey,Partition>(); 186 187 /* A Map for selectors that have an element type */ 188 private final Map<PartitionKey, Partition> typeMap = new HashMap<PartitionKey,Partition>(); 189 190 /* A Map for selectors that have style classes */ 191 private final Map<PartitionKey, Partition> styleClassMap = new HashMap<PartitionKey,Partition>(); 192 193 /** 194 * Keep track of the order in which a selector is added to the mapping so 195 * the original order can be restored for the cascade. 196 */ 197 private int ordinal; 198 199 /** clear current partitioning */ 200 public void reset() { 201 idMap.clear(); 202 typeMap.clear(); 203 styleClassMap.clear(); 204 ordinal = 0; 205 } 206 207 208 /** 209 * Helper to lookup an id in the given map, creating and adding a Partition 210 * 211 */ 212 private static Partition getPartition(PartitionKey id, Map<PartitionKey,Partition> map) { 213 214 Partition treeNode = map.get(id); 215 if (treeNode == null) { 216 treeNode = new Partition(id); 217 map.put(id, treeNode); 218 } 219 return treeNode; 220 } 221 222 /* Mask that indicates the selector has an id part, e.g. #title */ 223 private static final int ID_BIT = 4; 224 /* Mask that indicates the selector has a type part, e.g. Label */ 225 private static final int TYPE_BIT = 2; 226 /* Mask that indicates the selector has a styleclass part, e.g. .label */ 227 private static final int STYLECLASS_BIT = 1; 228 /* If there is no type part, then * is the default. */ 229 private static final PartitionKey WILDCARD = new PartitionKey<String>("*"); 230 231 /* Place this selector into the partitioning map. Package accessible */ 232 public void partition(Selector selector) { 233 234 SimpleSelector simpleSelector = null; 235 if (selector instanceof CompoundSelector) { 236 final List<SimpleSelector> selectors = ((CompoundSelector)selector).getSelectors(); 237 final int last = selectors.size()-1; 238 simpleSelector = selectors.get(last); 239 } else { 240 simpleSelector = (SimpleSelector)selector; 241 } 242 243 final String selectorId = simpleSelector.getId(); 244 final boolean hasId = 245 (selectorId != null && selectorId.isEmpty() == false); 246 final PartitionKey idKey = hasId 247 ? new PartitionKey(selectorId) 248 : null; 249 250 final String selectorType = simpleSelector.getName(); 251 final boolean hasType = 252 (selectorType != null && selectorType.isEmpty() == false); 253 final PartitionKey typeKey = hasType 254 ? new PartitionKey(selectorType) 255 : null; 256 257 final Set<StyleClass> selectorStyleClass = simpleSelector.getStyleClassSet(); 258 final boolean hasStyleClass = 259 (selectorStyleClass != null && selectorStyleClass.size() > 0); 260 final PartitionKey styleClassKey = hasStyleClass 261 ? new PartitionKey<Set<StyleClass>>(selectorStyleClass) 262 : null; 263 264 final int c = 265 (hasId ? ID_BIT : 0) | (hasType ? TYPE_BIT : 0) | (hasStyleClass ? STYLECLASS_BIT : 0); 266 267 Partition partition = null; 268 Slot slot = null; 269 270 selector.setOrdinal(ordinal++); 271 272 switch(c) { 273 case ID_BIT | TYPE_BIT | STYLECLASS_BIT: 274 case ID_BIT | TYPE_BIT: 275 276 partition = getPartition(idKey, idMap); 277 slot = partition.partition(typeKey, typeMap); 278 if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) { 279 slot = slot.partition(styleClassKey, styleClassMap); 280 } 281 slot.addSelector(selector); 282 break; 283 284 case TYPE_BIT | STYLECLASS_BIT: 285 case TYPE_BIT: 286 287 partition = getPartition(typeKey, typeMap); 288 if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) { 289 slot = partition.partition(styleClassKey, styleClassMap); 290 slot.addSelector(selector); 291 } else { 292 partition.addSelector(selector); 293 } 294 break; 295 296 // SimpleSelector always has a type which defaults to '*' 297 case ID_BIT | STYLECLASS_BIT: 298 case ID_BIT: 299 case STYLECLASS_BIT: 300 default: 301 assert(false); 302 } 303 304 } 305 306 /** Get the list of selectors that match this selector. Package accessible */ 307 public List<Selector> match(String selectorId, String selectorType, Set<StyleClass> selectorStyleClass) { 308 309 final boolean hasId = 310 (selectorId != null && selectorId.isEmpty() == false); 311 final PartitionKey idKey = hasId 312 ? new PartitionKey(selectorId) 313 : null; 314 315 final boolean hasType = 316 (selectorType != null && selectorType.isEmpty() == false); 317 final PartitionKey typeKey = hasType 318 ? new PartitionKey(selectorType) 319 : null; 320 321 final boolean hasStyleClass = 322 (selectorStyleClass != null && selectorStyleClass.size() > 0); 323 final PartitionKey styleClassKey = hasStyleClass 324 ? new PartitionKey<Set<StyleClass>>(selectorStyleClass) 325 : null; 326 327 int c = 328 (hasId ? ID_BIT : 0) | (hasType ? TYPE_BIT : 0) | (hasStyleClass ? STYLECLASS_BIT : 0); 329 330 Partition partition = null; 331 Slot slot = null; 332 List<Selector> selectors = new ArrayList<Selector>(); 333 334 while (c != 0) { 335 336 switch(c) { 337 case ID_BIT | TYPE_BIT | STYLECLASS_BIT: 338 case ID_BIT | TYPE_BIT: 339 { 340 341 partition = idMap.get(idKey); 342 if (partition != null) { 343 if (partition.selectors != null) { 344 selectors.addAll(partition.selectors); 345 } 346 // do-while handles A.b#c also matches A#c by first 347 // doing A.b#c then doing *.b#c 348 PartitionKey typePK = typeKey; 349 do { 350 slot = partition.slots.get(typePK); 351 if (slot != null) { 352 353 if (slot.selectors != null) { 354 selectors.addAll(slot.selectors); 355 } 356 if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) { 357 Set<StyleClass> key = (Set<StyleClass>)styleClassKey.key; 358 for (Slot s : slot.referents.values()) { 359 if (s.selectors == null || s.selectors.isEmpty()) continue; 360 Set<StyleClass> other = (Set<StyleClass>)s.partition.key.key; 361 if (key.containsAll(other)) { 362 selectors.addAll(s.selectors); 363 } 364 } 365 } 366 367 } 368 // if typePK is 'A', make it '*', if it is '*' make it null 369 typePK=WILDCARD.equals(typePK) == false ? WILDCARD : null; 370 371 } while(typePK != null); 372 } 373 374 c -= ID_BIT; 375 continue; 376 } 377 378 379 // SimpleSelector always has a type which defaults to '*' 380 case ID_BIT | STYLECLASS_BIT: 381 case ID_BIT: 382 c -= ID_BIT; 383 break; 384 385 case TYPE_BIT | STYLECLASS_BIT: 386 case TYPE_BIT: 387 { 388 389 // do-while handles A.b also matches .b by first 390 // doing A.b then doing *.b 391 PartitionKey typePK = typeKey; 392 do { 393 partition = typeMap.get(typePK); 394 if (partition != null) { 395 if (partition.selectors != null) { 396 selectors.addAll(partition.selectors); 397 } 398 if ((c & STYLECLASS_BIT) == STYLECLASS_BIT) { 399 Set<StyleClass> key = (Set<StyleClass>)styleClassKey.key; 400 for (Slot s : partition.slots.values()) { 401 if (s.selectors == null || s.selectors.isEmpty()) continue; 402 Set<StyleClass> other = (Set<StyleClass>)s.partition.key.key; 403 if (key.containsAll(other)) { 404 selectors.addAll(s.selectors); 405 } 406 } 407 } 408 } 409 // if typePK is 'A', make it '*', if it is '*' make it null 410 typePK=WILDCARD.equals(typePK) == false ? WILDCARD : null; 411 412 } while(typePK != null); 413 414 c -= TYPE_BIT; 415 continue; 416 } 417 418 // SimpleSelector always has a type which defaults to '*' 419 case STYLECLASS_BIT: 420 c -= STYLECLASS_BIT; 421 break; 422 423 default: 424 assert(false); 425 } 426 } 427 428 Collections.sort(selectors, COMPARATOR); 429 return selectors; 430 } 431 432 private static final Comparator<Selector> COMPARATOR = 433 (o1, o2) -> o1.getOrdinal() - o2.getOrdinal(); 434 435 436 }