java编译器API<二>

2.高级特性

在上面的章节中,我们了解了如何通过JavaCompiler工具编译文件。编译相关的高级特性,依赖于Manager服务和Diagnostics服务。这些服务分别由JavaFileManager和DiagnosticListener类来提供。

2.1 JavaFileManager

JavaFileManager(具体的实现是StandardJavaFileManager),管理与tools关联的所有文件对象。JavaFileManager不仅仅是管理JavaCompiler,它可以管理所有实现了标准Tool接口的对象。为了了解JavaFileManager为什么这么重要,我们来了解一下编译过程的那些事。

 javac MyClass.java

当我们在命令行中输入这个命令行时,会有很多事情发生。首先,编译器要解析与用户有关的选项、验证这些选项,然后,扫描扫描源文件和java源文件以及jar包的路径,最后,处理输入文件(在这里,输入文件是MyClass.java)和输出文件(MyClass.class).

JavaFileManager和很多工具相关联的,用来管理输入和输出文件。从管理的角度上来说,它就是创建输出文件以及扫描输入文件以及为了更好的性能把他们缓存起来。JavaFileManager的一个给定的实现就是StandardJavaFileManager.

被JavaFileManager管理的文件,不一定在硬盘上,它可以在硬盘上、内存中或者远程的网络。这就是为什么JavaFileManager不处理java.io.File的对象(这些对象一般指存在于物理硬盘的文件系统)。JavaFileManager管理FileObject和JavaFileObject格式的所有文件和内容。

FileObject和JavaFileObject都是JavaFileManager管理的文件,二者之间的区别是,FileObject可以表示所有的文件类型(比如text文件、properties文件、image文件等),而JavaFileObject仅仅用来表示Java源文件或者class文件。如果一个文件是java源文件或者一个class文件,那么,实现类要返回一个JavaFileObject.

2.2 Diagnostics

JavaCompiler依赖的第二个服务是Diagnostic服务,Diagnostic一般指在程序中出现的错误、预警或者有用的信息。为了获得编译过程的Diagnostic信息,我们可以给编译器对象安插一个监听器DiagnosticListener对象,它的report方法会和一个diagnostic方法一起调用,这个diagnostic方法包含diagnostics的类型(错误、预警、信息或者其他)、这个diagnostics的源代码、代码行等很多信息。

2.2.1CompilationTask

在通过示例来验证JavaFileManager、Diagnostic以及DiagnosticListener类之前,我们来了解一下CompilationTask这个类。从他的名字中可以看出,它是用来封装具体的编译操作的。我们可以通过调用CompilationTask中的call方法来发起一个编译操作。

但是,怎么获取CompilationTask实例呢?

由于,CompilationTask和JavaCompiler是紧密联系在一起的,我们可以简单的通过JavaCompiler.getCompilationTask(参数)来获取CompilationTask实例,我们现在来看一下需要传入到getCompilationTask方法里面的参数:

这个方法里面的参数除了最后一个,都可以为使用默认值,也就是传入null值,但是,最后一个参数不能为null,因为最后一个参数表示要翻译的java类或者jar包,最后一个参数是Iterable<? Extends JavaFileObject>类型的对象。

哦吼,我们该怎么填充这个参数呢?

【Iterable 是一个从JDK5.0之后新增的一个接口,通过hasNext和next方法,来遍历集合对象。为了类型安全的考虑,开发者需要制定特定的遍历对象, Iterable<? Extends JavaFileObject>也就意味着现在要遍历的对象需要实现JavaFileObject接口】

所以嘛,我们可以使用任何便利的方法来构造第四个参数(也就是代表需要编译的资源的那个参数)。现在,我们用一个简单的例子,来看一下前面说到的一些东西。

1.我们创建一个错误一堆的一个类 MoreErrors.java

MoreErrors.java:
package test;
public class MoreErrors {
    public void errorOne ()
        // No open braces here.  Line a
    }
    public void errorTwo(){
        System.out.println("No semicolon") // Line b
        // No semicolon at the end of the statement.
    }
    public void errorThree(){
        System.out.prntln("No method name called prntln()"); // Line c
    }
}

从上面的代码中可以看出,Line a\Line b\Line c对应的地方都有错误。

2.使用上面我们提到的那些类和方法来创建一个AdvancedCompilationTest.java

