1/*
2 * Copyright (c) 2007, 2014, 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
24package test.java.awt.regtesthelpers.process;
25
26import java.io.*;
27
28/**
29 *  This class is created to solve interprocess communication problems.
30 *  When you need to write a regression test which should verify inter jvm
31 *  behavior such as DnD data transfer, Clipboard data transfer, focus
32 *  transfer etc., you could use the next scenario:
33 *
34 *  1. Write an implementation for the parent JVM, using applet test.
35 *  2. Write an implementation for the child JVM or native application, using
36 *     main() function.
37 *  3. Execute child process using  ProcessCommunicator.executeChildProcess()
38 *     method.
39 *  4. You can decide whether the test is passed on the basis of
40 *     ProcessResults class data.
41 *
42 *  Note: The class is not thread safe. You should access its methods only from
43 *        the same thread.
44 */
45
46public class ProcessCommunicator {
47
48    private static final String javaHome = System.getProperty("java.home", "");
49    private static final String javaPath = javaHome + File.separator + "bin" +
50            File.separator + "java ";
51    private static String command = "";
52    private static volatile Process process;
53
54    private ProcessCommunicator() {}
55
56    /**
57     * The same as {#link #executeChildProcess(Class,String)} except
58     * the {@code classPathArgument} parameter. The class path
59     * parameter is for the debug purposes
60     *
61     * @param classToExecute is passed to the child JVM
62     * @param classPathArguments class path for the child JVM
63     * @param args arguments that will be passed to the executed class
64     * @return results of the executed {@code Process}
65     */
66    public static ProcessResults executeChildProcess(final Class classToExecute,
67                           final String classPathArguments, final String [] args)
68    {
69        try {
70            String command = buildCommand(classToExecute, classPathArguments, args);
71            process = Runtime.getRuntime().exec(command);
72            return doWaitFor(process);
73        } catch (IOException e) {
74            throw new RuntimeException(e);
75        }
76    }
77
78    /**
79     * Executes child {code Process}
80     *
81     * @param classToExecute class to be executed as a child java process
82     * @param args args to be passed in to the child process
83     * @return results of the executed {@code Process}
84     */
85    public static ProcessResults executeChildProcess(final Class classToExecute,
86                           final String [] args)
87    {
88        return executeChildProcess(classToExecute, System.getProperty("java.class.path"), args);
89    }
90
91    /**
92     * Waits for a process and return its results.
93     * This is a workaround for {@code Process.waitFor()} never returning.
94     *
95     * @return results of the executed {@code Process}
96     */
97    public static ProcessResults doWaitFor(final Process p) {
98        ProcessResults pres = new ProcessResults();
99
100        final InputStream in;
101        final InputStream err;
102
103        try {
104            in = p.getInputStream();
105            err = p.getErrorStream();
106
107            boolean finished = false;
108
109            while (!finished) {
110                try {
111                    while (in.available() > 0) {
112                        pres.appendToStdOut((char)in.read());
113                    }
114                    while (err.available() > 0) {
115                        pres.appendToStdErr((char)err.read());
116                    }
117                    // Ask the process for its exitValue. If the process
118                    // is not finished, an IllegalThreadStateException
119                    // is thrown. If it is finished, we fall through and
120                    // the variable finished is set to true.
121                    pres.setExitValue(p.exitValue());
122                    finished  = true;
123                }
124                catch (IllegalThreadStateException e) {
125                    // Process is not finished yet;
126                    // Sleep a little to save on CPU cycles
127                    Thread.currentThread().sleep(500);
128                }
129            }
130            if (in != null) in.close();
131            if (err != null) err.close();
132        }
133        catch (Throwable e) {
134            System.err.println("doWaitFor(): unexpected exception");
135            e.printStackTrace();
136        }
137        return pres;
138    }
139
140    /**
141     * Builds command on the basis of the passed class name,
142     * class path and arguments.
143     *
144     * @param classToExecute with class will be executed in the new JVM
145     * @param classPathArguments java class path (only for test purposes)
146     * @param args arguments for the new application. This could be used
147     *             to pass some information from the parent to child JVM.
148     * @return command to execute the {@code Process}
149     */
150    private static String buildCommand(final Class classToExecute,
151                         final String classPathArguments, final String [] args)
152    {
153        StringBuilder commandBuilder = new StringBuilder();
154        commandBuilder.append(javaPath).append(" ");
155        commandBuilder.append("-cp ").append(System.getProperty("test.classes", ".")).append(File.pathSeparatorChar);
156
157        if (classPathArguments.trim().length() > 0) {
158            commandBuilder.append(classPathArguments).append(" ");
159        }
160
161        commandBuilder.append(" ");
162        commandBuilder.append(classToExecute.getName());
163        for (String argument:args) {
164            commandBuilder.append(" ").append(argument);
165        }
166        command = commandBuilder.toString();
167        return command;
168    }
169
170    /**
171     * Could be used for the debug purposes.
172     *
173     * @return command that was build to execute the child process
174     */
175    public static String getExecutionCommand () {
176        return command;
177    }
178
179    /**
180     * Terminates the process created by {@code executeChildProcess} methods.
181     */
182    public static void destroyProcess() {
183        if (process != null) {
184            if (process.isAlive()) {
185                process.destroy();
186            }
187            process = null;
188        }
189    }
190}
191