javafx.css.Stylesheet Maven / Gradle / Ivy
The newest version!
/*
* Copyright (c) 2010, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javafx.css;
import javafx.css.StyleConverter.StringStore;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import com.sun.javafx.collections.TrackableObservableList;
import com.sun.javafx.css.FontFaceImpl;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* A stylesheet which can apply properties to a tree of objects. A stylesheet
* is a collection of zero or more {@link Rule Rules}, each of which is applied
* to each object in the tree. Typically the selector will examine the object to
* determine whether or not it is applicable, and if so it will apply certain
* property values to the object.
*
* @since 9
*/
public class Stylesheet {
/**
* Version number of binary CSS format. The value is incremented whenever the format of the
* binary stream changes. This number does not correlate with JavaFX versions.
* Version 5: persist @font-face
* Version 6: converter classes moved to public package
*/
final static int BINARY_CSS_VERSION = 6;
private final String url;
/**
* The URL from which this {@code Stylesheet} was loaded.
*
* @return A {@code String} representation of the URL from which the stylesheet was loaded, or {@code null} if
* the stylesheet was created from an inline style.
*/
public String getUrl() {
return url;
}
/**
* Specifies the origin of this {@code Stylesheet}. We need to know this so
* that we can make user important styles have higher priority than
* author styles.
*/
private StyleOrigin origin = StyleOrigin.AUTHOR;
/**
* Returns the origin of this {@code Stylesheet}.
*
* @return the origin of this {@code Stylesheet}
*/
public StyleOrigin getOrigin() {
return origin;
}
/**
* Sets the origin of this {@code Stylesheet}.
* @param origin the origin of this {@code Stylesheet}
*/
public void setOrigin(StyleOrigin origin) {
this.origin = origin;
}
/** All the rules contained in the stylesheet in the order they are in the file */
private final ObservableList rules = new TrackableObservableList<>() {
@Override protected void onChanged(Change c) {
c.reset();
while (c.next()) {
if (c.wasAdded()) {
for(Rule rule : c.getAddedSubList()) {
rule.setStylesheet(Stylesheet.this);
}
} else if (c.wasRemoved()) {
for (Rule rule : c.getRemoved()) {
if (rule.getStylesheet() == Stylesheet.this) rule.setStylesheet(null);
}
}
}
}
};
/** List of all font faces */
private final List fontFaces = new ArrayList<>();
/**
* Constructs a stylesheet with the base URI defaulting to the root
* path of the application.
*/
Stylesheet() {
// ClassLoader cl = Thread.currentThread().getContextClassLoader();
// this.url = (cl != null) ? cl.getResource("") : null;
//
// RT-17344
// The above code is unreliable. The getResource call is intended
// to return the root path of the Application instance, but it sometimes
// returns null. Here, we'll set url to null and then when a url is
// resolved, the url path can be used in the getResource call. For
// example, if the css is -fx-image: url("images/duke.png"), we can
// do cl.getResouce("images/duke.png") in URLConverter
//
this(null);
}
/**
* Constructs a Stylesheet using the given URL as the base URI. The
* parameter may not be null.
*
* @param url the base URI for this stylesheet
*/
Stylesheet(String url) {
this.url = url;
}
/**
* Returns the rules that are defined in this {@code Stylesheet}.
*
* @return a list of rules used by this {@code Stylesheet}
*/
public List getRules() {
return rules;
}
/**
* Returns the font faces used by this {@code Stylesheet}.
*
* @return a list of font faces used by this {@code Stylesheet}
*/
public List getFontFaces() {
return fontFaces;
}
/**
* Indicates whether this {@code Stylesheet} is "equal to" some other object. Equality of two {@code Stylesheet}s is
* based on the equality of their URL as defined by {@link #getUrl()}.
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof Stylesheet) {
Stylesheet other = (Stylesheet)obj;
if (this.url == null && other.url == null) {
return true;
} else if (this.url == null || other.url == null) {
return false;
} else {
return this.url.equals(other.url);
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override public int hashCode() {
int hash = 7;
hash = 13 * hash + (this.url != null ? this.url.hashCode() : 0);
return hash;
}
/** Returns a string representation of this object. */
@Override public String toString() {
StringBuilder sbuf = new StringBuilder();
sbuf.append("/* ");
if (url != null) sbuf.append(url);
if (rules.isEmpty()) {
sbuf.append(" */");
} else {
sbuf.append(" */\n");
for(int r=0; r fontFaceList = getFontFaces();
int nFontFaces = fontFaceList != null ? fontFaceList.size() : 0;
os.writeShort(nFontFaces);
for(int n=0; n persistedRules = new ArrayList<>(nRules);
for (int n=0; n= 5) {
List fontFaceList = this.getFontFaces();
int nFontFaces = is.readShort();
for (int n=0; n Stylesheet.BINARY_CSS_VERSION) {
throw new IOException(
String.format("Wrong binary CSS version %s, expected version less than or equal to %s",
uri != null ? bssVersion + " in stylesheet \"" + uri + "\"" : bssVersion,
Stylesheet.BINARY_CSS_VERSION));
}
// read strings
final String[] strings = StringStore.readBinary(dataInputStream);
// read binary data
stylesheet = new Stylesheet(uri);
try {
dataInputStream.mark(Integer.MAX_VALUE);
stylesheet.readBinary(bssVersion, dataInputStream, strings);
} catch (Exception e) {
stylesheet = new Stylesheet(uri);
dataInputStream.reset();
if (bssVersion == 2) {
// RT-31022
stylesheet.readBinary(3, dataInputStream, strings);
} else {
stylesheet.readBinary(Stylesheet.BINARY_CSS_VERSION, dataInputStream, strings);
}
}
}
// return stylesheet
return stylesheet;
}
/**
* Converts the css file referenced by {@code source} to binary format and writes it to {@code destination}.
*
* @param source the JavaFX compliant css file to convert
* @param destination the file to which the binary formatted data is written
* @throws IOException if the destination file can not be created or if an I/O error occurs
* @throws IllegalArgumentException if either parameter is {@code null}, if {@code source} and
* {@code destination} are the same, if {@code source} cannot be read, or if {@code destination}
* cannot be written
*/
public static void convertToBinary(File source, File destination) throws IOException {
if (source == null || destination == null) {
throw new IllegalArgumentException("parameters may not be null");
}
if (source.getAbsolutePath().equals(destination.getAbsolutePath())) {
throw new IllegalArgumentException("source and destination may not be the same");
}
if (source.canRead() == false) {
throw new IllegalArgumentException("cannot read source file");
}
if (destination.exists() ? (destination.canWrite() == false) : (destination.createNewFile() == false)) {
throw new IllegalArgumentException("cannot write destination file");
}
URI sourceURI = source.toURI();
Stylesheet stylesheet = new CssParser().parse(sourceURI.toURL());
// first write all the css binary data into the buffer and collect strings on way
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
StringStore stringStore = new StringStore();
stylesheet.writeBinary(dos, stringStore);
dos.flush();
dos.close();
FileOutputStream fos = new FileOutputStream(destination);
DataOutputStream os = new DataOutputStream(fos);
// write file version
os.writeShort(BINARY_CSS_VERSION);
// write strings
stringStore.writeBinary(os);
// write binary css
os.write(baos.toByteArray());
os.flush();
os.close();
}
// Add the rules from the other stylesheet to this one
void importStylesheet(Stylesheet importedStylesheet) {
if (importedStylesheet == null) return;
List rulesToImport = importedStylesheet.getRules();
if (rulesToImport == null || rulesToImport.isEmpty()) return;
List importedRules = new ArrayList<>(rulesToImport.size());
for (Rule rule : rulesToImport) {
List selectors = rule.getSelectors();
List declarations = rule.getUnobservedDeclarationList();
importedRules.add(new Rule(selectors, declarations));
}
rules.addAll(importedRules);
}
}