Skip to content

Commit fc12305

Browse files
committed
Rework Finder
- Query strings are now split into sub queries beforehand - Query parsing related methods moved to the new QueryParser class - Find and Find All look for nodes from all parents that match previous queries - Deprecated old style TestFX queries, a warning gets logged when these are used
1 parent 802a9d7 commit fc12305

File tree

5 files changed

+237
-220
lines changed

5 files changed

+237
-220
lines changed

src/main/java/javafxlibrary/utils/Finder.java

Lines changed: 119 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -4,199 +4,178 @@
44
import javafx.css.PseudoClass;
55
import javafx.scene.Node;
66
import javafx.scene.Parent;
7+
import javafx.stage.Window;
78
import javafxlibrary.exceptions.JavaFXLibraryNonFatalException;
89
import javafxlibrary.matchers.InstanceOfMatcher;
910
import org.testfx.matcher.control.LabeledMatchers;
1011
import org.testfx.service.query.NodeQuery;
1112

12-
import java.util.Arrays;
13-
import java.util.HashSet;
14-
import java.util.Set;
13+
import java.util.*;
1514

1615
import static javafxlibrary.utils.TestFxAdapter.robot;
1716

1817
public class Finder {
1918

2019
public enum FindPrefix { ID, CSS, CLASS, TEXT, XPATH, PSEUDO }
21-
private String[] prefixes;
22-
protected Parent currentRoot;
23-
private Parent originalRoot;
24-
private Set<Parent> rootNodes;
25-
private String originalQuery;
26-
27-
public Finder() {
28-
this.prefixes = new String[]{"id=", "css=", "class=", "text=", "xpath=", "pseudo="};
29-
this.currentRoot = robot.listTargetWindows().get(0).getScene().getRoot();
30-
}
3120

32-
// TODO: Add more debug logging about find status
33-
// TODO: Use newFind as default
21+
private String[] queries;
22+
private Set<Node> results = new LinkedHashSet<>();
23+
3424
public Node find(String query) {
35-
if (containsPrefixes(query)) {
36-
originalQuery = query;
37-
rootNodes = robot.fromAll().queryAll();
38-
return newFind(parseWholeQuery(query));
25+
// TODO: Remove old style lookup queries
26+
// Use TestFX lookup for queries with no prefixes
27+
if (!QueryParser.startsWithPrefix(query)) {
28+
RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " +
29+
"the updated lookup query syntax.");
30+
return robot.lookup(query).query();
3931
}
40-
return robot.lookup(query).query();
32+
33+
List<Window> windows = robot.listTargetWindows();
34+
RobotLog.debug("Finding with query \"" + query + "\" from " + windows.size() + " windows");
35+
36+
for (Window window : windows) {
37+
RobotLog.debug("Finding from window " + window);
38+
Node result = find(query, window.getScene().getRoot());
39+
if (result != null)
40+
return result;
41+
42+
}
43+
RobotLog.debug("Find finished, nothing was found with query: " + query);
44+
return null;
4145
}
4246

4347
public Node find(String query, Parent root) {
44-
if (containsPrefixes(query)) {
45-
this.currentRoot = root;
46-
return newFind(parseWholeQuery(query));
48+
// TODO: Remove old style lookup queries
49+
// Use TestFX lookup for queries with no prefixes
50+
if (!QueryParser.startsWithPrefix(query)) {
51+
RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " +
52+
"the updated lookup query syntax.");
53+
return robot.from(root).lookup(query).query();
54+
}
55+
56+
this.queries = QueryParser.getIndividualQueries(query);
57+
return find(root, 0);
58+
}
59+
60+
private Node find(Parent root, int queryIndex) {
61+
String query = queries[queryIndex];
62+
63+
if (queryIndex < queries.length - 1) {
64+
// lookupResults might be unmodifiable, copy contents to a new Set
65+
Set<Node> lookupResults = executeFindAll(root, query);
66+
Set<Node> nodes = new LinkedHashSet<>();
67+
nodes.addAll(lookupResults);
68+
nodes.remove(root);
69+
70+
for (Node node : nodes) {
71+
if (node instanceof Parent) {
72+
Node result = find((Parent) node, queryIndex + 1);
73+
if (result != null) {
74+
return result;
75+
}
76+
}
77+
}
78+
return null;
79+
} else {
80+
return executeFind(root, query);
4781
}
48-
return robot.from(root).lookup(query).query();
4982
}
5083

5184
public Set<Node> findAll(String query) {
52-
if (containsPrefixes(query)) {
53-
originalQuery = query;
54-
originalRoot = this.currentRoot;
55-
rootNodes = robot.fromAll().queryAll();
56-
Set<Node> allNodes = new HashSet<>();
57-
return newFindAll(parseWholeQuery(query), allNodes);
85+
// TODO: Remove old style lookup queries
86+
// Use TestFX lookup for queries with no prefixes
87+
if (!QueryParser.startsWithPrefix(query)) {
88+
RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " +
89+
"the updated lookup query syntax.");
90+
return robot.lookup(query).queryAll();
91+
}
92+
93+
List<Window> windows = robot.listTargetWindows();
94+
RobotLog.debug("Finding All with query \"" + query + "\" from " + windows.size() + " windows");
95+
96+
for (Window window : windows) {
97+
RobotLog.debug("Finding all from window " + window);
98+
findAll(query, window.getScene().getRoot());
5899
}
59-
return robot.lookup(query).queryAll();
100+
return results;
60101
}
61102

62103
public Set<Node> findAll(String query, Parent root) {
63-
RobotLog.debug("Executing Finder.findAll using query: " + query + " and root: " + root);
64-
if (containsPrefixes(query)) {
65-
this.currentRoot = root;
66-
Set<Node> allNodes = new HashSet<>();
67-
return newFindAll(parseWholeQuery(query), allNodes);
104+
// TODO: Remove old style lookup queries
105+
// Use TestFX lookup for queries with no prefixes
106+
if (!QueryParser.startsWithPrefix(query)) {
107+
RobotLog.warn("You are using deprecated lookup queries! See library documentation for information about " +
108+
"the updated lookup query syntax.");
109+
return robot.from(root).lookup(query).query();
68110
}
69-
return robot.from(root).lookup(query).queryAll();
111+
112+
this.queries = QueryParser.getIndividualQueries(query);
113+
return findAll(root, 0);
70114
}
71115

72-
private Node newFind(String query) {
73-
FindPrefix prefix = getPrefix(query);
74-
Node result = executeLookup(query, prefix);
75-
76-
if (result == null && rootNodes != null && rootNodes.size() > 1) {
77-
RobotLog.debug("Could not find anything from " + originalRoot + ", moving to the next root node");
78-
rootNodes.remove(originalRoot);
79-
originalRoot = rootNodes.iterator().next();
80-
currentRoot = originalRoot;
81-
result = newFind(parseWholeQuery(originalQuery));
116+
private Set<Node> findAll(Parent root, int queryIndex) {
117+
String query = queries[queryIndex];
118+
Set<Node> lookupResults = executeFindAll(root, query);
119+
Set<Node> nodes = new LinkedHashSet<>();
120+
nodes.addAll(lookupResults);
121+
nodes.remove(root);
122+
123+
if (queryIndex < queries.length - 1) {
124+
for (Node node : nodes)
125+
if (node instanceof Parent)
126+
findAll((Parent) node, queryIndex + 1);
127+
} else {
128+
results.addAll(nodes);
82129
}
83130

84-
return result;
131+
return results;
85132
}
86133

87-
private Set<Node> newFindAll(String query, Set<Node> allNodes) {
134+
private Node executeFind(Parent root, String query) {
135+
RobotLog.debug("Executing find with root: " + root + " and query: " + query);
88136
FindPrefix prefix = getPrefix(query);
89-
Set<Node> nodes = executeLookupAll(query, prefix);
90-
allNodes.addAll(nodes);
91-
92-
if (rootNodes != null && rootNodes.iterator().hasNext() && rootNodes.size() > 1) {
93-
RobotLog.debug("Finished lookup with root " + originalRoot + ", moving to the next root node");
94-
rootNodes.remove(originalRoot);
95-
originalRoot = rootNodes.iterator().next();
96-
currentRoot = originalRoot;
97-
RobotLog.debug("Starting another lookup using new root: " + currentRoot);
98-
newFindAll(parseWholeQuery(originalQuery), allNodes);
99-
}
100-
return allNodes;
101-
}
102137

103-
private Node executeLookup(String query, FindPrefix prefix) {
104138
switch (prefix) {
105139
case ID:
106-
return this.currentRoot.lookup("#" + query.substring(3));
140+
return root.lookup("#" + query.substring(3));
107141
case CSS:
108-
return this.currentRoot.lookup(query.substring(4));
142+
return root.lookup(query.substring(4));
109143
case CLASS:
110-
return classLookup(query).query();
144+
return classLookup(root, query).query();
111145
case TEXT:
112146
query = query.substring(6, query.length() - 1);
113-
return robot.from(this.currentRoot).lookup(LabeledMatchers.hasText(query)).query();
147+
return robot.from(root).lookup(LabeledMatchers.hasText(query)).query();
114148
case XPATH:
115-
return new XPathFinder().find(query.substring(6), currentRoot);
149+
return new XPathFinder().find(query.substring(6), root);
116150
case PSEUDO:
117-
return pseudoLookup(query).query();
151+
return pseudoLookup(root, query).query();
118152
}
119153
throw new IllegalArgumentException("FindPrefix value " + prefix + " of query " + query + " is not supported");
120154
}
121155

122-
private Set<Node> executeLookupAll(String query, FindPrefix prefix) {
156+
// TODO: Add support for using indexes in queries (css=VBox[3]), xPath already implements this
157+
private Set<Node> executeFindAll(Parent root, String query) {
158+
RobotLog.debug("Executing find all with root: " + root + " and query: " + query);
159+
FindPrefix prefix = getPrefix(query);
160+
123161
switch (prefix) {
124162
case ID:
125-
return this.currentRoot.lookupAll("#" + query.substring(3));
163+
return root.lookupAll("#" + query.substring(3));
126164
case CSS:
127-
return this.currentRoot.lookupAll(query.substring(4));
165+
return root.lookupAll(query.substring(4));
128166
case CLASS:
129-
return classLookup(query).queryAll();
167+
return classLookup(root, query).queryAll();
130168
case TEXT:
131169
query = query.substring(6, query.length() - 1);
132-
return robot.from(this.currentRoot).lookup(LabeledMatchers.hasText(query)).queryAll();
170+
return robot.from(root).lookup(LabeledMatchers.hasText(query)).queryAll();
133171
case XPATH:
134-
return new XPathFinder().findAll(query.substring(6), currentRoot);
172+
return new XPathFinder().findAll(query.substring(6), root);
135173
case PSEUDO:
136-
return pseudoLookup(query).queryAll();
174+
return pseudoLookup(root, query).queryAll();
137175
}
138176
throw new IllegalArgumentException("FindPrefix value " + prefix + " of query " + query + " is not supported");
139177
}
140178

141-
// TODO: Add parseWholeQuery for findAll -> this.currentRoot = (Parent) newFind(rootQuery); only looks from a single parent
142-
// TODO: Add support for using indexes in queries (css=VBox[3]), xPath already implements this
143-
private String parseWholeQuery(String query) {
144-
145-
while (containsMultiplePrefixes(query)) {
146-
147-
String[] queryArray = splitQuery(query);
148-
149-
for (int i = 1; i < queryArray.length; i++) {
150-
if (containsPrefixes(queryArray[i])) {
151-
String rootQuery = String.join(" ", Arrays.copyOfRange(queryArray, 0, i ));
152-
RobotLog.debug("Finding next root using query: " + rootQuery);
153-
this.currentRoot = (Parent) newFind(rootQuery);
154-
RobotLog.debug("New root set for find: " + this.currentRoot);
155-
156-
// TODO: Continue search if there are roots left in other windows / return values
157-
if (this.currentRoot == null)
158-
throw new JavaFXLibraryNonFatalException("Could not find a Parent node with query: \"" +
159-
rootQuery + "\" to be used as the next root node, quitting find!");
160-
161-
String[] remainingQuery = Arrays.copyOfRange(queryArray, i, queryArray.length);
162-
query = String.join(" ", remainingQuery);
163-
break;
164-
}
165-
}
166-
167-
/* Break when the last query has been checked. Without this block query values containing accepted prefixes
168-
like xpath=//Rectangle[@id="nodeId"] will cause an infinite loop. */
169-
if (queryArray.length == 1)
170-
break;
171-
}
172-
return query;
173-
}
174-
175-
protected String[] splitQuery(String query) {
176-
// Replace spaces of text values with temporary tag to prevent them interfering with parsing of the query
177-
boolean replaceSpaces = false;
178-
179-
for (int i = 0; i < query.length(); i++) {
180-
char current = query.charAt(i);
181-
182-
if (current == '"')
183-
replaceSpaces = !replaceSpaces;
184-
185-
// Query can have escaped quotation marks in it, skip these
186-
if (current == '\\' && query.charAt(i + 1) == '"')
187-
query = query.substring(0, i) + "" + query.substring(i + 1);
188-
189-
if (replaceSpaces && current == ' ')
190-
query = query.substring(0, i) + ";javafxlibraryfinderspace;" + query.substring(i + 1);
191-
}
192-
String [] splittedQuery = query.split(" ");
193-
194-
for (int i = 0; i < splittedQuery.length; i++)
195-
splittedQuery[i] = splittedQuery[i].replace(";javafxlibraryfinderspace;", " ");
196-
197-
return splittedQuery;
198-
}
199-
200179
protected FindPrefix getPrefix(String query) {
201180

202181
try {
@@ -223,28 +202,9 @@ protected FindPrefix getPrefix(String query) {
223202
}
224203
}
225204

226-
// True if starts with known prefix
227-
protected boolean containsPrefixes(String query) {
228-
for (String prefix : prefixes)
229-
if (query.startsWith(prefix))
230-
return true;
231-
232-
return false;
233-
}
234-
235-
// NOTE: Returns true when a single query contains known prefixes, e.g. in xpath=[@id="something"] !
236-
protected boolean containsMultiplePrefixes(String query) {
237-
String subQuery = query.substring(query.indexOf('='));
238-
for (String prefix : prefixes)
239-
if (subQuery.contains(prefix))
240-
return true;
241-
242-
return false;
243-
}
244-
245-
private NodeQuery pseudoLookup(String query) {
205+
private NodeQuery pseudoLookup(Parent root, String query) {
246206
String[] queries = query.substring(7).split(";");
247-
return robot.from(this.currentRoot).lookup((Node n) -> {
207+
return robot.from(root).lookup((Node n) -> {
248208
int matching = 0;
249209
ObservableSet<PseudoClass> pseudoStates = n.getPseudoClassStates();
250210

@@ -253,15 +213,15 @@ private NodeQuery pseudoLookup(String query) {
253213
if (c.getPseudoClassName().equals(q))
254214
matching++;
255215

256-
return n != this.currentRoot && (matching == queries.length);
216+
return n != root && (matching == queries.length);
257217
});
258218
}
259219

260-
private NodeQuery classLookup(String query) {
220+
private NodeQuery classLookup(Parent root, String query) {
261221
try {
262222
Class<?> clazz = Class.forName(query.substring(6));
263223
InstanceOfMatcher matcher = new InstanceOfMatcher(clazz);
264-
return robot.from(this.currentRoot).lookup(matcher);
224+
return robot.from(root).lookup(matcher);
265225
} catch (ClassNotFoundException e) {
266226
throw new JavaFXLibraryNonFatalException("Could not use \"" + query.substring(6) + "\" for " +
267227
"Node lookup: class was not found");

0 commit comments

Comments
 (0)