7
\$\begingroup\$

I was feeling bored with nothing to do (well, sort of) and decided to write a program that runs a program in Java. What it pretty much does:

  1. Creates a new file with the name classname + ".java"
  2. Write the code into that file
  3. if it is not already compiled, run javac filename.java on the command line
  4. run java filename on the command line

The code is below:

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * Represents some Java source code.
 * 
 * <p>
 * This source code then can be ran via the {@link #run()} method, or can be
 * just used as a storage for source code.
 * </p>
 */
public class JavaCode {

    private static final String JAVA = ".java";
    private static final String CLASS = ".class";
    private static final String COMPILE = "javac ";
    private static final String RUN = "java ";

    private static final String IO_ERROR_MESSAGE = "An I/O error has occured.";

    private final String code;
    private final File codeFile;
    private final String className;

    private boolean isCompiled = false;

    /**
     * Creates a new <code>JavaCode</code> object.
     * 
     * @param code
     *            The source code.
     * @param className
     *            The name of the <code>public</code> class
     */
    public JavaCode(String code, String className) {
        this.code = code;
        this.className = className;
        this.codeFile = new File(className + JAVA);
        try {
            // Delete any previous file with same name
            this.codeFile.delete();
            this.codeFile.createNewFile();
            writeCodeToFile();
        } catch (IOException e) {
            System.err.println(IO_ERROR_MESSAGE);
            e.printStackTrace();
        }
        codeFile.deleteOnExit();
        new File(className + CLASS).deleteOnExit();
    }

    private void writeCodeToFile() throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(
                this.codeFile))) {
            writer.write(this.code);
        }
    }

    /**
     * Gets the source code of the object.
     * 
     * <p>
     * Equivalent to {@link #toString()}.
     * </p>
     * 
     * @return the source code
     * 
     * @see #toString()
     */
    public String getCode() {
        return code;
    }

    /**
     * Gets the class name of the source code.
     * 
     * @return the class name of the source code
     */
    public String getClassName() {
        return className;
    }

    /**
     * {@inheritDoc}
     * 
     * Equivalent to {@link #getCode()}.
     * 
     * @return the source code
     * 
     * @see #toString()
     */
    @Override
    public String toString() {
        return getCode();
    }

    /**
     * Runs the source code using the command line.
     * 
     * <p>
     * Runs the program by first creating a new file named
     * <code>classname.java</code>, replacing <code>classname</code> with
     * whatever the class is named, executing <code>javac classname.java</code>,
     * then <code>java classname</code>.
     * </p>
     * 
     * <p>
     * It will delete the <code>.java</code> and <code>.class</code> files on
     * exit of program.
     * </p>
     */
    public void run() {
        try {
            if (!isCompiled) {
                ProcessInfo info = runProcess(COMPILE + this.className + JAVA);
                if (info.exitCode == 0) {
                    isCompiled = true;
                } else {
                    // Compiler error
                    System.err.print(info.errorMessage);
                    return;
                }
            }
            runProcess(RUN + this.className);
        } catch (IOException e) {
            System.err.println(IO_ERROR_MESSAGE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private ProcessInfo runProcess(String command) throws IOException,
            InterruptedException {
        Process process = Runtime.getRuntime().exec(command);
        System.out.print(getLines(process.getInputStream()));
        process.waitFor();
        return new ProcessInfo(process.exitValue(),
                getLines(process.getErrorStream()));
    }

    private String getLines(InputStream instream) throws IOException {
        String line = null;
        StringBuilder result = new StringBuilder();
        BufferedReader in = new BufferedReader(new InputStreamReader(instream));
        while ((line = in.readLine()) != null) {
            result.append(line).append('\n');
        }
        return result.toString();
    }

    private class ProcessInfo {

        private final int exitCode;
        private final String errorMessage;

        private ProcessInfo(int exitCode, String errorMessage) {
            this.exitCode = exitCode;
            this.errorMessage = errorMessage;
        }

    }

}

As an example, I ran a simple "Hello, World!" program and another (not-so-simple) Project Euler #1 that I wrote.

Code:

public static void main(String[] args) {
    new JavaCode("public class Main{public static void main(String[]args){System.out.println(\"Hello, World!\");}}", "Main").run();
    new JavaCode("public class MultipleFinder {private static final int MAX_NUMBER = 1000;private static final int[] MULTIPLES = new int[] { 3, 5 };public static void main(String[] args) {long time = System.nanoTime();int sum = 0;for(int multiple : MULTIPLES) {sum += triangle((MAX_NUMBER - 1) / multiple) * multiple;}sum -= triangle((MAX_NUMBER - 1) / (MULTIPLES[0] * MULTIPLES[1])) * (MULTIPLES[0] * MULTIPLES[1]);System.out.println(\"Result: \" + sum + \"\\nTime used for calculation in nanoseconds: \" + (System.nanoTime() - time));}private static int triangle(int i) {return (i + 1) * i / 2;}}", "MultipleFinder").run();
}

Note that I just put it on one line: I was too lazy to format it right.

The result makes sense (hover over to see the results; it is hidden for those that haven't solved Project Euler #1 yet):

Hello, World!
Result: 233168
Time used for calculation in nanoseconds: 34486

Concerns:

  1. Is there a simpler way to do what I did here?
  2. Anything in the java.nio package that can make my life easier?
  3. Is JavaDoc okay?
  4. And as usual, anything else?

Some notes:

  • The program may be ran multiple times. If this was the case, I would already have a compiled class file ready to be ran, therefore making it faster.
\$\endgroup\$
  • 2
    \$\begingroup\$ Is it a requirement to produce a .java file and a .class file? Or is your main goal to run some Java source code? \$\endgroup\$ – 200_success Nov 7 '15 at 8:17
  • 1
    \$\begingroup\$ @200_success My main goal is to run java source code. I just realised that forgot to make the files "delete on exit"... \$\endgroup\$ – TheCoffeeCup Nov 8 '15 at 2:07
5
\$\begingroup\$

Here:

    codeFile.deleteOnExit();
    new File(className + CLASS).deleteOnExit();

This does not actually delete all the files. Consider this example code:

public class Foo {
    public static void main(String[] args) {
        Bar.sayHi();
    }
}

class Bar {
    void sayHi() {
        System.out.println("hi");
    }
}

The program will delete the .java file, but will not delete the Bar.class file. Leaving a mess was not what I intended.

\$\endgroup\$
3
\$\begingroup\$

If your main goal is just to run some Java code, then you might be better off doing everything in memory using the Java Compiler API. Nothing needs to get written to disk.

I would expect it to be faster, but it's a lot more code to write, though.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.