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 }