Satellite Internet QuickBooks Advice international calling cards calling cards
JavaBeat Certifications Certifications Kits Articles Tips QNA Interview Questions SCJP 1.5 SCBCD 5.0 Java/J2EE Feeds
Java Interview Questions Submit Your Blog Feedback Request Article Print Email

The Java 6.0 Compiler API

Author : Raja
Date : Sun Apr 1st, 2007
Topic : java6  
Pages :
Enter email address:

Latest JavaBeat Articles Delivered

Introduction

One of the cool features available in Java 6.0 (Mustang) is the ‘Java Compiler API’. This API is a result of the JSR (Java Specification Request) 199 which proposes that there must be a standard way to compile java source files. The result of the JSR is the new ‘Java Compiler API’ and one can use this new feature to compile java source files from within java files. Previously developers were depending on the low-level issues like starting a process representing the javac.exe. Though this feature is not intended to every one, Editors or IDE (Integrated Development Environment) can make much use of this new feature for compiling Java source files in a better manner.

Compiler API

All the API (the client interfaces and the classes) needed by the developers for playing with the Java compiler API is available in the new javax.tools package. Not only this package represents classes and methods for invoking a java compiler, but it provides a common interface for representing any kind of Tool. A tool is generally a command line program (like javac.exe, javadoc.exe or javah.exe).

Instead of looking into all the classes and interfaces that are available in the javax.tools package, it makes more sense to go through some sample programs, then examining what the classes and the methods are doing.

Compiling a java source file from another Java source

Following is the small sample program that will demonstrate how to compile a Java source file from another java file on the fly.

[All the examples given here are written and tested with Mustang build 1.6.0-b105, and it seems that more API changes and restructuring of classes and methods are occurring in the newer builds].


Listing for MyClass.java file that is going to get compiled.

MyClass.java:

package test;
public class MyClass {
	public void myMethod(){
		System.out.println("My Method Called");
	}
}

Listing for SimpleCompileTest.java that compiles the MyClass.java file.

SimpleCompileTest.java:

package test;
import javax.tools.*;
public class SimpleCompileTest {
	public static void main(String[] args) {
String fileToCompile = "test" + java.io.File.separator +"MyClass.java";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int compilationResult =	compiler.run(null, null, null, fileToCompile);
		if(compilationResult == 0){
			System.out.println("Compilation is successful");
		}else{
			System.out.println("Compilation Failed");
		}
	}
}

The entry point for getting a compiler instance is to depend on the ToolProvider class. This class provides methods for locating a Tool object. (Remember a Tool can be anything like javac, javadoc, rmic, javah etc...) Though the only Tool available in Mustang build (1.6.0-b105) is the JavaCompiler as of now, it is expected that many more tools are to be added in the future.

The getSystemJavaCompiler() method in the ToolProvider class returns an object of some class that implements JavaCompiler (JavaCompiler is an interface that extends Tool interface and not a class). To be more specific, the method returns a JavaCompiler implementation that is shipped along with the Mustang Distribution. The implementation of the Java Compiler is available in the tools.jar file (which is usually available in the <JDK60_INSTALLATION_DIR>\lib\tools.jar).

After getting an instance of the JavaCompiler, compilation on a set of files (also known as compilation units) can be done by invoking the run(InputStream inputStream, OutputStream outputStream, OutputStream errorStream, String … arguments) method. To use the defaults, null can be passed for the first three parameters (which correspond to System.in, System.out and System.err), the fourth parameter which cannot be null is a variable argument that refers to the command-line arguments we usually pass to the javac compiler.

The file that we are going to compile is MyClass.java which is the same test package as of the SimpleCompileTest. The complete file name (along with the directory ‘test’) is passed as 4th argument to the run method. If there are no errors in the source file (MyClass.java, in this case), the method will return 0 which means that the source file was compiled successfully.

After compiling and running the SimpleCompileTest.java, one can see the following output message in the console.

‘Compilation is successful’

