Table of content
1 Dynamic compilation in Java 11
    1.1 Introduction
    1.2 Security
    1.3 Few notes about JDK 9
2 This project
3 Example
Compiling a Java file on the fly is not so complicated. First, you have to instantiate a new compiler:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();Then you need to create a compilation task providing a file manager:
CompilationTask task = compiler.getTask(...,fileManager,...);The file manager is responsible to provide the source code of the script and other pre-compiled classes. All those files implements the interface JavaFileObject
Then you run the compilation task:
boolean success = task.call();And voila! The compiled class will be available in the file manager.
Class<?> compiledClass = fileManager.getClassLoader(null).loadClass(className);Creating a plugin system could cause serious security holes if you run a malicious script. Fortunately the dynamic compilation API in Java allow you to control which class can be used by the script at compile time. This is handled by ForwardingJavaFileManager::list, so you have to inherit from this class to make your own file manager.
It's also a good idea to limit the scope of your script to a method class instead of an entire Java file. After all, your plugin will have to implement a known method to be usable. In this project all plugin will inherit from the abstract class Plugin and the abstract method will be execute
public abstract class Plugin {
	/**
	 * This method is implemented in the script
	 * @throws Exception
	 */
	abstract public void execute() throws Exception;
}Since JDK 9, you have now modules. So pay attention to the class javax.tools.StandardLocation, because there are more possible locations now and you must handle them in the file manager:
JDK 8:
| Enum Constant | Description | 
|---|---|
CLASS_OUTPUT | 
Location of new class files. | 
CLASS_PATH | 
Location to search for user class files. | 
NATIVE_HEADER_OUTPUT | 
Location of new native header files. | 
PLATFORM_CLASS_PATH | 
Location to search for platform classes. | 
SOURCE_OUTPUT | 
Location of new source files. | 
SOURCE_PATH | 
Location to search for existing source files. | 
JDK 9:
| Enum Constant | Description | 
|---|---|
CLASS_OUTPUT | 
Location of new class files. | 
CLASS_PATH | 
Location to search for user class files. | 
MODULE_PATH | 
Location to search for precompiled user modules. | 
MODULE_SOURCE_PATH | 
Location to search for the source code of modules. | 
NATIVE_HEADER_OUTPUT | 
Location of new native header files. | 
PATCH_MODULE_PATH | 
Location to search for module patches. | 
PLATFORM_CLASS_PATH | 
Location to search for platform classes. | 
SOURCE_OUTPUT | 
Location of new source files. | 
SOURCE_PATH | 
Location to search for existing source files. | 
SYSTEM_MODULES | 
Location to search for system modules. | 
UPGRADE_MODULE_PATH | 
Location to search for upgradeable system modules. | 
I made the things as simple as possible:
com.hypercube.scripts.FileManager: the most important class used by the compilercom.hypercube.scripts.PluginBinary: a class used by the compiler to store the compiled .class, we store in memory.com.hypercube.scripts.PluginScript: a class used by the compiler to get the source codecom.hypercube.scripts.PrecompiledJavaFile: a kind of wrapper used by the compiler to get some .class. We use it forPlugin.classcom.hypercube.scripts.model.Plugin: the base class of any plugin, provides a logger to the plugin.
Script located in the file scripts/MyScript.txt:
package com.hypercube.scripts.model;
public class MyScript extends Plugin {
	@Override
	public void execute() throws Exception {
		logger.info("Hello world !");
	}
}I made some logs to make you understand what's going on, especially what the compiler ask to the file manager:
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  Requires ClassLoader for location CLASS_PATH  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.lang kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION FILTERED: CLASS_PATH package: com.hypercube.scripts kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION FILTERED: CLASS_PATH package: com.hypercube kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION FILTERED: CLASS_PATH package: com kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.lang.annotation kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.io kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.logging] package: java.util.logging kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.util.function kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.lang.invoke kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.FileManager INFO:  Requires ClassLoader for location null  
[2020-09-08 03:22:56 PM] com.hypercube.DynamicCompilerExample INFO:  Script successfully compiled  
[2020-09-08 03:22:56 PM] com.hypercube.scripts.model.MyScript INFO:  Hello world !
If you remove "java.util.logging" or "java.util.function" from FileManager::acceptedPackages you will have:
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  Requires ClassLoader for location CLASS_PATH  
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.lang kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  LOCATION FILTERED: CLASS_PATH package: com.hypercube.scripts kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  LOCATION FILTERED: CLASS_PATH package: com.hypercube kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  LOCATION FILTERED: CLASS_PATH package: com kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.lang.annotation kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  LOCATION INCLUDED: SYSTEM_MODULES[java.base] package: java.io kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:25:12 PM] com.hypercube.scripts.FileManager INFO:  LOCATION FILTERED: SYSTEM_MODULES[java.logging] package: java.util.logging kinds: SOURCE,CLASS,HTML,OTHER  
[2020-09-08 03:25:12 PM] com.hypercube.DynamicCompilerExample SEVERE:  Compilation error:   
[2020-09-08 03:25:12 PM] com.hypercube.DynamicCompilerExample SEVERE:  MyScript:7: error: cannot access java.util.logging.Logger
		logger.info("Hello world !");
		      ^
  class file for java.util.logging.Logger not found