编译非文本形式的文件
JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件。JavaCompiler 类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager
- 解析 javac 的参数;
- 在 source path 和/或 CLASSPATH 中查找源文件或者 jar 包;
- 处理输入,输出文件;
在这个过程中,JavaFileManager 类可以起到创建输出文件,读入并缓存输出文件的作用。由于它可以读入并缓存输入文件,这就使得读入各种形式的输入文件成为可能。JDK 提供的命令行工具,处理机制也大致相似,在未来的版本中,其它的工具处理各种形式的源文件也成为可能。为此,新的 JDK 定义了 javax.tools.FileObject 和 javax.tools.JavaFileObject 接口。任何类,只要实现了这个接口,就可以被 JavaFileManager
如果要使用 JavaFileManager,就必须构造 CompilationTask。JDK 6 提供了 JavaCompiler.CompilationTask
JavaCompiler.getTask ( Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits)
方法得到。关于每个参数的含义,请参见 JDK 文档。传递不同的参数,会得到不同的 CompilationTask。通过构造这个类,一个编译过程可以被分成多步。进一步,CompilationTask 提供了 setProcessors(Iterable<? extends Processor>processors) 方法,用户可以制定处理 annotation 的处理器。图 1 展示了通过 CompilationTask
图 1. 使用 CompilationTask 进行编译
下面的例子通过构造 CompilationTask
清单 2. 构造 CompilationTask 进行编译
01 package math;02 public class Calculator {03 public int multiply(int multiplicand, int multiplier) {04 return multiplicand * multiplier;05 }06 }07 package compile;08 import javax.tools.*;09 import java.io.FileOutputStream;10 import java.util.Arrays;11 public class Compiler {12 public static void main(String[] args) throws Exception{13 String fullQuanlifiedFileName = "math" + java.io.File.separator +"Calculator.java";14 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();15 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);16 Iterable<? extends JavaFileObject> files = fileManager.getJavaFileObjectsFromStrings( Arrays.asList(fullQuanlifiedFileName));17 JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, null, null, null, files);18 Boolean result = task.call();19 if( result == true ) {20 System.out.println("Succeeded");21 }22 }23 }
以上是第一步,通过构造一个 CompilationTask 编译了一个 Java 文件。14-17 行实现了主要逻辑。第 14 行,首先取得一个编译器对象。由于仅仅需要编译普通文件,因此第 15 行中通过编译器对象取得了一个标准文件管理器。16 行,将需要编译的文件构造成了一个 Iterable 对象。最后将文件管理器和 Iterable 对象传递给 JavaCompiler 的 getTask 方法,取得了 JavaCompiler.CompilationTask
接下来第二步,开发者希望生成 Calculator
清单 3. 定制 JavaFileObject 对象
01 package math;02 import java.net.URI;03 public class StringObject extends SimpleJavaFileObject{04 private String contents = null;05 public StringObject(String className, String contents) throws Exception{06 super(new URI(className), Kind.SOURCE);07 this.contents = contents;08 }09 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {10 return contents;11 }12 }
SimpleJavaFileObject 是 JavaFileObject 的子类,它提供了默认的实现。继承 SimpleJavaObject 之后,只需要实现 getCharContent 方法。如 清单 3 中的 9-11 行所示。接下来,在内存中构造 Calculator 的测试类 CalculatorTest,并将代表该类的字符串放置到 StringObject 中,传递给 JavaCompiler 的 getTask 方法。清单 4 展现了这些步骤。
清单 4. 编译非文本形式的源文件
01 package math;02 import javax.tools.*;03 import java.io.FileOutputStream;04 import java.util.Arrays;05 public class AdvancedCompiler {06 public static void main(String[] args) throws Exception{07 // Steps used to compile Calculator08 // Steps used to compile StringObject09 // construct CalculatorTest in memory10 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();11 StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);12 JavaFileObject file = constructTestor();13 Iterable<? extends JavaFileObject> files = Arrays.asList(file);14 JavaCompiler.CompilationTask task = compiler.getTask ( null, fileManager, null, null, null, files);15 Boolean result = task.call();16 if( result == true ) {17 System.out.println("Succeeded");18 }19 }20 private static SimpleJavaFileObject constructTestor() {21 StringBuilder contents = new StringBuilder( "package math;" + "class CalculatorTest {\n" + " public void testMultiply() {\n" + " Calculator c = new Calculator();\n" + " System.out.println(c.multiply(2, 4));\n" + " }\n" + " public static void main(String[] args) {\n" + " CalculatorTest ct = new CalculatorTest();\n" + " ct.testMultiply();\n" + " }\n" + "}\n");22 StringObject so = null;23 try {24 so = new StringObject("math.CalculatorTest", contents.toString());25 } catch(Exception exception) {26 exception.printStackTrace();27 }28 return so;29 }30 }
实现逻辑和 清单 2 相似。不同的是在 20-30 行,程序在内存中构造了 CalculatorTest 类,并且通过 StringObject 的构造函数,将内存中的字符串,转换成了 JavaFileObject