1/*
2 * Copyright (c) 2015, 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 */
23
24/*
25 * @test
26 * @bug 8167636 8167639 8168972
27 * @summary Testing built-in editor.
28 * @modules java.desktop/java.awt
29 *          jdk.internal.ed/jdk.internal.editor.spi
30 *          jdk.editpad/jdk.editpad
31 * @run testng EditPadTest
32 */
33
34import java.awt.AWTException;
35import java.awt.Component;
36import java.awt.Container;
37import java.awt.Dimension;
38import java.awt.Frame;
39import java.awt.GraphicsEnvironment;
40import java.awt.Point;
41import java.awt.Robot;
42import java.awt.event.InputEvent;
43import java.awt.event.WindowEvent;
44import java.lang.reflect.InvocationTargetException;
45import java.util.ServiceLoader;
46import java.util.concurrent.ExecutionException;
47import java.util.concurrent.ExecutorService;
48import java.util.concurrent.Executors;
49import java.util.concurrent.Future;
50import java.util.function.Consumer;
51
52import javax.swing.JButton;
53import javax.swing.JFrame;
54import javax.swing.JPanel;
55import javax.swing.JScrollPane;
56import javax.swing.JTextArea;
57import javax.swing.JViewport;
58import javax.swing.SwingUtilities;
59
60import org.testng.annotations.AfterClass;
61import org.testng.annotations.BeforeClass;
62import org.testng.annotations.Test;
63import jdk.internal.editor.spi.BuildInEditorProvider;
64import static org.testng.Assert.assertEquals;
65import static org.testng.Assert.assertTrue;
66
67@Test
68public class EditPadTest {
69
70    private static final int DELAY = 500;
71    private static final String WINDOW_LABEL = "Test Edit Pad";
72
73    private static ExecutorService executor;
74    private static Robot robot;
75    private static JFrame frame = null;
76    private static JTextArea area = null;
77    private static JButton cancel = null;
78    private static JButton accept = null;
79    private static JButton exit = null;
80
81    @BeforeClass
82    public static void setUpEditorPadTest() {
83        if (!GraphicsEnvironment.isHeadless()) {
84            try {
85                robot = new Robot();
86                robot.setAutoWaitForIdle(true);
87                robot.setAutoDelay(DELAY);
88            } catch (AWTException e) {
89                throw new ExceptionInInitializerError(e);
90            }
91        }
92    }
93
94    @AfterClass
95    public static void shutdown() {
96        executorShutdown();
97    }
98
99    public void testSimple() {
100        testEdit("abcdef", 1, "xyz",
101                () -> assertSource("abcdef"),
102                () -> writeSource("xyz"),
103                () -> accept(),
104                () -> assertSource("xyz"),
105                () -> shutdownEditor());
106    }
107
108    public void testCancel() {
109        testEdit("abcdef", 0, "abcdef",
110                () -> assertSource("abcdef"),
111                () -> writeSource("xyz"),
112                () -> cancel());
113    }
114
115    public void testAbort() {
116        testEdit("abcdef", 0, "abcdef",
117                () -> assertSource("abcdef"),
118                () -> writeSource("xyz"),
119                () -> shutdownEditor());
120    }
121
122    public void testAcceptCancel() {
123        testEdit("abcdef", 1, "xyz",
124                () -> assertSource("abcdef"),
125                () -> writeSource("xyz"),
126                () -> accept(),
127                () -> assertSource("xyz"),
128                () -> writeSource("!!!!!!!!!"),
129                () -> cancel());
130    }
131
132    public void testAcceptEdit() {
133        testEdit("abcdef", 2, "xyz",
134                () -> assertSource("abcdef"),
135                () -> writeSource("NoNo"),
136                () -> accept(),
137                () -> assertSource("NoNo"),
138                () -> writeSource("xyz"),
139                () -> exit());
140    }
141
142    private void testEdit(String initialText,
143            int savedCount, String savedText, Runnable... actions) {
144        class Handler {
145
146            String text = null;
147            int count = 0;
148
149            void handle(String s) {
150                ++count;
151                text = s;
152            }
153        }
154        Handler save = new Handler();
155        Handler error = new Handler();
156
157        if (GraphicsEnvironment.isHeadless()) {
158            // Do not actually run if we are headless
159            return;
160        }
161        Future<?> task = doActions(actions);
162        builtInEdit(initialText, save::handle, error::handle);
163        complete(task);
164        assertEquals(error.count, 0, "Error: " + error.text);
165        assertTrue(save.count != savedCount
166                || save.text == null
167                    ? savedText != null
168                    : savedText.equals(save.text),
169                "Expected " + savedCount + " saves, got " + save.count
170                + ", expected \"" + savedText + "\" got \"" + save.text + "\"");
171    }
172
173    private static ExecutorService getExecutor() {
174        if (executor == null) {
175            executor = Executors.newSingleThreadExecutor();
176        }
177        return executor;
178    }
179
180    private static void executorShutdown() {
181        if (executor != null) {
182            executor.shutdown();
183            executor = null;
184        }
185    }
186
187    private void builtInEdit(String initialText,
188            Consumer<String> saveHandler, Consumer<String> errorHandler) {
189        ServiceLoader<BuildInEditorProvider> sl
190                = ServiceLoader.load(BuildInEditorProvider.class);
191        // Find the highest ranking provider
192        BuildInEditorProvider provider = null;
193        for (BuildInEditorProvider p : sl) {
194            if (provider == null || p.rank() > provider.rank()) {
195                provider = p;
196            }
197        }
198        if (provider != null) {
199            provider.edit(WINDOW_LABEL,
200                    initialText, saveHandler, errorHandler);
201        } else {
202            throw new InternalError("Cannot find provider");
203        }
204    }
205
206    private Future<?> doActions(Runnable... actions) {
207        return getExecutor().submit(() -> {
208            try {
209                waitForIdle();
210                SwingUtilities.invokeLater(this::seekElements);
211                waitForIdle();
212                for (Runnable act : actions) {
213                    act.run();
214                }
215            } catch (Throwable e) {
216                shutdownEditor();
217                if (e instanceof AssertionError) {
218                    throw (AssertionError) e;
219                }
220                throw new RuntimeException(e);
221            }
222        });
223    }
224
225    private void complete(Future<?> task) {
226        try {
227            task.get();
228            waitForIdle();
229        } catch (ExecutionException e) {
230            if (e.getCause() instanceof AssertionError) {
231                throw (AssertionError) e.getCause();
232            }
233            throw new RuntimeException(e);
234        } catch (Exception e) {
235            throw new RuntimeException(e);
236        } finally {
237            shutdownEditor();
238        }
239    }
240
241    private void writeSource(String s) {
242        SwingUtilities.invokeLater(() -> area.setText(s));
243    }
244
245    private void assertSource(String expected) {
246        String[] s = new String[1];
247        try {
248            SwingUtilities.invokeAndWait(() -> s[0] = area.getText());
249        } catch (InvocationTargetException | InterruptedException e) {
250            throw new RuntimeException(e);
251        }
252        assertEquals(s[0], expected);
253    }
254
255    private void accept() {
256        clickOn(accept);
257    }
258
259    private void exit() {
260        clickOn(exit);
261    }
262
263    private void cancel() {
264        clickOn(cancel);
265    }
266
267    private void shutdownEditor() {
268        SwingUtilities.invokeLater(this::clearElements);
269        waitForIdle();
270    }
271
272    private void waitForIdle() {
273        robot.waitForIdle();
274        robot.delay(DELAY);
275    }
276
277    private void seekElements() {
278        for (Frame f : Frame.getFrames()) {
279            if (f.getTitle().equals(WINDOW_LABEL)) {
280                frame = (JFrame) f;
281                // workaround
282                frame.setLocation(0, 0);
283                Container root = frame.getContentPane();
284                for (Component c : root.getComponents()) {
285                    if (c instanceof JScrollPane) {
286                        JScrollPane scrollPane = (JScrollPane) c;
287                        for (Component comp : scrollPane.getComponents()) {
288                            if (comp instanceof JViewport) {
289                                JViewport view = (JViewport) comp;
290                                area = (JTextArea) view.getComponent(0);
291                            }
292                        }
293                    }
294                    if (c instanceof JPanel) {
295                        JPanel p = (JPanel) c;
296                        for (Component comp : p.getComponents()) {
297                            if (comp instanceof JButton) {
298                                JButton b = (JButton) comp;
299                                switch (b.getText()) {
300                                    case "Cancel":
301                                        cancel = b;
302                                        break;
303                                    case "Exit":
304                                        exit = b;
305                                        break;
306                                    case "Accept":
307                                        accept = b;
308                                        break;
309                                }
310                            }
311                        }
312                    }
313                }
314            }
315        }
316    }
317
318    private void clearElements() {
319        if (frame != null) {
320            frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
321            frame = null;
322        }
323        area = null;
324        accept = null;
325        cancel = null;
326        exit = null;
327    }
328
329    private void clickOn(JButton button) {
330        waitForIdle();
331        waitForIdle();
332        waitForIdle();
333        waitForIdle();
334        waitForIdle();
335        waitForIdle();
336        Point p = button.getLocationOnScreen();
337        Dimension d = button.getSize();
338        robot.mouseMove(p.x + d.width / 2, p.y + d.height / 2);
339        robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
340        robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
341    }
342}
343