http://www.ibm.com/developerworks/cn/java/j-lo-jse64/
编译非文本形式的文件
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 |
方法得到。关于每个参数的含义,请参见 JDK 文档。传递不同的参数,会得到不同的 CompilationTask。通过构造这个类,一个编译过程可以被分成多步。进一步,CompilationTask 提供了 setProcessors(Iterable extends Processor>processors) 方法,用户可以制定处理 annotation 的处理器。图 1 展示了通过 CompilationTask 进行编译的过程:
下面的例子通过构造 CompilationTask 分多步编译一组 Java 源文件。
清单 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 的一个测试类,而不是手工编写。使用 compiler API,可以将内存中的一段字符串,编译成一个 CLASS 文件。
清单 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 Calculator 08 // Steps used to compile StringObject 09 // construct CalculatorTest in memory 10 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 对象。