Search code examples
javacxtextxtend

Is it possible to embed Java compiler into an application?


With Xtext and Xtend, I have written a DSL grammar as well as its associated code generator which creates a bunch of Java source files making up a Java application. I would like to provide my colleagues with a program which lets them select a file written in our Domain Specific language, runs the code generator to produce the Java source files of the actual Java application, compiles these files and launches the app. This process works fine on my development machine because I have everything installed, including the JDK. But providing my colleagues with this app would force them to install the JDK before using the app. So my question is: is it possible to embed the Java compiler in a distribution package? If not, do you see an other way to proceed? I am using Gradle and javapackager + Inno Setup to generate the distribution package embedding the JRE.

Update #1

 override public void afterGenerate(Resource input, IFileSystemAccess2 fsa, IGeneratorContext context)
 {
  // get the class loader
  val URL[] classLoaderUrls = #{new URL("file:jfxrt.jar")}
  val URLClassLoader cl = new URLClassLoader(classLoaderUrls)

  // set Java compiler options
  var CompilerOptions compilerOptions = new CompilerOptions
  compilerOptions.sourceLevel = ClassFileConstants.JDK1_8
  compilerOptions.complianceLevel = ClassFileConstants.JDK1_8
  val Map<String, String> options = new HashMap
  options.put(CompilerOptions.OPTION_ReportMissingSerialVersion, CompilerOptions.IGNORE)
  options.put(CompilerOptions.OPTION_ReportUnusedParameter, CompilerOptions.IGNORE)
  compilerOptions.set(options)

  val InMemoryJavaCompiler compiler = new InMemoryJavaCompiler(cl, compilerOptions)

  // compile all generated Java source files
  val result = compiler.compile(new JavaSource("xxxx/Bit.java", bitGen.javaSource.toString),
   new JavaSource("xxxx/BitNature.java", bitNatureGen.javaSource.toString),
   new JavaSource("xxxx/ImageMaker.java", imgMakerGen.javaSource.toString),
   new JavaSource("xxxx/MyApp.java", myAppGen.javaSource.toString))

  val URLClassLoader runcl = new URLClassLoader(classLoaderUrls, result.classLoader)

  val Class<?> mainClazz = runcl.loadClass("xxxx.MyApp")

  val Method mainMethod = mainClazz.getMethod("main", typeof(String[]))
  val String[] args = #["C:\\temp\\memory_a.yyy"]
  try
  {
   mainMethod.invoke(null, #[args])
  }
  catch (InvocationTargetException e)
  {
   e.getCause().printStackTrace()
  }
  catch (Exception e)
  {
   // generic exception handling
   e.printStackTrace();
  }
 }

Update #2

stepping through my code, I can see the following:

1 - this statement executes with no problem (mainClazz seems to be a valid reference)

val Class<?> mainClazz = result.classLoader.loadClass("xxxx.MyApp")

2 - mainMethod seems to be a valid reference too when I execute

val Method mainMethod = mainClazz.getMethod("main", typeof(String[]))

3 - things turn bad when I execute

mainMethod.invoke(null, #[args])

The issue arises in the JavaFX Application class, launch method on the following statement (ClassNotFoundException: xxxx.MyApp)

Class theClass = Class.forName(callingClassName, false,
                               Thread.currentThread().getContextClassLoader());

Solution

  • package org.xtext.example.mydsl2.tests
    
    import java.lang.reflect.Method
    import java.net.URLClassLoader
    import org.eclipse.xtext.util.JavaVersion
    import org.eclipse.xtext.xbase.testing.InMemoryJavaCompiler
    import org.eclipse.xtext.xbase.testing.JavaSource
    
    class SampleXXX {
    
        def static void main(String[] args) {
            val urls = #[]
            val classLoader = new URLClassLoader(urls)
            val compiler = new InMemoryJavaCompiler(classLoader, JavaVersion.JAVA8)
            val result = compiler.compile(new JavaSource("demo/Demo.java", '''
            package demo;
            public class Demo {
                public static void main(String[] args) throws Exception {
                    Class theClass = Class.forName("demo.Demo", false,
                        Thread.currentThread().getContextClassLoader());
                }
            }
            '''))
            val URLClassLoader runcl = new URLClassLoader(#[], result.classLoader)
            (new Thread() {
    
                override run() {
                val Class<?> mainClazz = runcl.loadClass("demo.Demo")
                val String[] args2 = #["C:\\temp\\memory_a.yyy"]
                val Method mainMethod = mainClazz.getMethod("main", typeof(String[]))
                mainMethod.invoke(null, #[args2])
                }
    
            }=>[
                contextClassLoader = runcl
            ]).start
        }
    
    }