javafx.css.SimpleSelector Maven / Gradle / Ivy
/*
* Copyright (c) 2008, 2023, 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.geometry.NodeOrientation;
import javafx.scene.Node;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.sun.javafx.css.ImmutablePseudoClassSetsCache;
import com.sun.javafx.css.PseudoClassState;
import com.sun.javafx.css.StyleClassSet;
import static javafx.geometry.NodeOrientation.INHERIT;
import static javafx.geometry.NodeOrientation.LEFT_TO_RIGHT;
import static javafx.geometry.NodeOrientation.RIGHT_TO_LEFT;
/**
* A simple selector which behaves according to the CSS standard.
*
* @since 9
*/
final public class SimpleSelector extends Selector {
/**
* If specified in the CSS file, the name of the java class to which
* this selector is applied. For example, if the CSS file had:
*
* Rectangle { }
*
* then name would be "Rectangle".
*/
final private String name;
/**
* Gets the name of the java class to which this selector is applied, or *.
* @return the name of the java class
*/
public String getName() {
return name;
}
/**
* Gets an immutable list of style-classes of the {@code Selector}.
* @return an immutable list of style-classes of the {@code Selector}
*/
public List getStyleClasses() {
final List names = new ArrayList<>();
Iterator iter = styleClassSet.iterator();
while (iter.hasNext()) {
names.add(iter.next().getStyleClassName());
}
return Collections.unmodifiableList(names);
}
/**
* Gets the immutable {@code Set} of {@code StyleClass}es of the {@code Selector}.
* @return the {@code Set} of {@code StyleClass}es
*/
public Set getStyleClassSet() {
return styleClassSet;
}
/**
* styleClasses converted to a set of bit masks
*/
private final Set styleClassSet;
private final Set unwrappedStyleClassSet;
private final String id;
/**
* Gets the value of the selector id.
* @return the value of the selector id, which may be an empty string
*/
public String getId() {
return id;
}
// a mask of bits corresponding to the pseudoclasses (immutable)
private final Set pseudoClassState;
// for test purposes
Set getPseudoClassStates() {
return pseudoClassState;
}
// true if name is not a wildcard
final private boolean matchOnName;
// true if id given
final private boolean matchOnId;
// true if style class given
final private boolean matchOnStyleClass;
// dir(ltr) or dir(rtl), otherwise inherit
final private NodeOrientation nodeOrientation;
// Used in Match. If nodeOrientation is ltr or rtl,
// then count it as a pseudoclass
/**
* Gets the {@code NodeOrientation} of this {@code Selector}.
* @return the {@code NodeOrientation}
*/
public NodeOrientation getNodeOrientation() {
return nodeOrientation;
}
// TODO: The parser passes styleClasses as a List. Should be array?
SimpleSelector(final String name, final List styleClasses,
final List pseudoClasses, final String id)
{
this.name = name == null ? "*" : name;
// if name is not null and not empty or wildcard,
// then match needs to check name
this.matchOnName = (name != null && !("".equals(name)) && !("*".equals(name)));
this.unwrappedStyleClassSet = new StyleClassSet();
if (styleClasses != null) {
for (int n = 0; n < styleClasses.size(); n++) {
final String styleClassName = styleClasses.get(n);
if (styleClassName == null || styleClassName.isEmpty()) continue;
unwrappedStyleClassSet.add(StyleClassSet.getStyleClass(styleClassName));
}
}
this.styleClassSet = Collections.unmodifiableSet(unwrappedStyleClassSet);
this.matchOnStyleClass = (this.styleClassSet.size() > 0);
PseudoClassState pcs = new PseudoClassState();
NodeOrientation dir = NodeOrientation.INHERIT;
if (pseudoClasses != null) {
for (int n = 0; n < pseudoClasses.size(); n++) {
final String pclass = pseudoClasses.get(n);
if (pclass == null || pclass.isEmpty()) continue;
// TODO: This is not how we should handle functional pseudo-classes in the long-run!
if ("dir(".regionMatches(true, 0, pclass, 0, 4)) {
final boolean rtl = "dir(rtl)".equalsIgnoreCase(pclass);
dir = rtl ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
continue;
}
pcs.add(PseudoClassState.getPseudoClass(pclass));
}
}
this.pseudoClassState = ImmutablePseudoClassSetsCache.of(pcs);
this.nodeOrientation = dir;
this.id = id == null ? "" : id;
// if id is not null and not empty, then match needs to check id
this.matchOnId = (id != null && !("".equals(id)));
}
@Override public Match createMatch() {
final int idCount = (matchOnId) ? 1 : 0;
int styleClassCount = styleClassSet.size();
return new Match(this, pseudoClassState, idCount, styleClassCount);
}
@Override public boolean applies(Styleable styleable) {
// handle functional pseudo-class :dir()
// INHERIT applies to both :dir(rtl) and :dir(ltr)
if (nodeOrientation != INHERIT && styleable instanceof Node) {
final Node node = (Node)styleable;
final NodeOrientation orientation = node.getNodeOrientation();
if (orientation == INHERIT
? node.getEffectiveNodeOrientation() != nodeOrientation
: orientation != nodeOrientation)
{
return false;
}
}
// if the selector has an id,
// then bail if it doesn't match the node's id
// (do this first since it is potentially the cheapest check)
if (matchOnId) {
final String otherId = styleable.getId();
final boolean idMatch = id.equals(otherId);
if (!idMatch) return false;
}
// If name is not a wildcard,
// then bail if it doesn't match the node's class name
// if not wildcard, then match name with node's class name
if (matchOnName) {
final String otherName = styleable.getTypeSelector();
final boolean classMatch = this.name.equals(otherName);
if (!classMatch) return false;
}
if (matchOnStyleClass) {
final StyleClassSet otherStyleClassSet = new StyleClassSet();
final List styleClasses = styleable.getStyleClass();
for(int n=0, nMax = styleClasses.size(); n[] pseudoClasses, int depth) {
final boolean applies = applies(styleable);
//
// We only need the pseudo-classes if the selector applies to the node.
//
if (applies && pseudoClasses != null && depth < pseudoClasses.length) {
if (pseudoClasses[depth] == null) {
pseudoClasses[depth] = new PseudoClassState();
}
pseudoClasses[depth].addAll(pseudoClassState);
}
return applies;
}
@Override public boolean stateMatches(final Styleable styleable, Set states) {
// [foo bar] matches [foo bar bang],
// but [foo bar bang] doesn't match [foo bar]
return states != null ? states.containsAll(pseudoClassState) : false;
}
// Are the Selector's style classes a subset of the Node's style classes?
//
// http://www.w3.org/TR/css3-selectors/#class-html
// The following selector matches any P element whose class attribute has been
// assigned a list of whitespace-separated values that includes both
// pastoral and marine:
//
// p.pastoral.marine { color: green }
//
// This selector matches when class="pastoral blue aqua marine" but does not
// match for class="pastoral blue".
private boolean matchStyleClasses(StyleClassSet otherStyleClasses) {
// checks against unwrapped version so BitSet can do its special casing for performance
return otherStyleClasses.containsAll(unwrappedStyleClassSet);
}
@Override public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final SimpleSelector other = (SimpleSelector) obj;
if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
return false;
}
if ((this.id == null) ? (other.id != null) : !this.id.equals(other.id)) {
return false;
}
if (this.styleClassSet.equals(other.styleClassSet) == false) {
return false;
}
if (this.pseudoClassState.equals(other.pseudoClassState) == false) {
return false;
}
return true;
}
/* Hash code is used in Style's hash code and Style's hash
code is used by StyleHelper */
@Override public int hashCode() {
int hash = 7;
hash = 31 * (hash + name.hashCode());
hash = 31 * (hash + styleClassSet.hashCode());
hash = 31 * (hash + styleClassSet.hashCode());
hash = (id != null) ? 31 * (hash + id.hashCode()) : 0;
hash = 31 * (hash + pseudoClassState.hashCode());
return hash;
}
/** Converts this object to a string. */
@Override public String toString() {
StringBuilder sbuf = new StringBuilder();
if (name != null && name.isEmpty() == false) sbuf.append(name);
else sbuf.append("*");
Iterator iter1 = styleClassSet.iterator();
while(iter1.hasNext()) {
final StyleClass styleClass = iter1.next();
sbuf.append('.').append(styleClass.getStyleClassName());
}
if (id != null && id.isEmpty() == false) {
sbuf.append('#');
sbuf.append(id);
}
Iterator iter2 = pseudoClassState.iterator();
while(iter2.hasNext()) {
final PseudoClass pseudoClass = iter2.next();
sbuf.append(':').append(pseudoClass.getPseudoClassName());
}
return sbuf.toString();
}
@Override protected final void writeBinary(final DataOutputStream os, final StyleConverter.StringStore stringStore)
throws IOException
{
super.writeBinary(os, stringStore);
os.writeShort(stringStore.addString(name));
os.writeShort(styleClassSet.size());
Iterator iter1 = styleClassSet.iterator();
while(iter1.hasNext()) {
final StyleClass sc = iter1.next();
os.writeShort(stringStore.addString(sc.getStyleClassName()));
}
os.writeShort(stringStore.addString(id));
int pclassSize = pseudoClassState.size()
+ (nodeOrientation == RIGHT_TO_LEFT || nodeOrientation == LEFT_TO_RIGHT ? 1 : 0);
os.writeShort(pclassSize);
Iterator iter2 = pseudoClassState.iterator();
while(iter2.hasNext()) {
final PseudoClass pc = iter2.next();
os.writeShort(stringStore.addString(pc.getPseudoClassName()));
}
if (nodeOrientation == RIGHT_TO_LEFT) {
os.writeShort(stringStore.addString("dir(rtl)"));
} else if (nodeOrientation == LEFT_TO_RIGHT) {
os.writeShort(stringStore.addString("dir(ltr)"));
}
}
static SimpleSelector readBinary(int bssVersion, final DataInputStream is, final String[] strings)
throws IOException
{
final String name = strings[is.readShort()];
final int nStyleClasses = is.readShort();
final List styleClasses = new ArrayList<>();
for (int n=0; n < nStyleClasses; n++) {
styleClasses.add(strings[is.readShort()]);
}
final String id = strings[is.readShort()];
final int nPseudoclasses = is.readShort();
final List pseudoclasses = new ArrayList<>();
for(int n=0; n < nPseudoclasses; n++) {
pseudoclasses.add(strings[is.readShort()]);
}
return new SimpleSelector(name, styleClasses, pseudoclasses, id);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy