처음부터 표준 자바 플랫폼은 해당 컴파일러를 사용하여 자바 바이트 코드를 호출 및 생성하는 표준 인터페이스가 부족했다. 썬의 플랫폼 구현을 사용하면 사용자는 com.sun.tools.javac 패키지의 비표준 Main 클래스에 액세스하여 코드를 컴파일할 수 있다(lib 하위 디렉토리의 tools.jar 파일 참고). 하지만 해당 패키지는 표준적인 공용 프로그래밍 인터페이스를 제공하지 않는다. 기타 구현의 사용자는 반드시 이 클래스에 액세스해야 할 필요가 없다. JSR-199에 의해 정의된 새로운 자바 컴파일러 API 및 Java SE 6을 사용하면 자신의 애플리케이션에서 javac 컴파일러 도구에 액세스할 수 있다.
이 도구를 사용하는 방법은 두 가지가 있다. 한 방법은 간단하고, 또 다른 방법은 더 복잡하지만 추가 옵션을 조작할 수 있다. 먼저 간단한 방법을 사용하여 다음과 같이 "Hello, World" 프로그램을 컴파일해 본다.
public class Hello { public static void main(String args[]) { System.out.println("Hello, World"); }} 자바 프로그램에서 자바 컴파일러를 호출하려면 JavaCompiler 인터페이스에 액세스해야 한다. 무엇보다도 이 인터페이스에 액세스하면 소스 경로, 클래스 경로 및 대상 디렉토리를 설정할 수 있다. 컴파일 가능한 각 파일을 JavaFileObject 인스턴스로 지정하면 각 파일을 컴파일할 수 있다. 하지만 아직 JavaFileObject에 대해 알아야 할 필요는 없다.
ToolProvider 클래스를 사용하여 JavaCompiler 클래스의 기본 구현을 요청해 보자. ToolProvider 클래스는 JavaCompiler 인스턴스의 인스턴스를 반환하는 getSystemJavaCompiler() 메소드를 제공한다.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaCompiler로 컴파일하는 가장 간단한 방법은 도구 인터페이스에 정의되어 구현된 run() 메소드를 사용하는 것이다.
int run(InputStream in, OutputStream out, OutputStream err, String... arguments) 처음 3개의 인수에 대해 각각 System.in, System.out 및 System.err의 기본값을 사용하여 null 스트림 인수를 전달한다. String 개체의 varargs 집합은 컴파일러에 전달할 파일 이름을 나타낸다.
따라서 앞의 Hello 클래스 소스를 컴파일하려면 다음과 같은 코드가 필요하다.
int results = tool.run(null, null, null, "Hello.java");
컴파일 오류가 발생하지 않으면 대상 디렉토리에 Hello.class 파일이 생성된다. 오류가 없었다면 run() 메소드가 표준 오류 스트림에 출력을 보내고 이는 run() 메소드의 3번째 인수가 된다. 오류가 발생하는 경우 이 메소드는 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 실행 시 다음과 같은 출력이 생성된다.
> java CompileItResult code: 0또한 CompileIt을 실행하면 다음 파일이 있는 디렉토리에 Hello.class 파일이 생성된다.> lsCompileIt.classCompileIt.javaHello.classHello.java
이것으로 현재 표준 컴파일러를 사용하기에 충분하므로 여기서 중지해도 되지만 더 할 일이 있다. 결과에 대한 더 나은 액세스를 원할 때 컴파일러에 액세스하는 두 번째 방법이 있다. 구체적으로 말해서 이 두 번째 방법을 사용하면 stderr에 전송된 오류 텍스트를 단순히 전달하는 데 그치지 않고 개발자가 컴파일 결과를 좀더 의미 있는 방식으로 나타낼 수 있다. 컴파일러를 사용하는 더 나은 접근방법은 StandardJavaFileManager 클래스를 활용하는 것이다. 파일 관리자는 입력 및 출력 작업 모두에 대해 정규 파일 작업을 위한 방법을 제공한다. 또한 DiagnosticListener 인스턴스의 도움으로 진단 메시지를 보고한다. 사용하게 될 DiagnosticCollector 클래스는 단지 해당 리스너 구현의 한 예일 뿐이다.
컴파일해야 할 대상을 식별하기 전에 파일 관리자가 필요하다. 다음 두 기본 단계를 사용하여 파일 관리자를 생성한다. DiagnosticCollector를 생성한 다음 해당 getStandardFileManager() 메소드를 사용하여 JavaCompiler에 파일 관리자를 요청한다. DiagnosticListener 개체를 getStandardFileManager() 메소드에 전달한다. 이 리스너는 치명적이지 않은 문제를 보고하고 사용자는 나중에 이 문제를 getTask() 메소드에 전달하여 컴파일러와 공유할 수 있다.
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, aLocale, aCharset);호출에 널 진단 리스너를 제공할 수도 있지만 이는 앞의 컴파일 방법을 사용하는 것과 동일하다.
StandardJavaFileManager의 세부사항을 살펴보기 전에 컴파일 프로세스에는 getTask()를 호출한 JavaCompiler의 단일 메소드가 포함된다는 것을 알아두자. 여기에는 6개의 인수가 필요하며 CompilationTask라는 내부 클래스의 인스턴스가 반환된다.
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: 컴파일러에 대한 명령줄 옵션 없음* classes: 주석 처리용 클래스 이름 없음
마지막 인수 compilationUnits는 컴파일 대상이므로 널이어서는 안된다. 그러면 StandardJavaFileManager로 다시 돌아간다. 인수 유형 Iterable<? extends JavaFileObject>을 눈여겨보자. StandardJavaFileManager의 두 메소드는 이 결과를 낳는다. File 개체 List를 사용하거나, 파일 이름을 나타내는 String 개체 List를 사용하여 시작할 수 있다.
Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles( Iterable<? extends File> files)Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings( Iterable<String> names)사실, List만이 아니라 Iterable한 것은 무엇이나 여기서 컴파일할 항목 모음을 식별하는 데 사용될 수 있다. 여기서는 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 변수에 의해 식별된 모든 파일을 컴파일한다. 모든 컴파일이 성공했는지 알아보려면 성공에 대한 Boolean 반환 값을 확인한다. 모든 컴파일 단위가 컴파일된 경우에만 call() 메소드는 Boolean.TRUE를 반환한다. 오류가 있으면 메소드는 Boolean.FALSE를 반환한다.
작업 예제를 살펴보기 전에 마지막으로 DiagnosticListener나 이를 구현하는 DiagnosticCollector를 추가해 보자. 이 리스너를 3번째 인수로 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() 메소드를 호출해야 한다.
이 모든 내용을 조합하면 역시 Hello 클래스를 컴파일하는 다음과 같은 프로그램이 만들어진다.
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 BigCompileSuccess: true하지만 println 메소드를 pritnln 메소드로 잘못 입력하면 실행 시에 다음과 같은 메시지가 대신 출력된다.
> java BigCompileCode: compiler.err.cant.resolve.locationKind: ERRORPosition: 80Start Position: 70End Position: 88Source: Hello.javaMessage: Hello.java:3: cannot find symbolsymbol : method pritnln(java.lang.String)location: class java.io.PrintStreamSuccess: false컴파일러 API를 사용하면 이 간단한 설명에서 다룬 내용보다 훨씬 복잡한 작업을 수행할 수 있다. 예를 들어, 입력 및 출력 디렉토리를 제어하거나 통합 편집기 환경에서 컴파일 오류를 강조 표시할 수 있다. 이제 자바 컴파일러 API 덕분에 표준 API 호출을 사용하여 이 모든 작업을 수행할 수 있다. 자바 컴파일러 API 및 JSR 199에 대한 자세한 내용은 JSR 199 규격을 참조하기 바란다.
"Java SE" 카테고리의 다른 글
- 리스너 리스트를 위한 WEAKHASHMAP 사용하기 (댓글 1개 / 트랙백 0개) 2006/03/08
- J2SE 5.0의 Java 2D API 기능 강화 (댓글 2개 / 트랙백 0개) 2006/05/12
- 3D 화면(scene)에 빛 효과 주기 (댓글 1개 / 트랙백 0개) 2004/07/30
- 다이얼로그 Modality (댓글 1개 / 트랙백 0개) 2006/06/09
- 쿠키 처리 (댓글 22개 / 트랙백 3개) 2007/07/23
- 사용자 데이터그램 프로토콜의 프로그래밍 (댓글 1개 / 트랙백 0개) 2004/06/30
- 락(LOCKS) (댓글 1개 / 트랙백 0개) 2005/09/22
- Java Web Start 퍼시스턴스 (댓글 3개 / 트랙백 0개) 2006/12/24
- AFFINETRANSFORM 이해하기 (댓글 3개 / 트랙백 0개) 2003/09/09
- 사용자 인터페이스에서 Action 사용하기 (댓글 5개 / 트랙백 2개) 2007/02/22
댓글을 달아 주세요
헤깔리네요..
2007/09/07 13:20다시 읽어 보아야 하나.. ㅠ.ㅠ;
유용한 자료 잘 활용하겠습니다.
2007/09/18 22:20좋은 정보 감사해요~
2007/09/19 03:47좋은거 많이 배우고 갑니다.
2007/09/19 23:06감사해요~