Java Compiler API (javax.tools) Programmatically in Java

Introduction to Java Compiler API

The Java Compiler API (javax.tools) provides programmatic access to the Java compiler (javac), allowing you to compile Java source code from within Java applications. This enables dynamic compilation, code generation, and runtime compilation scenarios.

Key Components

  • JavaCompiler: Main interface representing the Java compiler
  • JavaFileManager: Manages compilation units and output locations
  • DiagnosticCollector: Collects compilation errors and warnings
  • StandardJavaFileManager: Default file manager implementation
  • JavaFileObject: Represents a compilation unit (source or class)

1. Basic Compilation Examples

Simple In-Memory Compilation

import javax.tools.*;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
public class BasicCompilerExample {
public static void main(String[] args) {
// Get the system Java compiler
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
System.err.println("JDK required (not JRE) - compiler not available");
return;
}
// Diagnostic collector for errors and warnings
DiagnosticCollector<JavaFileObject> diagnostics = 
new DiagnosticCollector<>();
// Standard file manager
StandardJavaFileManager fileManager = 
compiler.getStandardFileManager(diagnostics, null, null);
// Define compilation units (source files)
List<JavaFileObject> compilationUnits = new ArrayList<>();
// Create a simple Java class as string
String sourceCode = 
"public class HelloWorld {\n" +
"    public static void main(String[] args) {\n" +
"        System.out.println(\"Hello, Compiled World!\");\n" +
"    }\n" +
"    \n" +
"    public String greet(String name) {\n" +
"        return \"Hello, \" + name + \"!\";\n" +
"    }\n" +
"}";
// Create JavaFileObject from string
JavaFileObject sourceFile = new JavaSourceFromString("HelloWorld", sourceCode);
compilationUnits.add(sourceFile);
// Set compilation options
List<String> options = Arrays.asList("-d", "target/classes");
// Create compilation task
JavaCompiler.CompilationTask task = compiler.getTask(
null,           // Writer for compiler output (null = System.err)
fileManager,    // File manager
diagnostics,    // Diagnostic listener
options,        // Compiler options
null,           // Classes for annotation processing
compilationUnits // Source files to compile
);
// Perform compilation
boolean success = task.call();
// Print diagnostics
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
System.out.format("Line: %d, Column: %d - %s%n",
diagnostic.getLineNumber(),
diagnostic.getColumnNumber(),
diagnostic.getMessage(null));
}
if (success) {
System.out.println("Compilation successful!");
} else {
System.out.println("Compilation failed!");
}
try {
fileManager.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Custom JavaFileObject that represents source code stored in a string
*/
static class JavaSourceFromString extends SimpleJavaFileObject {
private final String code;
public JavaSourceFromString(String name, String code) {
super(java.net.URI.create("string:///" + name.replace('.', '/') + 
Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
}

Compiling Multiple Files

import javax.tools.*;
import java.io.File;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
public class MultipleFileCompilation {
public static class SourceCodeProvider {
public static List<JavaFileObject> createSampleClasses() {
List<JavaFileObject> sources = new ArrayList<>();
// First class: Calculator
String calculatorCode = 
"public class Calculator {\n" +
"    public int add(int a, int b) {\n" +
"        return a + b;\n" +
"    }\n" +
"    \n" +
"    public int multiply(int a, int b) {\n" +
"        return a * b;\n" +
"    }\n" +
"    \n" +
"    public double divide(double a, double b) {\n" +
"        if (b == 0) throw new IllegalArgumentException(\"Division by zero\");\n" +
"        return a / b;\n" +
"    }\n" +
"}";
// Second class: StringUtils
String stringUtilsCode =
"public class StringUtils {\n" +
"    public static String reverse(String input) {\n" +
"        if (input == null) return null;\n" +
"        return new StringBuilder(input).reverse().toString();\n" +
"    }\n" +
"    \n" +
"    public static boolean isPalindrome(String str) {\n" +
"        if (str == null) return false;\n" +
"        String reversed = reverse(str);\n" +
"        return str.equals(reversed);\n" +
"    }\n" +
"}";
// Third class: Main class using both
String mainClassCode =
"public class MainApp {\n" +
"    public static void main(String[] args) {\n" +
"        Calculator calc = new Calculator();\n" +
"        System.out.println(\"5 + 3 = \" + calc.add(5, 3));\n" +
"        System.out.println(\"5 * 3 = \" + calc.multiply(5, 3));\n" +
"        \n" +
"        String test = \"radar\";\n" +
"        System.out.println(\"'\" + test + \"' is palindrome: \" + \n" +
"                         StringUtils.isPalindrome(test));\n" +
"    }\n" +
"}";
sources.add(new JavaSourceFromString("Calculator", calculatorCode));
sources.add(new JavaSourceFromString("StringUtils", stringUtilsCode));
sources.add(new JavaSourceFromString("MainApp", mainClassCode));
return sources;
}
}
public static void main(String[] args) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
System.err.println("Compiler not available - are you using JDK?");
return;
}
DiagnosticCollector<JavaFileObject> diagnostics = 
new DiagnosticCollector<>();
StandardJavaFileManager fileManager = 
compiler.getStandardFileManager(diagnostics, null, null);
// Create output directory
File outputDir = new File("compiled_classes");
if (!outputDir.exists()) {
outputDir.mkdirs();
}
// Get source files
List<JavaFileObject> compilationUnits = SourceCodeProvider.createSampleClasses();
// Set compiler options
List<String> options = Arrays.asList(
"-d", outputDir.getAbsolutePath(), // Output directory
"-verbose"                         // Verbose output
);
// Create compilation task
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, options, null, compilationUnits);
System.out.println("Compiling " + compilationUnits.size() + " files...");
boolean success = task.call();
// Print detailed diagnostics
System.out.println("\n=== COMPILATION DIAGNOSTICS ===");
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
System.out.format("%s: Line %d - %s%n",
diagnostic.getKind(),
diagnostic.getLineNumber(),
diagnostic.getMessage(null));
}
if (success) {
System.out.println("\nCompilation successful! Files compiled:");
for (JavaFileObject file : compilationUnits) {
System.out.println("  - " + file.getName());
}
System.out.println("Output directory: " + outputDir.getAbsolutePath());
} else {
System.out.println("\nCompilation failed!");
}
try {
fileManager.close();
} catch (Exception e) {
e.printStackTrace();
}
}
static class JavaSourceFromString extends SimpleJavaFileObject {
private final String code;
public JavaSourceFromString(String name, String code) {
super(java.net.URI.create("string:///" + name.replace('.', '/') + 
Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
}

2. Advanced Compilation Features

Custom Class Loading and Execution

import javax.tools.*;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
public class InMemoryCompilationAndExecution {
public static void main(String[] args) throws Exception {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Custom file manager that stores compiled classes in memory
InMemoryFileManager fileManager = 
new InMemoryFileManager(compiler.getStandardFileManager(null, null, null));
// Define a simple class to compile and execute
String sourceCode = 
"public class DynamicGreeter {\n" +
"    public String createGreeting(String name) {\n" +
"        return \"Hello, \" + name + \"! This was compiled and executed dynamically.\";\n" +
"    }\n" +
"    \n" +
"    public static void main(String[] args) {\n" +
"        DynamicGreeter greeter = new DynamicGreeter();\n" +
"        String greeting = greeter.createGreeting(\"Dynamic World\");\n" +
"        System.out.println(greeting);\n" +
"    }\n" +
"}";
List<JavaFileObject> compilationUnits = Arrays.asList(
new JavaSourceFromString("DynamicGreeter", sourceCode)
);
// Compile the class
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, null, null, null, compilationUnits);
boolean success = task.call();
if (!success) {
System.err.println("Compilation failed!");
return;
}
System.out.println("Compilation successful!");
// Load and use the compiled class
Map<String, byte[]> compiledClasses = fileManager.getCompiledClasses();
InMemoryClassLoader classLoader = new InMemoryClassLoader(compiledClasses);
try {
// Load the dynamically compiled class
Class<?> dynamicClass = classLoader.loadClass("DynamicGreeter");
// Create instance and call method
Object instance = dynamicClass.getDeclaredConstructor().newInstance();
java.lang.reflect.Method method = dynamicClass.getMethod("createGreeting", String.class);
String result = (String) method.invoke(instance, "Java Compiler API");
System.out.println("Method result: " + result);
// Execute main method
java.lang.reflect.Method mainMethod = dynamicClass.getMethod("main", String[].class);
System.out.println("\nExecuting main method:");
mainMethod.invoke(null, (Object) new String[]{});
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Custom JavaFileObject for source code in memory
*/
static class JavaSourceFromString extends SimpleJavaFileObject {
private final String code;
public JavaSourceFromString(String name, String code) {
super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), 
Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
/**
* Custom JavaFileObject for compiled class data in memory
*/
static class JavaClassInMemory extends SimpleJavaFileObject {
private final ByteArrayOutputStream outputStream;
public JavaClassInMemory(String name) {
super(URI.create("mem:///" + name.replace('.', '/') + Kind.CLASS.extension), 
Kind.CLASS);
this.outputStream = new ByteArrayOutputStream();
}
@Override
public OutputStream openOutputStream() {
return outputStream;
}
public byte[] getBytes() {
return outputStream.toByteArray();
}
}
/**
* Custom FileManager that stores compiled classes in memory
*/
static class InMemoryFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private final Map<String, JavaClassInMemory> compiledClasses;
public InMemoryFileManager(StandardJavaFileManager fileManager) {
super(fileManager);
this.compiledClasses = new HashMap<>();
}
@Override
public JavaFileObject getJavaFileForOutput(Location location, 
String className, 
JavaFileObject.Kind kind,
FileObject sibling) {
if (kind == JavaFileObject.Kind.CLASS) {
JavaClassInMemory classFile = new JavaClassInMemory(className);
compiledClasses.put(className, classFile);
return classFile;
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
public Map<String, byte[]> getCompiledClasses() {
Map<String, byte[]> result = new HashMap<>();
for (Map.Entry<String, JavaClassInMemory> entry : compiledClasses.entrySet()) {
result.put(entry.getKey(), entry.getValue().getBytes());
}
return result;
}
}
/**
* Custom ClassLoader that loads classes from memory
*/
static class InMemoryClassLoader extends ClassLoader {
private final Map<String, byte[]> classBytes;
public InMemoryClassLoader(Map<String, byte[]> classBytes) {
this.classBytes = new HashMap<>(classBytes);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = classBytes.get(name);
if (bytes != null) {
return defineClass(name, bytes, 0, bytes.length);
}
return super.findClass(name);
}
}
}

Compiler Options and Configuration

import javax.tools.*;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
public class CompilerOptionsExample {
public static void main(String[] args) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
// Test different compiler configurations
testCompilerOptions(compiler, "Default options", Arrays.asList());
testCompilerOptions(compiler, "With warnings", Arrays.asList("-Xlint:unchecked"));
testCompilerOptions(compiler, "Debug info", Arrays.asList("-g", "-parameters"));
testCompilerOptions(compiler, "Optimized", Arrays.asList("-O2"));
testCompilerOptions(compiler, "Source/target version", 
Arrays.asList("-source", "11", "-target", "11"));
}
private static void testCompilerOptions(JavaCompiler compiler, 
String testName, 
List<String> options) {
System.out.println("\n=== " + testName + " ===");
System.out.println("Options: " + options);
DiagnosticCollector<JavaFileObject> diagnostics = 
new DiagnosticCollector<>();
StandardJavaFileManager fileManager = 
compiler.getStandardFileManager(diagnostics, null, null);
// Create source code with various features
String sourceCode = 
"import java.util.*;\n" +
"public class OptionsTest {\n" +
"    @SuppressWarnings(\"rawtypes\")\n" +
"    private List list; // This might generate warnings\n" +
"    \n" +
"    public void methodWithParameters(String param1, int param2) {\n" +
"        // Method with parameters for -parameters option\n" +
"    }\n" +
"    \n" +
"    public void processData() {\n" +
"        @SuppressWarnings(\"unused\")\n" +
"        int unusedVariable = 42; // This might generate warnings\n" +
"        \n" +
"        // Raw type usage - might generate warnings with -Xlint\n" +
"        List rawList = new ArrayList();\n" +
"        rawList.add(\"test\");\n" +
"    }\n" +
"    \n" +
"    public static void main(String[] args) {\n" +
"        System.out.println(\"Testing compiler options\");\n" +
"    }\n" +
"}";
List<JavaFileObject> compilationUnits = Arrays.asList(
new JavaSourceFromString("OptionsTest", sourceCode)
);
// Add output directory option
List<String> allOptions = new ArrayList<>(options);
allOptions.add("-d");
allOptions.add("target/classes");
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, allOptions, null, compilationUnits);
boolean success = task.call();
// Print diagnostics
int warningCount = 0;
int errorCount = 0;
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
switch (diagnostic.getKind()) {
case WARNING:
case MANDATORY_WARNING:
warningCount++;
break;
case ERROR:
errorCount++;
break;
}
System.out.format("  %s: %s%n", 
diagnostic.getKind(), diagnostic.getMessage(null));
}
System.out.println("Result: " + (success ? "SUCCESS" : "FAILED"));
System.out.println("Warnings: " + warningCount + ", Errors: " + errorCount);
try {
fileManager.close();
} catch (Exception e) {
e.printStackTrace();
}
}
static class JavaSourceFromString extends SimpleJavaFileObject {
private final String code;
public JavaSourceFromString(String name, String code) {
super(java.net.URI.create("string:///" + name.replace('.', '/') + 
Kind.SOURCE.extension), Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
}

3. Real-World Use Cases

Dynamic Template Engine

import javax.tools.*;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
public class DynamicTemplateEngine {
public static class TemplateCompiler {
private final JavaCompiler compiler;
private final InMemoryClassLoader classLoader;
private final Map<String, byte[]> compiledClasses;
public TemplateCompiler() {
this.compiler = ToolProvider.getSystemJavaCompiler();
this.compiledClasses = new HashMap<>();
this.classLoader = new InMemoryClassLoader(compiledClasses);
}
/**
* Compiles a template into a executable Java class
*/
public Object compileTemplate(String templateName, String template) 
throws Exception {
// Generate Java source from template
String javaSource = generateJavaSource(templateName, template);
System.out.println("Generated source:\n" + javaSource);
// Compile the generated source
InMemoryFileManager fileManager = 
new InMemoryFileManager(compiler.getStandardFileManager(null, null, null));
List<JavaFileObject> compilationUnits = Arrays.asList(
new JavaSourceFromString(templateName, javaSource)
);
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, null, null, null, compilationUnits);
if (!task.call()) {
throw new RuntimeException("Template compilation failed");
}
// Update class loader with new classes
compiledClasses.putAll(fileManager.getCompiledClasses());
// Instantiate the compiled template
Class<?> templateClass = classLoader.loadClass(templateName);
return templateClass.getDeclaredConstructor().newInstance();
}
private String generateJavaSource(String className, String template) {
// Simple template language: {{variable}} gets replaced
StringBuilder source = new StringBuilder();
source.append("import java.util.*;\n");
source.append("public class ").append(className).append(" {\n");
source.append("    public String render(Map<String, Object> context) {\n");
source.append("        StringBuilder output = new StringBuilder();\n");
// Parse template and generate Java code
Pattern pattern = Pattern.compile("\\{\\{(\\w+)\\}\\}");
Matcher matcher = pattern.matcher(template);
int lastIndex = 0;
while (matcher.find()) {
// Add static text before variable
if (matcher.start() > lastIndex) {
String staticText = template.substring(lastIndex, matcher.start());
source.append("        output.append(\"").append(escape(staticText)).append("\");\n");
}
// Add variable reference
String varName = matcher.group(1);
source.append("        output.append(String.valueOf(context.get(\"")
.append(varName).append("\")));\n");
lastIndex = matcher.end();
}
// Add remaining static text
if (lastIndex < template.length()) {
String staticText = template.substring(lastIndex);
source.append("        output.append(\"").append(escape(staticText)).append("\");\n");
}
source.append("        return output.toString();\n");
source.append("    }\n");
source.append("}");
return source.toString();
}
private String escape(String text) {
return text.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\t", "\\t");
}
}
public static void main(String[] args) throws Exception {
TemplateCompiler templateCompiler = new TemplateCompiler();
// Define templates
String emailTemplate = 
"Dear {{name}},\n\n" +
"Thank you for your order #{{orderId}}.\n" +
"Total amount: ${{amount}}\n\n" +
"Sincerely,\n{{company}}";
String invoiceTemplate =
"INVOICE\n" +
"=======\n" +
"Customer: {{customerName}}\n" +
"Item: {{itemName}}\n" +
"Quantity: {{quantity}}\n" +
"Price: ${{price}}\n" +
"Total: ${{total}}";
// Compile templates
Object emailRenderer = templateCompiler.compileTemplate("EmailTemplate", emailTemplate);
Object invoiceRenderer = templateCompiler.compileTemplate("InvoiceTemplate", invoiceTemplate);
// Prepare data
Map<String, Object> emailData = new HashMap<>();
emailData.put("name", "John Doe");
emailData.put("orderId", "12345");
emailData.put("amount", "99.99");
emailData.put("company", "Our Company");
Map<String, Object> invoiceData = new HashMap<>();
invoiceData.put("customerName", "Jane Smith");
invoiceData.put("itemName", "Java Programming Book");
invoiceData.put("quantity", "2");
invoiceData.put("price", "49.99");
invoiceData.put("total", "99.98");
// Render templates
String emailResult = (String) emailRenderer.getClass()
.getMethod("render", Map.class).invoke(emailRenderer, emailData);
String invoiceResult = (String) invoiceRenderer.getClass()
.getMethod("render", Map.class).invoke(invoiceRenderer, invoiceData);
System.out.println("=== RENDERED EMAIL ===");
System.out.println(emailResult);
System.out.println("\n=== RENDERED INVOICE ===");
System.out.println(invoiceResult);
}
// Inner classes from previous examples (JavaSourceFromString, InMemoryFileManager, etc.)
static class JavaSourceFromString extends SimpleJavaFileObject {
private final String code;
public JavaSourceFromString(String name, String code) {
super(java.net.URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), 
Kind.SOURCE);
this.code = code;
}
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return code;
}
}
static class InMemoryFileManager extends ForwardingJavaFileManager<StandardJavaFileManager> {
private final Map<String, JavaClassInMemory> compiledClasses = new HashMap<>();
public InMemoryFileManager(StandardJavaFileManager fileManager) { super(fileManager); }
@Override
public JavaFileObject getJavaFileForOutput(Location location, String className, 
JavaFileObject.Kind kind, FileObject sibling) {
if (kind == JavaFileObject.Kind.CLASS) {
JavaClassInMemory classFile = new JavaClassInMemory(className);
compiledClasses.put(className, classFile);
return classFile;
}
return super.getJavaFileForOutput(location, className, kind, sibling);
}
public Map<String, byte[]> getCompiledClasses() {
Map<String, byte[]> result = new HashMap<>();
for (Map.Entry<String, JavaClassInMemory> entry : compiledClasses.entrySet()) {
result.put(entry.getKey(), entry.getValue().getBytes());
}
return result;
}
}
static class JavaClassInMemory extends SimpleJavaFileObject {
private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
public JavaClassInMemory(String name) {
super(URI.create("mem:///" + name.replace('.', '/') + Kind.CLASS.extension), Kind.CLASS);
}
@Override
public OutputStream openOutputStream() { return outputStream; }
public byte[] getBytes() { return outputStream.toByteArray(); }
}
static class InMemoryClassLoader extends ClassLoader {
private final Map<String, byte[]> classBytes;
public InMemoryClassLoader(Map<String, byte[]> classBytes) { this.classBytes = classBytes; }
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] bytes = classBytes.get(name);
if (bytes != null) return defineClass(name, bytes, 0, bytes.length);
return super.findClass(name);
}
}
}

Dynamic Rule Engine

import javax.tools.*;
import java.util.*;
import java.lang.reflect.Method;
public class DynamicRuleEngine {
public static class RuleCompiler {
private final JavaCompiler compiler;
private int ruleCounter = 0;
public RuleCompiler() {
this.compiler = ToolProvider.getSystemJavaCompiler();
}
public interface Rule {
boolean evaluate(Map<String, Object> context);
String getDescription();
}
/**
* Compiles a rule expressed as Java-like condition
*/
public Rule compileRule(String condition) throws Exception {
String ruleClassName = "DynamicRule_" + (ruleCounter++);
String javaSource = generateRuleSource(ruleClassName, condition);
System.out.println("Compiling rule: " + condition);
System.out.println("Generated source:\n" + javaSource);
InMemoryFileManager fileManager = 
new InMemoryFileManager(compiler.getStandardFileManager(null, null, null));
List<JavaFileObject> compilationUnits = Arrays.asList(
new JavaSourceFromString(ruleClassName, javaSource)
);
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, null, null, null, compilationUnits);
if (!task.call()) {
throw new RuntimeException("Rule compilation failed: " + condition);
}
Map<String, byte[]> compiledClasses = fileManager.getCompiledClasses();
InMemoryClassLoader classLoader = new InMemoryClassLoader(compiledClasses);
Class<?> ruleClass = classLoader.loadClass(ruleClassName);
return (Rule) ruleClass.getDeclaredConstructor().newInstance();
}
private String generateRuleSource(String className, String condition) {
return "import java.util.*;\n" +
"public class " + className + " implements DynamicRuleEngine.RuleCompiler.Rule {\n" +
"    public boolean evaluate(Map<String, Object> context) {\n" +
"        // Extract variables from context\n" +
generateVariableExtractions(condition) +
"        // Evaluate condition\n" +
"        return " + condition + ";\n" +
"    }\n" +
"    \n" +
"    public String getDescription() {\n" +
"        return \"" + escape(condition) + "\";\n" +
"    }\n" +
"    \n" +
generateHelperMethods() +
"}";
}
private String generateVariableExtractions(String condition) {
// Simple extraction - in real implementation, parse condition properly
Set<String> variables = extractVariables(condition);
StringBuilder code = new StringBuilder();
for (String var : variables) {
code.append("        Object ").append(var).append("Obj = context.get(\"")
.append(var).append("\");\n");
code.append("        ").append(getTypeDeclaration(var)).append(" ")
.append(var).append(" = ").append(getConversion(var)).append(";\n");
}
return code.toString();
}
private Set<String> extractVariables(String condition) {
Set<String> variables = new HashSet<>();
// Simple regex-based extraction - would need proper parsing in production
String[] tokens = condition.split("[^a-zA-Z_]");
for (String token : tokens) {
if (token.matches("[a-zA-Z_][a-zA-Z_0-9]*") && 
!Arrays.asList("true", "false", "null", "context").contains(token)) {
variables.add(token);
}
}
return variables;
}
private String getTypeDeclaration(String varName) {
// Simple heuristic for types
if (varName.startsWith("is") || varName.startsWith("has")) {
return "boolean";
} else if (varName.contains("Age") || varName.contains("Count")) {
return "int";
} else if (varName.contains("Price") || varName.contains("Amount")) {
return "double";
}
return "String";
}
private String getConversion(String varName) {
String type = getTypeDeclaration(varName);
switch (type) {
case "boolean": return varName + "Obj != null ? Boolean.parseBoolean(String.valueOf(" + varName + "Obj)) : false";
case "int": return varName + "Obj != null ? Integer.parseInt(String.valueOf(" + varName + "Obj)) : 0";
case "double": return varName + "Obj != null ? Double.parseDouble(String.valueOf(" + varName + "Obj)) : 0.0";
default: return varName + "Obj != null ? String.valueOf(" + varName + "Obj) : \"\"";
}
}
private String generateHelperMethods() {
return "    // Helper methods for string operations\n" +
"    private boolean contains(String str, String search) {\n" +
"        return str != null && search != null && str.contains(search);\n" +
"    }\n" +
"    \n" +
"    private boolean startsWith(String str, String prefix) {\n" +
"        return str != null && prefix != null && str.startsWith(prefix);\n" +
"    }\n" +
"    \n" +
"    private boolean endsWith(String str, String suffix) {\n" +
"        return str != null && suffix != null && str.endsWith(suffix);\n" +
"    }\n";
}
private String escape(String text) {
return text.replace("\\", "\\\\").replace("\"", "\\\"");
}
}
public static void main(String[] args) throws Exception {
RuleCompiler ruleCompiler = new RuleCompiler();
// Compile various business rules
List<RuleCompiler.Rule> rules = Arrays.asList(
ruleCompiler.compileRule("age >= 18 && age <= 65"),
ruleCompiler.compileRule("salary > 50000 && department.equals(\"Engineering\")"),
ruleCompiler.compileRule("yearsOfExperience >= 5 || hasCertification"),
ruleCompiler.compileRule("!isProbationary && performanceRating >= 4.0"),
ruleCompiler.compileRule("contains(productCategory, \"Electronics\") && price < 1000.0")
);
// Test data
Map<String, Object> candidate1 = new HashMap<>();
candidate1.put("age", 25);
candidate1.put("salary", 75000);
candidate1.put("department", "Engineering");
candidate1.put("yearsOfExperience", 3);
candidate1.put("hasCertification", true);
candidate1.put("isProbationary", false);
candidate1.put("performanceRating", 4.5);
candidate1.put("productCategory", "Consumer Electronics");
candidate1.put("price", 799.99);
Map<String, Object> candidate2 = new HashMap<>();
candidate2.put("age", 17);
candidate2.put("salary", 30000);
candidate2.put("department", "Marketing");
candidate2.put("yearsOfExperience", 1);
candidate2.put("hasCertification", false);
candidate2.put("isProbationary", true);
candidate2.put("performanceRating", 3.0);
candidate2.put("productCategory", "Books");
candidate2.put("price", 29.99);
// Evaluate rules for each candidate
System.out.println("=== CANDIDATE 1 EVALUATION ===");
evaluateCandidate(rules, candidate1);
System.out.println("\n=== CANDIDATE 2 EVALUATION ===");
evaluateCandidate(rules, candidate2);
}
private static void evaluateCandidate(List<RuleCompiler.Rule> rules, Map<String, Object> candidate) {
for (RuleCompiler.Rule rule : rules) {
boolean result = rule.evaluate(candidate);
System.out.printf("%-60s => %s%n", rule.getDescription(), result);
}
}
// Inner classes would be here (same as previous example)
// JavaSourceFromString, InMemoryFileManager, JavaClassInMemory, InMemoryClassLoader
}

4. Error Handling and Diagnostics

Comprehensive Error Reporting

import javax.tools.*;
import java.util.*;
import java.util.stream.Collectors;
public class AdvancedErrorHandling {
public static class CompilationResult {
private final boolean success;
private final List<Diagnostic<? extends JavaFileObject>> diagnostics;
private final Map<String, byte[]> compiledClasses;
public CompilationResult(boolean success, 
List<Diagnostic<? extends JavaFileObject>> diagnostics,
Map<String, byte[]> compiledClasses) {
this.success = success;
this.diagnostics = diagnostics;
this.compiledClasses = compiledClasses;
}
public boolean isSuccess() { return success; }
public List<Diagnostic<? extends JavaFileObject>> getDiagnostics() { return diagnostics; }
public Map<String, byte[]> getCompiledClasses() { return compiledClasses; }
public List<String> getErrorMessages() {
return diagnostics.stream()
.filter(d -> d.getKind() == Diagnostic.Kind.ERROR)
.map(this::formatDiagnostic)
.collect(Collectors.toList());
}
public List<String> getWarningMessages() {
return diagnostics.stream()
.filter(d -> d.getKind() == Diagnostic.Kind.WARNING || 
d.getKind() == Diagnostic.Kind.MANDATORY_WARNING)
.map(this::formatDiagnostic)
.collect(Collectors.toList());
}
private String formatDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) {
return String.format("Line %d: %s", 
diagnostic.getLineNumber(), diagnostic.getMessage(null));
}
}
public static CompilationResult compileWithDetailedErrors(
String className, String sourceCode, List<String> options) {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
InMemoryFileManager fileManager = new InMemoryFileManager(
compiler.getStandardFileManager(diagnostics, null, null));
List<JavaFileObject> compilationUnits = Arrays.asList(
new JavaSourceFromString(className, sourceCode)
);
JavaCompiler.CompilationTask task = compiler.getTask(
null, fileManager, diagnostics, options, null, compilationUnits);
boolean success = task.call();
Map<String, byte[]> compiledClasses = fileManager.getCompiledClasses();
return new CompilationResult(success, diagnostics.getDiagnostics(), compiledClasses);
}
public static void main(String[] args) {
// Test with various error scenarios
testErrorScenario("Syntax error", 
"public class Test { public void method() { missing semicolon } }");
testErrorScenario("Type error",
"public class Test { public void method() { String s = 123; } }");
testErrorScenario("Missing import",
"public class Test { public void method() { List list = new ArrayList(); } }");
testErrorScenario("Valid code",
"import java.util.*;\n" +
"public class Test {\n" +
"    public void method() {\n" +
"        List<String> list = new ArrayList<>();\n" +
"        list.add(\"test\");\n" +
"    }\n" +
"}");
}
private static void testErrorScenario(String scenario, String sourceCode) {
System.out.println("\n=== " + scenario + " ===");
System.out.println("Source: " + sourceCode.replace("\n", " "));
CompilationResult result = compileWithDetailedErrors("Test", sourceCode, Arrays.asList());
if (result.isSuccess()) {
System.out.println("✓ Compilation successful");
System.out.println("Generated classes: " + result.getCompiledClasses().keySet());
} else {
System.out.println("✗ Compilation failed");
System.out.println("Errors:");
result.getErrorMessages().forEach(error -> System.out.println("  - " + error));
}
List<String> warnings = result.getWarningMessages();
if (!warnings.isEmpty()) {
System.out.println("Warnings:");
warnings.forEach(warning -> System.out.println("  - " + warning));
}
}
// Inner classes would be here
}

Summary

Key Benefits of Java Compiler API:

  1. Dynamic Compilation: Compile Java code at runtime
  2. In-Memory Compilation: Generate classes without writing to disk
  3. Custom Compilation: Control compilation process programmatically
  4. Code Generation: Create and compile generated code
  5. Template Engines: Implement dynamic template processing

Common Use Cases:

  • Rule Engines: Compile business rules at runtime
  • Template Systems: Generate dynamic HTML/email templates
  • Educational Tools: Online Java compilers and IDEs
  • Plugin Systems: Dynamic loading of user-provided code
  • DSL Implementation: Domain-specific languages that compile to Java

Performance Considerations:

  • Caching: Cache compiled classes to avoid recompilation
  • ClassLoader Management: Properly manage class loader lifecycles
  • Memory Usage: Be mindful of memory when compiling many classes
  • Security: Validate generated code when compiling untrusted sources

Limitations:

  • Requires JDK (not just JRE)
  • Compilation overhead at runtime
  • Security implications when compiling untrusted code
  • Increased application complexity

The Java Compiler API is a powerful tool for building dynamic, code-generating applications, but should be used judiciously due to its complexity and performance characteristics.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper