How can I restart a Java AWT application? I have a button to which I have attached an event handler. What code should I use to restart the application?
I want to do the same thing that Application.Restart() do in a C# application.
Of course it is possible to restart a Java application.
The following method shows a way to restart a Java application:
public void restartApplication()
{
final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
final File currentJar = new File(MyClassInTheJar.class.getProtectionDomain().getCodeSource().getLocation().toURI());
/* is it a jar file? */
if(!currentJar.getName().endsWith(".jar"))
return;
/* Build command: java -jar application.jar */
final ArrayList<String> command = new ArrayList<String>();
command.add(javaBin);
command.add("-jar");
command.add(currentJar.getPath());
final ProcessBuilder builder = new ProcessBuilder(command);
builder.start();
System.exit(0);
}
Basically it does the following:
Find the java executable (I used the java binary here, but that depends on your requirements)
Find the application (a jar in my case, using the MyClassInTheJar class to find the jar location itself)
Build a command to restart the jar (using the java binary in this case)
Execute it! (and thus terminating the current application and starting it again)
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
StringBuilder cmd = new StringBuilder();
cmd.append(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java ");
for (String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
cmd.append(jvmArg + " ");
}
cmd.append("-cp ").append(ManagementFactory.getRuntimeMXBean().getClassPath()).append(" ");
cmd.append(Main.class.getName()).append(" ");
for (String arg : args) {
cmd.append(arg).append(" ");
}
Runtime.getRuntime().exec(cmd.toString());
System.exit(0);
}
}
Dedicated to all those who say it is impossible.
This program collects all information available to reconstruct the original commandline. Then, it launches it and since it is the very same command, your application starts a second time. Then we exit the original program, the child program remains running (even under Linux) and does the very same thing.
WARNING: If you run this, be aware that it never ends creating new processes, similar to a fork bomb.
Basically, you can't. At least not in a reliable way. However, you shouldn't need to.
The can't part
To restart a Java program, you need to restart the JVM. To restart the JVM you need to
Locate the java launcher that was used. You may try with System.getProperty("java.home") but there's no guarantee that this will actually point to the launcher that was used to launch your application. (The value returned may not point to the JRE used to launch the application or it could have been overridden by -Djava.home.)
You would presumably want to honor the original memory settings etc (-Xmx, -Xms, …) so you need to figure out which settings where used to start the first JVM. You could try using ManagementFactory.getRuntimeMXBean().getInputArguments() but there's no guarantee that this will reflect the settings used. This is even spelled out in the documentation of that method:
Typically, not all command-line options to the 'java' command are passed to the Java virtual machine. Thus, the returned input arguments may not include all command-line options.
If your program reads input from Standard.in the original stdin will be lost in the restart.
Lots of these tricks and hacks will fail in the presence of a SecurityManager.
The shouldn't need part
I recommend you to design your application so that it is easy to clean every thing up and after that create a new instance of your "main" class.
Many applications are designed to do nothing but create an instance in the main-method:
public class MainClass {
...
public static void main(String[] args) {
new MainClass().launch();
}
...
}
By using this pattern, it should be easy enough to do something like:
public class MainClass {
...
public static void main(String[] args) {
boolean restart;
do {
restart = new MainClass().launch();
} while (restart);
}
...
}
and let launch() return true if and only if the application was shut down in a way that it needs to be restarted.
Strictly speaking, a Java program cannot restart itself since to do so it must kill the JVM in which it is running and then start it again, but once the JVM is no longer running (killed) then no action can be taken.
You could do some tricks with custom classloaders to load, pack, and start the AWT components again but this will likely cause lots of headaches with regard to the GUI event loop.
Depending on how the application is launched, you could start the JVM in a wrapper script which contains a do/while loop, which continues while the JVM exits with a particular code, then the AWT app would have to call System.exit(RESTART_CODE). For example, in scripting pseudocode:
DO
# Launch the awt program
EXIT_CODE = # Get the exit code of the last process
WHILE (EXIT_CODE == RESTART_CODE)
The AWT app should exit the JVM with something other than the RESTART_CODE on "normal" termination which doesn't require restart.
Eclipse typically restarts after a plugin is installed. They do this using a wrapper eclipse.exe (launcher app) for windows. This application execs the core eclipse runner jar and if the eclipse java application terminates with a relaunch code, eclipse.exe restarts the workbench. You can build a similar bit of native code, shell script or another java code wrapper to achieve the restart.
Windows
public void restartApp(){
// This launches a new instance of application dirctly,
// remember to add some sleep to the start of the cmd file to make sure current instance is
// completely terminated, otherwise 2 instances of the application can overlap causing strange
// things:)
new ProcessBuilder("cmd","/c start /min c:/path/to/script/that/launches/my/application.cmd ^& exit").start();
System.exit(0);
}
/min to start script in minimized window
^& exit to close cmd window after finish
a sample cmd script could be
#echo off
rem add some sleep (e.g. 10 seconds) to allow the preceding application instance to release any open resources (like ports) and exit gracefully, otherwise the new instance could fail to start
sleep 10
set path=C:\someFolder\application_lib\libs;%path%
java -jar application.jar
sleep 10 sleep for 10 seconds
Just adding information which is not present in other answers.
If procfs /proc/self/cmdline is available
If you are running in an environment which provides procfs and therefore has the /proc file system available (which means this is not a portable solution), you can have Java read /proc/self/cmdline in order to restart itself, like this:
public static void restart() throws IOException {
new ProcessBuilder(getMyOwnCmdLine()).inheritIO().start();
}
public static String[] getMyOwnCmdLine() throws IOException {
return readFirstLine("/proc/self/cmdline").split("\u0000");
}
public static String readFirstLine(final String filename) throws IOException {
try (final BufferedReader in = new BufferedReader(new FileReader(filename))) {
return in.readLine();
}
}
On systems with /proc/self/cmdline available, this probably is the most elegant way of how to "restart" the current Java process from Java. No JNI involved, and no guessing of paths and stuff required. This will also take care of all JVM options passed to the java binary. The command line will be exactly identical to the one of the current JVM process.
Many UNIX systems including GNU/Linux (including Android) nowadays have procfs However on some like FreeBSD, it is deprecated and being phased out. Mac OS X is an exception in the sense that it does not have procfs. Windows also does not have procfs. Cygwin has procfs but it's invisible to Java because it's only visible to applications using the Cygwin DLLs instead of Windows system calls, and Java is unaware of Cygwin.
Don't forget to use ProcessBuilder.inheritIO()
The default is that stdin / stdout / stderr (in Java called System.in / System.out / System.err) of the started Process are set to pipes which allow the currently running process to communicate with the newly started process. If you want to restart the current process, this is most likely not what you want. Instead you would want that stdin / stdout / stderr are the same as those of the current VM. This is called inherited. You can do so by calling inheritIO() of your ProcessBuilder instance.
Pitfall on Windows
A frequent use case of a restart() function is to restart the application after an update. The last time I tried this on Windows this was problematic. When overwrote the application's .jar file with the new version, the application started to misbehave and giving exceptions about the .jar file. I'm just telling, in case this is your use case. Back then I solved the issue by wrapping the application in a batch file and using a magic return value from System.exit() that I queried in the batch file and had the batch file restart the application instead.
Although this question is old and answered, I've stumbled across a problem with some of the solutions and decided to add my suggestion into the mix.
The problem with some of the solutions is that they build a single command string. This creates issues when some parameters contain spaces, especially java.home.
For example, on windows, the line
final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
Might return something like this:C:\Program Files\Java\jre7\bin\java
This string has to be wrapped in quotes or escaped due to the space in Program Files. Not a huge problem, but somewhat annoying and error prone, especially in cross platform applications.
Therefore my solution builds the command as an array of commands:
public static void restart(String[] args) {
ArrayList<String> commands = new ArrayList<String>(4 + jvmArgs.size() + args.length);
List<String> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
// Java
commands.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
// Jvm arguments
for (String jvmArg : jvmArgs) {
commands.add(jvmArg);
}
// Classpath
commands.add("-cp");
commands.add(ManagementFactory.getRuntimeMXBean().getClassPath());
// Class to be executed
commands.add(BGAgent.class.getName());
// Command line arguments
for (String arg : args) {
commands.add(arg);
}
File workingDir = null; // Null working dir means that the child uses the same working directory
String[] env = null; // Null env means that the child uses the same environment
String[] commandArray = new String[commands.size()];
commandArray = commands.toArray(commandArray);
try {
Runtime.getRuntime().exec(commandArray, env, workingDir);
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
}
}
If you realy need to restart your app, you could write a separate app the start it...
This page provides many different examples for different scenarios:
http://www.rgagnon.com/javadetails/java-0014.html
Similar to Yoda's 'improved' answer, but with further improvements (both functional, readability, and testability). It's now safe to run, and restarts for as as many times as the amount of program arguments given.
No accumulation of JAVA_TOOL_OPTIONS options.
Automatically finds main class.
Inherits current stdout/stderr.
public static void main(String[] args) throws Exception {
if (args.length == 0)
return;
else
args = Arrays.copyOf(args, args.length - 1);
List<String> command = new ArrayList<>(32);
appendJavaExecutable(command);
appendVMArgs(command);
appendClassPath(command);
appendEntryPoint(command);
appendArgs(command, args);
System.out.println(command);
try {
new ProcessBuilder(command).inheritIO().start();
} catch (IOException ex) {
ex.printStackTrace();
}
}
private static void appendJavaExecutable(List<String> cmd) {
cmd.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
}
private static void appendVMArgs(Collection<String> cmd) {
Collection<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
String javaToolOptions = System.getenv("JAVA_TOOL_OPTIONS");
if (javaToolOptions != null) {
Collection<String> javaToolOptionsList = Arrays.asList(javaToolOptions.split(" "));
vmArguments = new ArrayList<>(vmArguments);
vmArguments.removeAll(javaToolOptionsList);
}
cmd.addAll(vmArguments);
}
private static void appendClassPath(List<String> cmd) {
cmd.add("-cp");
cmd.add(ManagementFactory.getRuntimeMXBean().getClassPath());
}
private static void appendEntryPoint(List<String> cmd) {
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
StackTraceElement stackTraceElement = stackTrace[stackTrace.length - 1];
String fullyQualifiedClass = stackTraceElement.getClassName();
String entryMethod = stackTraceElement.getMethodName();
if (!entryMethod.equals("main"))
throw new AssertionError("Entry point is not a 'main()': " + fullyQualifiedClass + '.' + entryMethod);
cmd.add(fullyQualifiedClass);
}
private static void appendArgs(List<String> cmd, String[] args) {
cmd.addAll(Arrays.asList(args));
}
V1.1 Bugfix: null pointer if JAVA_TOOL_OPTIONS is not set
Example:
$ java -cp Temp.jar Temp a b c d e
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c, d]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp]
$
I was researching the subject myself when came across this question.
Regardless of the fact that the answer is already accepted, I would still like to offer an alternative approach for completeness. Specifically, Apache Ant served as a very flexible solution.
Basically, everything boils down to an Ant script file with a single Java execution task (refer here and here) invoked from a Java code (see here). This Java code, which can be a method launch, could be a part of the application that needs to be restarted. The application needs to have a dependency on the Apache Ant library (jar).
Whenever application needs to be restarted, it should call method launch and exit the VM. The Ant java task should have options fork and spawn set to true.
Here is an example of an Ant script:
<project name="applaucher" default="launch" basedir=".">
<target name="launch">
<java classname="package.MasinClass" fork="true" spawn="true">
<jvmarg value="-splash:splash.jpg"/>
<jvmarg value="-D other VM params"/>
<classpath>
<pathelement location="lib-1.jar" />
...
<pathelement location="lib-n.jar" />
</classpath>
</java>
</target>
</project>
The code for the launch method may look something like this:
public final void launch(final String antScriptFile) {
/* configure Ant and execute the task */
final File buildFile = new File(antScriptFile);
final Project p = new Project();
p.setUserProperty("ant.file", buildFile.getAbsolutePath());
final DefaultLogger consoleLogger = new DefaultLogger();
consoleLogger.setErrorPrintStream(System.err);
consoleLogger.setOutputPrintStream(System.out);
consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
p.addBuildListener(consoleLogger);
try {
p.fireBuildStarted();
p.init();
final ProjectHelper helper = ProjectHelper.getProjectHelper();
p.addReference("ant.projectHelper", helper);
helper.parse(p, buildFile);
p.executeTarget(p.getDefaultTarget());
p.fireBuildFinished(null);
} catch (final BuildException e) {
p.fireBuildFinished(e);
}
/* exit the current VM */
System.exit(0);
}
A very convenient thing here is that the same script is used for initial application start up as well as for restarts.
Old question and all of that. But this is yet another way that offers some advantages.
On Windows, you could ask the task scheduler to start your app again for you. This has the advantage of waiting a specific amount of time before the app is restarted. You can go to task manager and delete the task and it stops repeating.
SimpleDateFormat hhmm = new SimpleDateFormat("kk:mm");
Calendar aCal = Calendar.getInstance();
aCal.add(Calendar.SECOND, 65);
String nextMinute = hhmm.format(aCal.getTime()); //Task Scheduler Doesn't accept seconds and won't do current minute.
String[] create = {"c:\\windows\\system32\\schtasks.exe", "/CREATE", "/F", "/TN", "RestartMyProg", "/SC", "ONCE", "/ST", nextMinute, "/TR", "java -jar c:\\my\\dev\\RestartTest.jar"};
Process proc = Runtime.getRuntime().exec(create, null, null);
System.out.println("Exit Now");
try {Thread.sleep(1000);} catch (Exception e){} // just so you can see it better
System.exit(0);
System.err.println("Someone is Restarting me...");
setVisible(false);
try {
Thread.sleep(600);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
setVisible(true);
I guess you don't really want to stop the application, but to "Restart" it. For that, you could use this and add your "Reset" before the sleep and after the invisible window.
Related
This has got to be one of the strangest things I have ever observed. Consider the following Java program:
import java.io.IOException;
public class StrangeError {
public static void main(String[] args) {
try {
Process process = new ProcessBuilder(
"cmd",
"/c",
"\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set"
).start();
process.waitFor();
} catch (IOException|InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
I compiled it with javac StrangeError.java, copied it to my server running Windows Server 2012 R2, and ran it with java StrangeError.
Here's where things start to get weird. The program hangs, waiting for the process it spawned to finish. This is not the expected behavior, since the vcvarsall.bat script should complete immediately as well as set.
So I started playing around and discovered the following:
Removing set causes vcvarsall.bat to terminate
Removing vcvarsall.bat causes set to terminate
Replacing && with || causes everything to terminate correctly
Copying vcvarsall.bat to a location on the desktop and changing the path causes everything to terminate correctly
A nearly equivalent program works fine in Go using the same commands
I get this output if I run everything in WinDbg and interrupt the process after it hangs
This does not appear to be reproducible with vcvarsall.bat from MSVC2013 but is also reproducible with MSVC2015 on Windows 10
What on earth is wrong with the original program? If I copy and paste the entire command (cmd /c "C:\...) into Start->Run, it immediately launches cmd and terminates, as expected.
Is this a bug with Java? Is this a bug with Windows?
Is this a bug with Java? Is this a bug with Windows?
It's a bug in your code. :-)
By default, a child process created using a ProcessBuilder object has output redirected to a pipe, the parent end of which can be obtained using Process.getInputStream() and which is not automatically drained if your code does not make use of it.
Since your code simply calls .waitFor without making any provision to drain the pipe, it will deadlock as soon as the pipe's buffer overflows. I believe the default buffer size is 4,096 bytes. On my machine, the output of the command you're running is 5,192 bytes, but this will vary depending on the original contents of the environment block. (From the sounds of it, the output length in your environment is borderline, only just above the limit, so that even small changes like changing the version of VS make a difference.)
One of the many possible solutions, depending on what you're actually trying to do, is to tell Java not to pipe the child's output:
import java.io.IOException;
public class StrangeError {
public static void main(String[] args) {
try {
ProcessBuilder processb = new ProcessBuilder(
"cmd",
"/c",
"\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\" amd64 && set"
);
processb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process process = processb.start();
process.waitFor();
} catch (IOException|InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
Not possible to read standard input and output error inside the same ProcessBuilder.
So you need to create two ProcessBuilder
Process process1 = new ProcessBuilder(
"cmd",
"/c",
"\"C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\vcvarsall.bat\",
"amd64");
Process process2 = new ProcessBuilder(
"cmd",
"/c",
"set");
process1.start();
if (process1.waitFor() == 0) {
process2.start();
if (process2.waitFor() == 0) {
// Successfull execution
}
}
And one thing : I don't think it is a good practice to do shell/batch launches with Java (or another language). Maybe you should use a script (shell, batch, python, perl...) to control standard input/output streams.
Is there anyway that a java application can read from an already open CMD window. For example, I've opened a cmd, ran "dir" and then decided to run my application, is there any way my application can read the information that's already in the open CMD window without opening another?
Thanks in advance
UPDATE:
My hope was that when one java application was ran, it would check any currently opened CMD windows to ensure that another one of my command-line applications hadn't been ran before it opened it itself.
Just spool the output of the executed command in a file.
For example : do dir > spool.txt
and from the java program open the spool.txt file with a FileInputStream.
To be sure that the content is totally written before reading it, you could for example :
use a marker in the written file to indicate it
check the modification date of the written file (if the modification date doesn't change during a defined period, it may be the signal that the writing is finished)
Update with updated question : checking whether a CMD windows is opened
UPDATE: My hope was that when one java application was ran, it would
check any currently opened CMD windows to ensure that another one of
my command-line applications hadn't been ran before it opened it
itself.
You could simply list the running processes of Windows by using the tasklist.exe program.
The information is retrieved with one line by process.
Then, check if one of the lines starts with cmd.exe.
If it is the case, it means a cmd.exe program is already running.
Here is a sample code ;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class CmdProcessSearch {
public static void main(String[] args) throws IOException {
boolean anyCmdProgramOpened = isAnyCmdProgramOpened();
System.out.println("isOpened = " + anyCmdProgramOpened);
}
public static boolean isAnyCmdProgramOpened() {
Process p;
try {
p = Runtime.getRuntime().exec(System.getenv("windir") + "\\system32\\" + "tasklist.exe");
String line;
BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((line = input.readLine()) != null) {
System.out.println(line);
if (line.startsWith("cmd.exe")) {
return true;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
The C:\windows\system32 is usually in the PATH environment variable.
So, p = Runtime.getRuntime().exec("tasklist.exe"); should be enough
but to prevent any side effect or misconfiguration problem, specifying the absolute path is better.
New update with new comment : checking whether the Java application is running
I have used this code for a different section but if there is a cmd
window running, there is no guarantee that the user has used it to run
my program, package process;
In this case, if you can install jdk tools on the client machine you could check if is running a Java process using as main class, the main class of your application.
You could use jps (Java Virtual Machine Process Status Tool).
Here is the available options :
OPTIONS
The jps command supports a number of options that modify the
output of the command. These options are subject to change or removal
in the future.
-q
Suppress the output of the class name, JAR file name, and arguments passed to the main method, producing only a list of local VM
identifiers.
-m
Output the arguments passed to the main method. The output may be null for embedded JVMs.
-l
Output the full package name for the application's main class or the full path name to the application's JAR file.
-v
Output the arguments passed to the JVM.
-V
Output the arguments passed to the JVM through the flags file (the .hotspotrc file or the file specified by the -XX:Flags=
argument).
-Joption
Pass option to the java launcher called by jps. For example, -J-Xms48m sets the startup memory to 48 megabytes. It is a common convention for -J to pass options to the underlying VM executing
applications written in Java.
jps -l command that outputs the full package name for the application's main class or the full path name to the application's JAR file.
Here is a sample code by searching the main class of the application process.JavaProcessSearch :
package process;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class JavaProcessSearch {
public static void main(String[] args) throws IOException {
boolean isProcessRunning = isAJavaProcessWithMainClass("process.JavaProcessSearch");
System.out.println(isProcessRunning);
}
public static boolean isAJavaProcessWithMainClass(String mainClass) {
Process p;
try {
String javaHome = System.getenv("JAVA_HOME");
p = Runtime.getRuntime().exec(javaHome + File.separator + "bin/jps -l");
String line;
BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
while ((line = input.readLine()) != null) {
System.out.println(line);
if (line.contains(mainClass)) {
return true;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
If you cannot install jdk tools, you could store "somewhere" on the client machine, the process id of the application when it is started.
You can retrieve this information with the following method : ManagementFactory.getRuntimeMXBean().getName().
When it is not any longer running (stopped or crashed), you could remove this process id information.
When the application ia started, if a process id is retrieved on the client machine, you don't allow the application to start.
For stopped application case, shutdown hook should do the job.
For crashed application case, you could use a deamon thread that checks on a regular basis whether the application is always running by relying on the stored process id.
This is my first question on stackoverflow so I'll try to keep it concise and relevant.
I'm currently creating a Java program that is attempting to call an external program located on the system, in order to do this however I am required to call a shell script that sets up links to the relevant libraries to ensure that the system is linked to these before the external program can be executed.
The issue at hand is that I cannot invoke the shell script through Java, I've researched high and low and am aware that of alternative ways such as the use of the ProcessBuilder class. Unfortunately I'm quite new to the world of trying to invoke command line statements through Java and so I'm stuck for answers.
An example of the code I am using can be found below:
private void analyse_JButtonActionPerformed(java.awt.event.ActionEvent evt) {
// Get project path for copying of Fortran program to folder and execution
String projectPath = Newproject_GUI.getProjectPath();
String sourcePath [] = {"/bin/sh ", "-c ","source ~/set_env_WRF_gnu.sh"} ;
Runtime fortranAnalyser = Runtime.getRuntime();
try {
Process p = fortranAnalyser.exec("cp main.exe " + projectPath);
Process k = fortranAnalyser.exec(sourcePath);
BufferedReader is = new BufferedReader(new InputStreamReader(k.getInputStream()));
String line;
while ((line = is.readLine()) != null) {
System.out.println(line);
}
} catch (IOException ex) {
Logger.getLogger(Analyser_GUI.class.getName()).log(Level.SEVERE, null, ex);
}
}
Process p works fine and does indeed copy main.exe to the intended directory when the method is called. Process k however does not and this is where the issue is.
Thanks in advance.
The issue is "source" is internal command of BASH (you are using "sh" but that is just BASH in the simplified mode). So what you do is:
you spawn new process "sh" and source something there (setting some VARIABLES I guess)
the process ends and all VARIABLES are lost
you spawn another process, but VARIABLES are already gone
I am not sure if you use those variables later on, but according to the script name it is probably setting some. Don't do that like this.
By the way if you only want to execute script in bash, you don't need to source it. To get it's side effects, just execute it with:
String sourcePath [] = {"/bin/sh ", "/home/XYZ/set_env_WRF_gnu.sh"} ;
Please note you cannot use ~ in this case, use Java to get your home dir.
I am working on a program written in Java which, for some actions, launches external programs using user-configured command lines. Currently it uses Runtime.exec() and does not retain the Process reference (the launched programs are either a text editor or archive utility, so no need for the system in/out/err streams).
There is a minor problem with this though, in that when the Java program exits, it doesn't really quit until all the launched programs are exited.
I would greatly prefer it if the launched programs were completely independent of the JVM which launched them.
The target operating system is multiple, with Windows, Linux and Mac being the minimum, but any GUI system with a JVM is really what is desired (hence the user configurability of the actual command lines).
Does anyone know how to make the launched program execute completely independently of the JVM?
Edit in response to a comment
The launch code is as follows. The code may launch an editor positioned at a specific line and column, or it may launch an archive viewer. Quoted values in the configured command line are treated as ECMA-262 encoded, and are decoded and the quotes stripped to form the desired exec parameter.
The launch occurs on the EDT.
static Throwable launch(String cmd, File fil, int lin, int col) throws Throwable {
String frs[][]={
{ "$FILE$" ,fil.getAbsolutePath().replace('\\','/') },
{ "$LINE$" ,(lin>0 ? Integer.toString(lin) : "") },
{ "$COLUMN$",(col>0 ? Integer.toString(col) : "") },
};
String[] arr; // array of parsed tokens (exec(cmd) does not handle quoted values)
cmd=TextUtil.replace(cmd,frs,true,"$$","$");
arr=(String[])ArrayUtil.removeNulls(TextUtil.stringComponents(cmd,' ',-1,true,true,true));
for(int xa=0; xa<arr.length; xa++) {
if(TextUtil.isQuoted(arr[xa],true)) {
arr[xa]=TextDecode.ecma262(TextUtil.stripQuotes(arr[xa]));
}
}
log.println("Launching: "+cmd);
Runtime.getRuntime().exec(arr);
return null;
}
This appears to be happening only when the program is launched from my IDE. I am closing this question since the problem exists only in my development environment; it is not a problem in production. From the test program in one of the answers, and further testing I have conducted I am satisfied that it is not a problem that will be seen by any user of the program on any platform.
There is a parent child relation between your processes and you have to break that.
For Windows you can try:
Runtime.getRuntime().exec("cmd /c start editor.exe");
For Linux the process seem to run detached anyway, no nohup necessary.
I tried it with gvim, midori and acroread.
import java.io.IOException;
public class Exec {
public static void main(String[] args) {
try {
Runtime.getRuntime().exec("/usr/bin/acroread");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("Finished");
}
}
I think it is not possible to to it with Runtime.exec in a platform independent way.
for POSIX-Compatible system:
Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", "your command"}).waitFor();
I have some observations that may help other people facing similar issue.
When you use Runtime.getRuntime().exec() and then you ignore the java.lang.Process handle you get back (like in the code from original poster), there is a chance that the launched process may hang.
I have faced this issue in Windows environment and traced the problem to the stdout and stderr streams. If the launched application is writing to these streams, and the buffer for these stream fills up then the launched application may appear to hang when it tries to write to the streams. The solutions are:
Capture the Process handle and empty out the streams continually - but if you want to terminate the java application right after launching the process then this is not a feasible solution
Execute the process call as cmd /c <<process>> (this is only for Windows environment).
Suffix the process command and redirect the stdout and stderr streams to nul using 'command > nul 2>&1'
It may help if you post a test section of minimal code needed to reproduce the problem. I tested the following code on Windows and a Linux system.
public class Main {
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws Exception {
Runtime.getRuntime().exec(args[0]);
}
}
And tested with the following on Linux:
java -jar JustForTesting.jar /home/monceaux/Desktop/__TMP/test.sh
where test.sh looks like:
#!/bin/bash
ping -i 20 localhost
as well as this on Linux:
java -jar JustForTesting.jar gedit
And tested this on Windows:
java -jar JustForTesting.jar notepad.exe
All of these launched their intended programs, but the Java application had no problems exiting. I have the following versions of Sun's JVM as reported by java -version :
Windows: 1.6.0_13-b03
Linux: 1.6.0_10-b33
I have not had a chance to test on my Mac yet. Perhaps there is some interaction occuring with other code in your project that may not be clear. You may want to try this test app and see what the results are.
You want to launch the program in the background, and separate it from the parent. I'd consider nohup(1).
I suspect this would require a actual process fork. Basically, the C equivalent of what you want is:
pid_t id = fork();
if(id == 0)
system(command_line);
The problem is you can't do a fork() in pure Java. What I would do is:
Thread t = new Thread(new Runnable()
{
public void run()
{
try
{
Runtime.getRuntime().exec(command);
}
catch(IOException e)
{
// Handle error.
e.printStackTrace();
}
}
});
t.start();
That way the JVM still won't exit, but no GUI and only a limited memory footprint will remain.
I tried everything mentioned here but without success. Main parent Java process can't quit until the quit of subthread even with cmd /c start and redirecting streams tu nul.
Only one reliable solution for me is this:
try {
Runtime.getRuntime().exec("psexec -i cmd /c start cmd.cmd");
}
catch (Exception e) {
// handle it
}
I know that this is not clear, but this small utility from SysInternals is very helpful and proven. Here is the link.
One way I can think of is to use Runtime.addShutdownHook to register a thread that kills off all the processes (you'd need to retain the process objects somewhere of course).
The shutdown hook is only called when the JVM exits so it should work fine.
A little bit of a hack but effective.
I have a java class file with a main method. In Windows, I would like to be able to drag files onto a desktop icon/short/etc that would call supply the filenames to my main method. Basically, I want to allow users to drag-and-drop files at program execution instead of having type them on the command line.
Any thoughts?
To build on daub815's answer, in Windows, you can use a batch file to pass
arguments to another command. In this case, we'll use the java launcher to
launch your class with the main method.
I did a quick Google search on how to do write a batch file to take multiple arguments,
and found a page with a batch file to pass arguments to another command. Adapting from
the example, here is what you can do:
#ECHO OFF
:Loop
IF "%1" == "" GOTO Done
java YourClass %1
SHIFT
GOTO Loop
:Done
Save the above file as a batch file (with a ".bat" extension), and then you can drag-and-drop
files onto it, and it will be passed as arguments.
Also, you can call the batch file from the command line and pass arguments as well.
Edit: It appears that the batch file will not work with quoted arguments which contain spaces. Using a workaround presented in the site I've linked to will split the spaces contained in the quoted full path of the file into separate arguments, so that won't work either. If anyone has a good idea how to fix this, please either edit this entry, or post another answer. I will make this a community wiki.
PhiLho's answer works perfectly if you pack the classes in an executable JAR file (it's how you're meant to do it anyway) and make a .reg file that looks like the one below. Then just double-click that .reg file to merge it into the registry and you're good to go. This lets you both double-click a JAR file to run it, and starting it by Drag & Drop.
Do remember to change the path to where your Java executable is installed.
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.jar]
#="jarfile"
[HKEY_CLASSES_ROOT\jarfile\DefaultIcon]
#="C:\\Java\\jdk1.7.0\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\jarfile\shell\open]
#="Run Java Program"
[HKEY_CLASSES_ROOT\jarfile\shell\open\command]
#="\"C:\\Java\\jdk1.7.0\\bin\\java.exe\" -jar \"%1\" %*"
[HKEY_CLASSES_ROOT\jarfile\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
OK, I made it work... The base knowledge is to use DropHandler UUID in the registry. I made a base setting, as follow:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\.class]
#="JavaClass"
[HKEY_CLASSES_ROOT\JavaClass\DefaultIcon]
#="C:\\Java\\jdk1.6.0_05\\bin\\java.exe,1"
[HKEY_CLASSES_ROOT\JavaClass\shell\open]
#="Run Java class"
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\"C:\\Java\\jdk1.6.0_05\\bin\\java.exe\" \"%1\" %*"
[HKEY_CLASSES_ROOT\JavaClass\shellex\DropHandler]
#="{86C86720-42A0-1069-A2E8-08002B30309D}"
and... it didn't work!
I just forgot that java.exe wants a class name, not a file name! But I see no way to do that in the registry.
Fortunately, there is a workaround, which still need a script file if we want to be generic, to work on any/all class files (with static main function, of course!). Not batch, I avoid them when I can. I chose to use WSH, as it should be available on any modern Windows system. I also chose to make a JS script, it could have been a VB script as well.
So I made the following script (LaunchJavaClass.js):
if (WScript.Arguments.count() == 0)
{
WScript.StdOut.Write("No parameters");
WScript.Quit(1);
}
var className = WScript.Arguments.Item(0);
//~ WScript.StdOut.Write(className + "\n");
var m = className.match(/^(.*)\\(.+?)\.class$/);
if (m == null)
{
WScript.StdOut.Write("Not a class file");
WScript.Quit(1);
}
var classPath = m[1];
className = m[2];
//~ WScript.StdOut.Write(classPath + " >>> " + className + "\n");
var params = new Array();
for (i = 1; i < WScript.Arguments.count(); i++)
{
params[params.length] = WScript.Arguments.Item(i);
}
var cmd = "cmd /c cd /D " + classPath +
" & C:/Java/jdk1.6.0_05/bin/java.exe " +
className + " " + params.join(" ");
//~ WScript.StdOut.Write(cmd + "\n");
var shell = WScript.CreateObject("WScript.Shell");
//~ var exec = shell.Exec(cmd); // Can be used to get stdout
shell.Run(cmd, 0);
I left some output, not useful in this context, but usable for debugging (run with cscript).
Of course, the path to the JRE must be adjusted.
And I changed the command in the registry, as follow:
[HKEY_CLASSES_ROOT\JavaClass\shell\open\command]
#="\wscript -b "D:\\_PhiLhoSoft\\WSH\\LaunchJavaClass.js\" %1 %*"
Of course, adjust path, and keep the above other lines.
Now, if I drag'n'drop some files to a .class file, it gets the short file paths as arguments of the main() function.
import java.io.*;
class TestDnD
{
public static void main(String[] args)
{
Writer output = null;
try
{
output = new BufferedWriter(new FileWriter(new File("LogFile.txt")));
for (String arg : args)
{
output.write(arg + "\n");
}
}
catch (IOException ioe)
{
ioe.printStackTrace();
return;
}
finally
{
try { output.close(); } catch (IOException e) {}
}
}
}
I think the first version of the .reg file can be used for something else, eg. to drag'n'drop on .jar files (adapting it, of course).
This technique has limited use: we rarely make one-class programs in Java! But it looked like a good and interesting challenge, so I didn't resist to solve it. Note: you can add stuff like -Djava.ext.dirs="some path;another path" if you ever need to use external libraries (in jar files).
Adding onto Adiel A. If you create a batch file, which launches your a Java window using Swing. You would have the user drop the files onto that window. You could then be able to root through those dropped files.
So there's no way to have windows itself pass the args into main() via drag and drop?