src/share/classes/com/sun/tools/javac/processing/JavacFiler.java

Thu, 02 Oct 2008 19:58:40 -0700

author
xdono
date
Thu, 02 Oct 2008 19:58:40 -0700
changeset 117
24a47c3062fe
parent 104
5e89c4ca637c
child 554
9d9f26857129
permissions
-rw-r--r--

6754988: Update copyright year
Summary: Update for files that have been modified starting July 2008
Reviewed-by: ohair, tbell

     1 /*
     2  * Copyright 2005-2008 Sun Microsystems, Inc.  All Rights Reserved.
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
     4  *
     5  * This code is free software; you can redistribute it and/or modify it
     6  * under the terms of the GNU General Public License version 2 only, as
     7  * published by the Free Software Foundation.  Sun designates this
     8  * particular file as subject to the "Classpath" exception as provided
     9  * by Sun in the LICENSE file that accompanied this code.
    10  *
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
    14  * version 2 for more details (a copy is included in the LICENSE file that
    15  * accompanied this code).
    16  *
    17  * You should have received a copy of the GNU General Public License version
    18  * 2 along with this work; if not, write to the Free Software Foundation,
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
    20  *
    21  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
    22  * CA 95054 USA or visit www.sun.com if you need additional information or
    23  * have any questions.
    24  */
    26 package com.sun.tools.javac.processing;
    28 import com.sun.tools.javac.util.*;
    29 import javax.annotation.processing.*;
    30 import javax.lang.model.SourceVersion;
    31 import javax.lang.model.element.NestingKind;
    32 import javax.lang.model.element.Modifier;
    33 import javax.lang.model.element.Element;
    34 import java.util.*;
    36 import java.io.Closeable;
    37 import java.io.InputStream;
    38 import java.io.OutputStream;
    39 import java.io.FilterOutputStream;
    40 import java.io.Reader;
    41 import java.io.Writer;
    42 import java.io.FilterWriter;
    43 import java.io.PrintWriter;
    44 import java.io.IOException;
    46 import javax.tools.*;
    47 import static java.util.Collections.*;
    49 import javax.tools.JavaFileManager.Location;
    50 import static javax.tools.StandardLocation.SOURCE_OUTPUT;
    51 import static javax.tools.StandardLocation.CLASS_OUTPUT;
    53 /**
    54  * The FilerImplementation class must maintain a number of
    55  * constraints.  First, multiple attempts to open the same path within
    56  * the same invocation of the tool results in an IOException being
    57  * thrown.  For example, trying to open the same source file twice:
    58  *
    59  * <pre>
    60  * createSourceFile("foo.Bar")
    61  * ...
    62  * createSourceFile("foo.Bar")
    63  * </pre>
    64  *
    65  * is disallowed as is opening a text file that happens to have
    66  * the same name as a source file:
    67  *
    68  * <pre>
    69  * createSourceFile("foo.Bar")
    70  * ...
    71  * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
    72  * </pre>
    73  *
    74  * <p>Additionally, creating a source file that corresponds to an
    75  * already created class file (or vice versa) also results in an
    76  * IOException since each type can only be created once.  However, if
    77  * the Filer is used to create a text file named *.java that happens
    78  * to correspond to an existing class file, a warning is *not*
    79  * generated.  Similarly, a warning is not generated for a binary file
    80  * named *.class and an existing source file.
    81  *
    82  * <p>The reason for this difference is that source files and class
    83  * files are registered with the tool and can get passed on as
    84  * declarations to the next round of processing.  Files that are just
    85  * named *.java and *.class are not processed in that manner; although
    86  * having extra source files and class files on the source path and
    87  * class path can alter the behavior of the tool and any final
    88  * compile.
    89  *
    90  * <p><b>This is NOT part of any API supported by Sun Microsystems.
    91  * If you write code that depends on this, you do so at your own risk.
    92  * This code and its internal interfaces are subject to change or
    93  * deletion without notice.</b>
    94  */
    95 public class JavacFiler implements Filer, Closeable {
    96     // TODO: Implement different transaction model for updating the
    97     // Filer's record keeping on file close.
    99     private static final String ALREADY_OPENED =
   100         "Output stream or writer has already been opened.";
   101     private static final String NOT_FOR_READING =
   102         "FileObject was not opened for reading.";
   103     private static final String NOT_FOR_WRITING =
   104         "FileObject was not opened for writing.";
   106     /**
   107      * Wrap a JavaFileObject to manage writing by the Filer.
   108      */
   109     private class FilerOutputFileObject extends ForwardingFileObject<FileObject> {
   110         private boolean opened = false;
   111         private String name;
   113         FilerOutputFileObject(String name, FileObject fileObject) {
   114             super(fileObject);
   115             this.name = name;
   116         }
   118         @Override
   119         public synchronized OutputStream openOutputStream() throws IOException {
   120             if (opened)
   121                 throw new IOException(ALREADY_OPENED);
   122             opened = true;
   123             return new FilerOutputStream(name, fileObject);
   124         }
   126         @Override
   127         public synchronized Writer openWriter() throws IOException {
   128             if (opened)
   129                 throw new IOException(ALREADY_OPENED);
   130             opened = true;
   131             return new FilerWriter(name, fileObject);
   132         }
   134         // Three anti-literacy methods
   135         @Override
   136         public InputStream openInputStream() throws IOException {
   137             throw new IllegalStateException(NOT_FOR_READING);
   138         }
   140         @Override
   141         public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
   142             throw new IllegalStateException(NOT_FOR_READING);
   143         }
   145         @Override
   146         public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
   147             throw new IllegalStateException(NOT_FOR_READING);
   148         }
   150         @Override
   151         public boolean delete() {
   152             return false;
   153         }
   154     }
   156     private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject {
   157         private final JavaFileObject javaFileObject;
   158         FilerOutputJavaFileObject(String name, JavaFileObject javaFileObject) {
   159             super(name, javaFileObject);
   160             this.javaFileObject = javaFileObject;
   161         }
   163         public JavaFileObject.Kind getKind() {
   164             return javaFileObject.getKind();
   165         }
   167         public boolean isNameCompatible(String simpleName,
   168                                         JavaFileObject.Kind kind) {
   169             return javaFileObject.isNameCompatible(simpleName, kind);
   170         }
   172         public NestingKind getNestingKind() {
   173             return javaFileObject.getNestingKind();
   174         }
   176         public Modifier getAccessLevel() {
   177             return javaFileObject.getAccessLevel();
   178         }
   179     }
   181     /**
   182      * Wrap a JavaFileObject to manage reading by the Filer.
   183      */
   184     private class FilerInputFileObject extends ForwardingFileObject<FileObject> {
   185         FilerInputFileObject(FileObject fileObject) {
   186             super(fileObject);
   187         }
   189         @Override
   190         public OutputStream openOutputStream() throws IOException {
   191             throw new IllegalStateException(NOT_FOR_WRITING);
   192         }
   194         @Override
   195         public Writer openWriter() throws IOException {
   196             throw new IllegalStateException(NOT_FOR_WRITING);
   197         }
   199         @Override
   200         public boolean delete() {
   201             return false;
   202         }
   203     }
   205     private class FilerInputJavaFileObject extends FilerInputFileObject implements JavaFileObject {
   206         private final JavaFileObject javaFileObject;
   207         FilerInputJavaFileObject(JavaFileObject javaFileObject) {
   208             super(javaFileObject);
   209             this.javaFileObject = javaFileObject;
   210         }
   212         public JavaFileObject.Kind getKind() {
   213             return javaFileObject.getKind();
   214         }
   216         public boolean isNameCompatible(String simpleName,
   217                                         JavaFileObject.Kind kind) {
   218             return javaFileObject.isNameCompatible(simpleName, kind);
   219         }
   221         public NestingKind getNestingKind() {
   222             return javaFileObject.getNestingKind();
   223         }
   225         public Modifier getAccessLevel() {
   226             return javaFileObject.getAccessLevel();
   227         }
   228     }
   231     /**
   232      * Wrap a {@code OutputStream} returned from the {@code
   233      * JavaFileManager} to properly register source or class files
   234      * when they are closed.
   235      */
   236     private class FilerOutputStream extends FilterOutputStream {
   237         String typeName;
   238         FileObject fileObject;
   239         boolean closed = false;
   241         /**
   242          * @param typeName name of class or {@code null} if just a
   243          * binary file
   244          */
   245         FilerOutputStream(String typeName, FileObject fileObject) throws IOException {
   246             super(fileObject.openOutputStream());
   247             this.typeName = typeName;
   248             this.fileObject = fileObject;
   249         }
   251         public synchronized void close() throws IOException {
   252             if (!closed) {
   253                 closed = true;
   254                 /*
   255                  * If an IOException occurs when closing the underlying
   256                  * stream, still try to process the file.
   257                  */
   259                 closeFileObject(typeName, fileObject);
   260                 out.close();
   261             }
   262         }
   263     }
   265     /**
   266      * Wrap a {@code Writer} returned from the {@code JavaFileManager}
   267      * to properly register source or class files when they are
   268      * closed.
   269      */
   270     private class FilerWriter extends FilterWriter {
   271         String typeName;
   272         FileObject fileObject;
   273         boolean closed = false;
   275         /**
   276          * @param fileObject the fileObject to be written to
   277          * @param typeName name of source file or {@code null} if just a
   278          * text file
   279          */
   280         FilerWriter(String typeName, FileObject fileObject) throws IOException {
   281             super(fileObject.openWriter());
   282             this.typeName = typeName;
   283             this.fileObject = fileObject;
   284         }
   286         public synchronized void close() throws IOException {
   287             if (!closed) {
   288                 closed = true;
   289                 /*
   290                  * If an IOException occurs when closing the underlying
   291                  * Writer, still try to process the file.
   292                  */
   294                 closeFileObject(typeName, fileObject);
   295                 out.close();
   296             }
   297         }
   298     }
   300     JavaFileManager fileManager;
   301     Log log;
   302     Context context;
   303     boolean lastRound;
   305     private final boolean lint;
   307     /**
   308      * Logical names of all created files.  This set must be
   309      * synchronized.
   310      */
   311     private final Set<FileObject> fileObjectHistory;
   313     /**
   314      * Names of types that have had files created but not closed.
   315      */
   316     private final Set<String> openTypeNames;
   318     /**
   319      * Names of source files closed in this round.  This set must be
   320      * synchronized.  Its iterators should preserve insertion order.
   321      */
   322     private Set<String> generatedSourceNames;
   324     /**
   325      * Names and class files of the class files closed in this round.
   326      * This set must be synchronized.  Its iterators should preserve
   327      * insertion order.
   328      */
   329     private final Map<String, JavaFileObject> generatedClasses;
   331     /**
   332      * JavaFileObjects for source files closed in this round.  This
   333      * set must be synchronized.  Its iterators should preserve
   334      * insertion order.
   335      */
   336     private Set<JavaFileObject> generatedSourceFileObjects;
   338     /**
   339      * Names of all created source files.  Its iterators should
   340      * preserve insertion order.
   341      */
   342     private final Set<String> aggregateGeneratedSourceNames;
   344     /**
   345      * Names of all created class files.  Its iterators should
   346      * preserve insertion order.
   347      */
   348     private final Set<String> aggregateGeneratedClassNames;
   351     JavacFiler(Context context) {
   352         this.context = context;
   353         fileManager = context.get(JavaFileManager.class);
   355         log = Log.instance(context);
   357         fileObjectHistory = synchronizedSet(new LinkedHashSet<FileObject>());
   358         generatedSourceNames = synchronizedSet(new LinkedHashSet<String>());
   359         generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<JavaFileObject>());
   361         generatedClasses = synchronizedMap(new LinkedHashMap<String, JavaFileObject>());
   363         openTypeNames  = synchronizedSet(new LinkedHashSet<String>());
   365         aggregateGeneratedSourceNames = new LinkedHashSet<String>();
   366         aggregateGeneratedClassNames  = new LinkedHashSet<String>();
   368         lint = (Options.instance(context)).lint("processing");
   369     }
   371     public JavaFileObject createSourceFile(CharSequence name,
   372                                            Element... originatingElements) throws IOException {
   373         return createSourceOrClassFile(true, name.toString());
   374     }
   376     public JavaFileObject createClassFile(CharSequence name,
   377                                            Element... originatingElements) throws IOException {
   378         return createSourceOrClassFile(false, name.toString());
   379     }
   381     private JavaFileObject createSourceOrClassFile(boolean isSourceFile, String name) throws IOException {
   382         checkNameAndExistence(name, isSourceFile);
   383         Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT);
   384         JavaFileObject.Kind kind = (isSourceFile ?
   385                                     JavaFileObject.Kind.SOURCE :
   386                                     JavaFileObject.Kind.CLASS);
   388         JavaFileObject fileObject =
   389             fileManager.getJavaFileForOutput(loc, name, kind, null);
   390         checkFileReopening(fileObject, true);
   392         if (lastRound)
   393             log.warning("proc.file.create.last.round", name);
   395         if (isSourceFile)
   396             aggregateGeneratedSourceNames.add(name);
   397         else
   398             aggregateGeneratedClassNames.add(name);
   399         openTypeNames.add(name);
   401         return new FilerOutputJavaFileObject(name, fileObject);
   402     }
   404     public FileObject createResource(JavaFileManager.Location location,
   405                                      CharSequence pkg,
   406                                      CharSequence relativeName,
   407                                      Element... originatingElements) throws IOException {
   408         locationCheck(location);
   410         String strPkg = pkg.toString();
   411         if (strPkg.length() > 0)
   412             checkName(strPkg);
   414         FileObject fileObject =
   415             fileManager.getFileForOutput(location, strPkg,
   416                                          relativeName.toString(), null);
   417         checkFileReopening(fileObject, true);
   419         if (fileObject instanceof JavaFileObject)
   420             return new FilerOutputJavaFileObject(null, (JavaFileObject)fileObject);
   421         else
   422             return new FilerOutputFileObject(null, fileObject);
   423     }
   425     private void locationCheck(JavaFileManager.Location location) {
   426         if (location instanceof StandardLocation) {
   427             StandardLocation stdLoc = (StandardLocation) location;
   428             if (!stdLoc.isOutputLocation())
   429                 throw new IllegalArgumentException("Resource creation not supported in location " +
   430                                                    stdLoc);
   431         }
   432     }
   434     public FileObject getResource(JavaFileManager.Location location,
   435                                   CharSequence pkg,
   436                                   CharSequence relativeName) throws IOException {
   437         String strPkg = pkg.toString();
   438         if (strPkg.length() > 0)
   439             checkName(strPkg);
   441         // TODO: Only support reading resources in selected output
   442         // locations?  Only allow reading of non-source, non-class
   443         // files from the supported input locations?
   444         FileObject fileObject = fileManager.getFileForOutput(location,
   445                                                              pkg.toString(),
   446                                                              relativeName.toString(),
   447                                                              null);
   448         // If the path was already opened for writing, throw an exception.
   449         checkFileReopening(fileObject, false);
   450         return new FilerInputFileObject(fileObject);
   451     }
   453     private void checkName(String name) throws FilerException {
   454         checkName(name, false);
   455     }
   457     private void checkName(String name, boolean allowUnnamedPackageInfo) throws FilerException {
   458         if (!SourceVersion.isName(name) && !isPackageInfo(name, allowUnnamedPackageInfo)) {
   459             if (lint)
   460                 log.warning("proc.illegal.file.name", name);
   461             throw new FilerException("Illegal name " + name);
   462         }
   463     }
   465     private boolean isPackageInfo(String name, boolean allowUnnamedPackageInfo) {
   466         // Is the name of the form "package-info" or
   467         // "foo.bar.package-info"?
   468         final String PKG_INFO = "package-info";
   469         int periodIndex = name.lastIndexOf(".");
   470         if (periodIndex == -1) {
   471             return allowUnnamedPackageInfo ? name.equals(PKG_INFO) : false;
   472         } else {
   473             // "foo.bar.package-info." illegal
   474             String prefix = name.substring(0, periodIndex);
   475             String simple = name.substring(periodIndex+1);
   476             return SourceVersion.isName(prefix) && simple.equals(PKG_INFO);
   477         }
   478     }
   480     private void checkNameAndExistence(String typename, boolean allowUnnamedPackageInfo) throws FilerException {
   481         // TODO: Check if type already exists on source or class path?
   482         // If so, use warning message key proc.type.already.exists
   483         checkName(typename, allowUnnamedPackageInfo);
   484         if (aggregateGeneratedSourceNames.contains(typename) ||
   485             aggregateGeneratedClassNames.contains(typename)) {
   486             if (lint)
   487                 log.warning("proc.type.recreate", typename);
   488             throw new FilerException("Attempt to recreate a file for type " + typename);
   489         }
   490     }
   492     /**
   493      * Check to see if the file has already been opened; if so, throw
   494      * an exception, otherwise add it to the set of files.
   495      */
   496     private void checkFileReopening(FileObject fileObject, boolean addToHistory) throws FilerException {
   497         for(FileObject veteran : fileObjectHistory) {
   498             if (fileManager.isSameFile(veteran, fileObject)) {
   499                 if (lint)
   500                     log.warning("proc.file.reopening", fileObject.getName());
   501                 throw new FilerException("Attempt to reopen a file for path " + fileObject.getName());
   502             }
   503         }
   504         if (addToHistory)
   505             fileObjectHistory.add(fileObject);
   506     }
   508     public boolean newFiles() {
   509         return (!generatedSourceNames.isEmpty())
   510             || (!generatedClasses.isEmpty());
   511     }
   513     public Set<String> getGeneratedSourceNames() {
   514         return generatedSourceNames;
   515     }
   517     public Set<JavaFileObject> getGeneratedSourceFileObjects() {
   518         return generatedSourceFileObjects;
   519     }
   521     public Map<String, JavaFileObject> getGeneratedClasses() {
   522         return generatedClasses;
   523     }
   525     public void warnIfUnclosedFiles() {
   526         if (!openTypeNames.isEmpty())
   527             log.warning("proc.unclosed.type.files", openTypeNames.toString());
   528     }
   530     /**
   531      * Update internal state for a new round.
   532      */
   533     public void newRound(Context context, boolean lastRound) {
   534         this.context = context;
   535         this.log = Log.instance(context);
   536         this.lastRound = lastRound;
   537         clearRoundState();
   538     }
   540     public void close() {
   541         clearRoundState();
   542         // Cross-round state
   543         fileObjectHistory.clear();
   544         openTypeNames.clear();
   545         aggregateGeneratedSourceNames.clear();
   546         aggregateGeneratedClassNames.clear();
   547     }
   549     private void clearRoundState() {
   550         generatedSourceNames.clear();
   551         generatedSourceFileObjects.clear();
   552         generatedClasses.clear();
   553     }
   555     /**
   556      * Debugging function to display internal state.
   557      */
   558     public void displayState() {
   559         PrintWriter xout = context.get(Log.outKey);
   560         xout.println("File Object History : " +  fileObjectHistory);
   561         xout.println("Open Type Names     : " +  openTypeNames);
   562         xout.println("Gen. Src Names      : " +  generatedSourceNames);
   563         xout.println("Gen. Cls Names      : " +  generatedClasses.keySet());
   564         xout.println("Agg. Gen. Src Names : " +  aggregateGeneratedSourceNames);
   565         xout.println("Agg. Gen. Cls Names : " +  aggregateGeneratedClassNames);
   566     }
   568     public String toString() {
   569         return "javac Filer";
   570     }
   572     /**
   573      * Upon close, register files opened by create{Source, Class}File
   574      * for annotation processing.
   575      */
   576     private void closeFileObject(String typeName, FileObject fileObject) {
   577         /*
   578          * If typeName is non-null, the file object was opened as a
   579          * source or class file by the user.  If a file was opened as
   580          * a resource, typeName will be null and the file is *not*
   581          * subject to annotation processing.
   582          */
   583         if ((typeName != null)) {
   584             if (!(fileObject instanceof JavaFileObject))
   585                 throw new AssertionError("JavaFileOject not found for " + fileObject);
   586             JavaFileObject javaFileObject = (JavaFileObject)fileObject;
   587             switch(javaFileObject.getKind()) {
   588             case SOURCE:
   589                 generatedSourceNames.add(typeName);
   590                 generatedSourceFileObjects.add(javaFileObject);
   591                 openTypeNames.remove(typeName);
   592                 break;
   594             case CLASS:
   595                 generatedClasses.put(typeName, javaFileObject);
   596                 openTypeNames.remove(typeName);
   597                 break;
   599             default:
   600                 break;
   601             }
   602         }
   603     }
   605 }

mercurial