package com.mindprod.example;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Date;
import static java.lang.System.*;
interface Calculator
{
double calc( double a, double b );
Date whenCompiled();
}
/**
* Demonstrate generating Java source code on the fly, compiling it and executing it.
* <p/>
* Demonstrate generating Java source.
* <p/>
* This example generates Java source code on the fly, compiles it with the JavaCompiler class and executes it.
* Note JavaCompiler is quite different from JavaCompilerTool that was released with JDK 1.6 beta, now withdrawn.
* This sample program generates the Java source code RAM and never writes it to disk.
* <p/>
* Note: the Hypotenuse.class file, that is dynamically generated by TestJavaCompiler in the CWD,
* MUST be accessible via the classpath to be able to load.
* For example, if the CWD is E:\myprojects\com\mindprod\example, then E:\myprojects must be on the classpath!!
* <p/>
* If you are doing experiments with TestJavaCompiler, it is wise to delete the generated Hypotenuse.class file
* after each run so you can be sure on the next run you are accessing a freshly generated copy.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.2 2009-08-04 add comments about the class files being placed in the CWD,
* and the necessity of the CWD being accessible from the classpath.
* @since 2009
*/
public final class TestJavaCompiler
{
/**
* Compile from within this JVM without spawning javac.exe or a separate JVM.
*
* @param source points to source, in this case, will be in RAM.
*
* @return status of the compile, true all went perfectly without error.
* @throws java.io.IOException if trouble writing class files.
*/
@SuppressWarnings( { "JavaDoc" } )
private static boolean compile( JavaFileObject... source )
{
final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
final JavaCompiler.CompilationTask task = compiler.getTask( null,
null,
null,
null,
null,
Arrays.asList( source )
);
return task.call();
}
/**
* Generate source for sample Java program on the fly.
*
* @param className name of class you want to generate
* @param expression a an expression involving a and/or b you want to calculate.
*
* @return text of an on-the-fly composed Java class.
*/
@SuppressWarnings( { "SameParameterValue" } )
private static String composeAProgram( String className, String expression )
{
final StringBuilder sb = new StringBuilder( 1000 );
sb.append( "package com.mindprod.example;import static java.lang.System.out;import static java.lang.System.err;\n" );
sb.append( "import java.util.Date;\n" );
sb.append( "public final class " ).append( className ).append( " implements Calculator\n" );
sb.append( "{\n" );
sb.append( "public double calc( double a, double b )\n" );
sb.append( " {\n" );
sb.append( " return " );
sb.append( expression );
sb.append( ";\n" );
sb.append( " }\n" );
sb.append( "public Date whenCompiled()\n" );
sb.append( " {\n" );
sb.append( " return new Date(" );
sb.append( System.currentTimeMillis() );
sb.append( "L);\n" );
sb.append( " }\n" );
sb.append( "}\n" );
return sb.toString();
}
/**
* Generate a program on the fly, compile it, and execute it.
*
* @param args not used
*
* @throws java.io.IOException if problems writing class files.
* @throws ClassNotFoundException if generated class cannot be found.
* @throws IllegalAccessException if try to instantiate a class we are not permitted to access.
* @throws InstantiationException if cant instantiate class
* @throws java.net.URISyntaxException if malformed class name.
*/
public static void main( String[] args ) throws URISyntaxException,
IOException,
ClassNotFoundException,
IllegalAccessException,
InstantiationException
{
final String programText =
composeAProgram( "Hypotenuse", "Math.sqrt( a*a + b*b )" );
out.println( "PROGRAM TO BE COMPILED" );
out.println( programText );
final boolean status = compile( new RAMResidentJavaFileObject( "Hypotenuse", programText ) );
out.println( "status of compile: " + status );
Calculator calculator = null;
try
{
calculator =
( Calculator ) Class.forName( "com.mindprod.example.Hypotenuse" )
.newInstance();
}
catch ( Exception e )
{
err.println();
err.println( "E R R O R" );
err.println( e.getMessage() );
err.println( "Unable to load generated Hypotenuse.class file in package com.mindprod.example." );
err.println(
"Note: the Hypotenuse.class file, that is dynamically generated by TestJavaCompiler in the CWD, " +
"must be accessible via the classpath to be able to load."
);
err.println(
"For example, if the CWD is E:\\myprojects\\com\\mindprod\\example, " +
"then E:\\myprojects must be on the classpath."
);
err.println( "Your CWD is " + new File( "." ).getCanonicalPath() );
err.println( "Your classpath is " + System.getProperty( "java.class.path", "" ) );
System.exit( 1 );
}
out.println( "Hypotenuse.calc( 3, 4 ) is : "
+ calculator.calc( 3.0, 4.0 ) );
out.println( "compiled on: " + calculator.whenCompiled() );
}
}
/**
* Represents the source text of a Java program in RAM.
*/
class RAMResidentJavaFileObject extends SimpleJavaFileObject
{
/**
* source text of the program to be compiled
*/
private final String programText;
/**
* constructor
*
* @param className class name, without package
* @param programText text of the program.
*
* @throws java.net.URISyntaxException if malformed class name.
*/
@SuppressWarnings( { "SameParameterValue" } )
public RAMResidentJavaFileObject( String className, String programText ) throws URISyntaxException
{
super( new URI( className + ".java" ), Kind.SOURCE );
this.programText = programText;
}
/**
* Get the text of the java program
*
* @param ignoreEncodingErrors ignored.
*/
@Override
public CharSequence getCharContent( boolean ignoreEncodingErrors ) throws IOException
{
return programText;
}
}