1/*
2 * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23package org.netbeans.jemmy;
24
25import java.awt.Component;
26import java.awt.Container;
27import java.util.Vector;
28
29/**
30 *
31 * Contains methods to search for components below a a given
32 * {@code java.awt.Container} in the display containment hierarchy. Uses a
33 * {@code ComponentChooser} interface implementation to find a component.
34 *
35 * @see ComponentChooser
36 *
37 * @author Alexandre Iline (alexandre.iline@oracle.com)
38 */
39public class ComponentSearcher implements Outputable {
40
41    private int ordinalIndex;
42    private Container container;
43    private TestOut out;
44    private QueueTool queueTool;
45    private String containerToString;
46
47    /**
48     * Contructor. The search is constrained so that only components that lie
49     * below the given container in the containment hierarchy are considered.
50     *
51     * @param c Container to find components in.
52     */
53    public ComponentSearcher(Container c) {
54        super();
55        container = c;
56        setOutput(JemmyProperties.getProperties().getOutput());
57        queueTool = new QueueTool();
58    }
59
60    /**
61     * Creates {@code ComponentChooser} implementation whose
62     * {@code checkComponent(Component)} method returns {@code true}
63     * for any component.
64     *
65     * @param description Component description.
66     * @return ComponentChooser instance.
67     */
68    public static ComponentChooser getTrueChooser(String description) {
69        class TrueChooser implements ComponentChooser {
70
71            private String description;
72
73            public TrueChooser(String desc) {
74                description = desc;
75            }
76
77            @Override
78            public boolean checkComponent(Component comp) {
79                return true;
80            }
81
82            @Override
83            public String getDescription() {
84                return description;
85            }
86
87            @Override
88            public String toString() {
89                return "TrueChooser{" + "description=" + description + '}';
90            }
91        }
92        return new TrueChooser(description);
93    }
94
95    /**
96     * Defines print output streams or writers.
97     *
98     * @param output ?out? Identify the streams or writers used for print
99     * output.
100     * @see org.netbeans.jemmy.TestOut
101     * @see org.netbeans.jemmy.Outputable
102     * @see #getOutput
103     */
104    @Override
105    public void setOutput(TestOut output) {
106        out = output;
107    }
108
109    /**
110     * Returns print output streams or writers.
111     *
112     * @return an object that contains references to objects for printing to
113     * output and err streams.
114     * @see org.netbeans.jemmy.TestOut
115     * @see org.netbeans.jemmy.Outputable
116     * @see #setOutput
117     */
118    @Override
119    public TestOut getOutput() {
120        return out;
121    }
122
123    /**
124     * Returns container.toString(). It is called in dispatch thread.
125     *
126     * @return container.toString()
127     */
128    private String containerToString() {
129        if (containerToString == null) {
130            containerToString = container == null ? "null"
131                    : queueTool.invokeSmoothly(
132                            new QueueTool.QueueAction<String>("container.toString()") {
133                        @Override
134                        public String launch() {
135                            return container.toString();
136                        }
137                    }
138                    );
139        }
140        return containerToString;
141    }
142
143    /**
144     * Searches for a component. The search for the component proceeds
145     * recursively in the component hierarchy rooted in this
146     * {@code ComponentChooser}'s container.
147     *
148     * @param chooser ComponentChooser instance, defining and applying the
149     * search criteria.
150     * @param index Ordinal component index. Indices start at 0.
151     * @return the {@code index}'th component from among those components
152     * for which the chooser's {@code checkComponent(Component)} method
153     * returns {@code true}. A {@code null} reference is returned if
154     * there are fewer than {@code index-1} components meeting the search
155     * criteria exist in the component hierarchy rooted in this
156     * {@code ComponentChooser}'s container.
157     */
158    public Component findComponent(ComponentChooser chooser, int index) {
159        ordinalIndex = 0;
160        final Component result = findComponentInContainer(container, chooser, index, null);
161        if (result != null) {
162            // get result.toString() - run in dispatch thread
163            String resultToString = queueTool.invokeSmoothly(
164                    new QueueTool.QueueAction<String>("result.toString()") {
165                @Override
166                public String launch() {
167                    return result.toString();
168                }
169            }
170            );
171            out.printTrace("Component " + chooser.getDescription()
172                    + "\n    was found in container " + containerToString()
173                    + "\n    " + resultToString);
174            out.printGolden("Component \"" + chooser.getDescription() + "\" was found");
175        } else {
176            out.printTrace("Component " + chooser.getDescription()
177                    + "\n    was not found in container " + containerToString());
178            out.printGolden("Component \"" + chooser.getDescription() + "\" was not found");
179        }
180        return result;
181    }
182
183    /**
184     * Searches for a component. The search for the component proceeds
185     * recursively in the component hierarchy rooted in this
186     * {@code ComponentChooser}'s container.
187     *
188     * @param chooser ComponentChooser instance, defining and applying the
189     * search criteria.
190     * @return the first component for which the chooser's
191     * {@code checkComponent(Component)} method returns {@code true}.
192     * A {@code null} reference is returned if no component meeting the
193     * search criteria exist in the component hierarchy rooted in this
194     * {@code ComponentChooser}'s container.
195     */
196    public Component findComponent(ComponentChooser chooser) {
197        return findComponent(chooser, 0);
198    }
199
200    /**
201     * Searches for all components. The search for the components proceeds
202     * recursively in the component hierarchy rooted in this
203     * {@code ComponentChooser}'s container.
204     *
205     * @param chooser ComponentChooser instance, defining and applying the
206     * search criteria.
207     * @return the components for which the chooser's
208     * {@code checkComponent(Component)} method returns {@code true}.
209     * An empty array is returned if no component meeting the search criteria
210     * exists in the component hierarchy rooted in this
211     * {@code ComponentChooser}'s container.
212     */
213    public Component[] findComponents(ComponentChooser chooser) {
214        Vector<Component> allSeen = new Vector<>();
215        findComponentInContainer(container, chooser, 0, allSeen);
216        Component[] result = new Component[allSeen.size()];
217        for (int i = 0, n = allSeen.size(); i < n; i++) {
218            result[i] = allSeen.get(i);
219        }
220        return result;
221    }
222
223    private Component findComponentInContainer(final Container cont, final ComponentChooser chooser, final int index,
224            final Vector<Component> allSeen) {
225        return queueTool.invokeSmoothly(new QueueTool.QueueAction<Component>("findComponentInContainer with " + chooser.getDescription()) {
226
227            @Override
228            public Component launch() throws Exception {
229                Component[] components = cont.getComponents();
230                Component target;
231                for (Component component : components) {
232                    if (component != null) {
233                        if (chooser.checkComponent(component)) {
234                            if (allSeen != null) {
235                                allSeen.add(component);
236                            } else if (ordinalIndex == index) {
237                                return component;
238                            } else {
239                                ordinalIndex++;
240                            }
241                        }
242                        if (component instanceof Container) {
243                            if ((target = findComponentInContainer((Container) component,
244                                    chooser, index, allSeen)) != null) {
245                                return target;
246                            }
247                        }
248                    }
249                }
250                return null;
251            }
252        });
253    }
254
255}
256