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 javafx.css.PseudoClass;
29 import javafx.css.Styleable;
30 import javafx.geometry.NodeOrientation;
31 import javafx.scene.Node;
32
33 import java.io.DataInputStream;
34 import java.io.DataOutputStream;
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Set;
41
42 import static javafx.geometry.NodeOrientation.INHERIT;
43 import static javafx.geometry.NodeOrientation.LEFT_TO_RIGHT;
44 import static javafx.geometry.NodeOrientation.RIGHT_TO_LEFT;
45
46 /**
47 * A simple selector which behaves according to the CSS standard.
48 *
49 */
50 final public class SimpleSelector extends Selector {
51
52 /**
53 * If specified in the CSS file, the name of the java class to which
54 * this selector is applied. For example, if the CSS file had:
55 * <code><pre>
56 * Rectangle { }
57 * </pre></code>
58 * then name would be "Rectangle".
59 */
60 final private String name;
61 /**
62 * @return The name of the java class to which this selector is applied, or *.
63 */
64 public String getName() {
65 return name;
66 }
67
68 /**
69 * @return Immutable List<String> of style-classes of the selector
70 */
71 public List<String> getStyleClasses() {
72
73 final List<String> names = new ArrayList<String>();
74
75 Iterator<StyleClass> iter = styleClassSet.iterator();
76 while (iter.hasNext()) {
77 names.add(iter.next().getStyleClassName());
78 }
79
80 return Collections.unmodifiableList(names);
81 }
82
83 Set<StyleClass> getStyleClassSet() {
84 return styleClassSet;
85 }
86
87 /** styleClasses converted to a set of bit masks */
88 final private StyleClassSet styleClassSet;
89
90 final private String id;
91 /*
92 * @return The value of the selector id, which may be an empty string.
93 */
94 public String getId() {
95 return id;
96 }
97
98 // a mask of bits corresponding to the pseudoclasses
99 final private PseudoClassState pseudoClassState;
100
101 Set<PseudoClass> getPseudoClassStates() {
102 return pseudoClassState;
103 }
104
105 /**
106 * @return Immutable List<String> of pseudo-classes of the selector
107 */
108 public List<String> getPseudoclasses() {
109
110 final List<String> names = new ArrayList<String>();
111
112 Iterator<PseudoClass> iter = pseudoClassState.iterator();
113 while (iter.hasNext()) {
114 names.add(iter.next().getPseudoClassName());
115 }
116
117 if (nodeOrientation == RIGHT_TO_LEFT) {
118 names.add("dir(rtl)");
119 } else if (nodeOrientation == LEFT_TO_RIGHT) {
120 names.add("dir(ltr)");
121 }
122
123 return Collections.unmodifiableList(names);
124 }
125
126 // true if name is not a wildcard
127 final private boolean matchOnName;
128
129 // true if id given
130 final private boolean matchOnId;
131
132 // true if style class given
133 final private boolean matchOnStyleClass;
134
135 // dir(ltr) or dir(rtl), otherwise inherit
136 final private NodeOrientation nodeOrientation;
137
138 // Used in Match. If nodeOrientation is ltr or rtl,
139 // then count it as a pseudoclass
140 NodeOrientation getNodeOrientation() {
141 return nodeOrientation;
142 }
143
144 // TODO: The parser passes styleClasses as a List. Should be array?
145 public SimpleSelector(final String name, final List<String> styleClasses,
146 final List<String> pseudoClasses, final String id)
147 {
148 this.name = name == null ? "*" : name;
149 // if name is not null and not empty or wildcard,
150 // then match needs to check name
151 this.matchOnName = (name != null && !("".equals(name)) && !("*".equals(name)));
152
153 this.styleClassSet = new StyleClassSet();
154
155 int nMax = styleClasses != null ? styleClasses.size() : 0;
156 for(int n=0; n<nMax; n++) {
157
158 final String styleClassName = styleClasses.get(n);
159 if (styleClassName == null || styleClassName.isEmpty()) continue;
160
161 final StyleClass styleClass = StyleClassSet.getStyleClass(styleClassName);
162 this.styleClassSet.add(styleClass);
163 }
164
165 this.matchOnStyleClass = (this.styleClassSet.size() > 0);
175 if (pclass == null || pclass.isEmpty()) continue;
176
177 // TODO: This is not how we should handle functional pseudo-classes in the long-run!
178 if ("dir(".regionMatches(true, 0, pclass, 0, 4)) {
179 final boolean rtl = "dir(rtl)".equalsIgnoreCase(pclass);
180 dir = rtl ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
181 continue;
182 }
183
184 final PseudoClass pseudoClass = PseudoClassState.getPseudoClass(pclass);
185 this.pseudoClassState.add(pseudoClass);
186 }
187
188 this.nodeOrientation = dir;
189 this.id = id == null ? "" : id;
190 // if id is not null and not empty, then match needs to check id
191 this.matchOnId = (id != null && !("".equals(id)));
192
193 }
194
195 Match createMatch() {
196 final int idCount = (matchOnId) ? 1 : 0;
197 int styleClassCount = styleClassSet.size();
198 return new Match(this, pseudoClassState, idCount, styleClassCount);
199 }
200
201 @Override
202 public boolean applies(Styleable styleable) {
203
204 // handle functional pseudo-class :dir()
205 // INHERIT applies to both :dir(rtl) and :dir(ltr)
206 if (nodeOrientation != INHERIT && styleable instanceof Node) {
207 final Node node = (Node)styleable;
208 final NodeOrientation orientation = node.getNodeOrientation();
209
210 if (orientation == INHERIT
211 ? node.getEffectiveNodeOrientation() != nodeOrientation
212 : orientation != nodeOrientation)
213 {
214 return false;
215 }
216 }
217
218 // if the selector has an id,
219 // then bail if it doesn't match the node's id
220 // (do this first since it is potentially the cheapest check)
221 if (matchOnId) {
222 final String otherId = styleable.getId();
236 if (matchOnStyleClass) {
237
238 final StyleClassSet otherStyleClassSet = new StyleClassSet();
239 final List<String> styleClasses = styleable.getStyleClass();
240 for(int n=0, nMax = styleClasses.size(); n<nMax; n++) {
241
242 final String styleClassName = styleClasses.get(n);
243 if (styleClassName == null || styleClassName.isEmpty()) continue;
244
245 final StyleClass styleClass = StyleClassSet.getStyleClass(styleClassName);
246 otherStyleClassSet.add(styleClass);
247 }
248
249 boolean styleClassMatch = matchStyleClasses(otherStyleClassSet);
250 if (!styleClassMatch) return false;
251 }
252
253 return true;
254 }
255
256 @Override
257 boolean applies(Styleable styleable, Set<PseudoClass>[] pseudoClasses, int depth) {
258
259
260 final boolean applies = applies(styleable);
261
262 //
263 // We only need the pseudo-classes if the selector applies to the node.
264 //
265 if (applies && pseudoClasses != null && depth < pseudoClasses.length) {
266
267 if (pseudoClasses[depth] == null) {
268 pseudoClasses[depth] = new PseudoClassState();
269 }
270
271 pseudoClasses[depth].addAll(pseudoClassState);
272
273 }
274 return applies;
275 }
276
277 @Override
278 public boolean stateMatches(final Styleable styleable, Set<PseudoClass> states) {
279 // [foo bar] matches [foo bar bang],
280 // but [foo bar bang] doesn't match [foo bar]
281 return states != null ? states.containsAll(pseudoClassState) : false;
282 }
283
284 // Are the Selector's style classes a subset of the Node's style classes?
285 //
286 // http://www.w3.org/TR/css3-selectors/#class-html
287 // The following selector matches any P element whose class attribute has been
288 // assigned a list of whitespace-separated values that includes both
289 // pastoral and marine:
290 //
291 // p.pastoral.marine { color: green }
292 //
293 // This selector matches when class="pastoral blue aqua marine" but does not
294 // match for class="pastoral blue".
295 private boolean matchStyleClasses(StyleClassSet otherStyleClasses) {
296 return otherStyleClasses.containsAll(styleClassSet);
297 }
298
299 @Override
300 public boolean equals(Object obj) {
301 if (obj == null) {
302 return false;
303 }
304 if (getClass() != obj.getClass()) {
305 return false;
306 }
307 final SimpleSelector other = (SimpleSelector) obj;
308 if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
309 return false;
310 }
311 if ((this.id == null) ? (other.id != null) : !this.id.equals(other.id)) {
312 return false;
313 }
314 if (this.styleClassSet.equals(other.styleClassSet) == false) {
315 return false;
316 }
317 if (this.pseudoClassState.equals(other.pseudoClassState) == false) {
318 return false;
319 }
320
340 if (name != null && name.isEmpty() == false) sbuf.append(name);
341 else sbuf.append("*");
342 Iterator<StyleClass> iter1 = styleClassSet.iterator();
343 while(iter1.hasNext()) {
344 final StyleClass styleClass = iter1.next();
345 sbuf.append('.').append(styleClass.getStyleClassName());
346 }
347 if (id != null && id.isEmpty() == false) {
348 sbuf.append('#');
349 sbuf.append(id);
350 }
351 Iterator<PseudoClass> iter2 = pseudoClassState.iterator();
352 while(iter2.hasNext()) {
353 final PseudoClass pseudoClass = iter2.next();
354 sbuf.append(':').append(pseudoClass.getPseudoClassName());
355 }
356
357 return sbuf.toString();
358 }
359
360 public final void writeBinary(final DataOutputStream os, final StringStore stringStore)
361 throws IOException
362 {
363 super.writeBinary(os, stringStore);
364 os.writeShort(stringStore.addString(name));
365 os.writeShort(styleClassSet.size());
366 Iterator<StyleClass> iter1 = styleClassSet.iterator();
367 while(iter1.hasNext()) {
368 final StyleClass sc = iter1.next();
369 os.writeShort(stringStore.addString(sc.getStyleClassName()));
370 }
371 os.writeShort(stringStore.addString(id));
372 int pclassSize = pseudoClassState.size()
373 + (nodeOrientation == RIGHT_TO_LEFT || nodeOrientation == LEFT_TO_RIGHT ? 1 : 0);
374 os.writeShort(pclassSize);
375 Iterator<PseudoClass> iter2 = pseudoClassState.iterator();
376 while(iter2.hasNext()) {
377 final PseudoClass pc = iter2.next();
378 os.writeShort(stringStore.addString(pc.getPseudoClassName()));
379 }
380 if (nodeOrientation == RIGHT_TO_LEFT) {
|
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.geometry.NodeOrientation;
29 import javafx.scene.Node;
30
31 import java.io.DataInputStream;
32 import java.io.DataOutputStream;
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.Set;
39
40 import com.sun.javafx.css.PseudoClassState;
41 import com.sun.javafx.css.StyleClassSet;
42
43 import static javafx.geometry.NodeOrientation.INHERIT;
44 import static javafx.geometry.NodeOrientation.LEFT_TO_RIGHT;
45 import static javafx.geometry.NodeOrientation.RIGHT_TO_LEFT;
46
47 /**
48 * A simple selector which behaves according to the CSS standard.
49 *
50 * @since 9
51 */
52 final public class SimpleSelector extends Selector {
53
54 /**
55 * If specified in the CSS file, the name of the java class to which
56 * this selector is applied. For example, if the CSS file had:
57 * <code><pre>
58 * Rectangle { }
59 * </pre></code>
60 * then name would be "Rectangle".
61 */
62 final private String name;
63 /**
64 * @return The name of the java class to which this selector is applied, or *.
65 */
66 public String getName() {
67 return name;
68 }
69
70 /**
71 * @return Immutable List<String> of style-classes of the selector
72 */
73 public List<String> getStyleClasses() {
74
75 final List<String> names = new ArrayList<String>();
76
77 Iterator<StyleClass> iter = styleClassSet.iterator();
78 while (iter.hasNext()) {
79 names.add(iter.next().getStyleClassName());
80 }
81
82 return Collections.unmodifiableList(names);
83 }
84
85 public Set<StyleClass> getStyleClassSet() {
86 return styleClassSet;
87 }
88
89 /** styleClasses converted to a set of bit masks */
90 final private StyleClassSet styleClassSet;
91
92 final private String id;
93 /*
94 * @return The value of the selector id, which may be an empty string.
95 */
96 public String getId() {
97 return id;
98 }
99
100 // a mask of bits corresponding to the pseudoclasses
101 final private PseudoClassState pseudoClassState;
102
103 Set<PseudoClass> getPseudoClassStates() {
104 return pseudoClassState;
105 }
106
107 /**
108 * @return Immutable List<String> of pseudo-classes of the selector
109 */
110 List<String> getPseudoclasses() {
111
112 final List<String> names = new ArrayList<String>();
113
114 Iterator<PseudoClass> iter = pseudoClassState.iterator();
115 while (iter.hasNext()) {
116 names.add(iter.next().getPseudoClassName());
117 }
118
119 if (nodeOrientation == RIGHT_TO_LEFT) {
120 names.add("dir(rtl)");
121 } else if (nodeOrientation == LEFT_TO_RIGHT) {
122 names.add("dir(ltr)");
123 }
124
125 return Collections.unmodifiableList(names);
126 }
127
128 // true if name is not a wildcard
129 final private boolean matchOnName;
130
131 // true if id given
132 final private boolean matchOnId;
133
134 // true if style class given
135 final private boolean matchOnStyleClass;
136
137 // dir(ltr) or dir(rtl), otherwise inherit
138 final private NodeOrientation nodeOrientation;
139
140 // Used in Match. If nodeOrientation is ltr or rtl,
141 // then count it as a pseudoclass
142 public NodeOrientation getNodeOrientation() {
143 return nodeOrientation;
144 }
145
146 // TODO: The parser passes styleClasses as a List. Should be array?
147 SimpleSelector(final String name, final List<String> styleClasses,
148 final List<String> pseudoClasses, final String id)
149 {
150 this.name = name == null ? "*" : name;
151 // if name is not null and not empty or wildcard,
152 // then match needs to check name
153 this.matchOnName = (name != null && !("".equals(name)) && !("*".equals(name)));
154
155 this.styleClassSet = new StyleClassSet();
156
157 int nMax = styleClasses != null ? styleClasses.size() : 0;
158 for(int n=0; n<nMax; n++) {
159
160 final String styleClassName = styleClasses.get(n);
161 if (styleClassName == null || styleClassName.isEmpty()) continue;
162
163 final StyleClass styleClass = StyleClassSet.getStyleClass(styleClassName);
164 this.styleClassSet.add(styleClass);
165 }
166
167 this.matchOnStyleClass = (this.styleClassSet.size() > 0);
177 if (pclass == null || pclass.isEmpty()) continue;
178
179 // TODO: This is not how we should handle functional pseudo-classes in the long-run!
180 if ("dir(".regionMatches(true, 0, pclass, 0, 4)) {
181 final boolean rtl = "dir(rtl)".equalsIgnoreCase(pclass);
182 dir = rtl ? RIGHT_TO_LEFT : LEFT_TO_RIGHT;
183 continue;
184 }
185
186 final PseudoClass pseudoClass = PseudoClassState.getPseudoClass(pclass);
187 this.pseudoClassState.add(pseudoClass);
188 }
189
190 this.nodeOrientation = dir;
191 this.id = id == null ? "" : id;
192 // if id is not null and not empty, then match needs to check id
193 this.matchOnId = (id != null && !("".equals(id)));
194
195 }
196
197 @Override public Match createMatch() {
198 final int idCount = (matchOnId) ? 1 : 0;
199 int styleClassCount = styleClassSet.size();
200 return new Match(this, pseudoClassState, idCount, styleClassCount);
201 }
202
203 @Override public boolean applies(Styleable styleable) {
204
205 // handle functional pseudo-class :dir()
206 // INHERIT applies to both :dir(rtl) and :dir(ltr)
207 if (nodeOrientation != INHERIT && styleable instanceof Node) {
208 final Node node = (Node)styleable;
209 final NodeOrientation orientation = node.getNodeOrientation();
210
211 if (orientation == INHERIT
212 ? node.getEffectiveNodeOrientation() != nodeOrientation
213 : orientation != nodeOrientation)
214 {
215 return false;
216 }
217 }
218
219 // if the selector has an id,
220 // then bail if it doesn't match the node's id
221 // (do this first since it is potentially the cheapest check)
222 if (matchOnId) {
223 final String otherId = styleable.getId();
237 if (matchOnStyleClass) {
238
239 final StyleClassSet otherStyleClassSet = new StyleClassSet();
240 final List<String> styleClasses = styleable.getStyleClass();
241 for(int n=0, nMax = styleClasses.size(); n<nMax; n++) {
242
243 final String styleClassName = styleClasses.get(n);
244 if (styleClassName == null || styleClassName.isEmpty()) continue;
245
246 final StyleClass styleClass = StyleClassSet.getStyleClass(styleClassName);
247 otherStyleClassSet.add(styleClass);
248 }
249
250 boolean styleClassMatch = matchStyleClasses(otherStyleClassSet);
251 if (!styleClassMatch) return false;
252 }
253
254 return true;
255 }
256
257 @Override public boolean applies(Styleable styleable, Set<PseudoClass>[] pseudoClasses, int depth) {
258
259
260 final boolean applies = applies(styleable);
261
262 //
263 // We only need the pseudo-classes if the selector applies to the node.
264 //
265 if (applies && pseudoClasses != null && depth < pseudoClasses.length) {
266
267 if (pseudoClasses[depth] == null) {
268 pseudoClasses[depth] = new PseudoClassState();
269 }
270
271 pseudoClasses[depth].addAll(pseudoClassState);
272
273 }
274 return applies;
275 }
276
277 @Override public boolean stateMatches(final Styleable styleable, Set<PseudoClass> states) {
278 // [foo bar] matches [foo bar bang],
279 // but [foo bar bang] doesn't match [foo bar]
280 return states != null ? states.containsAll(pseudoClassState) : false;
281 }
282
283 // Are the Selector's style classes a subset of the Node's style classes?
284 //
285 // http://www.w3.org/TR/css3-selectors/#class-html
286 // The following selector matches any P element whose class attribute has been
287 // assigned a list of whitespace-separated values that includes both
288 // pastoral and marine:
289 //
290 // p.pastoral.marine { color: green }
291 //
292 // This selector matches when class="pastoral blue aqua marine" but does not
293 // match for class="pastoral blue".
294 private boolean matchStyleClasses(StyleClassSet otherStyleClasses) {
295 return otherStyleClasses.containsAll(styleClassSet);
296 }
297
298 @Override public boolean equals(Object obj) {
299 if (obj == null) {
300 return false;
301 }
302 if (getClass() != obj.getClass()) {
303 return false;
304 }
305 final SimpleSelector other = (SimpleSelector) obj;
306 if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) {
307 return false;
308 }
309 if ((this.id == null) ? (other.id != null) : !this.id.equals(other.id)) {
310 return false;
311 }
312 if (this.styleClassSet.equals(other.styleClassSet) == false) {
313 return false;
314 }
315 if (this.pseudoClassState.equals(other.pseudoClassState) == false) {
316 return false;
317 }
318
338 if (name != null && name.isEmpty() == false) sbuf.append(name);
339 else sbuf.append("*");
340 Iterator<StyleClass> iter1 = styleClassSet.iterator();
341 while(iter1.hasNext()) {
342 final StyleClass styleClass = iter1.next();
343 sbuf.append('.').append(styleClass.getStyleClassName());
344 }
345 if (id != null && id.isEmpty() == false) {
346 sbuf.append('#');
347 sbuf.append(id);
348 }
349 Iterator<PseudoClass> iter2 = pseudoClassState.iterator();
350 while(iter2.hasNext()) {
351 final PseudoClass pseudoClass = iter2.next();
352 sbuf.append(':').append(pseudoClass.getPseudoClassName());
353 }
354
355 return sbuf.toString();
356 }
357
358 @Override protected final void writeBinary(final DataOutputStream os, final StyleConverter.StringStore stringStore)
359 throws IOException
360 {
361 super.writeBinary(os, stringStore);
362 os.writeShort(stringStore.addString(name));
363 os.writeShort(styleClassSet.size());
364 Iterator<StyleClass> iter1 = styleClassSet.iterator();
365 while(iter1.hasNext()) {
366 final StyleClass sc = iter1.next();
367 os.writeShort(stringStore.addString(sc.getStyleClassName()));
368 }
369 os.writeShort(stringStore.addString(id));
370 int pclassSize = pseudoClassState.size()
371 + (nodeOrientation == RIGHT_TO_LEFT || nodeOrientation == LEFT_TO_RIGHT ? 1 : 0);
372 os.writeShort(pclassSize);
373 Iterator<PseudoClass> iter2 = pseudoClassState.iterator();
374 while(iter2.hasNext()) {
375 final PseudoClass pc = iter2.next();
376 os.writeShort(stringStore.addString(pc.getPseudoClassName()));
377 }
378 if (nodeOrientation == RIGHT_TO_LEFT) {
|