AdvancedCompilationTest.java:
package test;
import java.io.*;
import java.util.*;
import javax.tools.*;
import javax.tools.JavaCompiler.*;
public class AdvancedCompilationTest {
    public static void main(String[] args) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();// Line 1.
        MyDiagnosticListener listener = new MyDiagnosticListener(); // Line 2.
        StandardJavaFileManager fileManager  =
        compiler.getStandardFileManager(listener, null, null); // Line 3.
        String fileToCompile = "test" + File.separator + "ManyErrors.java";// Line 4
        Iterable fileObjects = fileManager.getJavaFileObjectsFromStrings(Arrays.asList(fileToCompile));  // Line 5
        CompilationTask task = compiler.getTask(null, fileManager, listener, null, null, fileObjects); // Line 6
        Boolean result = task.call(); // Line 7
        if(result == true){
            System.out.println("Compilation has succeeded");
        }
    }
}
class MyDiagnosticListener implements DiagnosticListener{
    public void report(Diagnostic diagnostic) {
        System.out.println("Code-->" +  diagnostic.getCode());
        System.out.println("Column Number-->" + diagnostic.getColumnNumber());
        System.out.println("End Position-->" + diagnostic.getEndPosition());
        System.out.println("Kind-->" + diagnostic.getKind());
        System.out.println("Line Number-->" + diagnostic.getLineNumber());
        System.out.println("Message-->"+ diagnostic.getMessage(Locale.ENGLISH));
        System.out.println("Position-->" + diagnostic.getPosition());
        System.out.println("Source->" + diagnostic.getSource());
        System.out.println("Start Position-->" + diagnostic.getStartPosition());
        System.out.println("\n");
    }
}

      3.我们详细了解一下上面的代码

第一行,通过ToolProvider类创建一个JavaCompiler的实例:JavaCompiler compiler = ToolProvider.getSystemJavaCompiler()

第二行和第三行,标记编译器使用FileManager和Diagnostics的服务,JavaFileManager对象用来管理输入和输出文件,Diagnostics对象值在程序编译过程中出现的错误、预警或者消息。

    MyDiagnosticListener listener =new MyDiagnosticListener();

第二行,创建了一个DiagnosticListener对象,用来监控编译时可能出现的信息。重写report方法用来提取所有尽可能的消息。diagnostics可能发生在任何文件对象中,我们该怎么指定Diagnostics是为了某个java文件对象呢?MyDiagnosticListener是一个类,也就是说它可以用在任何具有dagnostics的对象中,在这里我们需要显示的展示出来,这个diagnostics为JavaFileObject特定的,不是为了所有的对象。

class MyDiagnosticListener implements DiagnosticListener{
    public void report(Diagnostic diagnostic) {
        System.out.println("Code->" +  diagnostic.getCode());
        System.out.println("Column Number->" + diagnostic.getColumnNumber());
        ….
        ….
    }
}

第三行,通过标准的java文件管理器,将Diagnostics监听器对象和编译器对象关联在一起。

StandardJavaFileManager fileManager  = compiler.getStandardFileManager(listener,null,null);

这样绑定在一起之后,无论任何时候,编译器对象在执行一个编译操作的时候,如果有任何的diagnostics发生,都会被Diagnostic对象封装,并传回给DiagnosticListener接口的report方法。

最后两个参数,是本地话与语言类型的参数,可以为null。第四行和第五行,在StandardJavaFileManager类中填充需要编译的对象。

String fileToCompile = “test”+ File.separator +”ManyErrors.java”;

Iterable fileObjects = fileManager.getJavaFileObjectsFromStrings(Arrays.asList(fileToCompile));

第六行,就是获取CompilationTask对象实例的地方,在调用getCompilationTask方法的时候,传入fileManager\listener以及fileObjects对象。null参数,是指写对象(从编译器中获取的输出)、可选项列表等。

最后,实际的编译操作是通过调用call方法完成的,如果它返回true,代表正常编译完毕,否则,代表编译出错。对于我们上面的这个例子,MoreErrors.java文件中存在多处错误,所以,在编译的时候,report方法将会被调用,用来打印出需要的一些信息。

2.3 DiagnosticCollector

在上面的程序中,我们写了一个MyDiagnosticListener类,来收集需要打印到控制台的diagnostic信息。其实啊,在Java6.0中,有类似的实现,它就是DiagnosticCollection<SourceObject>,它有一个getDiagnostics方法,这个方法返回一个list,通过这个list我们遍历出diagnostic信息到控制台。

