From eef1e050177ef079c7630ac4bd9eb709dbe90726 Mon Sep 17 00:00:00 2001 From: IfThen2 <57305322+IfThen2@users.noreply.github.com> Date: Wed, 18 Jan 2023 05:54:00 -0700 Subject: [PATCH] code for BAEL-5999 Converting String Object to Java Compilable Code (#13179) * code for BAEL-5999 Converting String Object to Java Compilable Code * move code from core-java-jvm-2 to core-java-jvm-3 fix unit test name fix core-java-jvm-3 test directory structure disable non-working unit tests from core-java-jvm-3 --- .../inmemorycompilation/InMemoryClass.java | 6 ++ .../InMemoryClassLoader.java | 30 ++++++++++ .../InMemoryFileManager.java | 52 +++++++++++++++++ .../inmemorycompilation/JavaClassAsBytes.java | 27 +++++++++ .../JavaSourceFromString.java | 26 +++++++++ .../InMemoryCompilationUnitTest.java | 57 +++++++++++++++++++ .../resource/ClassGetResourceUnitTest.java | 2 + .../ClassLoaderGetResourceUnitTest.java | 2 + 8 files changed, 202 insertions(+) create mode 100644 core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClass.java create mode 100644 core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClassLoader.java create mode 100644 core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryFileManager.java create mode 100644 core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaClassAsBytes.java create mode 100644 core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaSourceFromString.java create mode 100644 core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/inmemorycompilation/InMemoryCompilationUnitTest.java rename core-java-modules/core-java-jvm-3/{ => src}/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java (93%) rename core-java-modules/core-java-jvm-3/{ => src}/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java (93%) diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClass.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClass.java new file mode 100644 index 0000000000..c61f28bb79 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClass.java @@ -0,0 +1,6 @@ +package com.baeldung.inmemorycompilation; + +public interface InMemoryClass { + + void runCode(); +} diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClassLoader.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClassLoader.java new file mode 100644 index 0000000000..b4951e9d91 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryClassLoader.java @@ -0,0 +1,30 @@ +package com.baeldung.inmemorycompilation; + +import static java.util.Objects.requireNonNull; + +import java.util.Map; + +public class InMemoryClassLoader extends ClassLoader { + + private final InMemoryFileManager manager; + + public InMemoryClassLoader(ClassLoader parent, InMemoryFileManager manager) { + super(parent); + this.manager = requireNonNull(manager, "manager must not be null"); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + + Map compiledClasses = manager + .getBytesMap(); + + if (compiledClasses.containsKey(name)) { + byte[] bytes = compiledClasses.get(name) + .getBytes(); + return defineClass(name, bytes, 0, bytes.length); + } else { + throw new ClassNotFoundException(); + } + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryFileManager.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryFileManager.java new file mode 100644 index 0000000000..34ad78856a --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/InMemoryFileManager.java @@ -0,0 +1,52 @@ +package com.baeldung.inmemorycompilation; + +import java.util.Hashtable; +import java.util.Map; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.StandardJavaFileManager; + +public class InMemoryFileManager extends ForwardingJavaFileManager { + + private final Map compiledClasses; + private final ClassLoader loader; + + public InMemoryFileManager(StandardJavaFileManager standardManager) { + super(standardManager); + this.compiledClasses = new Hashtable<>(); + this.loader = new InMemoryClassLoader(this.getClass() + .getClassLoader(), + this + ); + } + + /** + * Used to get the class loader for our compiled class. It creates an anonymous class extending + * the SecureClassLoader which uses the byte code created by the compiler and stored in the + * JavaClassObject, and returns the Class for it + * + * @param location where to place or search for file objects. + */ + @Override + public ClassLoader getClassLoader(Location location) { + return loader; + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, + FileObject sibling) { + + JavaClassAsBytes classAsBytes = new JavaClassAsBytes( + className, kind); + compiledClasses.put(className, classAsBytes); + + return classAsBytes; + } + + public Map getBytesMap() { + return compiledClasses; + } +} diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaClassAsBytes.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaClassAsBytes.java new file mode 100644 index 0000000000..b7af9a76ba --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaClassAsBytes.java @@ -0,0 +1,27 @@ +package com.baeldung.inmemorycompilation; + +import java.io.ByteArrayOutputStream; +import java.io.OutputStream; +import java.net.URI; +import javax.tools.SimpleJavaFileObject; + +/** + * Represents a Java class file (compiled byte-code) + */ +public class JavaClassAsBytes extends SimpleJavaFileObject { + + protected final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + public JavaClassAsBytes(String name, Kind kind) { + super(URI.create("string:///" + name.replace('.', '/') + kind.extension), kind); + } + + public byte[] getBytes() { + return bos.toByteArray(); + } + + @Override + public OutputStream openOutputStream() { + return bos; + } +} diff --git a/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaSourceFromString.java b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaSourceFromString.java new file mode 100644 index 0000000000..eaa6c21b54 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/main/java/com/baeldung/inmemorycompilation/JavaSourceFromString.java @@ -0,0 +1,26 @@ +package com.baeldung.inmemorycompilation; + +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import javax.tools.SimpleJavaFileObject; + +/** + * Represents a Java source code file + */ +public class JavaSourceFromString extends SimpleJavaFileObject { + + private final String sourceCode; + + public JavaSourceFromString(String name, String sourceCode) { + super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), + Kind.SOURCE + ); + this.sourceCode = requireNonNull(sourceCode, "sourceCode must not be null"); + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) { + return sourceCode; + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/inmemorycompilation/InMemoryCompilationUnitTest.java b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/inmemorycompilation/InMemoryCompilationUnitTest.java new file mode 100644 index 0000000000..410bdca866 --- /dev/null +++ b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/inmemorycompilation/InMemoryCompilationUnitTest.java @@ -0,0 +1,57 @@ +package com.baeldung.inmemorycompilation; + +import java.util.Collections; +import java.util.List; + +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.ToolProvider; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class InMemoryCompilationUnitTest { + + private static final Logger LOGGER = LoggerFactory.getLogger(InMemoryCompilationUnitTest.class); + + final static String QUALIFIED_CLASS_NAME = "com.baeldung.inmemorycompilation.TestClass"; + + final static String SOURCE_CODE = + "package com.baeldung.inmemorycompilation;\n" + + "public class TestClass implements InMemoryClass {\n" + + "@Override\n" + + " public void runCode() {\n" + + " System.out.println(\"code is running...\");\n" + + " }\n" + + "}\n"; + + @Test + public void whenStringIsCompiled_ThenCodeShouldExecute() throws ClassNotFoundException, InstantiationException, IllegalAccessException { + + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + DiagnosticCollector diagnostics = new DiagnosticCollector<>(); + InMemoryFileManager manager = new InMemoryFileManager(compiler.getStandardFileManager(null, null, null)); + + List sourceFiles = Collections.singletonList(new JavaSourceFromString(QUALIFIED_CLASS_NAME, SOURCE_CODE)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, null, null, sourceFiles); + + boolean result = task.call(); + + if (result) { + diagnostics.getDiagnostics() + .forEach(d -> LOGGER.error(String.valueOf(d))); + } else { + ClassLoader classLoader = manager.getClassLoader(null); + Class clazz = classLoader.loadClass(QUALIFIED_CLASS_NAME); + InMemoryClass instanceOfClass = (InMemoryClass) clazz.newInstance(); + + Assertions.assertInstanceOf(InMemoryClass.class, instanceOfClass); + + instanceOfClass.runCode(); + } + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java similarity index 93% rename from core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java rename to core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java index fb0c88f4bb..2fe512384a 100644 --- a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java +++ b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassGetResourceUnitTest.java @@ -1,10 +1,12 @@ package com.baeldung.resource; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.net.URL; +@Disabled class ClassGetResourceUnitTest { @Test diff --git a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java similarity index 93% rename from core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java rename to core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java index 54025c5404..d2d0600165 100644 --- a/core-java-modules/core-java-jvm-3/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java +++ b/core-java-modules/core-java-jvm-3/src/test/java/com/baeldung/resource/ClassLoaderGetResourceUnitTest.java @@ -1,10 +1,12 @@ package com.baeldung.resource; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.net.URL; +@Disabled class ClassLoaderGetResourceUnitTest { @Test