package com.mindprod.example;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;

/**
 * Demonstrate generating Java source code on the fly, compiling it and executing it. Demonstrate generating Java source
 * code on the fly, compiling it with the JavaCompiler class and executing it. Note JavaCompiler is quite different from
 * JavaCompilerTool that was released with JDK 1.6 beta, now withdrawn. The source code is generated in RAM and never
 * written to disk.
 * <p/>
 * composed with IntelliJ IDEA
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.1, 2008-02-19
 */
public final class TestJavaCompiler
    {
    // -------------------------- STATIC METHODS --------------------------

    /**
     * Compile from within this JVM without spawning javac.exe or a separate JVM.
     *
     * @param source points to source, possibly 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
                /* default System.err */,
                null
                /* standard file manager,
          If we wrote our own we could control the location
          of the generated  class files. */,
                null
                /* standard DiagnosticListener */,
                null
                /* no options */,
                null
                /* no annotation classes */,
                // we must convert JavaFileObject... to Iterable<? extends JavaFileObject>
                Arrays.asList( source )
                /* source code */ );
        return task.call();
        }

    /**
     * Compose 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;\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();
        }

    // --------------------------- main() method ---------------------------

    /**
     * Compose 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
        {
        // compose text of Java program on the fly.
        final String programText =
                composeAProgram( "Hypotenuse", "Math.sqrt( a*a + b*b )" );
        System.out.println( "PROGRAM TO BE COMPILED" );
        System.out.println( programText );
        // compile item
        final boolean status = compile( new RAMResidentJavaFileObject( "Hypotenuse", programText ) );
        System.out.println( "status of compile: " + status );
        // Load class and create an instance.
        final Calculator calculator =
                ( Calculator ) Class.forName( "com.mindprod.example.Hypotenuse" )
                        .newInstance();
        // execute its methods:
        System.out
                .println( "Hypotenuse.calc( 3, 4 ) is : "
                          + calculator.calc( 3.0, 4.0 ) );
        System.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;

    // -------------------------- PUBLIC INSTANCE  METHODS --------------------------
    /**
     * 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;
        }
    }