AdvancedCompilationTest2.java:
package test;
import java.io.*;
import java.util.*;
import javax.tools.*;
import javax.tools.JavaCompiler.*;
public class AdvancedCompilationTest2 {
    public static void main(String[] args) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); // Line 1.
        DiagnosticCollector diagnosticsCollector =
        new DiagnosticCollector();
        StandardJavaFileManager fileManager  =compiler.getStandardFileManager(diagnosticsCollector, null, null); // Line 3.
        String fileToCompile = "test" + File.separator + "ManyErrors.java"; // Line 4
        Iterable fileObjects = fileManager.getJavaFileObjectsFromStrings(Arrays.asList(fileToCompile)); // Line 5
        CompilationTask task = compiler.getTask(null, fileManager, diagnosticsCollector, null, null, fileObjects); // Line 6
        Boolean result = task.call(); // Line 7
        List diagnostics = diagnosticsCollector.getDiagnostics();
        for(Diagnostic d : diagnostics){
            // Print all the information here.
        }
        if(result == true){
            System.out.println("Compilation has succeeded");
        }else{
            System.out.println("Compilation fails.");
        }
}
}

2.4 String对象中的java源文件编译

上面已经讨论了多种编译java文件的方式,现在,我们讨论一下如何编译封装在String对象中的java文件。像上面提到的那样,java源文件不一定非要在硬盘上,它可以驻留在内存中,也就是编译一个String对象中的java源文件,这个源文件驻留在内存中,更具体的是,它驻留在RAM中。

对于这种情况,我们需要封装一个代表String中java文件的类,我们可以从SimpleJavaFileObject(重载了JavaFileObject类所有的方法,有些方法是默认实现)类中扩展。唯一需要重载的方法就是getCharContent,这个方法会被Java编译器调用,用来获得java源文件的内容。

JavaObjectFromString.java:
package test;
import java.net.URI;
class JavaObjectFromString extends SimpleJavaFileObject{
    private String contents = null;
public JavaObjectFromString(String className, String contents) throws Exception{
            super(new URI(className), Kind.SOURCE);
this.contents = contents;
    }
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
            return contents;
    }
}

由于SimpleJavaFileObject有一个具有两个参数的构造器,这两个参数分别为URI(文件对象的URI)和一个Kind对象(Kind是用来标明对象的类型,可以选择的类型有Kind.SOURCE,Kind.CLASS,Kind.HTML,Kind.OTHER,在我们这里,需要的是Kind.SOURCE),所以,在JavaObjectFromString类中,我们定义了一个具有两个参数的构造器,并把具体的控制委派给父类。在这里,重载了getCharContent()方法(因为这个方法会在JavaCompiler获取具体的java源文件内容的时候会被调用),这个方法返回整个java源文件的字符串(这个方法的返回类型要实现CharSequence)。

下面是使用JavaObjectFromString类的代码:

AdvancedCompilationTest3.java:
package test;
import java.io.*;
import java.util.*;
import javax.tools.*;
import javax.tools.JavaCompiler.*;
public class AdvancedCompilationTest3 {
    public static void main(String[] args) throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        DiagnosticCollector diagnosticsCollector = new DiagnosticCollector();
        StandardJavaFileManager fileManager  = compiler.getStandardFileManager(diagnosticsCollector, null, null);
        JavaFileObject javaObjectFromString = getJavaFileContentsAsString();
        Iterable fileObjects = Arrays.asList(javaObjectFromString);
        CompilationTask task = compiler.getTask(null, fileManager, diagnosticsCollector, null, null, fileObjects);
        Boolean result = task.call();
        List diagnostics = diagnosticsCollector.getDiagnostics();
        for(Diagnostic d : diagnostics){
            // Print all the information here.
        }
        if(result == true){
            System.out.println("Compilation has succeeded");
        }else{
            System.out.println("Compilation fails.");
        }
    }
    static SimpleJavaFileObject getJavaFileContentsAsString(){
        StringBuilder javaFileContents = new StringBuilder("" +
                "class TestClass{" +
                "   public void testMethod(){" +
        "System.out.println(" + "\"test\"" +           ");" +
                "}" +
                "}");
        JavaObjectFromString javaFileObject = null;
        try{
        javaFileObject = new JavaObjectFromString("TestClass", javaFileContents.toString());
        }catch(Exception exception){
            exception.printStackTrace();
        }
        return javaFileObject;
    }
}

3.结论

这个功能的出现,多少让人为之一震,我们可以直接在java程序中编译java文件,是不是有点兴奋?是不是觉得想想的空间又大了很多?现在,可能这个API的功能还不够强大或者或多或少有点bug和不足,毕竟这是一个开始,我们希望,在接下来的版本中,这个功能越来越强大,让我们的想象变成现实。

原文地址:http://www.javabeat.net/2007/04/the-java-6-0-compiler-api/


Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>