Summary: /* Can I use Frgaal via {{JDK|javax/tools|ToolProvider}} API? */
I love [http://frgaal.org Frgaal] and you'll love it too: Wouldn't it be great to have '''record'''s and other latest [[Java]] language features available on your old [[JDK]]s? As old as [[JDK]]8? [[Frgaal]] allows you to do it. There is a [[Maven]] plugin, integration with [[Gradle]], a way to use it from command line... read more at http://frgaal.org
=== Can I use [[Frgaal]] via {{JDK|javax/tools|ToolProvider}} [[API]]? ===
Let's imagine we have a following code invoking the {{JDK|javax/tool|JavaCompiler}} via {{JDK|javax/tool|ToolProvider}} for a custom ''CODE'' snippet defining {{JDK|java/lang|Record}} with main method:
<source lang="java">
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
class ModernJavaCompiler {
private static final String CODE = ""
+ "record R (String name) {\n"
+ " public static void main(String... args) {\n"
+ " System.out.println(new R(\"Hello World!\"));\n"
+ " }\n"
+ "}\n";
public static void main(String... args) throws Exception {
JavaCompiler compiler = javax.tools.ToolProvider.getSystemJavaCompiler();
List<String> arguments = Arrays.asList(args);
List<JavaFileObject> sources = Collections.singletonList(new Src(CODE));
JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, arguments, null, sources);
Boolean res = task.call();
if (!Boolean.TRUE.equals(res)) {
throw new IllegalStateException("Compilation failed: " + res);
} else {
System.out.println("Compiled OK");
}
}
static class Src extends SimpleJavaFileObject {
private final String code;
public Src(String code) throws URISyntaxException {
super(new URI("memory://Code.java"), Kind.SOURCE);
this.code = code;
}
@Override
public String getName() {
return "Code.java";
}
@Override
public CharSequence getCharContent(boolean bln) throws IOException {
return code;
}
}
}
</source>
This is a regular Java source file that can be compiled using standard [[Javac]] compiler:
<source lang="bash">
$ javac -source 8 -target 8 ModernJavaCompiler.java
</source>
The ''ModernJavaCompiler'' source code then uses {{JDK|javax/tool|JavaCompiler}} on its own to generate class ''R''.
It can be executed [[JDK]]-17 as
<source lang="bash">
$ /jdk-17/bin/java ModernJavaCompiler
Compiled OK
$ /jdk-17/bin/java R
R[name=Hello World!]
</source>
However executing the same steps with [[JDK]]-11 is going to fail as the [[Javac]] from [[JDK]]-11 doesn't support '''record''' keyword:
<source lang="bash">
$ /jdk11/bin/java ModernJavaCompiler
Code.java:1: error: class, interface, or enum expected
record R (String name) {
^
</source>
Can we fix that? Sure...
The idea would be to replace the standard "compiler tool" with the one coming from the [[frgaal]] project. Let's try to disable the regular compiler first. There is an option '''--limit-modules''' in [[JDK]]-11+ and the idea is to use it to disable standard [[JavaC]]:
<source lang="bash">
$ /jdk11/bin/java --limit-modules java.base ModernJavaCompiler
Exception in thread "main" java.lang.NoClassDefFoundError: javax/tools/ToolProvider
</source>
Great! Disabling works. The "compiler tool" is completely missing. Now there is a time to inject [[Frgaal]] instead. Following script shows how to get it all working:
<source lang="bash">
$ mkdir -p META-INF/services
$ echo com.sun.tools.javac.api.JavacTool > META-INF/services/javax.tools.JavaCompiler
$ curl -o compiler.jar https://repo1.maven.org/maven2/org/frgaal/compiler/18.0.0/compiler-18.0.0.jar
$ /jdk-11/bin/java --limit-modules java.base,jdk.zipfs -cp compiler.jar:. ModernJavaCompiler
warning: [options] system modules path not set in conjunction with -source 17
warning: No output directory specified, cannot perform multi-release compilation
2 warnings
Compiled OK
/jdk-11/bin/java R
R[name=Hello World!]
</source>
Hey, using '''record''' on JDK-11!
Having '''record''' (and other [[JDK]]-18 language features) available on old JDKs is great, isn't it? All that is necessary is to provide extra {{JDK|java/util|ServiceLoader}} registration in ''META-INF/services'' path on the class path. Then the [[Frgaal]] compiler gets picked up and is used for compilation even on older [[JDK]]s.
Embed [[Frgaal]] into your own programs and use the latest [[Java]] features on any [[JDK]]!
=== Can I use [[Frgaal]] via {{JDK|javax/tools|ToolProvider}} [[API]]? ===
Let's imagine we have a following code invoking the {{JDK|javax/tool|JavaCompiler}} via {{JDK|javax/tool|ToolProvider}} for a custom ''CODE'' snippet defining {{JDK|java/lang|Record}} with main method:
<source lang="java">
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
class ModernJavaCompiler {
private static final String CODE = ""
+ "record R (String name) {\n"
+ " public static void main(String... args) {\n"
+ " System.out.println(new R(\"Hello World!\"));\n"
+ " }\n"
+ "}\n";
public static void main(String... args) throws Exception {
JavaCompiler compiler = javax.tools.ToolProvider.getSystemJavaCompiler();
List<String> arguments = Arrays.asList(args);
List<JavaFileObject> sources = Collections.singletonList(new Src(CODE));
JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, arguments, null, sources);
Boolean res = task.call();
if (!Boolean.TRUE.equals(res)) {
throw new IllegalStateException("Compilation failed: " + res);
} else {
System.out.println("Compiled OK");
}
}
static class Src extends SimpleJavaFileObject {
private final String code;
public Src(String code) throws URISyntaxException {
super(new URI("memory://Code.java"), Kind.SOURCE);
this.code = code;
}
@Override
public String getName() {
return "Code.java";
}
@Override
public CharSequence getCharContent(boolean bln) throws IOException {
return code;
}
}
}
</source>
This is a regular Java source file that can be compiled using standard [[Javac]] compiler:
<source lang="bash">
$ javac -source 8 -target 8 ModernJavaCompiler.java
</source>
The ''ModernJavaCompiler'' source code then uses {{JDK|javax/tool|JavaCompiler}} on its own to generate class ''R''.
It can be executed [[JDK]]-17 as
<source lang="bash">
$ /jdk-17/bin/java ModernJavaCompiler
Compiled OK
$ /jdk-17/bin/java R
R[name=Hello World!]
</source>
However executing the same steps with [[JDK]]-11 is going to fail as the [[Javac]] from [[JDK]]-11 doesn't support '''record''' keyword:
<source lang="bash">
$ /jdk11/bin/java ModernJavaCompiler
Code.java:1: error: class, interface, or enum expected
record R (String name) {
^
</source>
Can we fix that? Sure...
The idea would be to replace the standard "compiler tool" with the one coming from the [[frgaal]] project. Let's try to disable the regular compiler first. There is an option '''--limit-modules''' in [[JDK]]-11+ and the idea is to use it to disable standard [[JavaC]]:
<source lang="bash">
$ /jdk11/bin/java --limit-modules java.base ModernJavaCompiler
Exception in thread "main" java.lang.NoClassDefFoundError: javax/tools/ToolProvider
</source>
Great! Disabling works. The "compiler tool" is completely missing. Now there is a time to inject [[Frgaal]] instead. Following script shows how to get it all working:
<source lang="bash">
$ mkdir -p META-INF/services
$ echo com.sun.tools.javac.api.JavacTool > META-INF/services/javax.tools.JavaCompiler
$ curl -o compiler.jar https://repo1.maven.org/maven2/org/frgaal/compiler/18.0.0/compiler-18.0.0.jar
$ /jdk-11/bin/java --limit-modules java.base,jdk.zipfs -cp compiler.jar:. ModernJavaCompiler
warning: [options] system modules path not set in conjunction with -source 17
warning: No output directory specified, cannot perform multi-release compilation
2 warnings
Compiled OK
/jdk-11/bin/java R
R[name=Hello World!]
</source>
Hey, using '''record''' on JDK-11!
Having '''record''' (and other [[JDK]]-18 language features) available on old JDKs is great, isn't it? All that is necessary is to provide extra {{JDK|java/util|ServiceLoader}} registration in ''META-INF/services'' path on the class path. Then the [[Frgaal]] compiler gets picked up and is used for compilation even on older [[JDK]]s.
Embed [[Frgaal]] into your own programs and use the latest [[Java]] features on any [[JDK]]!