duke@1: /* duke@1: * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. duke@1: * duke@1: * This code is free software; you can redistribute it and/or modify it duke@1: * under the terms of the GNU General Public License version 2 only, as duke@1: * published by the Free Software Foundation. Sun designates this duke@1: * particular file as subject to the "Classpath" exception as provided duke@1: * by Sun in the LICENSE file that accompanied this code. duke@1: * duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License duke@1: * version 2 for more details (a copy is included in the LICENSE file that duke@1: * accompanied this code). duke@1: * duke@1: * You should have received a copy of the GNU General Public License version duke@1: * 2 along with this work; if not, write to the Free Software Foundation, duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. duke@1: * duke@1: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, duke@1: * CA 95054 USA or visit www.sun.com if you need additional information or duke@1: * have any questions. duke@1: */ duke@1: duke@1: package com.sun.tools.javac.processing; duke@1: duke@1: import com.sun.tools.javac.util.*; duke@1: import javax.annotation.processing.*; duke@1: import javax.lang.model.SourceVersion; duke@1: import javax.lang.model.element.NestingKind; duke@1: import javax.lang.model.element.Modifier; duke@1: import javax.lang.model.element.Element; duke@1: import java.util.*; duke@1: duke@1: import java.io.Closeable; duke@1: import java.io.InputStream; duke@1: import java.io.OutputStream; duke@1: import java.io.FilterOutputStream; duke@1: import java.io.Reader; duke@1: import java.io.Writer; duke@1: import java.io.FilterWriter; duke@1: import java.io.PrintWriter; duke@1: import java.io.IOException; duke@1: duke@1: import javax.tools.*; duke@1: import static java.util.Collections.*; duke@1: duke@1: import javax.tools.JavaFileManager.Location; duke@1: import static javax.tools.StandardLocation.SOURCE_OUTPUT; duke@1: import static javax.tools.StandardLocation.CLASS_OUTPUT; duke@1: duke@1: /** duke@1: * The FilerImplementation class must maintain a number of duke@1: * constraints. First, multiple attempts to open the same path within duke@1: * the same invocation of the tool results in an IOException being duke@1: * thrown. For example, trying to open the same source file twice: duke@1: * duke@1: *
duke@1:  * createSourceFile("foo.Bar")
duke@1:  * ...
duke@1:  * createSourceFile("foo.Bar")
duke@1:  * 
duke@1: * duke@1: * is disallowed as is opening a text file that happens to have duke@1: * the same name as a source file: duke@1: * duke@1: *
duke@1:  * createSourceFile("foo.Bar")
duke@1:  * ...
duke@1:  * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
duke@1:  * 
duke@1: * duke@1: *

Additionally, creating a source file that corresponds to an duke@1: * already created class file (or vice versa) also results in an duke@1: * IOException since each type can only be created once. However, if duke@1: * the Filer is used to create a text file named *.java that happens duke@1: * to correspond to an existing class file, a warning is *not* duke@1: * generated. Similarly, a warning is not generated for a binary file duke@1: * named *.class and an existing source file. duke@1: * duke@1: *

The reason for this difference is that source files and class duke@1: * files are registered with the tool and can get passed on as duke@1: * declarations to the next round of processing. Files that are just duke@1: * named *.java and *.class are not processed in that manner; although duke@1: * having extra source files and class files on the source path and duke@1: * class path can alter the behavior of the tool and any final duke@1: * compile. duke@1: * duke@1: *

This is NOT part of any API supported by Sun Microsystems. duke@1: * If you write code that depends on this, you do so at your own risk. duke@1: * This code and its internal interfaces are subject to change or duke@1: * deletion without notice. duke@1: */ duke@1: public class JavacFiler implements Filer, Closeable { duke@1: // TODO: Implement different transaction model for updating the duke@1: // Filer's record keeping on file close. duke@1: duke@1: private static final String ALREADY_OPENED = duke@1: "Output stream or writer has already been opened."; duke@1: private static final String NOT_FOR_READING = duke@1: "FileObject was not opened for reading."; duke@1: private static final String NOT_FOR_WRITING = duke@1: "FileObject was not opened for writing."; duke@1: duke@1: /** duke@1: * Wrap a JavaFileObject to manage writing by the Filer. duke@1: */ duke@1: private class FilerOutputFileObject extends ForwardingFileObject { duke@1: private boolean opened = false; duke@1: private String name; duke@1: duke@1: FilerOutputFileObject(String name, FileObject fileObject) { duke@1: super(fileObject); duke@1: this.name = name; duke@1: } duke@1: duke@1: @Override duke@1: public synchronized OutputStream openOutputStream() throws IOException { duke@1: if (opened) duke@1: throw new IOException(ALREADY_OPENED); duke@1: opened = true; duke@1: return new FilerOutputStream(name, fileObject); duke@1: } duke@1: duke@1: @Override duke@1: public synchronized Writer openWriter() throws IOException { duke@1: if (opened) duke@1: throw new IOException(ALREADY_OPENED); duke@1: opened = true; duke@1: return new FilerWriter(name, fileObject); duke@1: } duke@1: duke@1: // Three anti-literacy methods duke@1: @Override duke@1: public InputStream openInputStream() throws IOException { duke@1: throw new IllegalStateException(NOT_FOR_READING); duke@1: } duke@1: duke@1: @Override duke@1: public Reader openReader(boolean ignoreEncodingErrors) throws IOException { duke@1: throw new IllegalStateException(NOT_FOR_READING); duke@1: } duke@1: duke@1: @Override duke@1: public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { duke@1: throw new IllegalStateException(NOT_FOR_READING); duke@1: } duke@1: duke@1: @Override duke@1: public boolean delete() { duke@1: return false; duke@1: } duke@1: } duke@1: duke@1: private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject { duke@1: private final JavaFileObject javaFileObject; duke@1: FilerOutputJavaFileObject(String name, JavaFileObject javaFileObject) { duke@1: super(name, javaFileObject); duke@1: this.javaFileObject = javaFileObject; duke@1: } duke@1: duke@1: public JavaFileObject.Kind getKind() { duke@1: return javaFileObject.getKind(); duke@1: } duke@1: duke@1: public boolean isNameCompatible(String simpleName, duke@1: JavaFileObject.Kind kind) { duke@1: return javaFileObject.isNameCompatible(simpleName, kind); duke@1: } duke@1: duke@1: public NestingKind getNestingKind() { duke@1: return javaFileObject.getNestingKind(); duke@1: } duke@1: duke@1: public Modifier getAccessLevel() { duke@1: return javaFileObject.getAccessLevel(); duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Wrap a JavaFileObject to manage reading by the Filer. duke@1: */ duke@1: private class FilerInputFileObject extends ForwardingFileObject { duke@1: FilerInputFileObject(FileObject fileObject) { duke@1: super(fileObject); duke@1: } duke@1: duke@1: @Override duke@1: public OutputStream openOutputStream() throws IOException { duke@1: throw new IllegalStateException(NOT_FOR_WRITING); duke@1: } duke@1: duke@1: @Override duke@1: public Writer openWriter() throws IOException { duke@1: throw new IllegalStateException(NOT_FOR_WRITING); duke@1: } duke@1: duke@1: @Override duke@1: public boolean delete() { duke@1: return false; duke@1: } duke@1: } duke@1: duke@1: private class FilerInputJavaFileObject extends FilerInputFileObject implements JavaFileObject { duke@1: private final JavaFileObject javaFileObject; duke@1: FilerInputJavaFileObject(JavaFileObject javaFileObject) { duke@1: super(javaFileObject); duke@1: this.javaFileObject = javaFileObject; duke@1: } duke@1: duke@1: public JavaFileObject.Kind getKind() { duke@1: return javaFileObject.getKind(); duke@1: } duke@1: duke@1: public boolean isNameCompatible(String simpleName, duke@1: JavaFileObject.Kind kind) { duke@1: return javaFileObject.isNameCompatible(simpleName, kind); duke@1: } duke@1: duke@1: public NestingKind getNestingKind() { duke@1: return javaFileObject.getNestingKind(); duke@1: } duke@1: duke@1: public Modifier getAccessLevel() { duke@1: return javaFileObject.getAccessLevel(); duke@1: } duke@1: } duke@1: duke@1: duke@1: /** duke@1: * Wrap a {@code OutputStream} returned from the {@code duke@1: * JavaFileManager} to properly register source or class files duke@1: * when they are closed. duke@1: */ duke@1: private class FilerOutputStream extends FilterOutputStream { duke@1: String typeName; duke@1: FileObject fileObject; duke@1: boolean closed = false; duke@1: duke@1: /** duke@1: * @param typeName name of class or {@code null} if just a duke@1: * binary file duke@1: */ duke@1: FilerOutputStream(String typeName, FileObject fileObject) throws IOException { duke@1: super(fileObject.openOutputStream()); duke@1: this.typeName = typeName; duke@1: this.fileObject = fileObject; duke@1: } duke@1: duke@1: public synchronized void close() throws IOException { duke@1: if (!closed) { duke@1: closed = true; duke@1: /* duke@1: * If an IOException occurs when closing the underlying duke@1: * stream, still try to process the file. duke@1: */ duke@1: duke@1: closeFileObject(typeName, fileObject); duke@1: out.close(); duke@1: } duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Wrap a {@code Writer} returned from the {@code JavaFileManager} duke@1: * to properly register source or class files when they are duke@1: * closed. duke@1: */ duke@1: private class FilerWriter extends FilterWriter { duke@1: String typeName; duke@1: FileObject fileObject; duke@1: boolean closed = false; duke@1: duke@1: /** duke@1: * @param fileObject the fileObject to be written to duke@1: * @param typeName name of source file or {@code null} if just a duke@1: * text file duke@1: */ duke@1: FilerWriter(String typeName, FileObject fileObject) throws IOException { duke@1: super(fileObject.openWriter()); duke@1: this.typeName = typeName; duke@1: this.fileObject = fileObject; duke@1: } duke@1: duke@1: public synchronized void close() throws IOException { duke@1: if (!closed) { duke@1: closed = true; duke@1: /* duke@1: * If an IOException occurs when closing the underlying duke@1: * Writer, still try to process the file. duke@1: */ duke@1: duke@1: closeFileObject(typeName, fileObject); duke@1: out.close(); duke@1: } duke@1: } duke@1: } duke@1: duke@1: JavaFileManager fileManager; duke@1: Log log; duke@1: Context context; duke@1: boolean lastRound; duke@1: duke@1: private final boolean lint; duke@1: duke@1: /** duke@1: * Logical names of all created files. This set must be duke@1: * synchronized. duke@1: */ duke@1: private final Set fileObjectHistory; duke@1: duke@1: /** duke@1: * Names of types that have had files created but not closed. duke@1: */ duke@1: private final Set openTypeNames; duke@1: duke@1: /** duke@1: * Names of source files closed in this round. This set must be duke@1: * synchronized. Its iterators should preserve insertion order. duke@1: */ duke@1: private Set generatedSourceNames; duke@1: duke@1: /** duke@1: * Names and class files of the class files closed in this round. duke@1: * This set must be synchronized. Its iterators should preserve duke@1: * insertion order. duke@1: */ duke@1: private final Map generatedClasses; duke@1: duke@1: /** duke@1: * JavaFileObjects for source files closed in this round. This duke@1: * set must be synchronized. Its iterators should preserve duke@1: * insertion order. duke@1: */ duke@1: private Set generatedSourceFileObjects; duke@1: duke@1: /** duke@1: * Names of all created source files. Its iterators should duke@1: * preserve insertion order. duke@1: */ duke@1: private final Set aggregateGeneratedSourceNames; duke@1: duke@1: /** duke@1: * Names of all created class files. Its iterators should duke@1: * preserve insertion order. duke@1: */ duke@1: private final Set aggregateGeneratedClassNames; duke@1: duke@1: duke@1: JavacFiler(Context context) { duke@1: this.context = context; duke@1: fileManager = context.get(JavaFileManager.class); duke@1: duke@1: log = Log.instance(context); duke@1: duke@1: fileObjectHistory = synchronizedSet(new LinkedHashSet()); duke@1: generatedSourceNames = synchronizedSet(new LinkedHashSet()); duke@1: generatedSourceFileObjects = synchronizedSet(new LinkedHashSet()); duke@1: duke@1: generatedClasses = synchronizedMap(new LinkedHashMap()); duke@1: duke@1: openTypeNames = synchronizedSet(new LinkedHashSet()); duke@1: duke@1: aggregateGeneratedSourceNames = new LinkedHashSet(); duke@1: aggregateGeneratedClassNames = new LinkedHashSet(); duke@1: duke@1: lint = (Options.instance(context)).lint("processing"); duke@1: } duke@1: duke@1: public JavaFileObject createSourceFile(CharSequence name, duke@1: Element... originatingElements) throws IOException { duke@1: return createSourceOrClassFile(true, name.toString()); duke@1: } duke@1: duke@1: public JavaFileObject createClassFile(CharSequence name, duke@1: Element... originatingElements) throws IOException { duke@1: return createSourceOrClassFile(false, name.toString()); duke@1: } duke@1: duke@1: private JavaFileObject createSourceOrClassFile(boolean isSourceFile, String name) throws IOException { duke@1: checkNameAndExistence(name, isSourceFile); duke@1: Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT); duke@1: JavaFileObject.Kind kind = (isSourceFile ? duke@1: JavaFileObject.Kind.SOURCE : duke@1: JavaFileObject.Kind.CLASS); duke@1: duke@1: JavaFileObject fileObject = duke@1: fileManager.getJavaFileForOutput(loc, name, kind, null); duke@1: checkFileReopening(fileObject, true); duke@1: duke@1: if (lastRound) duke@1: log.warning("proc.file.create.last.round", name); duke@1: duke@1: if (isSourceFile) duke@1: aggregateGeneratedSourceNames.add(name); duke@1: else duke@1: aggregateGeneratedClassNames.add(name); duke@1: openTypeNames.add(name); duke@1: duke@1: return new FilerOutputJavaFileObject(name, fileObject); duke@1: } duke@1: duke@1: public FileObject createResource(JavaFileManager.Location location, duke@1: CharSequence pkg, duke@1: CharSequence relativeName, duke@1: Element... originatingElements) throws IOException { duke@1: locationCheck(location); duke@1: duke@1: String strPkg = pkg.toString(); duke@1: if (strPkg.length() > 0) duke@1: checkName(strPkg); duke@1: duke@1: FileObject fileObject = duke@1: fileManager.getFileForOutput(location, strPkg, duke@1: relativeName.toString(), null); duke@1: checkFileReopening(fileObject, true); duke@1: duke@1: if (fileObject instanceof JavaFileObject) duke@1: return new FilerOutputJavaFileObject(null, (JavaFileObject)fileObject); duke@1: else duke@1: return new FilerOutputFileObject(null, fileObject); duke@1: } duke@1: duke@1: private void locationCheck(JavaFileManager.Location location) { duke@1: if (location instanceof StandardLocation) { duke@1: StandardLocation stdLoc = (StandardLocation) location; duke@1: if (!stdLoc.isOutputLocation()) duke@1: throw new IllegalArgumentException("Resource creation not supported in location " + duke@1: stdLoc); duke@1: } duke@1: } duke@1: duke@1: public FileObject getResource(JavaFileManager.Location location, duke@1: CharSequence pkg, duke@1: CharSequence relativeName) throws IOException { duke@1: String strPkg = pkg.toString(); duke@1: if (strPkg.length() > 0) duke@1: checkName(strPkg); duke@1: duke@1: // TODO: Only support reading resources in selected output duke@1: // locations? Only allow reading of non-source, non-class duke@1: // files from the supported input locations? duke@1: FileObject fileObject = fileManager.getFileForOutput(location, duke@1: pkg.toString(), duke@1: relativeName.toString(), duke@1: null); duke@1: // If the path was already opened for writing, throw an exception. duke@1: checkFileReopening(fileObject, false); duke@1: return new FilerInputFileObject(fileObject); duke@1: } duke@1: duke@1: private void checkName(String name) throws FilerException { duke@1: checkName(name, false); duke@1: } duke@1: duke@1: private void checkName(String name, boolean allowUnnamedPackageInfo) throws FilerException { duke@1: if (!SourceVersion.isName(name) && !isPackageInfo(name, allowUnnamedPackageInfo)) { duke@1: if (lint) duke@1: log.warning("proc.illegal.file.name", name); duke@1: throw new FilerException("Illegal name " + name); duke@1: } duke@1: } duke@1: duke@1: private boolean isPackageInfo(String name, boolean allowUnnamedPackageInfo) { duke@1: // Is the name of the form "package-info" or duke@1: // "foo.bar.package-info"? duke@1: final String PKG_INFO = "package-info"; duke@1: int periodIndex = name.lastIndexOf("."); duke@1: if (periodIndex == -1) { duke@1: return allowUnnamedPackageInfo ? name.equals(PKG_INFO) : false; duke@1: } else { duke@1: // "foo.bar.package-info." illegal duke@1: String prefix = name.substring(0, periodIndex); duke@1: String simple = name.substring(periodIndex+1); duke@1: return SourceVersion.isName(prefix) && simple.equals(PKG_INFO); duke@1: } duke@1: } duke@1: duke@1: private void checkNameAndExistence(String typename, boolean allowUnnamedPackageInfo) throws FilerException { duke@1: // TODO: Check if type already exists on source or class path? duke@1: // If so, use warning message key proc.type.already.exists duke@1: checkName(typename, allowUnnamedPackageInfo); duke@1: if (aggregateGeneratedSourceNames.contains(typename) || duke@1: aggregateGeneratedClassNames.contains(typename)) { duke@1: if (lint) duke@1: log.warning("proc.type.recreate", typename); duke@1: throw new FilerException("Attempt to recreate a file for type " + typename); duke@1: } duke@1: } duke@1: duke@1: /** duke@1: * Check to see if the file has already been opened; if so, throw duke@1: * an exception, otherwise add it to the set of files. duke@1: */ duke@1: private void checkFileReopening(FileObject fileObject, boolean addToHistory) throws FilerException { duke@1: for(FileObject veteran : fileObjectHistory) { duke@1: if (fileManager.isSameFile(veteran, fileObject)) { duke@1: if (lint) duke@1: log.warning("proc.file.reopening", fileObject.getName()); duke@1: throw new FilerException("Attempt to reopen a file for path " + fileObject.getName()); duke@1: } duke@1: } duke@1: if (addToHistory) duke@1: fileObjectHistory.add(fileObject); duke@1: } duke@1: duke@1: public boolean newFiles() { duke@1: return (!generatedSourceNames.isEmpty()) duke@1: || (!generatedClasses.isEmpty()); duke@1: } duke@1: duke@1: public Set getGeneratedSourceNames() { duke@1: return generatedSourceNames; duke@1: } duke@1: duke@1: public Set getGeneratedSourceFileObjects() { duke@1: return generatedSourceFileObjects; duke@1: } duke@1: duke@1: public Map getGeneratedClasses() { duke@1: return generatedClasses; duke@1: } duke@1: duke@1: public void warnIfUnclosedFiles() { duke@1: if (!openTypeNames.isEmpty()) duke@1: log.warning("proc.unclosed.type.files", openTypeNames.toString()); duke@1: } duke@1: duke@1: /** duke@1: * Update internal state for a new round. duke@1: */ duke@1: public void newRound(Context context, boolean lastRound) { duke@1: this.context = context; duke@1: this.log = Log.instance(context); duke@1: this.lastRound = lastRound; duke@1: clearRoundState(); duke@1: } duke@1: duke@1: public void close() { duke@1: clearRoundState(); duke@1: // Cross-round state duke@1: fileObjectHistory.clear(); duke@1: openTypeNames.clear(); duke@1: aggregateGeneratedSourceNames.clear(); duke@1: aggregateGeneratedClassNames.clear(); duke@1: } duke@1: duke@1: private void clearRoundState() { duke@1: generatedSourceNames.clear(); duke@1: generatedSourceFileObjects.clear(); duke@1: generatedClasses.clear(); duke@1: } duke@1: duke@1: /** duke@1: * Debugging function to display internal state. duke@1: */ duke@1: public void displayState() { duke@1: PrintWriter xout = context.get(Log.outKey); duke@1: xout.println("File Object History : " + fileObjectHistory); duke@1: xout.println("Open Type Names : " + openTypeNames); duke@1: xout.println("Gen. Src Names : " + generatedSourceNames); duke@1: xout.println("Gen. Cls Names : " + generatedClasses.keySet()); duke@1: xout.println("Agg. Gen. Src Names : " + aggregateGeneratedSourceNames); duke@1: xout.println("Agg. Gen. Cls Names : " + aggregateGeneratedClassNames); duke@1: } duke@1: duke@1: public String toString() { duke@1: return "javac Filer"; duke@1: } duke@1: duke@1: /** duke@1: * Upon close, register files opened by create{Source, Class}File duke@1: * for annotation processing. duke@1: */ duke@1: private void closeFileObject(String typeName, FileObject fileObject) { duke@1: /* duke@1: * If typeName is non-null, the file object was opened as a duke@1: * source or class file by the user. If a file was opened as duke@1: * a resource, typeName will be null and the file is *not* duke@1: * subject to annotation processing. duke@1: */ duke@1: if ((typeName != null)) { duke@1: if (!(fileObject instanceof JavaFileObject)) duke@1: throw new AssertionError("JavaFileOject not found for " + fileObject); duke@1: JavaFileObject javaFileObject = (JavaFileObject)fileObject; duke@1: switch(javaFileObject.getKind()) { duke@1: case SOURCE: duke@1: generatedSourceNames.add(typeName); duke@1: generatedSourceFileObjects.add(javaFileObject); duke@1: openTypeNames.remove(typeName); duke@1: break; duke@1: duke@1: case CLASS: duke@1: generatedClasses.put(typeName, javaFileObject); duke@1: openTypeNames.remove(typeName); duke@1: break; duke@1: duke@1: default: duke@1: break; duke@1: } duke@1: } duke@1: } duke@1: duke@1: }