New API features
JDK 6 provides an API for calling the compiler at runtime. We will assume that this API is applied to JSP technology later. In traditional JSP technology, the following six steps are usually required for servers to process JSP:
- Analyze JSP code;
- Generate Java code;
- Write Java code into memory;
- Start another process and run the compiler to compile Java code;
- Write class files into the memory;
- The server reads and runs class files;
However, if you use runtime compilation, You can simplify steps 4 and 5 at the same time, saving the overhead of new processes and writing Memory output overhead, and improving system efficiency. In fact, Sun also provides a programming interface for calling the compiler in JDK 5. However, the difference is that the programming interface of the old version is not part of the standard API, but provided as sun's proprietary implementation. The new version brings the advantages of standardization.
The second new feature of the new API is the ability to compile abstract files, theoretically any form of Object-as long as the object implements a specific interface. With this feature, step 3 in the preceding example can be omitted. The compilation of the entire JSP is completed in one process, and additional input and output operations are eliminated.
The third new feature is to collect diagnostic information during compilation. As a supplement to the first two new features, it allows developers to easily output necessary compilation errors or warning information, thus saving a lot of redirection troubles.
Compile java files during runtime
In JDK 6, the class library passesjavax.tools
The package provides the API for calling the compiler when the program is running. From the Tool Name of this package, we can see that the functions provided by this development kit are not limited to compilers. Tools also include javah, jar, and pack200. They are all command line tools provided by JDK. This development kit hopes to call these tools at runtime by implementing a unified interface. In JDK 6, the compiler has been given special attention. For the compiler, JDK has designed two interfaces:JavaCompiler
AndJavaCompiler.CompilationTask
.
The following example shows how to call the compiler at runtime.
- Specify the name of the compiled file (this file must be available in classpath ):
String fullQuanlifiedFileName = "compile" + java.io.File.separator +"Target.java";
- Obtain the compiler object:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
By callingToolProvider
OfgetSystemJavaCompiler
Method: JDK provides the ability to map the compiler of the current platform to an object in the memory. In this way, you can manipulate the compiler at runtime.JavaCompiler
Is an interface that inheritsjavax.tools.Tool
Interface. Therefore, a third-party compiler can be called through a unified interface as long as it complies with the specifications. At the same time, the Tools Development Kit wants to provide a unified runtime call interface for all tools. Believe in the future,ToolProvider
Class will provide more toolsgetSystemXXXTool
Method. The tools development kit provides a framework for coexistence of different tools and implementations.
- Compile the file:
int result = compiler.run(null, null, null, fileToCompile);
After obtaining the compiler object, you can callTool.run
Method to compile the source file.Run
The first three parameters of the method can be used to redirect standard input, standard output, and standard error output, respectively,null
The default value is used. Listing 1 provides a complete example:
Listing 1. compile files during program running
01 package compile; 02 import java. util. date; 03 public class target { 04 public void dosomething (){ 05 date = new date (10, 3, 3 ); // This constructor is marked as deprecated. // Output error information. 06 system. Out. println ("doing ..."); 07} 08}
09 package compile; 10 Import javax. Tools .*; 11 import java. Io. fileoutputstream; 12 public class compiler { 13 public static void main (string [] ARGs) throws exception { 14 string fullquanlifiedfilename = "compile" + Java. Io. file. Separator + "Target. Java "; 15 javacompiler compiler = toolprovider. getsystemjavacompiler ();
16 fileoutputstream err = new fileoutputstream ("err.txt ");
17 int compilationresult = compiler. Run (null, null, err, fullquanlifiedfilename );
18 if (compilationresult = 0 ){ 19 system. Out. println ("done "); 20} else { 21 system. Out. println ("fail "); 22} 23} 24}
|
Run <jdk60_installation_dir>/bin/javac compiler. Java first, and then run <jdk60_installation_dir>/jdk1.6.0/bin/Java compile. compiler. Output on screenDone
And an err.txt file is generated in the current directory. The file content is as follows:
Note: compile/Target.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details.
|
Observe carefullyrun
Method, you can find that the last parameter isString...arguments
Is a variable-length string array. Its actual function is to accept the parameters passed to javac. Suppose you want to compile the target. Java file and display the detailed information during the compilation process. Command Behavior:javac Target.java -verbose
. You can change 17 sentences:
int compilationResult = compiler.run(null, null, err, “-verbose”,fullQuanlifiedFileName);
|
Compile non-text files
Another powerful feature of the JDK 6 compiler API is that the form of source files that can be compiled is not limited to text files.JavaCompiler
Class relies on the file management service to compile multiple forms of source files. For example, files directly constructed by strings in the memory or files retrieved from the database. This service is composedJavaFileManager
Class. The compilation process consists of the following steps:
- Parse javac parameters;
- Find the source file or jar package in Source Path and/or classpath;
- Process input and output files;
In this process,JavaFileManager
Class can be used to create an output file, read and cache the output file. Because it can read and cache input files, it is possible to read various forms of input files. The command line tools provided by JDK have similar processing mechanisms. In future versions, it is also possible for other tools to process various forms of source files. To this end, the new JDK definesjavax.tools.FileObject
Andjavax.tools.JavaFileObject
Interface. Any class, as long as this interface is implemented, can beJavaFileManager
Recognition.
If you want to useJavaFileManager
, You must constructCompilationTask
. JDK 6 providesJavaCompiler.CompilationTask
Class to encapsulate a compilation operation. This class can be passed through:
JavaCompiler.getTask ( Writer out, JavaFileManager fileManager, DiagnosticListener<? super JavaFileObject> diagnosticListener, Iterable<String> options, Iterable<String> classes, Iterable<? extends JavaFileObject> compilationUnits )
|
Method. For more information about the meaning of each parameter, see the JDK documentation. Pass different parameters to get differentCompilationTask
. By constructing this class, a compilation process can be divided into multiple steps. Further,CompilationTask
ProvidedsetProcessors(Iterable<? extends Processor>processors)
Method, you can develop a processor for processing annotation. Figure 1 showsCompilationTask
Compilation process:
Figure 1. Compile with compilationtask
In the following exampleCompilationTask
Compile a set of Java source files in multiple steps.
Listing 2. Construct compilationtask for compilation
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 }
|
The above is the first step, by constructingCompilationTask
Compiled a Java file. Lines 14-17 implement the main logic. First, get a compiler object. Because only common files need to be compiled, a standard file manager is obtained through the compiler object in line 1. Line 16: Construct the file to be compiled intoIterable
Object. Finally, the file manager andIterable
ObjectJavaCompiler
OfgetTask
Method, obtainedJavaCompiler.CompilationTask
Object.
Step 2: developers want to generateCalculator
Instead of writing it manually. Using the compiler API, You can compile a string in the memory into a class file.
Listing 3. Customizing the javafileobject object
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
YesJavaFileObject
It provides the default implementation. InheritanceSimpleJavaObject
After that, you only need to implementgetCharContent
Method. See rows 9-11 in listing 3. Next, constructCalculator
Test classCalculatorTest
And place the string representing the classStringObject
ToJavaCompiler
OfgetTask
Method. Listing 4 shows these steps.
Listing 4. compile non-text source files
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 }
|
The implementation logic is similar to that in Listing 2. The difference is that in 20-30 rows, the program constructs CalculatorTest
AndStringObject
The constructor converts the strings in the memoryJavaFileObject
Object.
Collect compiler diagnostic information
The third new feature is to collect diagnostic information during compilation. Diagnostic information, usually refers to errors, warnings, or detailed output during compilation. JDK 6 passesListener
To obtain the information. If you want to registerDiagnosticListener
, Must be usedCompilationTask
Because the toolrun
Method cannot be registeredListener
. The procedure is simple. First, constructListener
And then pass itJavaFileManager
Constructor. Listing 5 makes changes to listing 2, showing how to registerDiagnosticListener
.
Listing 5. Register a diagnosticlistener to collect compilation Information
01 package math;
02 public class Calculator { 03 public int multiply(int multiplicand, int multiplier) { 04 return multiplicand * multiplier // deliberately omit semicolon, ADiagnosticListener // will take effect 05 } 06 }
07 package compile; 08 import javax.tools.*; 09 import java.io.FileOutputStream; 10 import java.util.Arrays; 11 public class CompilerWithListener { 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 DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<JavaFileObject>(); 18 JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, collector, null, null, files);
19 Boolean result = task.call(); 20 List<Diagnostic<? extends JavaFileObject>> diagnostics = collector.getDiagnostics(); 21 for(Diagnostic<? extends JavaFileObject> d : diagnostics){ 22 System.out.println("Line Number->" + d.getLineNumber()); 23 System.out.println("Message->"+ d.getMessage(Locale.ENGLISH)); 24 System.out.println("Source" + d.getCode()); 25 System.out.println("/n"); 26 }
27 if( result == true ) { 28 System.out.println("Succeeded"); 29 } 30 } 31 }
|
In row 17,DiagnosticCollector
Object provided by JDK, which implementsDiagnosticListener
Interface. Line 18 registers itCompilationTask
. A compilation process may contain multiple diagnostic information. Each diagnostic information is abstracted asDiagnostic
. 20-26 rows. All diagnostic information is output one by one. Compile and run compiler to get the following output:
Listing 6. Compilation information collected by diagnosticcollector
Line Number->5 Message->math/Calculator.java:5: ';' expected Source->compiler.err.expected
|
In fact, it can also be customized by the user. Listing 7 provides a customizedListener
.
Listing 7. Custom diagnosticlistener
01 class ADiagnosticListener implements DiagnosticListener<JavaFileObject>{ 02 public void report(Diagnostic<? extends JavaFileObject> diagnostic) { 03 System.out.println("Line Number->" + diagnostic.getLineNumber()); 04 System.out.println("Message->"+ diagnostic.getMessage(Locale.ENGLISH)); 05 System.out.println("Source" + diagnostic.getCode()); 06 System.out.println("/n"); 07 } 08 }
|
Summary
The new features of the JDK 6 compiler allow developers to control the compilation process more freely, which gives Tool developers more flexibility. By calling APIs to complete compilation operations, developers can conveniently and efficiently turn compilation into a service when the software system is running. Compilation of more extensive source code provides powerful support for integrating more data sources and functions. We believe that with the continuous improvement of JDK, more tools will have API support, and we will wait and see.
References
- ReadJava SE 6 new feature seriesA complete list of articles to learn about other important enhancements of Java SE 6.
- Java SE 6 Document: Specification document of Java SE 6, which can be found in the official description of most new features.
- Refer to the Java compiler API.