JDK-7068437 : Regression: Filer.getResource(SOURCE_OUTPUT, ...) no longer works in JDK 7 w/o -s
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 7
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2011-07-19
  • Updated: 2012-05-08
  • Resolved: 2011-10-06
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7 JDK 8
7u2 b08Fixed 8Fixed
Related Reports
Relates :  
Relates :  
Description
Run the following program with JDK 6:

---%<---
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
public class Demo {
    public static void main(String[] args) {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        System.err.println("using " + compiler.getClass() + " from " + compiler.getClass().getProtectionDomain().getCodeSource());
        CompilationTask task = compiler.getTask(null, null, null, Collections.singleton("-proc:only"), Collections.singleton("java.lang.Object"), null);
        task.setProcessors(Collections.singleton(new Proc()));
        System.err.println("success? " + task.call());
        task = compiler.getTask(null, null, null, Collections.singleton("-proc:only"), Collections.singleton("java.lang.Object"), null);
        task.setProcessors(Collections.singleton(new Proc()));
        System.err.println("success? " + task.call());
    }
    @SupportedAnnotationTypes("*")
    @SupportedSourceVersion(SourceVersion.RELEASE_6)
    private static class Proc extends AbstractProcessor {
        int count;
        @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            if (roundEnv.processingOver() || count++ > 0) {
                return false;
            }
            System.err.println("running Proc");
            try {
                processingEnv.getMessager().printMessage(Kind.NOTE, "found previous content of length " +
                        processingEnv.getFiler().getResource(StandardLocation.SOURCE_OUTPUT, "p", "C.java").getCharContent(false).length());
            } catch (FileNotFoundException x) {
                processingEnv.getMessager().printMessage(Kind.NOTE, "not previously there");
            } catch (IOException x) {
                processingEnv.getMessager().printMessage(Kind.ERROR, "while reading: " + x);
            }
            try {
                Writer w = processingEnv.getFiler().createSourceFile("p.C").openWriter();
                w.write("/* hello! */ package p; class C {}");
                w.close();
                processingEnv.getMessager().printMessage(Kind.NOTE, "wrote new content");
            } catch (IOException x) {
                processingEnv.getMessager().printMessage(Kind.ERROR, "while writing: " + x);
            }
            return true;
        }
    }
    private Demo() {}
}
---%<---

I get:

---%<---
using class com.sun.tools.javac.api.JavacTool from (file:/.../jdk1.6.0_26/lib/tools.jar <no signer certificates>)
running Proc
Note: not previously there
Note: wrote new content
success? true
running Proc
Note: found previous content of length 34
Note: wrote new content
success? true
---%<---

as expected. Now running the same program on JDK 7:

---%<---
using class com.sun.tools.javac.api.JavacTool from (file:/.../jdk1.7.0-b146/lib/tools.jar <no signer certificates>)
running Proc
warning: Supported source version 'RELEASE_6' from annotation processor 'Demo$Proc' less than -source '1.7'
Note: not previously there
Note: wrote new content
1 warning
success? true
running Proc
warning: Supported source version 'RELEASE_6' from annotation processor 'Demo$Proc' less than -source '1.7'
Note: not previously there
Note: wrote new content
1 warning
success? true
---%<---

Note that Filer.getResource(SOURCE_OUTPUT, ...) is throwing FileNotFoundException even though the source file is in fact present.

Comments
EVALUATION We could do a short term fix to revert the Filer behavior back to that of JDK 6, but that ignores the more general problem of how should JavaFileManager handle "undefined" locations? For example, what if a user writes a file to an "undefined" SOURCE_OUTPUT, and then tries to list the contents of that location? Is that supposed to rollover to CLASS_OUTPUT? What if CLASS_OUTPUT is "undefined"? -- it is not possible to list the directories that will be used using the sibling hint.
10-09-2011

EVALUATION This is a side effect of an otherwise obvious change to JavacFiler. In JDK 6, the method Filer.getResource (a method to read a file) used JavaFileManager.getFileForOutput to locate the resource. In JDK 7, the method Filer.getResource (a method to read a file) was changed to use the more obvious method JavaFileManager.getFileForInput to locate the resource. The problem is that javac is inconsistent with how these methods handle unset locations, and the spec does not define the behavior in this case. For getFileForOutput, the method embodies the "standard" javac logic that if -s is unset, it rolls over to -d, and if -d is unset, it uses the sibling file to determine the directory to use. For getFileForInput, the method is simpler and simply returns the file in the given Location, or FileNotFoundException if it does not exist. This leads to the curious behavior that: 1. you can create a file using SOURCE_OUTPUT or CLASS_OUTPUT even if filemanager.hasLocation(...) is false for these locations. 2. you can create a file using SOURCE_OUTPUT or CLASS_OUTPUT and not be able to read it back in (i.e. this bug).
10-09-2011

WORK AROUND Pass -s to javac.
19-07-2011

PUBLIC COMMENTS https://hg.netbeans.org/core-main/raw-file/default/openide.util/test/unit/src/org/netbeans/modules/openide/util/NbBundleProcessorTest.java similarly fails when run on JDK 7.
19-07-2011