编译非文本形式的文件

JDK 6 的编译器 API 的另外一个强大之处在于,它可以编译的源文件的形式并不局限于文本文件。JavaCompiler 类依靠文件管理服务可以编译多种形式的源文件。比如直接由内存中的字符串构造的文件,或者是从数据库中取出的文件。这种服务是由 JavaFileManager

  1. 解析 javac 的参数;
  2. 在 source path 和/或 CLASSPATH 中查找源文件或者 jar 包;
  3. 处理输入,输出文件;

在这个过程中,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