Gridarta Editor
ProcessRunner.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2015 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.utils;
21 
22 import java.awt.BorderLayout;
23 import java.awt.Color;
24 import java.awt.Component;
25 import java.awt.Font;
26 import java.awt.Frame;
27 import java.awt.Window;
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.util.Queue;
32 import java.util.concurrent.ConcurrentLinkedQueue;
33 import javax.swing.Action;
34 import javax.swing.JDialog;
35 import javax.swing.JPanel;
36 import javax.swing.JScrollPane;
37 import javax.swing.JTextArea;
38 import javax.swing.JToolBar;
39 import javax.swing.SwingUtilities;
40 import net.sf.japi.swing.action.ActionBuilder;
41 import net.sf.japi.swing.action.ActionBuilderFactory;
42 import net.sf.japi.swing.action.ActionMethod;
43 import org.apache.log4j.Category;
44 import org.apache.log4j.Logger;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
47 
52 public class ProcessRunner extends JPanel {
53 
57  @NotNull
58  private static final Category LOG = Logger.getLogger(ProcessRunner.class);
59 
63  private static final long serialVersionUID = 1L;
64 
68  @NotNull
69  private static final ActionBuilder ACTION_BUILDER = ActionBuilderFactory.getInstance().getActionBuilder("net.sf.gridarta");
70 
75  @Nullable
76  private Window dialog;
77 
82  @NotNull
83  private final String key;
84 
89  @NotNull
90  private String[] command;
91 
96  @Nullable
97  private final File dir;
98 
102  @Nullable
103  private transient Process process;
104 
109  @NotNull
110  private final JTextArea stdtxt = new JTextArea(25, 80);
111 
116  @NotNull
117  private final CopyOutput stdout = new CopyOutput("stdout", stdtxt);
118 
123  @NotNull
124  private final CopyOutput stderr = new CopyOutput("stderr", stdtxt);
125 
130  @NotNull
131  private final Action controlStart = ACTION_BUILDER.createAction(false, "controlStart", this);
132 
137  @NotNull
138  private final Action controlStop = ACTION_BUILDER.createAction(false, "controlStop", this);
139 
143  @NotNull
144  private final Object lock = new Object();
145 
153  private ProcessRunner(@NotNull final String key, @NotNull final String[] command, @Nullable final String dir) {
154  this.key = key;
155  this.command = command.clone();
156  this.dir = dir == null ? null : new File(dir);
157  setLayout(new BorderLayout());
158  stdtxt.setFont(new Font("monospaced", Font.PLAIN, stdtxt.getFont().getSize()));
159  stdtxt.setEditable(false);
160  stdtxt.setFocusable(false);
161  stdtxt.setLineWrap(true);
162  stdtxt.setBackground(Color.BLACK);
163  stdtxt.setForeground(Color.WHITE);
164  final Component scrollPane = new JScrollPane(stdtxt);
165  scrollPane.setFocusable(true);
166  add(scrollPane, BorderLayout.CENTER);
167  final JToolBar toolBar = new JToolBar();
168  toolBar.add(controlStart);
169  toolBar.add(controlStop);
170  toolBar.add(ACTION_BUILDER.createAction(false, "controlClear", this));
171  toolBar.add(ActionBuilderUtils.newLabel(ACTION_BUILDER, "controlCloseOkay"));
172  controlStop.setEnabled(false);
173  add(toolBar, BorderLayout.SOUTH);
174  }
175 
181  public ProcessRunner(@NotNull final String key, @NotNull final String[] command) {
182  this(key, command, new File(command[0]).getParent());
183  }
184 
189  public void showDialog(@NotNull final Frame parent) {
190  if (dialog == null || dialog.getOwner() != parent) {
191  createDialog(parent);
192  }
193  assert dialog != null;
194  dialog.setVisible(true);
195  assert dialog != null;
196  dialog.requestFocus();
197  }
198 
203  private void createDialog(@NotNull final Frame parent) {
204  dialog = new JDialog(parent, ActionBuilderUtils.getString(ACTION_BUILDER, key + ".title"));
205  dialog.add(this);
206  assert dialog != null;
207  dialog.pack();
208  assert dialog != null;
209  dialog.setLocationRelativeTo(parent);
210  }
211 
216  public void setCommand(@NotNull final String[] command) {
217  this.command = command.clone();
218  }
219 
223  @ActionMethod
224  public void controlStart() {
225  synchronized (lock) {
226  if (process != null) {
227  try {
228  try {
229  process.getInputStream().close();
230  } catch (final IOException ignored) {
231  // ignore
232  }
233  try {
234  process.getErrorStream().close();
235  } catch (final IOException ignored) {
236  // ignore
237  }
238  try {
239  process.getOutputStream().close();
240  } catch (final IOException ignored) {
241  // ignore
242  }
243  process.exitValue();
244  } catch (final IllegalThreadStateException ignored) {
245  LOG.error("Still running!");
246  // Process is still running, don't start a new one
247  return;
248  }
249  process = null;
250  }
251  try {
252  process = new ProcessBuilder(command).directory(dir).redirectErrorStream(true).start();
253  final InputStream out = process.getInputStream();
254  final InputStream err = process.getErrorStream();
255  stdout.start(out);
256  if (out != err) {
257  stderr.start(err);
258  }
259  controlStop.setEnabled(true);
260  controlStart.setEnabled(false);
261  } catch (final IOException e) {
262  ACTION_BUILDER.showMessageDialog(this, "controlError", e);
263  }
264  }
265  }
266 
270  @ActionMethod
271  public void controlStop() {
272  if (process != null) {
273  process.destroy();
274  }
275  controlStop.setEnabled(false);
276  controlStart.setEnabled(true);
277  }
278 
282  @ActionMethod
283  public void controlClear() {
284  stdtxt.setText("");
285  }
286 
290  private static class CopyOutput implements Runnable {
291 
295  @Nullable
296  private InputStream in;
297 
301  @NotNull
302  private final Appender appender;
303 
307  @NotNull
308  private final String title;
309 
315  private CopyOutput(@NotNull final String title, @NotNull final JTextArea textArea) {
316  this.title = title;
317  appender = new Appender(textArea);
318  }
319 
320  @Override
321  public void run() {
322  try {
323  try {
324  final byte[] buf = new byte[4096];
325  while (true) {
326  assert in != null;
327  final int bytesRead = in.read(buf);
328  if (bytesRead == -1) {
329  break;
330  }
331  appender.append(new String(buf, 0, bytesRead));
332  }
333  //for (String line; (line = in.readLine()) != null;) {
334  // appender.append(title, line, "\n");
335  //}
336  } finally {
337  assert in != null;
338  in.close();
339  }
340  } catch (final IOException e) {
341  appender.append(title, ": ", e.toString());
342  } finally {
343  in = null;
344  }
345  }
346 
351  private void start(@NotNull final InputStream stream) {
352  if (in != null) {
353  if (LOG.isInfoEnabled()) {
354  LOG.info("Trying to stop previous stream.");
355  }
356  try {
357  assert in != null;
358  in.close();
359  } catch (final IOException ignored) {
360  // ignore
361  }
362  if (LOG.isInfoEnabled()) {
363  LOG.info("Stopped previous stream.");
364  }
365  }
366  //in = new BufferedInputStream(stream);
367  in = stream;
368  new Thread(this).start();
369  }
370 
379  private static class Appender implements Runnable {
380 
384  @NotNull
385  private final Queue<String> texts = new ConcurrentLinkedQueue<>();
386 
390  @NotNull
391  private final JTextArea textArea;
392 
397  Appender(@NotNull final JTextArea textArea) {
398  this.textArea = textArea;
399  }
400 
405  public void append(@NotNull final String... texts) {
406  for (final String text : texts) {
407  this.texts.offer(text);
408  }
409  SwingUtilities.invokeLater(this);
410  }
411 
412  @Override
413  public void run() {
414  while (!texts.isEmpty()) {
415  textArea.append(texts.poll());
416  }
417  }
418 
419  }
420 
421  }
422 
423 }
final Appender appender
JTextArea to write to.
String [] command
The command and arguments.
final CopyOutput stdout
CopyOutput for stdout.
void controlStop()
Action method for stopping.
void start(@NotNull final InputStream stream)
Start running.
final Queue< String > texts
Strings to append.
void showDialog(@NotNull final Frame parent)
Show a dialog if not already visible.
static String getString(@NotNull final ActionBuilder actionBuilder, @NotNull final String key, @NotNull final String defaultValue)
Returns the value of a key.
void controlStart()
Action method for starting.
final CopyOutput stderr
CopyOutput for stderr.
CopyOutput(@NotNull final String title, @NotNull final JTextArea textArea)
Create a CopyOutput.
void createDialog(@NotNull final Frame parent)
Create the dialog.
static final ActionBuilder ACTION_BUILDER
Action Builder.
void setCommand(@NotNull final String[] command)
Set the command to be executed by this ProcessRunner.
final JTextArea textArea
JTextArea to append to.
transient Process process
The Process.
InputStream in
BufferedReader to read from.
void append(@NotNull final String... texts)
Append text to the JTextArea.
Class for SwingUtilities to append text.
final String key
The i18n key.
Utility class for ActionBuilder related functions.
final JTextArea stdtxt
JTextArea with log.
static final Category LOG
The Logger for printing log messages.
static JLabel newLabel(@NotNull final ActionBuilder actionBuilder, @NotNull final String key)
Creates a new JLabel from a resource key.
ProcessRunner(@NotNull final String key, @NotNull final String[] command, @Nullable final String dir)
Creates a ProcessRunner for running the given command in the given directory.
ProcessRunner(@NotNull final String key, @NotNull final String[] command)
Creates a ProcessRunner for running the given command in its directory.
void controlClear()
Action method for clearing the log.
static final long serialVersionUID
Serial Version.
Class for reading data from a stream and appending it to a JTextArea.
final File dir
The working directory for the command.
Class to run an external process.
final Object lock
The lock object for thread synchronization.