Skip to content

Instantly share code, notes, and snippets.

@ryan-beckett
Created February 4, 2012 07:06
Show Gist options
  • Save ryan-beckett/1735999 to your computer and use it in GitHub Desktop.
Save ryan-beckett/1735999 to your computer and use it in GitHub Desktop.
JInterpreterDemo: A demo displaying how the JavaCompiler API can be used to create an application that executes java code given as runtime input from a user. This gist also includes a facade for the JavaCompiler API and a JInterpreter class for interpreti
import java.io.*;
import java.net.*;
import java.util.*;
/**
* A Java statement interpreter. Add a list of statements with
* <code>newStatement</code> and run them with
* <code>run</code>. After running the interpreter, all
* previously added statements are forgotten.
*/
public class JInterpreter {
private static JInterpreter INSTANCE;
private JPlatform compiler;
private StringBuilder source = new StringBuilder();
private StringBuilder imports = new StringBuilder();
private JInterpreter(String outputDir) {
compiler = JPlatform.getInstance(outputDir);
}
/**
* Get an instance of the interpreter.
*
* @param outputDir An absolute path where support
* files will be created. They aren't
* deleted on exit.
*/
public static JInterpreter getInstance(String outputDir) {
if (INSTANCE == null)
INSTANCE = new JInterpreter(outputDir);
INSTANCE.reset();
return INSTANCE;
}
public void setOutputDirectory(String outputDir) {
compiler.setOutputDirectory(outputDir);
}
public String getOutputDirectory() {
return compiler.getOutputDirectory();
}
/**
* Reset the interpreter to a fresh state.
*/
public void reset() {
source.delete(0, source.length());
imports.delete(0, imports.length());
}
private void beginSource() {
source.insert(0, "public class Runner { public void run() { try { ");
source.insert(0, imports.toString());
}
private void endSource() {
source.append(" }catch(Exception e){ e.printStackTrace(); } } }");
}
/**
* Add a new statement to the interpreter.
*
* @param statement A valid Java statement other than a return statement.
*
* @throws Exception If a return statement is added.
*/
public void newStatement(String statement) throws Exception {
if(statement.length() == 6) {
if (statement.substring(0, 6).equals("return"))
throw new Exception("No return statements allowed.");
else if (statement.substring(0, 6).equals("import"))
newImportStatement(statement);
}
else
source.append(statement);
}
private void newImportStatement(String statement) {
imports.append(statement);
}
/**
* Run the statements given to the interpreter.
*
* @return Returns <code>true</code> if the run was successful, otherwise
* returns <code>false</code>.
*
* @throws Exception If an error occurs.
*/
public boolean run() throws Exception {
beginSource();
endSource();
File sourceFile = compiler.createSourceFile("Runner.java",
source.toString());
if (compiler.compile(sourceFile)) {
Object instance = compiler.newInstance("Runner");
compiler.invokeMethod(instance, "run", null);
reset();
return true;
}
reset();
System.out.println(compiler.getCompileError());
return false;
}
}
import java.io.*;
import java.util.*;
public class JInterpreterDemo {
private static String tmpLocation;
private static JInterpreter interpreter;
public static void main(String[] args) throws Exception {
System.out.print("Enter a valid temporary "
+ "file directory (that you have access permissions for): ");
Scanner kbd = new Scanner(System.in);
tmpLocation = kbd.nextLine();
if (!isFilePathValid(tmpLocation)) {
System.out.println("Invalid file location. Quitting.");
System.exit(1);
}
interpreter = JInterpreter.getInstance(tmpLocation);
System.out.print("Run in interactive mode (Y/N): ");
String choice = kbd.nextLine();
if (choice.equalsIgnoreCase("Y"))
runInteractiveMode();
else if(choice.equalsIgnoreCase("N"))
runStaticMode();
else
System.out.println("Invalid choice. Quitting.");
}
private static boolean isFilePathValid(String path) {
return new File(path).canWrite();
}
private static void runInteractiveMode() {
showUsage();
Scanner kbd = new Scanner(System.in);
String command = null;
try {
do {
System.out.print("> ");
command = kbd.nextLine().trim();
if (command.equals("quit"))
break;
else if (command.equals("reset"))
interpreter.reset();
else if (command.equals("run"))
interpreter.run();
else
interpreter.newStatement(command);
} while (true);
System.out.println("Bye!");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void showUsage() {
System.out
.println("\nEnter a valid Java statement "
+ "or import statement. \nNo return or package statements, field, "
+ "method, or top-level class declarations are allowed."
+ "\nLocal class declarations and anonymous classes are allowed!"
+ "\nYou can enter an import statement at any point."
+ "\nException-handling is taken care of for you."
+ "\nEnter 'run' to run the interpreter, 'reset' to reset it, or 'quit' to quit."
+ "\nNote: You must enter all statements to be interpreted *before* running."
+ "\nAfter running the interpreter all statements are lost.");
System.out
.println("\nSome examples of valid interpreter statements: \n\n"
+ "import java.util.*;\n"
+ "int i = 0;\n"
+ "if(Math.pow(2, 2) == 4.0)System.out.println(\"Equal\");\n"
+ "int i = 0 ; if( i < 100) System.out.println(\"Nice!\");\n"
+ "class HelloWorld { void hello() { System.out.println(\"Hello!\"); } } new HelloWorld().hello(); \n");
}
private static void runStaticMode() {
System.out
.println("\nThe following output was produced from interpreted "
+ "Java statements sent in from a client of the Interpreter class.\n");
try {
interpreter.newStatement("int i = 10;");
interpreter.newStatement("System.out.println(i);");
interpreter
.newStatement("if(i < 100)System.out.println(\"i is less than 100\");");
interpreter.newStatement("for(int r = 0; r < 5; r++)");
interpreter.newStatement("System.out.println(r);");
interpreter.run();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import javax.tools.*;
import java.io.*;
import java.net.*;
import java.util.*;
import java.lang.reflect.*;
/**
* A facade to the JavaCompiler API. If the source file resides
* in a package, be sure to specify the proper relative path
* to <code>createSourceFile</code>. E.g., if the source belongs
* to the package some.folder, the relative path is
* <code>/some/folder</code>.
*/
public class JPlatform {
private static JPlatform INSTANCE;
private File log;
private String outputDir;
private JPlatform(String outputDir) {
this.outputDir = outputDir;
log = new File(outputDir + "/error.log");
}
/**
* Get an instance of the compiler.
*
* @param outputDir An absolute path where the source and class
* files will be created.
*/
public static JPlatform getInstance(String outputDir) {
if (INSTANCE == null)
INSTANCE = new JPlatform(outputDir);
return INSTANCE;
}
public void setOutputDirectory(String outputDir) {
this.outputDir = outputDir;
log = new File(outputDir + "/error.log");
}
public String getOutputDirectory() {
return this.outputDir;
}
/**
* Create the source file.
*
* @param file A relative path for the file to be created.
* The path is relative to the output directory.
* @param source The source code.
*
* @return The newly created source file.
*/
public File createSourceFile(String file, String source) {
File root = new File(outputDir);
File sourceFile = new File(root, file);
try {
new FileWriter(sourceFile).append(source).close();
} catch (IOException e) {
e.printStackTrace();
}
return sourceFile;
}
/**
* Compile the source file.
*
* @param source The source file. The class file is written to the
* same directory as the source file.
*
* @return Returns <code>true</code> if the compilation succeeded, otherwise
* returns <code>false</code>.
*/
public boolean compile(File source) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int success = 1;
try {
success = compiler.run(null, null, new FileOutputStream(log),
source.getPath());
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return (success == 0) ? true : false;
}
/**
* Create a new instance of the specified class.
*
* @param qualifiedClass The fully-qualified class name.
*
* @return A new instance of the specified class.
*
* @throws Exception If an error occurs.
*/
public Object newInstance(String qualifiedClass) throws Exception {
File outputDirFile = new File(outputDir);
URLClassLoader classLoader = URLClassLoader
.newInstance(new URL[] { outputDirFile.toURI().toURL() });
Class<?> cls = Class.forName(qualifiedClass, true, classLoader);
return cls.newInstance();
}
/**
* Invoke the specified method on the given instance with the specified arguments.
*
* @param instance The instance to invoke the method on.
* @param method The method to be invoked.
* @param args The arguments to be passed to the method.
*
* @return The return value of the invoked method.
*
* @throws Exception If the method couldn't be found or an error occurs.
*/
public Object invokeMethod(Object instance, String method,
Object... args) throws Exception {
Class<?> cls = instance.getClass();
for (Method m : cls.getDeclaredMethods()) {
if (m.getName().equals(method)) {
return m.invoke(instance, args);
}
}
throw new Exception("Couldn't find the declared method with that name.");
}
/**
* Return the compiler error if <code>compile</code> fails.
*
* @return The compiler error or <code>null</code> if no error is found.
*/
public String getCompileError() {
StringBuilder error = new StringBuilder();
try {
Scanner scnr = new Scanner(log);
if(!scnr.hasNextLine())
return null;
String errorLine = scnr.nextLine();
String[] tokens = errorLine.split(" ");
for(int i = 1; i < tokens.length; i++)
error.append(tokens[i]+" ");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return error.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment