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 }
|