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.class
com.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