star2wars3
star2wars3

Reputation: 83

How Do I Compile/Run a java program from a Java Program (Input failure?)

Background info:

I am a high school student who is currently learning Java and as so if my code has an obvious flaw in it/ I accidentally reinvent the wheel with the code, I apologize.

Recently I have been working on writing an esoteric language and decided that I wanted to write it as an interpreter that translates the code to Java and then ran the code. My first step towards this was an attempt to create a mini-program that compiled and ran a java program. Most of the code from that was scrounged from another article, which is the third or fourth article I've looked threw: how to compile & run java program in another java program?

I used the code from the third answer on that thread and initially thought that it worked. Unfortunately, when I tried running the code using the filename of the class for the program to be compiled and run within itself, the program failed.

Here is the modified code:

 /**
 *Functions printLines, Run, and parts of main came from stacks overflow 
 *originaly but modifications have been made
 *https://stackoverflow.com/questions/4842684/how-to-compile-run-java-program-in-another-java-program
 */
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.util.Scanner;

public class JTest
{
        private static void printLines(String name, InputStream ins) throws Exception 
    {
            String line = null;
        BufferedReader in = new BufferedReader(new InputStreamReader(ins));
            while ((line = in.readLine()) != null) 
            {
                //System.out.println(name + " " + line);
                System.out.println(line);
            }
    }
private static int run(String command) throws Exception 
    {
        System.out.println(command);//prints command
            Process pro = Runtime.getRuntime().exec(command);
            printLines(command, pro.getInputStream());
            printLines(command + " stderr:", pro.getErrorStream());
            pro.waitFor();
        // System.out.println(command + " exitValue() " + pro.exitValue());
            return pro.exitValue();
    }
    public static void main(String args[]) 
    {
    System.out.println("Enter the name of the file you want to run: ");
    Scanner cin = new Scanner(System.in);  
        String jFileName = cin.nextLine();  

        try 
        {
            int k =  run("javac " + jFileName + ".java");
            if (k==0)
            k=run("java " + jFileName);
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
        }
    }  
}

I also used another class:

public class Cout
{
    public static void main(String args [])
    {
        System.out.println("Hello World");
    }
}

In my initial test...

Output:

Enter the name of the file you want to run:

Input:

Cout

Output:

javac Cout.java
java Cout
Hello World

Here's what Happened when I tried to run JTest from JTest...

Output:

Enter the name of the file you want to run:

Input:

JTest

Output:

javac JTest.java
java JTest
Enter the name of the file you want to run:

Input:

Cout

After I entered this, nothing more was outputted onto the terminal window which leads to my main question:

Why didn't my code run the Cout class and how do I fix it? (Preferably in a way that makes my code compatible with both linux and windows) Or is there a resource someone could point me towards?

Upvotes: 2

Views: 1576

Answers (3)

star2wars3
star2wars3

Reputation: 83

After looking at the answers above, I realized that I forgot that I could call the main method to create a bit of a workaround. So while I will need to create a variable string at some point, here is the code along with its input and Output.

Class JTest

/**
*Functions printLines, Run, and parts of main came from stacks overflow 
*originaly but modifications have been made
*http://stackoverflow.com/questions/4842684/how-to-compile-run-java-program-in-another-java-program
*/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Scanner;

public class JTest
{
    private static void printLines(String name, InputStream ins) throws Exception 
    {
        String line = null;
    BufferedReader in = new BufferedReader(new InputStreamReader(ins));
        while ((line = in.readLine()) != null) 
        {
            System.out.println(line);
        }
    }
    private static int run(String command) throws Exception 
    {
        Process pro = Runtime.getRuntime().exec(command);
        printLines(command, pro.getInputStream());
        printLines(command + " stderr:", pro.getErrorStream());
        pro.waitFor();
        return pro.exitValue();
    }





    public static void main(String args[]) 
    {
    System.out.println("Enter the name of the file you want to run: ");
    Scanner cin = new Scanner(System.in);  
        String jFileName = cin.nextLine();  

        try 
        {
        String arg[] = { "" } ;
        int binary = cin.nextInt();
            int k =  run("javac " + jFileName + ".java");
            if (k == 0)
            if (binary == 1)
                JTest.main(arg);
            else
                Foo.main(arg);
        } 
        catch (Exception e) 
        {
            e.printStackTrace();
        }
    }  
}

Class Foo

import java.util.Scanner;
public class Foo
{
    public static void main(String args [])
    {
        Scanner cin = new Scanner(System.in);  
        int bar = cin.nextInt();
        System.out.println("Your number times 2 is: " + (bar * 2));
    }

}

Input Output Dialogue

Output:

Enter the name of the file you want to run:

Input:

JTest
1

Output:

Enter the name of the file you want to run:

Input:

JTest
1

Output:

Enter the name of the file you want to run:

Input

Foo
0
4

Output:

Your number times 2 is: 4

As the program demonstrates, both input and output work fine.

Upvotes: 0

RealSkeptic
RealSkeptic

Reputation: 34628

Your main issue is understanding input and output streams.

Every process has three standard streams: standard input, standard output and standard error.

When you normally run a program from a command shell, be it Windows CMD or Linux terminal/console, the standard input is attached to the terminal's input stream, and the standard output and error to the console output.

When you run a process from within Java, especially when you use Runtime.exec rather than use a ProcessBuilder, the standard streams are piped from and two the calling program.

What you type into your "front" program doesn't automatically go to the "back" program. The "back" program calls nextLine on a scanner on System.in. Its System.in is redirected to the "front" program through Process.getOutputStream(). It is waiting for something to come through from that pipe. But your "front" program doesn't write anything to that stream. The only streams it has taken care of are the standard output and standard error - the output from the "back" program which is input from the point of view of the "front" program.

So the "back" program will sit and wait and do nothing. And your "front" program at this stage is trying to read its output. It will not stop reading it until the "back" program terminates or closes its standard output. Which of course it doesn't do.

So the two processes are deadlocked. Each of them is waiting for something from the other process.

In fact, there is another possible problem with the way you handle your streams. For example, if the program has errors, those errors will be placed in the standard error stream. If the program terminates, good. But if not, you'll never get to reading the standard error, because you'll still be endlessly waiting for the "standard output" from that program, which may not exist at all.


A possible solution to all this is to have separate threads handling each of the streams.

  • One thread will need to read the console input ("front" program System.in), and pass anything it reads to the getOutputStream() (standard input of "back" program).
  • One thread will need to read the "back" program's standard output (getInputStream()), and send everything to its own System.out.
  • One thread will need to do the same for the error stream and System.err.

But the complication is that when the "back" program terminates, you need to have those threads stop, so that you can read your own System.in again and run another command. The output-handling threads are relatively easy - when the process terminates, they will see "end of file" and they can terminate then. But the "input" reading thread will need to have a mechanism that interrupts it when the "back" program terminated.

BTW, if you use ProcessBuilder to build your process, you'll have better control of the redirection of your input and output. You could let your program write its output and error messages directly to console. You'll still need to design the input properly - lines that are intended for the "front" program should not be consumed by mistake by the "back" program, so you can't do without redirection for input.

Upvotes: 1

Dad
Dad

Reputation: 1

It works for me under Fedora 23.

Here is my output:

$ java JTest 
Enter the name of the file you want to run: 
Cout
javac Cout.java
java Cout
Hello World

I have both JTest.java and Cout.java in the current directory when I run them.

Upvotes: 0

Related Questions