Compiling with the Java Compiler API

来源:互联网 发布:3dmax2017 mac破解版 编辑:程序博客网 时间:2024/04/29 19:34
  利用Java Compiler API 来编译
  Compiling with the Java Compiler API     (本文翻译自Core Java Technology Tech Tips for April 2007

起 初,标准java平台没有提供标准的接口来调用其自身的编译器生成Java字节码。在sun提供的平台实现上,用户可以通过一个非标准的 com.sun.tools.javac包来编译自己的代码。但是这个包并不能提供标准的、开放的编程借口。使用其他java平台实现的用户不能访问这个 类。使用java se 6和它的java compiler API (由JSR-199定义),你可以从自己的应用中直接调用javac编译工具。

有两种方法来使用这个工具。一种相对简单,另外一种要复杂一些但是允许你处理更多的要求。你可以首先使用简单的方法来编译下面的“hello world“程序:

public class Hello {
  public static void main(String args[]) {
    System.out.println("Hello, World");
  }
}

为了从你的程序中调用java编译工具,你需要使用JavaCompiler借口。使用这个借口,你可以设置源代码路径,classpath,还有目的路径。通过将每个待编译的文件作为一个个JavaFileObject实例,你可以编译它
们。暂时你还不需要理解这个JavaFileObject。
使用ToolProvider类来获得JavaCompiler接口的一个默认实现。ToolProvider类提供一个getSystemJavaCompiler()方法,返回JavaCompiler接口的一个实例。

JavaCompiler compiler=ToolProvider.getSystemJavaCompiler();

使用JavaCompiler最简单的方法是直接调用run()方法,run()方法是在Tool接口中实现的,其声明为:

int run(InputStream in, 
    OutputStream out, 
    OutputStream err, 
    String... arguments)

传递null作为其中的流参数会使用默认的System.in, System.out, 和 System.err作为函数的前三个参数。后面可变的String实例表示需要传递给编译器的文件名。

所以,为了编译前面提出的Hello类,你只需要:

int results = tool.run(null, null, null, "Hello.java");

假如没有错误的话,这段代码会在目标文件夹里产生一个Hello.class文件。如果出错,run()方法会将消息输出到标准错误流(System.err)中,也就是函数中第三个参数指定的流。函数出错时会返回一个非0值。

你可以使用下面的代码来编译Hello.java文件:

import java.io.*;
import javax.tools.*;

public class CompileIt {
  public static void main(String args[]) throws IOException {
    JavaCompiler compiler =
        ToolProvider.getSystemJavaCompiler();
    int results = compiler.run(
        null, null, null, "Hello.java");
    System.out.println("Result code: " + results);
  }
}

当你编译好上面的CompileIt程序后,你可以多次使用它来重新编译Hello.java文件,而不需要一再地重新编译CompileIt文件。如果没有出错,运行CompileIt输出如下:

> java CompileIt
Result code: 0

运行CompileIt会在相同的文件夹里产生一个Hello.class文件:

> ls
CompileIt.class
CompileIt.java
Hello.class
Hello.java

读 到这里,你可以停下来了,因为上面的这些东西已经足够你使用这个标准的编译器了,但是还有更多。如果你想要更好地获得结果,你还有第二种方法来使用编译 器。准确地说,这第二种方法允许开发者使用一种更加好看的表达方式来显示编译的结果,而不仅仅是传递一段错误信息到stderr。这个方法利用了 StandardJavaFileManager类的优点。这个文件管理器提供了一种方法来完成普通文件的输入输出工作。同时在一个 DiagnosticListener实例的帮助下报告编译的诊断信息。后面将要用到的DiagnosticCollector类只是前面那个 listener的一个实现。

在确定什么东西是需要编译的之前,你需要一个文件管理器。创建一个文件管理器需要两个基本的步骤:创建一个DiagnosticCollector然后使用getStandardFileManager()方法JavaCompiler申请文件管理器。传递 DiagnosticListener实例作为getStandardFileManager()方法的参数。这个listener报告非致命性的错误,你也可以选择通过将它传递给getTask()方法与编译器共享这个listener。

DiagnosticCollector<JavaFileObject> diagnostics =
    new DiagnosticCollector<JavaFileObject>();
StandardJavaFileManager fileManager =
    compiler.getStandardFileManager(diagnostics, aLocale, aCharset);

你也可以给这个调用提供一个null的diagnostics listener,但这样只是跟前面的编译器方法一样的效果(dmshi:也就是说实用系统默认值)。

在细致地讨论StandardJavaFileManager之前,编译过程包含了JavaCompiler的一个getTask()方法。它接受6个参数并返回一个内部类CompilatoinTask的实例:

JavaCompiler.CompilationTask getTask(
    Writer out,
    JavaFileManager fileManager,
    DiagnosticListener<? super JavaFileObject> diagnosticListener,
    Iterable<String> options,
    Iterable<String> classes,
    Iterable<? extends JavaFileObject> compilationUnits)

大部分的参数都可以为null,他们都有默认值:
* out: System.err
* fileManager: 编译器的标准文件管理器
* diagnosticListener: 编译器的默认操作
* options:没有传递给compiler的命令行 no command-line options to compiler
* classes: 没有annotation处理的类名(no class names for annotation processing)

最后一个参数compilationUnits不应该为null,因为那指明你所需要编译的内容。这里我们又需要重新开始讨论StandardJavaFileManager。注意这里参数类型为:Iterable<? extends JavaFileObject>。StandardJavaFileManager的两个方法可以得到这个结果。你可以选择两种方式来表示文件名:或者用一个File对象的列表,或者用String对象的列表:

Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(
    Iterable<? extends File> files)
Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(
    Iterable<String> names)

事实上,任何满足Iterable的对象都可以用来指明所需要编译对象的集合,而不仅仅是List。这里使用List仅仅是因为它最容易创建^_^:

String[] filenames = ...;
Iterable<? extends JavaFileObject> compilationUnits =
    fileManager.getJavaFileObjectsFromFiles(Arrays.asList(filenames));

现在你拥有足够的信息来编译你的源文件。getTask()方法返回的JavaCompiler.CompilationTask实现了Callable接口。所以,需要调用call()方法来开始:

JavaCompiler.CompilationTask task =
    compiler.getTask(null, fileManager, null, null, null, compilationUnits);
Boolean success = task.call();

假定没有编译错误发生,call()方法会编译所有由compilationUnits变量所指明的文件,以及相关的编译依赖。为了查明编译是否成功,检查上面的success值。call()方法在所有的编译单元都完成之后返回Boolean.TRUE,否则返回 Boolean.FALSE

在展示例子之前,我们还有最有一个对象需要了解:DiagnosticListener,或者准确地说,它的实现者DiagnosticCollector。将listener作为getTask()的第三个参数,那么在编译后你可以查询编译的诊断信息:

for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
  System.console().printf(
      "Code: %s%n" +
      "Kind: %s%n" +
      "Position: %s%n" +
      "Start Position: %s%n" +
      "End Position: %s%n" +
      "Source: %s%n" +
      "Message:  %s%n",
      diagnostic.getCode(), diagnostic.getKind(),
      diagnostic.getPosition(), diagnostic.getStartPosition(),
      diagnostic.getEndPosition(), diagnostic.getSource(),
      diagnostic.getMessage(null));
}

