diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01195f9..4fd31af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,5 +18,7 @@ jobs: uses: actions/setup-java@v1 with: java-version: 13 + - name: Gradle version + run: ./gradlew -version - name: Build with Gradle run: ./gradlew build diff --git a/example/build.gradle b/example/build.gradle index 852b738..67aab77 100644 --- a/example/build.gradle +++ b/example/build.gradle @@ -19,6 +19,11 @@ compileTestJava { test { doFirst { + def javaVersion = System.getProperty("java.version").tokenize(".").get(0).toInteger() + if (javaVersion != 13) { + throw new IllegalStateException("Must run build on Java 13 (system reported: ${javaVersion})") + } + def java8home = System.getenv("JAVA_HOME_8") if (!java8home) { throw new IllegalStateException("JAVA_HOME_8 must be set") diff --git a/example/src/main/java/com/example/JabelExample.java b/example/src/main/java/com/example/JabelExample.java index 1b7862a..d64ad07 100644 --- a/example/src/main/java/com/example/JabelExample.java +++ b/example/src/main/java/com/example/JabelExample.java @@ -63,4 +63,20 @@ public String innerPublic() { return outerPrivate(); } } + + +// public void whenSwitchingOnOperationSquareMe_thenWillReturnSquare() { +// var me = 4; +// var operation = "squareMe"; +// var result = switch (operation) { +// case "doubleMe" -> { +// yield me * 2; +// } +// case "squareMe" -> { +// yield me * me; +// } +// default -> me; +// }; +// +// } } diff --git a/example/src/test/java/com/example/JabelExampleTest.java b/example/src/test/java/com/example/JabelExampleTest.java index d6869f5..ff62857 100644 --- a/example/src/test/java/com/example/JabelExampleTest.java +++ b/example/src/test/java/com/example/JabelExampleTest.java @@ -7,17 +7,61 @@ public class JabelExampleTest { @Test(expected = ClassNotFoundException.class) - public void isJava8() throws Exception { + public void isProbablyJava8() throws Exception { Class.forName("java.lang.StackWalker"); } @Test - public void shouldWork() { + public void testRuntimeIsJava8() { + String jver = System.getProperty("java.version"); + String needle = "1.8"; + assertTrue("Java version expected: " + needle + " actual: " + jver, jver.startsWith(needle)); + } + + @Test + public void testPostJava8Features() { JabelExample jabelExample = new JabelExample(); String result = jabelExample.run(new String[0]); assertTrue("'" + result + "' should start with 'idk '", result.startsWith("idk ")); + } + + @Test + public void testJava13PreviewFeatureTextBlocksJep355(){ + String TEXT_BLOCK_JSON = """ +{ + "name" : "Baeldung", + "website" : "https://www.%s.com/" +} +"""; + } + /** + * JEP 354: Switch Expressions (Second Preview) + * https://openjdk.java.net/jeps/354 + */ + @Test + public void testJava13PreviewJep354() { + var me = 4; + var operation = "squareMe"; + var result = switch (operation) { + case "doubleMe" -> { + yield me * 2; + } + case "squareMe" -> { + yield me * me; + } + default -> me; + }; + + assertEquals(16, result); } +// +// @Test +// public void tgesd(){ +// JabelExampleTest jabelExampleTest = new JabelExampleTest(); +// jabelExampleTest.whenSwitchingOnOperationSquareMe_thenWillReturnSquare(); +// } + } \ No newline at end of file diff --git a/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/CheckSourceLevelAdvice.java b/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/CheckSourceLevelAdvice.java index f28f34b..e2dfea9 100644 --- a/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/CheckSourceLevelAdvice.java +++ b/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/CheckSourceLevelAdvice.java @@ -6,6 +6,12 @@ public class CheckSourceLevelAdvice { + /** + * If the {@link com.sun.tools.javac.code.Source.Feature} is allowed in JDK8, make javac think it has the same + * characteristics as {@link Source.Feature.LAMBDA} + * + * @param feature + */ @Advice.OnMethodEnter public static void checkSourceLevel( @Advice.Argument(value = 1, readOnly = false) Feature feature diff --git a/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/IsPreviewAdvice.java b/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/IsPreviewAdvice.java new file mode 100644 index 0000000..9c4d5f3 --- /dev/null +++ b/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/IsPreviewAdvice.java @@ -0,0 +1,25 @@ +package com.github.bsideup.jabel; + +import com.sun.tools.javac.code.Preview; +import com.sun.tools.javac.code.Source; +import net.bytebuddy.asm.Advice; + +public class IsPreviewAdvice { + + static final boolean debug = false; + + /** + * Force javac to think that no features are preview features - on {@link Preview#isPreview} exit, override the + * return value to always be false. + */ + @Advice.OnMethodExit + static public boolean isPreview(Source.Feature feature) { + if (debug) { + /** + * Can't use {@link org.apache.maven.plugin.logging.Log} from instrumented javac classes. + */ + System.out.println("Feature being set to NOT preview: " + feature); + } + return false; + } +} diff --git a/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/JabelJavacProcessor.java b/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/JabelJavacProcessor.java index 904801c..0697768 100644 --- a/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/JabelJavacProcessor.java +++ b/jabel-javac-plugin/src/main/java/com/github/bsideup/jabel/JabelJavacProcessor.java @@ -1,11 +1,13 @@ package com.github.bsideup.jabel; +import com.sun.tools.javac.code.Preview; import com.sun.tools.javac.code.Source; import com.sun.tools.javac.parser.JavaTokenizer; import com.sun.tools.javac.parser.JavacParser; import net.bytebuddy.ByteBuddy; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.asm.Advice; +import net.bytebuddy.asm.AsmVisitorWrapper.ForDeclaredMethods; import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; import javax.annotation.processing.Completion; @@ -61,21 +63,29 @@ public class JabelJavacProcessor implements Processor { .collect(Collectors.toSet()); static { - ByteBuddyAgent.install(); + // log that we've started, otherwise if there's certain types of errors, no output is seen at all from Jabel (e.g. errors compiling our code) + // helps for verifying Jabel is being picked up correctly from project settings + logInfo("Jabel static initialising ByteBuddy"); + ByteBuddyAgent.install(); ByteBuddy byteBuddy = new ByteBuddy(); - for (Class clazz : Arrays.asList(JavacParser.class, JavaTokenizer.class)) { - byteBuddy - .redefine(clazz) - .visit( - Advice.to(CheckSourceLevelAdvice.class) - .on(named("checkSourceLevel").and(takesArguments(2))) - ) - .make() - .load(clazz.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); - } + sourceLevelCheck(byteBuddy); + +// previewFeatureChecks(byteBuddy); + + featureMinLevelChecks(); + + logInfo("Jabel ByteBuddy initialisation complete"); + } + /** + * For all the features in {@link #ENABLED_FEATURES}, override the min JDK level, reducing it to 8 + * + * @see #ENABLED_FEATURES + */ + private static void featureMinLevelChecks() { + logInfo("Disabling feature min level checks"); try { Field field = Source.Feature.class.getDeclaredField("minLevel"); field.setAccessible(true); @@ -91,6 +101,46 @@ public class JabelJavacProcessor implements Processor { } } + /** + * Intercept calls to {@link JavacParser#checkSourceLevel} + * + * @see JavacParser#checkSourceLevel + * @see JavaTokenizer#checkSourceLevel + * @see CheckSourceLevelAdvice + */ + private static void sourceLevelCheck(ByteBuddy byteBuddy) { + logInfo("Disabling source level check"); + for (Class clazz : Arrays.asList(JavacParser.class, JavaTokenizer.class)) { + ForDeclaredMethods checkSourceLevelMethod = Advice.to(CheckSourceLevelAdvice.class) + .on(named("checkSourceLevel").and(takesArguments(2))); + byteBuddy + .redefine(clazz) + .visit(checkSourceLevelMethod) + .make() + .load(clazz.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + } + } + + /** + * Intercept call return from {@link Preview#isPreview} + * + * @see Preview#isPreview + * @see IsPreviewAdvice + */ + private static void previewFeatureChecks(ByteBuddy byteBuddy) { + logInfo("Disabling preview feature flag checks"); + Class previewClass = Preview.class; + ForDeclaredMethods isPreviewMethod = Advice.to(IsPreviewAdvice.class).on(named("isPreview")); + byteBuddy.redefine(previewClass) + .visit(isPreviewMethod) + .make() + .load(previewClass.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()); + } + + static private void logInfo(String msg) { + System.out.println(msg); + } + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_8; @@ -98,7 +148,7 @@ public SourceVersion getSupportedSourceVersion() { @Override public void init(ProcessingEnvironment processingEnv) { - System.out.println( + logInfo( ENABLED_FEATURES.stream() .map(Enum::name) .collect(Collectors.joining(