Skip to content

Commit 1dabe9d

Browse files
authored
Merge pull request #18358 from dhrubo55/BAEL-8962-Check-if-a-code-compiles-in-Java-using-JavaCompiler
[BAEL-8962] Check if a code compiles in Java using JavaCompiler
2 parents 02e2d01 + 2a2680e commit 1dabe9d

File tree

4 files changed

+248
-0
lines changed

4 files changed

+248
-0
lines changed

core-java-modules/core-java-compiler/pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,21 @@
2020
<artifactId>core</artifactId>
2121
<version>${gdata.version}</version>
2222
</dependency>
23+
<dependency>
24+
<groupId>org.slf4j</groupId>
25+
<artifactId>slf4j-api</artifactId>
26+
<version>${org.slf4j.version}</version>
27+
</dependency>
28+
<dependency>
29+
<groupId>com.github.stefanbirkner</groupId>
30+
<artifactId>system-lambda</artifactId>
31+
<version>${system-lambda-version}</version>
32+
</dependency>
2333
</dependencies>
2434

2535
<properties>
2636
<gdata.version>1.47.1</gdata.version>
37+
<system-lambda-version>1.2.1</system-lambda-version>
2738
</properties>
2839

2940
</project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.baeldung.compilerapi;
2+
3+
import javax.tools.SimpleJavaFileObject;
4+
import java.net.URI;
5+
6+
public class InMemoryJavaFile extends SimpleJavaFileObject {
7+
private final String code;
8+
9+
InMemoryJavaFile(String name, String code) {
10+
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
11+
this.code = code;
12+
}
13+
14+
@Override
15+
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
16+
return code;
17+
}
18+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.baeldung.compilerapi;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import javax.tools.*;
7+
import java.io.*;
8+
import java.net.URL;
9+
import java.net.URLClassLoader;
10+
import java.nio.charset.StandardCharsets;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
import java.util.*;
14+
15+
public class JavaCompilerUtils {
16+
17+
private final JavaCompiler compiler;
18+
private final StandardJavaFileManager standardFileManager;
19+
private final Path outputDirectory;
20+
21+
private static final Logger logger = LoggerFactory.getLogger(JavaCompilerUtils.class);
22+
23+
public JavaCompilerUtils(Path outputDirectory) throws IOException {
24+
this.outputDirectory = outputDirectory;
25+
this.compiler = ToolProvider.getSystemJavaCompiler();
26+
27+
if (compiler == null) {
28+
throw new IllegalStateException("Java compiler not available. Ensure you're using JDK, not JRE.");
29+
}
30+
31+
this.standardFileManager = compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8);
32+
33+
if (!Files.exists(outputDirectory)) {
34+
Files.createDirectories(outputDirectory);
35+
}
36+
37+
standardFileManager.setLocation(StandardLocation.CLASS_OUTPUT, Collections.singleton(outputDirectory.toFile()));
38+
}
39+
40+
public boolean compileFile(Path sourceFile) {
41+
if (!Files.exists(sourceFile)) {
42+
throw new IllegalArgumentException("Source file does not exist: " + sourceFile);
43+
}
44+
45+
try {
46+
Iterable<? extends JavaFileObject> compilationUnits =
47+
standardFileManager.getJavaFileObjectsFromFiles(Collections.singletonList(sourceFile.toFile()));
48+
return compile(compilationUnits);
49+
} catch (Exception e) {
50+
logger.error("Compilation failed: ", e);
51+
return false;
52+
}
53+
}
54+
55+
public boolean compileFromString(String className, String sourceCode) {
56+
JavaFileObject sourceObject = new InMemoryJavaFile(className, sourceCode);
57+
return compile(Collections.singletonList(sourceObject));
58+
}
59+
60+
private boolean compile(Iterable<? extends JavaFileObject> compilationUnits) {
61+
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
62+
63+
JavaCompiler.CompilationTask task = compiler.getTask(
64+
null,
65+
standardFileManager,
66+
diagnostics,
67+
null,
68+
null,
69+
compilationUnits
70+
);
71+
72+
boolean success = task.call();
73+
74+
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
75+
logger.debug(diagnostic.getMessage(null));
76+
}
77+
78+
return success;
79+
}
80+
81+
public void runClass(String className, String... args) throws Exception {
82+
try (URLClassLoader classLoader = new URLClassLoader(new URL[]{outputDirectory.toUri().toURL()})) {
83+
Class<?> loadedClass = classLoader.loadClass(className);
84+
loadedClass.getMethod("main", String[].class).invoke(null, (Object) args);
85+
}
86+
}
87+
88+
public Path getOutputDirectory() {
89+
return outputDirectory;
90+
}
91+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package com.baeldung.compilerapi;
2+
3+
import org.junit.jupiter.api.*;
4+
import org.junit.jupiter.api.io.TempDir;
5+
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.nio.file.Paths;
9+
10+
import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemErr;
11+
import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut;
12+
import static org.junit.jupiter.api.Assertions.*;
13+
14+
public class JavaCompilerUnitTest {
15+
16+
@TempDir
17+
static Path tempDir;
18+
19+
private JavaCompilerUtils compilerUtil;
20+
21+
@BeforeEach
22+
void setUp() throws Exception {
23+
Path outputDir = tempDir.resolve("classes");
24+
Files.createDirectories(outputDir);
25+
26+
compilerUtil = new JavaCompilerUtils(outputDir);
27+
}
28+
29+
@Test
30+
void givenSimpleHelloWorldClass_whenCompiledFromString_thenCompilationSucceeds() {
31+
String className = "HelloWorld";
32+
String sourceCode = "public class HelloWorld {\n" +
33+
" public static void main(String[] args) {\n" +
34+
" System.out.println(\"Hello, World!\");\n" +
35+
" }\n" +
36+
"}";
37+
38+
boolean result = compilerUtil.compileFromString(className, sourceCode);
39+
40+
assertTrue(result, "Compilation should succeed");
41+
42+
Path classFile = compilerUtil.getOutputDirectory().resolve(className + ".class");
43+
assertTrue(Files.exists(classFile), "Class file should be created");
44+
}
45+
46+
@Test
47+
void givenClassWithPackage_whenCompiledFromString_thenCompilationSucceedsInPackageDirectory() {
48+
String className = "com.example.PackagedClass";
49+
String sourceCode = "package com.example;\n\n" +
50+
"public class PackagedClass {\n" +
51+
" public static void main(String[] args) {\n" +
52+
" System.out.println(\"Hello from packaged class!\");\n" +
53+
" }\n" +
54+
"}";
55+
56+
boolean result = compilerUtil.compileFromString(className, sourceCode);
57+
58+
assertTrue(result, "Compilation should succeed");
59+
60+
Path classFile = compilerUtil.getOutputDirectory().resolve(
61+
Paths.get("com", "example", "PackagedClass.class"));
62+
assertTrue(Files.exists(classFile), "Class file should be created in the package directory");
63+
}
64+
65+
@Test
66+
void givenClassWithSyntaxError_whenCompiledFromString_thenCompilationFails() {
67+
String className = "ErrorClass";
68+
String sourceCode = "public class ErrorClass {\n" +
69+
" public static void main(String[] args) {\n" +
70+
" System.out.println(\"This has an error\")\n" +
71+
" }\n" +
72+
"}";
73+
74+
boolean result = compilerUtil.compileFromString(className, sourceCode);
75+
assertFalse(result, "Compilation should fail due to syntax error");
76+
77+
Path classFile = compilerUtil.getOutputDirectory().resolve(className + ".class");
78+
assertFalse(Files.exists(classFile), "No class file should be created for failed compilation");
79+
}
80+
81+
@Test
82+
void givenJavaSourceFile_whenCompiled_thenCompilationSucceeds() throws Exception {
83+
String className = "FileTest";
84+
String sourceCode = "public class FileTest {\n" +
85+
" public static void main(String[] args) {\n" +
86+
" System.out.println(\"Hello from file!\");\n" +
87+
" }\n" +
88+
"}";
89+
90+
Path sourceFile = tempDir.resolve(className + ".java");
91+
Files.write(sourceFile, sourceCode.getBytes());
92+
93+
boolean result = compilerUtil.compileFile(sourceFile);
94+
95+
assertTrue(result, "Compilation should succeed");
96+
97+
Path classFile = compilerUtil.getOutputDirectory().resolve(className + ".class");
98+
assertTrue(Files.exists(classFile), "Class file should be created");
99+
}
100+
101+
@Test
102+
void givenCompiledClass_whenRunWithArguments_thenOutputsExpectedResult() throws Exception {
103+
String className = "Runner";
104+
String sourceCode = "public class Runner {\n" +
105+
" public static void main(String[] args) {\n" +
106+
" System.out.println(\"Running: \" + String.join(\", \", args));\n" +
107+
" }\n" +
108+
"}";
109+
110+
boolean result = compilerUtil.compileFromString(className, sourceCode);
111+
assertTrue(result, "Compilation should succeed");
112+
113+
String output = tapSystemOut(() -> {
114+
compilerUtil.runClass(className, "arg1", "arg2");
115+
});
116+
117+
assertEquals("Running: arg1, arg2", output.trim());
118+
}
119+
120+
@Test
121+
void whenCompilingNonExistentFile_thenThrowsIllegalArgumentException() {
122+
Path nonExistentFile = tempDir.resolve("NonExistent.java");
123+
124+
assertThrows(IllegalArgumentException.class, () -> {
125+
compilerUtil.compileFile(nonExistentFile);
126+
});
127+
}
128+
}

0 commit comments

Comments
 (0)