I'm using Java 1.6 and extending AbstractProcessor to handle a custom annotation.
What I want to do is load the source file in which my annotation is found and extract a few lines of the code from around the annotation into a text file and save the text file.
I'm trying to use
compiler#getResource(...) to get a reference to a FileObject for the source file. I have the correct package and file name for the source but I have been unable to determine how I get the root of the source path from which all the source files originate. If I try the code with source root hardwired it all works i.e I prepend the source root to the package name and call
FileObject fObj = filer#getResource(StandardLocation.SOURCE_PATH, <src_root_string>+<package string>, fileName.java)
then the fObj correct, but this is not a practical solution. Without the <src_root_string> I just get a file not found exception.
So my question is how can I discover the source root folder from inside the AbstractProcessor and so not need to hardwire it in?
Alternatively is this the best approach? Should I try another approach?
You don't need a File to do what you want. Even if you could, it would be wrong to use a file.
It would be wrong, because the compiler might not by using a JavaFileManager that is actually backed by a filesystem. It could be backed by some form of repository such as a database, or it could be backed by a transient in memory file system ( An example of the latter is the [annotation processor test framework|https://hickory.dev.java.net/nonav/apidocs/index.html?net/java/dev/hickory/testing/package-summary.html] in hickory)
But probably of more interest to you, you don't need to because you can simply [get an InputStream|http://java.sun.com/javase/6/docs/api/javax/tools/FileObject.html#openInputStream()] from the FileObject returned from [filer.getResource(...)|http://java.sun.com/javase/6/docs/api/javax/annotation/processing/Filer.html#getResource(javax.tools.JavaFileManager.Location,%20java.lang.CharSequence,%20java.lang.CharSequence)] and read it.
You might also find the [Compiler Tree API|http://java.sun.com/javase/6/docs/jdk/api/javac/tree/index.html] useful. The Compiler Tree API is available to annotation processors running in Oracle's (Sun's) javac, but not in other compilers (possibly limiting portability).
With this API you can [convert |http://java.sun.com/javase/6/docs/jdk/api/javac/tree/com/sun/source/util/Trees.html#getPath(javax.lang.model.element.Element)] the Element representing the annotation to a Treepath then [get the leaf node|http://java.sun.com/javase/6/docs/jdk/api/javac/tree/com/sun/source/util/TreePath.html#getLeaf()] and from that tree [obtain its offsets|http://java.sun.com/javase/6/docs/jdk/api/javac/tree/com/sun/source/util/Trees.html#getSourcePositions()] within the source file.
Hi Bruce, thanks for the reply. I've tried the Complier Tree API as you suggested and that works fine. However I would prefer to use the filer#getResource(...) and that's the core of the problem. I don't know how to create the parameters correctly without embedding a hardwired string, representing the source files root folder, as a prefix to the package string in the second parameter of the getResource(...) method.
I'd try [StandardLocation.SOURCE_PATH|http://java.sun.com/javase/6/docs/api/javax/tools/StandardLocation.html#SOURCE_PATH] as first argument to getResource()
The second argument is simply the package name with dots. from the Element recursively call getEnclosingElement until element.getKind() returns ElementKind.PACKAGE, , thats the PackageElement - gets its qualified name - second arg done.
The third argument is the filename which you have to (in theory) guess. In the above recursion the element you had before the package is the outermost TypeElement (class, interface, enum or annotation type).
You have to assume there is only one type element per file (which is not necessarily the case, but for a system following conventions it will be - and even for an unconventional system it will be if the top level element is public).
So get the simple name of this top level type element, and append ".java" to it. That will probably be the file name of the source file relative to the package - 3rd arg done.
Get the resource, get the input stream.
Possibly use the tree APIs CompilationUnitTree.getSourceMap() to convert to TreePositions to line numbers and work out which line you want to index to, and work out the offset in the file of the start of that line. Index through the stream and start reading.
If you are using Tree API, you could just bypass Filer.getResource() and simply convert your Element to a TreePath, gets its CompilationUnitTree (1st item in TreePath), and from there getSourceFile() and thence the InputStream, and proceed with ofset calculations.
What you've just said is exactly what I tried initially. I got the x.y.z.whatever package name, I got the name of the file and used StandardLocation.SOURCE_PATH and
called Filer#getResource(...). It just doesn't work unless I prepend the root of the source folder to the package name!