/*
 * [TestOnTheFly.java]
 *
 * Summary: Demonstrate generating Java source code on the fly, compiling item and executing item.
 *
 * Copyright: (c) 2009-2017 Roedy Green, Canadian Mind Products, http://mindprod.com
 *
 * Licence: This software may be copied and used freely for any purpose but military.
 *          http://mindprod.com/contact/nonmil.html
 *
 * Requires: JDK 1.7+
 *
 * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/
 *
 * Version History:
 *  1.0 2006-02-04
 */
package com.mindprod.example;

import com.mindprod.hunkio.HunkIO;
import com.sun.tools.javac.Main;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

import static java.lang.System.out;

/**
 * interface that our on-the-fly compiled classes must implement.
 */
interface Calculator
    {
    /**
     * do some sort of calculation on two doubles.
     *
     * @param a first parameter
     * @param b second parameter
     *
     * @return result of custom calculation
     */
    @SuppressWarnings({ "SameParameterValue" })
    public double calc( double a, double b );

    /**
     * get timestamp when compiled.
     *
     * @return the timestamp when the program was compiled.
     */
    public Date whenCompiled();
    }

/**
 * Demonstrate generating Java source code on the fly, compiling item and executing item.
 * <p/>
 * ^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V<br>
 * V E R Y _ I M P O R T A N T !<br>
 * You MUST have F:\Program Files\java\jdk_1.7.0\lib\tools.jar from the JDK
 * or similar on the path both to compile and run com.sun.tools.javac.Main.<br>
 * The current directory when you execute MUST be E:\com\mindprod\example <br>
 * E:\ MUST be on the classpath.<br>
 * ^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V^V<br>
 *
 * @author Roedy Green, Canadian Mind Products
 * @version 1.0 2006-02-04
 * @since 2006-02-04
 */
public final class TestOnTheFly
    {
    /**
     * Compile from within this JVM without spawning javac.exe or a separate JVM com.sun.tools.javac.Main lives in
     * tools.jar Make sure J:\Program Files\Java\jdk1.7.0\lib\tools.jar is on the classpath. It won't be by default.
     * <p/>
     * THE JAVAC.MAIN TECHNIQUE USED IN THIS PROGRAM IS BOTH UNDOCUMENTED AND DEPRECATED!!!
     * Normally you would use javax.tools.JavaCompiler instead, the way TestJavaCompiler does as
     * described in http://mindprod.com/jgloss/javacompiler.html
     *
     * @param className   name of the class to compile.
     * @param programText source code text.
     *
     * @return status of the compile.
     * @throws java.io.IOException on I/O failure.
     * @see TestJavaCompiler
     */
    @SuppressWarnings({ "SameParameterValue" })
    private static int compile( String className,
                                String programText ) throws IOException
        {
        // save program to disk, using ISO-8859-1 encoding
        // e.g. item will show up in E:\com\mindprod\example\Hypotenuse.java
        HunkIO.writeEntireFile( new File( className + ".java" ),
                programText,
                HunkIO.ISO88591 );
        // simulate an arbitrarily long command line.
        // No wildcards since no command line interpreter to expand them.
        String[] optionsAndSources =
                { "-source", "1.5", "-target", "1.5", className + ".java" };
        PrintWriter listing = new PrintWriter( System.out, false );
        return Main.compile( optionsAndSources, listing );
        // e.g. the class file will show up in
        // E:\com\mindprod\example\Hypotenuse.class
        }

    /**
     * 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 " );
        sb.append( className );
        sb.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    on I/O failure.
     * @throws ClassNotFoundException If can't find generated class files to execute.
     * @throws IllegalAccessException if try to illegally access files.
     * @throws InstantiationException if can't create instance of new class.
     */
    public static void main( String[] args ) throws
            IOException,
            ClassNotFoundException,
            IllegalAccessException,
            InstantiationException
        {
        // compose text of Java program on the fly.
        String programText =
                composeAProgram( "Hypotenuse", "Math.sqrt( a*a + b*b )" );
        out.println( programText );
        // compile item
        int status = compile( "Hypotenuse", programText );
        out.println( "status of compile: " + status );
        // Load class and create an instance.
        Calculator calculator =
                ( Calculator ) Class.forName( "com.mindprod.example.Hypotenuse" )
                        .newInstance();
        // execute its methods:
        out.println( "Hypotenuse.calc( 3, 4 ) is : "
                     + calculator.calc( 3.0, 4.0 ) );
        out.println( "compiled on: " + calculator.whenCompiled() );
        }
    }