1 /* 2 * Copyright (c) 2011, 2017, 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.collections.ListChangeListener.Change; 29 import javafx.collections.ObservableList; 30 import javafx.scene.Node; 31 32 import com.sun.javafx.collections.TrackableObservableList; 33 34 import java.io.ByteArrayInputStream; 35 import java.io.ByteArrayOutputStream; 36 import java.io.DataInputStream; 37 import java.io.DataOutputStream; 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Set; 42 43 /* 44 * A selector is a collection of selectors and declarations. 45 * 46 * @since 9 47 */ 48 final public class Rule { 49 50 private List<Selector> selectors = null; 51 52 /** 53 * The list returned from this method should be treated as unmodifiable. 54 * Tools should use {@link #getSelectors()} which tracks adds and removes. 55 */ 56 List<Selector> getUnobservedSelectorList() { 57 if (selectors == null) { 58 selectors = new ArrayList<Selector>(); 59 } 60 return selectors; 61 } 62 63 private List<Declaration> declarations = null; 64 /** 65 * The list returned from this method should be treated as unmodifiable. 66 * Tools should use {@link #getDeclarations()} which tracks adds and removes. 67 */ 68 List<Declaration> getUnobservedDeclarationList() { 69 70 if (declarations == null && serializedDecls != null) { 71 72 try { 73 ByteArrayInputStream bis = new ByteArrayInputStream(serializedDecls); 74 DataInputStream dis = new DataInputStream(bis); 75 76 short nDeclarations = dis.readShort(); 77 declarations = new ArrayList<Declaration>(nDeclarations); 78 for (int i = 0; i < nDeclarations; i++) { 79 80 Declaration decl = Declaration.readBinary(bssVersion, dis, stylesheet.getStringStore()); 81 decl.rule = Rule.this; 82 83 if (stylesheet != null && stylesheet.getUrl() != null) { 84 String stylesheetUrl = stylesheet.getUrl(); 85 decl.fixUrl(stylesheetUrl); 86 } 87 88 declarations.add(decl); 89 } 90 91 } catch (IOException ioe) { 92 declarations = new ArrayList<>(); 93 assert false; ioe.getMessage(); 94 95 } finally { 96 serializedDecls = null; 97 } 98 99 } 100 101 return declarations; 102 } 103 104 private Observables observables = null; 105 106 /** 107 * This method is to support tooling that may want to add declarations to 108 * or remove declarations from a Rule. Changes to the list are tracked 109 * so that added declarations are tagged as belonging to this rule, and 110 * the rule for removed declarations is nulled out. 111 * If the list is not going to be modified, then it is more efficient to 112 * call {@link #getUnobservedDeclarationList()}, but the returned list 113 * must be treated as unmodifiable. 114 * @return a observable list of declarations 115 */ 116 public final ObservableList<Declaration> getDeclarations() { 117 118 if (observables == null) { 119 observables = new Observables(this); 120 } 121 122 return observables.getDeclarations(); 123 } 124 125 /** 126 * This method is to support tooling that may want to add selectors to 127 * or remove selectors from a Rule. Changes to the list are tracked 128 * so that added selectors are tagged as belonging to this rule, and 129 * the rule for removed selectors is nulled out. 130 * If the list is not going to be modified, then it is more efficient to 131 * call {@link #getUnobservedSelectorList()}, but the returned list 132 * must be treated as unmodifiable. 133 * @return an observable list of selectors 134 */ 135 public final ObservableList<Selector> getSelectors() { 136 137 if (observables == null) { 138 observables = new Observables(this); 139 } 140 141 return observables.getSelectors(); 142 } 143 144 /** The stylesheet this selector belongs to */ 145 private Stylesheet stylesheet; 146 public Stylesheet getStylesheet() { 147 return stylesheet; 148 } 149 150 /* package */ 151 void setStylesheet(Stylesheet stylesheet) { 152 153 this.stylesheet = stylesheet; 154 155 if (stylesheet != null && stylesheet.getUrl() != null) { 156 final String stylesheetUrl = stylesheet.getUrl(); 157 158 int nDeclarations = declarations != null ? declarations.size() : 0; 159 for (int d=0; d<nDeclarations; d++) { 160 declarations.get(d).fixUrl(stylesheetUrl); 161 } 162 } 163 } 164 165 public StyleOrigin getOrigin() { 166 return stylesheet != null ? stylesheet.getOrigin() : null; 167 } 168 169 170 Rule(List<Selector> selectors, List<Declaration> declarations) { 171 172 this.selectors = selectors; 173 this.declarations = declarations; 174 serializedDecls = null; 175 this.bssVersion = Stylesheet.BINARY_CSS_VERSION; 176 177 int sMax = selectors != null ? selectors.size() : 0; 178 for(int i = 0; i < sMax; i++) { 179 Selector sel = selectors.get(i); 180 sel.setRule(Rule.this); 181 } 182 183 int dMax = declarations != null ? declarations.size() : 0; 184 for (int d=0; d<dMax; d++) { 185 Declaration decl = declarations.get(d); 186 decl.rule = this; 187 } 188 } 189 190 private byte[] serializedDecls; 191 private final int bssVersion; 192 193 private Rule(List<Selector> selectors, byte[] buf, int bssVersion) { 194 195 this.selectors = selectors; 196 this.declarations = null; 197 this.serializedDecls = buf; 198 this.bssVersion = bssVersion; 199 200 int sMax = selectors != null ? selectors.size() : 0; 201 for(int i = 0; i < sMax; i++) { 202 Selector sel = selectors.get(i); 203 sel.setRule(Rule.this); 204 } 205 206 } 207 208 // Return mask of selectors that match 209 long applies(Node node, Set<PseudoClass>[] triggerStates) { 210 long mask = 0; 211 for (int i = 0; i < selectors.size(); i++) { 212 Selector sel = selectors.get(i); 213 if (sel.applies(node, triggerStates, 0)) { 214 mask |= 1l << i; 215 } 216 } 217 return mask; 218 } 219 220 /** Converts this object to a string. 221 * @return the converted string 222 */ 223 @Override public String toString() { 224 StringBuilder sb = new StringBuilder(); 225 if (selectors.size()>0) { 226 sb.append(selectors.get(0)); 227 } 228 for (int n=1; n<selectors.size(); n++) { 229 sb.append(','); 230 sb.append(selectors.get(n)); 231 } 232 sb.append("{\n"); 233 int nDeclarations = declarations != null ? declarations.size() : 0; 234 for (int n=0; n<nDeclarations; n++) { 235 sb.append("\t"); 236 sb.append(declarations.get(n)); 237 sb.append("\n"); 238 } 239 sb .append("}"); 240 return sb.toString(); 241 } 242 243 /* 244 * If an authoring tool adds or removes a selector or declaration, 245 * then the selector or declaration needs to be tweaked to know that 246 * this is the Rule to which it belongs. 247 */ 248 private final static class Observables { 249 250 private Observables(Rule rule) { 251 252 this.rule = rule; 253 254 selectorObservableList = new TrackableObservableList<Selector>(rule.getUnobservedSelectorList()) { 255 @Override protected void onChanged(Change<Selector> c) { 256 while (c.next()) { 257 if (c.wasAdded()) { 258 List<Selector> added = c.getAddedSubList(); 259 for(int i = 0, max = added.size(); i < max; i++) { 260 Selector sel = added.get(i); 261 sel.setRule(Observables.this.rule); 262 } 263 } 264 265 if (c.wasRemoved()) { 266 List<Selector> removed = c.getAddedSubList(); 267 for(int i = 0, max = removed.size(); i < max; i++) { 268 Selector sel = removed.get(i); 269 if (sel.getRule() == Observables.this.rule) { 270 sel.setRule(null); 271 } 272 } 273 } 274 } 275 } 276 }; 277 278 declarationObservableList = new TrackableObservableList<Declaration>(rule.getUnobservedDeclarationList()) { 279 @Override protected void onChanged(Change<Declaration> c) { 280 while (c.next()) { 281 if (c.wasAdded()) { 282 List<Declaration> added = c.getAddedSubList(); 283 for(int i = 0, max = added.size(); i < max; i++) { 284 Declaration decl = added.get(i); 285 decl.rule = Observables.this.rule; 286 287 Stylesheet stylesheet = Observables.this.rule.stylesheet; 288 if (stylesheet != null && stylesheet.getUrl() != null) { 289 final String stylesheetUrl = stylesheet.getUrl(); 290 decl.fixUrl(stylesheetUrl); 291 } 292 } 293 } 294 295 if (c.wasRemoved()) { 296 List<Declaration> removed = c.getRemoved(); 297 for(int i = 0, max = removed.size(); i < max; i++) { 298 Declaration decl = removed.get(i); 299 if (decl.rule == Observables.this.rule) { 300 decl.rule = null; 301 } 302 } 303 } 304 } 305 } 306 }; 307 308 } 309 310 private ObservableList<Selector> getSelectors() { 311 return selectorObservableList; 312 } 313 314 private ObservableList<Declaration> getDeclarations() { 315 return declarationObservableList; 316 } 317 318 private final Rule rule; 319 private final ObservableList<Selector> selectorObservableList; 320 private final ObservableList<Declaration> declarationObservableList; 321 322 } 323 324 final void writeBinary(DataOutputStream os, StyleConverter.StringStore stringStore) 325 throws IOException { 326 327 final int nSelectors = this.selectors != null ? this.selectors.size() : 0; 328 os.writeShort(nSelectors); 329 for (int i = 0; i < nSelectors; i++) { 330 Selector sel = this.selectors.get(i); 331 sel.writeBinary(os, stringStore); 332 } 333 334 List<Declaration> decls = getUnobservedDeclarationList(); 335 if (decls != null) { 336 337 ByteArrayOutputStream bos = new ByteArrayOutputStream(5192); 338 DataOutputStream dos = new DataOutputStream(bos); 339 340 int nDeclarations = decls.size(); 341 dos.writeShort(nDeclarations); 342 343 for (int i = 0; i < nDeclarations; i++) { 344 Declaration decl = declarations.get(i); 345 decl.writeBinary(dos, stringStore); 346 } 347 348 os.writeInt(bos.size()); 349 os.write(bos.toByteArray()); 350 351 } else { 352 // no declarations! 353 os.writeShort(0); 354 } 355 } 356 357 static Rule readBinary(int bssVersion, DataInputStream is, String[] strings) 358 throws IOException 359 { 360 short nSelectors = is.readShort(); 361 List<Selector> selectors = new ArrayList<Selector>(nSelectors); 362 for (int i = 0; i < nSelectors; i++) { 363 Selector s = Selector.readBinary(bssVersion, is, strings); 364 selectors.add(s); 365 } 366 367 if (bssVersion < 4) { 368 short nDeclarations = is.readShort(); 369 List<Declaration> declarations = new ArrayList<Declaration>(nDeclarations); 370 for (int i = 0; i < nDeclarations; i++) { 371 Declaration d = Declaration.readBinary(bssVersion, is, strings); 372 declarations.add(d); 373 } 374 375 return new Rule(selectors, declarations); 376 } 377 378 // de-serialize decls into byte array 379 int nBytes = is.readInt(); 380 byte[] buf = new byte[nBytes]; 381 382 if (nBytes > 0) { 383 is.readFully(buf); 384 } 385 return new Rule(selectors, buf, bssVersion); 386 } 387 }