ColdFusion 8: Experiment with compiling Java code from ColdFusion - Part 2
In Part 1 I described how you can use JavaCompiler's run() method to compile source code from a java.io.File. However the api also describes how you might extend the SimpleJavaFileObject class so you can compile source code from a string, rather than a physical file.
Since you can now compile code on-the-fly,  you can easily use the CF code from part 1 to create their sample class: JavaSourceFromString.
<cfsavecontent variable="javaCode">
import javax.tools.SimpleJavaFileObject;
import java.net.URI;
/**
  * A file object used to represent source coming from a string.
  */
 public class JavaSourceFromString extends SimpleJavaFileObject {
     /**
      * The source code of this "file".
      */
     final String code;
     /**
      * Constructs a new JavaSourceFromString.
      * @param name the name of the compilation unit represented by this file object
      * @param code the source code for the compilation unit represented by this file object
      */
     public JavaSourceFromString(String name, String code) {
         super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension),
               Kind.SOURCE);
         this.code = code;
     }
     @Override
     public CharSequence getCharContent(boolean ignoreEncodingErrors) {
         return code;
     }
 }
</cfsavecontent>
<cfset pathToInputFile = "C:\myFiles\JavaSourceFromString.java" />
<cfset fileWrite(pathToInputFile, javaCode) />
However, instead of using the JavaLoader this time,  we are going to output the class file directly to the WEB-INF\classes\ directory.  That way the class will be automatically detected by ColdFusion.  Now to redirect the output of the compiler, simply prepend the -d <directory> flag to the list of sources passed into the compiler.
<!--- create a stream to capture errors --->
<cfset errStream = createObject("java", "java.io.ByteArrayOutputStream").init() />
<!--- get a compiler reference --->
<cfset provider = createObject("java", "javax.tools.ToolProvider") />
<cfset compiler = provider.getSystemJavaCompiler() />
<cfset args = [ "-d", "C:\ColdFusion8\wwwroot\WEB-INF\classes\", pathToInputFile ]>
<cfset status = compiler.run(     javacast("null", ""),
                           javacast("null", ""),
                           errStream,
                           args
                        ) />
<!--- display the status and any error messages --->
<!--- status: 0 == success --->
<cfset message = toString(errStream.toByteArray()) />
<cfset errStream.close() />
<b>COMPILER RESULTS:</b><hr />
<cfoutput>
STATUS: #status# (0 == success)<br />
ERRORS: #message# <br /><br />
</cfoutput>
Once the code is compiled, ColdFusion should automatically detect the new .class file in WEB-INF\classes\.   So if everything went as planned, the new class should be ready to use right away, without rebooting the server. (Note: If you make changes and compile the class a second time, ColdFusion will not automatically reload the class). 
Using the new class is very simple.  Just create a new instance of JavaSourceFromString, and pass in the name of your class and the source code as strings.
<cfsavecontent variable="javaCode">
import java.util.List;
import java.util.Vector;
public class MyClass
{
   public static List<String> test() {
       List<String> list = new Vector<String>();
       list.add("Congratulations");
       list.add("It worked");
       return list;
   }
}
</cfsavecontent>
<cfset SourceFromString = createObject("java", "JavaSourceFromString") />
<cfset fileObject = SourceFromString.init( "MyClass", javaCode) />
<cfset sourceList = [ fileObject ] />
Now to compile these sources, we are going to try something a little different:  using a  CompilationTask.  This option just provides some additional control over the compile process.   We are also going to use a DiagnosticCollector object, which provides an easy way to access error message details.
You can create a CompilationTask using the compiler's getTask() method.  Most of the arguments are the same as for the run() method. But there are two additional arguments: options and classNames.     The options argument is just an array of settings you want to pass to the compiler.   Such as the -d flag used in the previous example.    We are going to use that flag again to send the output to the WEB-INF\classes directory.   Now to perform the actual compilation,  use the call() method.
<cfset provider = createObject("java", "javax.tools.ToolProvider") />
<cfset compiler = provider.getSystemJavaCompiler() />
<cfset collector = createObject("java", "javax.tools.DiagnosticCollector").init() />
<cfset args = ["-d", "C:\ColdFusion8\wwwroot\WEB-INF\classes\" ]>
<cfset task = compiler.getTask(     javacast("null", ""),
                                javacast("null", ""),
                                collector,
                                args,
                                javacast("null", ""),
                                sourceList
                          ) />
<cfset status = task.call() />
To retrieve any error messages from the DiagnosticCollector, simply call the getDiagnostics() method.  It returns an array of Diagnostic objects.
<cfset results = collector.getDiagnostics() />
COMPILER RESULTS:<hr />
<cfoutput>
STATUS: #status# (0 == success) <br />
<cfloop array="#results#" index="diagnostic">
    Error on #diagnostic.getLineNumber()#
    Starting at #diagnostic.getStartPosition()# <hr />
    #diagnostic.getMessage(javacast("null", ""))# <br />
</cfloop>
<br />
</cfoutput>
All that is left is to test the compiled class.  If everything went well, your results should look something like this.   Ah, cfdump has never looked better ;)
COLDFUSION RESULTS:<hr />
<cfset myObj = createObject("java", "MyClass") />
<cfdump var="#myObj.test()#" label="MyClass.test();" />
As always comments/questions/corrections are welcome.
 
 
 Posts
Posts
 
 
3 comments:
This is awesome. Thanks, I was wondering how to compile without using the cfcompile bat. Keep up the great posts.
Interesting post! I can't see many use cases where I would want to use it, but it does give one more insight into the inner workings of CF and how closely it is tied to the JVM. I could possibly see this being used to compile a class dynamically onServerStart, although for what purpose I have no idea!
Re-post of a user comment deleted by accident
By User: DonOmite
I agree this is interesting, but what can I do with it for a web app? Wouldn't it just be easier to make a cfx or such and use that? If I could run java on the fly and use it, now that would be kewl.
Post a Comment