44import javafx .css .PseudoClass ;
55import javafx .scene .Node ;
66import javafx .scene .Parent ;
7+ import javafx .stage .Window ;
78import javafxlibrary .exceptions .JavaFXLibraryNonFatalException ;
89import javafxlibrary .matchers .InstanceOfMatcher ;
910import org .testfx .matcher .control .LabeledMatchers ;
1011import 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
1615import static javafxlibrary .utils .TestFxAdapter .robot ;
1716
1817public 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