Created
February 4, 2012 07:06
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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