modules/graphics/src/main/java/javafx/css/Stylesheet.java

Print this page
rev 9240 : 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization


   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 com.sun.javafx.collections.TrackableObservableList;
  29 import com.sun.javafx.css.parser.CSSParser;
  30 import javafx.collections.ListChangeListener.Change;
  31 import javafx.collections.ObservableList;
  32 import javafx.css.StyleOrigin;


  33 
  34 import java.io.BufferedInputStream;
  35 import java.io.ByteArrayOutputStream;
  36 import java.io.DataInputStream;
  37 import java.io.DataOutputStream;
  38 import java.io.File;
  39 import java.io.FileNotFoundException;
  40 import java.io.FileOutputStream;
  41 import java.io.IOException;
  42 import java.net.URI;
  43 import java.net.URL;
  44 import java.util.ArrayList;
  45 import java.util.List;
  46 
  47 /**
  48  * A stylesheet which can apply properties to a tree of objects.  A stylesheet 
  49  * is a collection of zero or more {@link Rule Rules}, each of which is applied 
  50  * to each object in the tree.  Typically the selector will examine the object to
  51  * determine whether or not it is applicable, and if so it will apply certain 
  52  * property values to the object.
  53  * <p>

  54  */
  55 public class Stylesheet {
  56 
  57     /**
  58      * Version number of binary CSS format. The value is incremented whenever the format of the
  59      * binary stream changes. This number does not correlate with JavaFX versions.
  60      * Version 5: persist @font-face

  61      */
  62     final static int BINARY_CSS_VERSION = 5;
  63             
  64     private final String url;
  65     /** The URL from which the stylesheet was loaded.
  66      * @return The URL from which the stylesheet was loaded, or null if 
  67      *         the stylesheet was created from an inline style. 
  68      */
  69     public String getUrl() {
  70         return url;
  71     }
  72 
  73     /**
  74      * True if this style came from user stylesheet, we need to know this so 
  75      * that we can make user important styles have higher priority than
  76      * author styles
  77      */
  78     private StyleOrigin origin = StyleOrigin.AUTHOR;
  79     public StyleOrigin getOrigin() {
  80         return origin;
  81     }
  82     public void setOrigin(StyleOrigin origin) {
  83         this.origin = origin;
  84     }
  85 
  86     /** All the rules contained in the stylesheet in the order they are in the file */
  87     private final ObservableList<Rule> rules = new TrackableObservableList<Rule>() {
  88 
  89         @Override
  90         protected void onChanged(Change<Rule> c) {
  91             c.reset();
  92             while (c.next()) {
  93                 if (c.wasAdded()) {
  94                     for(Rule rule : c.getAddedSubList()) {
  95                         rule.setStylesheet(Stylesheet.this);
  96                     }
  97                 } else if (c.wasRemoved()) {
  98                     for (Rule rule : c.getRemoved()) {
  99                         if (rule.getStylesheet() == Stylesheet.this) rule.setStylesheet(null);
 100                     }
 101                 }
 102             }
 103         }
 104     };
 105 
 106     /** List of all font faces */
 107     private final List<FontFace> fontFaces = new ArrayList<FontFace>();
 108 
 109     /**
 110      * Constructs a stylesheet with the base URI defaulting to the root
 111      * path of the application.
 112      */
 113     public Stylesheet() {
 114 
 115 //        ClassLoader cl = Thread.currentThread().getContextClassLoader();
 116 //        this.url = (cl != null) ? cl.getResource("") : null;
 117         //
 118         // RT-17344
 119         // The above code is unreliable. The getResource call is intended
 120         // to return the root path of the Application instance, but it sometimes
 121         // returns null. Here, we'll set url to null and then when a url is 
 122         // resolved, the url path can be used in the getResource call. For 
 123         // example, if the css is -fx-image: url("images/duke.png"), we can
 124         // do cl.getResouce("images/duke.png") in URLConverter
 125         //
 126 
 127         this(null);
 128     }
 129 
 130     /**
 131      * Constructs a Stylesheet using the given URL as the base URI. The
 132      * parameter may not be null.
 133      * @param url
 134      */
 135     public Stylesheet(String url) {
 136 
 137         this.url = url;
 138         
 139     }
 140 
 141     public List<Rule> getRules() {
 142         return rules;
 143     }
 144 
 145     public List<FontFace> getFontFaces() {
 146         return fontFaces;
 147     }
 148 
 149     @Override public boolean equals(Object obj) {
 150         if (this == obj) return true;
 151         if (obj instanceof Stylesheet) {
 152             Stylesheet other = (Stylesheet)obj;
 153             
 154             if (this.url == null && other.url == null) {
 155                 return true;


 185         return sbuf.toString();
 186     }
 187 
 188     // protected for unit testing
 189     final void writeBinary(final DataOutputStream os, final StringStore stringStore)
 190         throws IOException 
 191     {
 192         // Note: url is not written since it depends on runtime environment.
 193         int index = stringStore.addString(origin.name());
 194         os.writeShort(index);
 195         os.writeShort(rules.size());
 196         for (Rule r : rules) r.writeBinary(os,stringStore);
 197 
 198         // Version 5 adds persistence of FontFace
 199         List<FontFace> fontFaceList = getFontFaces();
 200         int nFontFaces = fontFaceList != null ? fontFaceList.size() : 0;
 201         os.writeShort(nFontFaces);
 202 
 203         for(int n=0; n<nFontFaces; n++) {
 204             FontFace fontFace = fontFaceList.get(n);
 205             fontFace.writeBinary(os, stringStore);


 206         }
 207     }
 208     
 209     // protected for unit testing 
 210     final void readBinary(int bssVersion, DataInputStream is, String[] strings)
 211         throws IOException 
 212     {
 213         this.stringStore = strings;
 214         final int index = is.readShort();
 215         this.setOrigin(StyleOrigin.valueOf(strings[index]));
 216         final int nRules = is.readShort();
 217         List<Rule> persistedRules = new ArrayList<Rule>(nRules);
 218         for (int n=0; n<nRules; n++) {
 219             persistedRules.add(Rule.readBinary(bssVersion,is,strings));
 220         }
 221         this.rules.addAll(persistedRules);
 222 
 223         if (bssVersion >= 5) {
 224             List<FontFace> fontFaceList = this.getFontFaces();
 225             int nFontFaces = is.readShort();
 226             for (int n=0; n<nFontFaces; n++) {
 227                 FontFace fontFace = FontFace.readBinary(bssVersion, is, strings);
 228                 fontFaceList.add(fontFace);
 229             }
 230         }
 231     }
 232 
 233     private String[] stringStore;
 234     final String[] getStringStore() { return stringStore; }
 235 
 236     /** Load a binary stylesheet file from a input stream */
 237     public static Stylesheet loadBinary(URL url) throws IOException {
 238 
 239         if (url == null) return null;
 240 
 241         Stylesheet stylesheet = null;
 242 
 243         try (DataInputStream dataInputStream =
 244                      new DataInputStream(new BufferedInputStream(url.openStream(), 40 * 1024))) {
 245 
 246             // read file version
 247             final int bssVersion = dataInputStream.readShort();


 293      */
 294     public static void convertToBinary(File source, File destination) throws IOException {
 295 
 296         if (source == null || destination == null) {
 297             throw new IllegalArgumentException("parameters may not be null");
 298         }
 299 
 300         if (source.getAbsolutePath().equals(destination.getAbsolutePath())) {
 301             throw new IllegalArgumentException("source and destination may not be the same");
 302         }
 303 
 304         if (source.canRead() == false) {
 305             throw new IllegalArgumentException("cannot read source file");
 306         }
 307 
 308         if (destination.exists() ? (destination.canWrite() == false) : (destination.createNewFile() == false)) {
 309             throw new IllegalArgumentException("cannot write destination file");
 310         }
 311 
 312         URI sourceURI = source.toURI();
 313         Stylesheet stylesheet = new CSSParser().parse(sourceURI.toURL());
 314 
 315         // first write all the css binary data into the buffer and collect strings on way
 316         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 317         DataOutputStream dos = new DataOutputStream(baos);
 318         StringStore stringStore = new StringStore();
 319         stylesheet.writeBinary(dos, stringStore);
 320         dos.flush();
 321         dos.close();
 322 
 323         FileOutputStream fos = new FileOutputStream(destination);
 324         DataOutputStream os = new DataOutputStream(fos);
 325 
 326         // write file version
 327         os.writeShort(BINARY_CSS_VERSION);
 328 
 329         // write strings
 330         stringStore.writeBinary(os);
 331 
 332         // write binary css
 333         os.write(baos.toByteArray());
 334         os.flush();
 335         os.close();
 336     }
 337 
 338     // Add the rules from the other stylesheet to this one
 339     public void importStylesheet(Stylesheet importedStylesheet) {
 340         if (importedStylesheet == null) return;
 341 
 342         List<Rule> rulesToImport = importedStylesheet.getRules();
 343         if (rulesToImport == null || rulesToImport.isEmpty()) return;
 344 
 345         List<Rule> importedRules = new ArrayList<>(rulesToImport.size());
 346         for (Rule rule : rulesToImport) {
 347             List<Selector> selectors = rule.getSelectors();
 348             List<Declaration> declarations = rule.getUnobservedDeclarationList();
 349             importedRules.add(new Rule(selectors, declarations));
 350         }
 351 
 352         rules.addAll(importedRules);
 353     }
 354 }


   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.css.StyleConverter.StringStore;
  29 


  30 import javafx.collections.ListChangeListener.Change;
  31 import javafx.collections.ObservableList;
  32 
  33 import com.sun.javafx.collections.TrackableObservableList;
  34 import com.sun.javafx.css.FontFaceImpl;
  35 
  36 import java.io.BufferedInputStream;
  37 import java.io.ByteArrayOutputStream;
  38 import java.io.DataInputStream;
  39 import java.io.DataOutputStream;
  40 import java.io.File;
  41 import java.io.FileNotFoundException;
  42 import java.io.FileOutputStream;
  43 import java.io.IOException;
  44 import java.net.URI;
  45 import java.net.URL;
  46 import java.util.ArrayList;
  47 import java.util.List;
  48 
  49 /**
  50  * A stylesheet which can apply properties to a tree of objects.  A stylesheet 
  51  * is a collection of zero or more {@link Rule Rules}, each of which is applied 
  52  * to each object in the tree.  Typically the selector will examine the object to
  53  * determine whether or not it is applicable, and if so it will apply certain 
  54  * property values to the object.
  55  *
  56  * @since 9
  57  */
  58 public class Stylesheet {
  59 
  60     /**
  61      * Version number of binary CSS format. The value is incremented whenever the format of the
  62      * binary stream changes. This number does not correlate with JavaFX versions.
  63      * Version 5: persist @font-face
  64      * Version 6: converter classes moved to public package
  65      */
  66     final static int BINARY_CSS_VERSION = 6;
  67             
  68     private final String url;
  69     /** The URL from which the stylesheet was loaded.
  70      * @return The URL from which the stylesheet was loaded, or null if 
  71      *         the stylesheet was created from an inline style. 
  72      */
  73     public String getUrl() {
  74         return url;
  75     }
  76 
  77     /**
  78      * True if this style came from user stylesheet, we need to know this so 
  79      * that we can make user important styles have higher priority than
  80      * author styles
  81      */
  82     private StyleOrigin origin = StyleOrigin.AUTHOR;
  83     public StyleOrigin getOrigin() {
  84         return origin;
  85     }
  86     public void setOrigin(StyleOrigin origin) {
  87         this.origin = origin;
  88     }
  89 
  90     /** All the rules contained in the stylesheet in the order they are in the file */
  91     private final ObservableList<Rule> rules = new TrackableObservableList<Rule>() {
  92 
  93         @Override protected void onChanged(Change<Rule> c) {

  94             c.reset();
  95             while (c.next()) {
  96                 if (c.wasAdded()) {
  97                     for(Rule rule : c.getAddedSubList()) {
  98                         rule.setStylesheet(Stylesheet.this);
  99                     }
 100                 } else if (c.wasRemoved()) {
 101                     for (Rule rule : c.getRemoved()) {
 102                         if (rule.getStylesheet() == Stylesheet.this) rule.setStylesheet(null);
 103                     }
 104                 }
 105             }
 106         }
 107     };
 108 
 109     /** List of all font faces */
 110     private final List<FontFace> fontFaces = new ArrayList<FontFace>();
 111 
 112     /**
 113      * Constructs a stylesheet with the base URI defaulting to the root
 114      * path of the application.
 115      */
 116     Stylesheet() {
 117 
 118 //        ClassLoader cl = Thread.currentThread().getContextClassLoader();
 119 //        this.url = (cl != null) ? cl.getResource("") : null;
 120         //
 121         // RT-17344
 122         // The above code is unreliable. The getResource call is intended
 123         // to return the root path of the Application instance, but it sometimes
 124         // returns null. Here, we'll set url to null and then when a url is 
 125         // resolved, the url path can be used in the getResource call. For 
 126         // example, if the css is -fx-image: url("images/duke.png"), we can
 127         // do cl.getResouce("images/duke.png") in URLConverter
 128         //
 129 
 130         this(null);
 131     }
 132 
 133     /**
 134      * Constructs a Stylesheet using the given URL as the base URI. The
 135      * parameter may not be null.
 136      * @param url
 137      */
 138     Stylesheet(String url) {
 139 
 140         this.url = url;
 141         
 142     }
 143 
 144     public List<Rule> getRules() {
 145         return rules;
 146     }
 147 
 148     public List<FontFace> getFontFaces() {
 149         return fontFaces;
 150     }
 151 
 152     @Override public boolean equals(Object obj) {
 153         if (this == obj) return true;
 154         if (obj instanceof Stylesheet) {
 155             Stylesheet other = (Stylesheet)obj;
 156             
 157             if (this.url == null && other.url == null) {
 158                 return true;


 188         return sbuf.toString();
 189     }
 190 
 191     // protected for unit testing
 192     final void writeBinary(final DataOutputStream os, final StringStore stringStore)
 193         throws IOException 
 194     {
 195         // Note: url is not written since it depends on runtime environment.
 196         int index = stringStore.addString(origin.name());
 197         os.writeShort(index);
 198         os.writeShort(rules.size());
 199         for (Rule r : rules) r.writeBinary(os,stringStore);
 200 
 201         // Version 5 adds persistence of FontFace
 202         List<FontFace> fontFaceList = getFontFaces();
 203         int nFontFaces = fontFaceList != null ? fontFaceList.size() : 0;
 204         os.writeShort(nFontFaces);
 205 
 206         for(int n=0; n<nFontFaces; n++) {
 207             FontFace fontFace = fontFaceList.get(n);
 208             if (fontFace instanceof FontFaceImpl) {
 209                 ((FontFaceImpl)fontFace).writeBinary(os, stringStore);
 210             }
 211         }
 212     }
 213     
 214     // protected for unit testing 
 215     final void readBinary(int bssVersion, DataInputStream is, String[] strings)
 216         throws IOException 
 217     {
 218         this.stringStore = strings;
 219         final int index = is.readShort();
 220         this.setOrigin(StyleOrigin.valueOf(strings[index]));
 221         final int nRules = is.readShort();
 222         List<Rule> persistedRules = new ArrayList<Rule>(nRules);
 223         for (int n=0; n<nRules; n++) {
 224             persistedRules.add(Rule.readBinary(bssVersion,is,strings));
 225         }
 226         this.rules.addAll(persistedRules);
 227 
 228         if (bssVersion >= 5) {
 229             List<FontFace> fontFaceList = this.getFontFaces();
 230             int nFontFaces = is.readShort();
 231             for (int n=0; n<nFontFaces; n++) {
 232                 FontFace fontFace = FontFaceImpl.readBinary(bssVersion, is, strings);
 233                 fontFaceList.add(fontFace);
 234             }
 235         }
 236     }
 237 
 238     private String[] stringStore;
 239     final String[] getStringStore() { return stringStore; }
 240 
 241     /** Load a binary stylesheet file from a input stream */
 242     public static Stylesheet loadBinary(URL url) throws IOException {
 243 
 244         if (url == null) return null;
 245 
 246         Stylesheet stylesheet = null;
 247 
 248         try (DataInputStream dataInputStream =
 249                      new DataInputStream(new BufferedInputStream(url.openStream(), 40 * 1024))) {
 250 
 251             // read file version
 252             final int bssVersion = dataInputStream.readShort();


 298      */
 299     public static void convertToBinary(File source, File destination) throws IOException {
 300 
 301         if (source == null || destination == null) {
 302             throw new IllegalArgumentException("parameters may not be null");
 303         }
 304 
 305         if (source.getAbsolutePath().equals(destination.getAbsolutePath())) {
 306             throw new IllegalArgumentException("source and destination may not be the same");
 307         }
 308 
 309         if (source.canRead() == false) {
 310             throw new IllegalArgumentException("cannot read source file");
 311         }
 312 
 313         if (destination.exists() ? (destination.canWrite() == false) : (destination.createNewFile() == false)) {
 314             throw new IllegalArgumentException("cannot write destination file");
 315         }
 316 
 317         URI sourceURI = source.toURI();
 318         Stylesheet stylesheet = new CssParser().parse(sourceURI.toURL());
 319 
 320         // first write all the css binary data into the buffer and collect strings on way
 321         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 322         DataOutputStream dos = new DataOutputStream(baos);
 323         StringStore stringStore = new StringStore();
 324         stylesheet.writeBinary(dos, stringStore);
 325         dos.flush();
 326         dos.close();
 327 
 328         FileOutputStream fos = new FileOutputStream(destination);
 329         DataOutputStream os = new DataOutputStream(fos);
 330 
 331         // write file version
 332         os.writeShort(BINARY_CSS_VERSION);
 333 
 334         // write strings
 335         stringStore.writeBinary(os);
 336 
 337         // write binary css
 338         os.write(baos.toByteArray());
 339         os.flush();
 340         os.close();
 341     }
 342 
 343     // Add the rules from the other stylesheet to this one
 344     void importStylesheet(Stylesheet importedStylesheet) {
 345         if (importedStylesheet == null) return;
 346 
 347         List<Rule> rulesToImport = importedStylesheet.getRules();
 348         if (rulesToImport == null || rulesToImport.isEmpty()) return;
 349 
 350         List<Rule> importedRules = new ArrayList<>(rulesToImport.size());
 351         for (Rule rule : rulesToImport) {
 352             List<Selector> selectors = rule.getSelectors();
 353             List<Declaration> declarations = rule.getUnobservedDeclarationList();
 354             importedRules.add(new Rule(selectors, declarations));
 355         }
 356 
 357         rules.addAll(importedRules);
 358     }
 359 }