Let us modify the source code by introducing a small error by removing the semi-colon after the end of the println() statement and see what happens to the output. MyClass.java: package test; public class MyClass { public void myMethod(){ System.out.println("My Method Called") // Semi-colon removed here purposefully. } } Now, running the SimpleCompileTest.java leads to the following output, test\MyClass.java:5: ';' expected System.out.println("My Method Called") ^ 1 error Compilation Failed

This is the error message one normally sees when compiling a java file using javac.exe in the command prompt. The above represents the error message(s) and since we have made the error output stream point to null (which defaults to System.err, which is the console) we are getting the output error messages in the console. If instead we have pointed the errorStream to something like this,


FileOutputStream errorStream = new FileOutputStream("Errors.txt");
int compilationResult = compiler.run(null, null, errorStream, fileToCompile);

a new file called Errors.txt will be created in the current directory and the file would be populated with the error messages that we saw before.

Compiling multiple files

One might be tempted to think that the following code will work for compiling multiple java files (assuming that the two files that are to be compiled are One.java and Two.java).


String filesToCompile = new String(“One.java Two.java”) ;

But surprisingly, when you try this, you will get a ‘Compilation Failed’ error message in the console.

The answer is JavaCompiler needs to extract individual options and arguments and these options and arguments should not be separated by spaces, but should be given as individual strings.

So, this won’t work at all.


compiler.run(null, null, null, “One.java Two.java”);

But, the below code will work nicely.


compiler.run(null, null, null, “One.java”, “Two.java”);

One reason for forcing this kind of restriction is that sometimes the complete java file names (which includes directory path as well) itself can have white-spaces , in such a case it would be difficult for the parser to parse all the tokens correctly into options and arguments.

Following is a sample code that compiles multiple java files.


MyClass.java:
package test;

public class MyClass {
}


MyAnotherClass.java:
package test;

public class MyAnotherClass {
}


MultipleFilesCompileTest.java:
package test;
import javax.tools.*;

public class MultipleFilesCompileTest {
	public static void main(String[] args) throws Exception{
String file1ToCompile = "test" + java.io.File.separator + "MyClass.java";
String file2ToCompile = "test" + java.io.File.separator + "MyAnotherClass.java";
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
int compilationResult =	compiler.run(null, null, null, file1ToCompile, file2ToCompile);
		if(compilationResult == 0){
			System.out.println("Compilation is successful");
		}else{
			System.out.println("Compilation Failed");
		}
	}
}

The above program compiles fine with the output message ‘Compilation is successful’.

Do remember, that the final argument is a variable argument and it can accept any number of arguments.

[Starting with Java 5.0, variable argument is a new feature where the callers (the calling method) can pass any number of arguments. To illustrate this concept, look at the sample code.


public int addNumbers(int …numbers){

int total = 0;
For(int temp : numbers){
	Total = total + temp;
}
return total;
}

A variable argument is represented by ellipsis (…) preceding the variable name like this int … numbers. And , one can call the above method in different styles, like the below


addNumbers(10, 10, 30,40); // This will work.
addNumbers(10,10) // This also will work.

type must be the last one. Variable arguments are internally treated as arrays. So, this is also possible now.


addNumbers(new int[]{10, 34, 54});

So, great care should be exercised when passing multiple options along with values to the run method. As an example, the ideal way to pass options along with its option values might me,


compiler.run(null, null, null, ”-classpath”, ”PathToClasses”, ”-sourcepath”, ”PathToSources”, ”One.java”, ”Two.java”);

Ass one can see, even the option and its option values must be treated as a separate string.

Submit Your Blog Feedback Request Article Print Email

Favorites
C# problem error
Free Newsgroups
Latest QnA
SCJD Tips
When we start a thread by applying start() method on it ,how does it knows that to execute run()method?
About Wrapper class in Java
How to configure weblogic 7.0 in MyEclipse?
Static Block and Static Initializer in Java

JavaBeat Website (2004-2009), India
javabeat | about us | useful resources
Copyright (2004 - 2009), JavaBeat