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-2023 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 @NotNull [] 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 @NotNull [] 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 @NotNull [] 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 @NotNull [] 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  protected 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 }
net.sf.gridarta.utils.ProcessRunner.CopyOutput.Appender.textArea
final JTextArea textArea
JTextArea to append to.
Definition: ProcessRunner.java:391
net.sf.gridarta.utils.ProcessRunner.controlStop
final Action controlStop
Action for stop.
Definition: ProcessRunner.java:138
net.sf.gridarta.utils.ProcessRunner.CopyOutput.in
InputStream in
BufferedReader to read from.
Definition: ProcessRunner.java:296
net.sf.gridarta.utils.ProcessRunner.key
final String key
The i18n key.
Definition: ProcessRunner.java:83
net.sf.gridarta.utils.ProcessRunner.dialog
Window dialog
The Dialog.
Definition: ProcessRunner.java:76
net.sf.gridarta.utils.ProcessRunner.controlStart
final Action controlStart
Action for start.
Definition: ProcessRunner.java:131
net.sf.gridarta.utils.ProcessRunner.CopyOutput.run
void run()
Definition: ProcessRunner.java:321
net.sf
net.sf.gridarta.utils.ProcessRunner.CopyOutput.CopyOutput
CopyOutput(@NotNull final String title, @NotNull final JTextArea textArea)
Creates a new instance.
Definition: ProcessRunner.java:315
net.sf.gridarta.utils.ProcessRunner
Class to run an external process.
Definition: ProcessRunner.java:52
net.sf.gridarta.utils.ActionBuilderUtils.newLabel
static JLabel newLabel(@NotNull final ActionBuilder actionBuilder, @NotNull final String key)
Creates a new JLabel from a resource key.
Definition: ActionBuilderUtils.java:117
net.sf.gridarta.utils.ProcessRunner.controlClear
void controlClear()
Action method for clearing the log.
Definition: ProcessRunner.java:283
net.sf.gridarta.utils.ProcessRunner.serialVersionUID
static final long serialVersionUID
Serial Version.
Definition: ProcessRunner.java:63
net.sf.gridarta.utils.ProcessRunner.CopyOutput
Class for reading data from a stream and appending it to a JTextArea.
Definition: ProcessRunner.java:290
net.sf.gridarta.utils.ProcessRunner.stdtxt
final JTextArea stdtxt
JTextArea with log.
Definition: ProcessRunner.java:110
net.sf.gridarta.utils.ProcessRunner.createDialog
void createDialog(@NotNull final Frame parent)
Creates the dialog.
Definition: ProcessRunner.java:203
net.sf.gridarta.utils.ProcessRunner.ProcessRunner
ProcessRunner(@NotNull final String key, @NotNull final String @NotNull[] command)
Creates a ProcessRunner for running the given command in its directory.
Definition: ProcessRunner.java:181
net.sf.gridarta.utils.ProcessRunner.command
String[] command
The command and arguments.
Definition: ProcessRunner.java:90
net
net.sf.gridarta.utils.ProcessRunner.CopyOutput.title
final String title
Title.
Definition: ProcessRunner.java:308
net.sf.gridarta.utils.ProcessRunner.stdout
final CopyOutput stdout
CopyOutput for stdout.
Definition: ProcessRunner.java:117
net.sf.gridarta.utils.ProcessRunner.controlStop
void controlStop()
Action method for stopping.
Definition: ProcessRunner.java:271
net.sf.gridarta.utils.ProcessRunner.lock
final Object lock
The lock object for thread synchronization.
Definition: ProcessRunner.java:144
net.sf.gridarta.utils.ProcessRunner.setCommand
void setCommand(@NotNull final String @NotNull[] command)
Sets the command to be executed by this ProcessRunner.
Definition: ProcessRunner.java:216
net.sf.gridarta.utils.ProcessRunner.ACTION_BUILDER
static final ActionBuilder ACTION_BUILDER
Action Builder.
Definition: ProcessRunner.java:69
net.sf.gridarta.utils.ProcessRunner.CopyOutput.Appender.append
void append(@NotNull final String... texts)
Appends text to the text area.
Definition: ProcessRunner.java:405
net.sf.gridarta.utils.ActionBuilderUtils.getString
static String getString(@NotNull final ActionBuilder actionBuilder, @NotNull final String key, @NotNull final String defaultValue)
Returns the value of a key.
Definition: ActionBuilderUtils.java:71
net.sf.gridarta.utils.ProcessRunner.CopyOutput.Appender
Class for SwingUtilities to append text.
Definition: ProcessRunner.java:379
net.sf.gridarta.utils.ProcessRunner.CopyOutput.Appender.run
void run()
Definition: ProcessRunner.java:413
net.sf.gridarta.utils.ProcessRunner.stderr
final CopyOutput stderr
CopyOutput for stderr.
Definition: ProcessRunner.java:124
net.sf.gridarta.utils.ProcessRunner.ProcessRunner
ProcessRunner(@NotNull final String key, @NotNull final String @NotNull[] command, @Nullable final String dir)
Creates a ProcessRunner for running the given command in the given directory.
Definition: ProcessRunner.java:153
net.sf.gridarta.utils.ProcessRunner.CopyOutput.Appender.Appender
Appender(@NotNull final JTextArea textArea)
Creates a new instance.
Definition: ProcessRunner.java:397
net.sf.gridarta.utils.ProcessRunner.LOG
static final Category LOG
The Logger for printing log messages.
Definition: ProcessRunner.java:58
net.sf.gridarta.utils.ActionBuilderUtils
Utility class for ActionBuilder related functions.
Definition: ActionBuilderUtils.java:31
net.sf.gridarta.utils.ProcessRunner.process
transient Process process
The Process.
Definition: ProcessRunner.java:103
net.sf.gridarta.utils.ProcessRunner.showDialog
void showDialog(@NotNull final Frame parent)
Show a dialog if not already visible.
Definition: ProcessRunner.java:189
net.sf.gridarta.utils.ProcessRunner.CopyOutput.Appender.texts
final Queue< String > texts
Strings to append.
Definition: ProcessRunner.java:385
net.sf.gridarta.utils.ProcessRunner.dir
final File dir
The working directory for the command.
Definition: ProcessRunner.java:97
net.sf.gridarta.utils.ProcessRunner.CopyOutput.start
void start(@NotNull final InputStream stream)
Starts running.
Definition: ProcessRunner.java:351
net.sf.gridarta.utils.ProcessRunner.CopyOutput.appender
final Appender appender
JTextArea to write to.
Definition: ProcessRunner.java:302
net.sf.gridarta.utils.ProcessRunner.controlStart
void controlStart()
Action method for starting.
Definition: ProcessRunner.java:224