最后,你可以调用文件管理器的close()方法。

将上面所有的整理起来得到下面的程序:

import java.io.*;
import java.util.*;
import javax.tools.*;

public class BigCompile {
  public static void main(String args[]) throws IOException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnostics =
        new DiagnosticCollector<JavaFileObject>();
    StandardJavaFileManager fileManager =
        compiler.getStandardFileManager(diagnostics, null, null);
    Iterable<? extends JavaFileObject> compilationUnits =
        fileManager.getJavaFileObjectsFromStrings(Arrays.asList("Hello.java"));
    JavaCompiler.CompilationTask task = compiler.getTask(
        null, fileManager, diagnostics, null, null, compilationUnits);
    Boolean success = task.call();
    for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
      System.console().printf(
          "Code: %s%n" +
          "Kind: %s%n" +
          "Position: %s%n" +
          "Start Position: %s%n" +
          "End Position: %s%n" +
          "Source: %s%n" +
          "Message:  %s%n",
          diagnostic.getCode(), diagnostic.getKind(),
          diagnostic.getPosition(), diagnostic.getStartPosition(),
          diagnostic.getEndPosition(), diagnostic.getSource(),
          diagnostic.getMessage(null));
    }
    fileManager.close();
    System.out.println("Success: " + success);
  }
}

编译执行上面的程序会输出下面的成功信息:

> javac BigCompile.java
> java BigCompile
Success: true

如果程序出错,比如将println写成了pritnln,你会得到下面的信息:

> java BigCompile
Code: compiler.err.cant.resolve.location
Kind: ERROR
Position: 80
Start Position: 70
End Position: 88
Source: Hello.java
Message:  Hello.java:3: cannot find symbol
symbol  : method pritnln(java.lang.String)
location: class java.io.PrintStream
Success: false

使 用Compiler API,你可以做的事情远远不止上面所简单描述的内容。例如,你可以控制输入和输出的文件夹,或者在一个IDE里面实现编译错误的高亮处理。Now, thanks to the Java Compiler API, you can do all that with standard API calls. For more information on the Java Compiler API and JSR 199, see the JSR 199 specification.