1 /*
   2  * Copyright 2004-2005 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  */
  25 
  26 package com.sun.tools.apt.mirror.apt;
  27 
  28 
  29 import java.io.*;
  30 import java.util.Collection;
  31 import java.util.EnumMap;
  32 import java.util.HashSet;
  33 import java.util.Set;
  34 
  35 import com.sun.mirror.apt.Filer;
  36 import com.sun.tools.apt.mirror.declaration.DeclarationMaker;
  37 import com.sun.tools.javac.util.Context;
  38 import com.sun.tools.javac.util.Options;
  39 import com.sun.tools.javac.util.Position;
  40 import com.sun.tools.apt.util.Bark;
  41 
  42 import static com.sun.mirror.apt.Filer.Location.*;
  43 
  44 
  45 /**
  46  * Implementation of Filer.
  47  */
  48 
  49 public class FilerImpl implements Filer {
  50     /*
  51      * The Filer class must maintain a number of constraints.  First,
  52      * multiple attempts to open the same path within the same
  53      * invocation of apt results in an IOException being thrown.  For
  54      * example, trying to open the same source file twice:
  55      *
  56      * createSourceFile("foo.Bar")
  57      * ...
  58      * createSourceFile("foo.Bar")
  59      *
  60      * is disallowed as is opening a text file that happens to have
  61      * the same name as a source file:
  62      *
  63      * createSourceFile("foo.Bar")
  64      * ...
  65      * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
  66      *
  67      * Additionally, creating a source file that corresponds to an
  68      * already created class file (or vice versa) generates at least a
  69      * warning.  This is an error if -XclassesAsDecls is being used
  70      * since you can't create the same type twice.  However, if the
  71      * Filer is used to create a text file named *.java that happens
  72      * to correspond to an existing class file, a warning is *not*
  73      * generated.  Similarly, a warning is not generated for a binary
  74      * file named *.class and an existing source file.
  75      *
  76      * The reason for this difference is that source files and class
  77      * files are registered with apt and can get passed on as
  78      * declarations to the next round of processing.  Files that are
  79      * just named *.java and *.class are not processed in that manner;
  80      * although having extra source files and class files on the
  81      * source path and class path can alter the behavior of the tool
  82      * and any final compile.
  83      */
  84 
  85     private enum FileKind {
  86         SOURCE {
  87             void register(File file, String name, FilerImpl that) throws IOException {
  88                 // Check for corresponding class file
  89                 if (that.filesCreated.contains(new File(that.locations.get(CLASS_TREE),
  90                                                         that.nameToPath(name, ".class")))) {
  91 
  92                     that.bark.aptWarning("CorrespondingClassFile", name);
  93                     if (that.opts.get("-XclassesAsDecls") != null)
  94                         throw new IOException();
  95                 }
  96                 that.sourceFileNames.add(file.getPath());
  97             }
  98         },
  99 
 100         CLASS  {
 101             void register(File file, String name, FilerImpl that) throws IOException {
 102                 if (that.filesCreated.contains(new File(that.locations.get(SOURCE_TREE),
 103                                                         that.nameToPath(name, ".java")))) {
 104                     that.bark.aptWarning("CorrespondingSourceFile", name);
 105                     if (that.opts.get("-XclassesAsDecls") != null)
 106                         throw new IOException();
 107                 }
 108                 // Track the binary name instead of the filesystem location
 109                 that.classFileNames.add(name);
 110             }
 111         },
 112 
 113         OTHER  {
 114             // Nothing special to do
 115             void register(File file, String name, FilerImpl that) throws IOException {}
 116         };
 117 
 118         abstract void register(File file, String name, FilerImpl that) throws IOException;
 119     }
 120 
 121     private final Options opts;
 122     private final DeclarationMaker declMaker;
 123     private final com.sun.tools.apt.main.JavaCompiler comp;
 124 
 125     // Platform's default encoding
 126     private final static String DEFAULT_ENCODING =
 127             new OutputStreamWriter(new ByteArrayOutputStream()).getEncoding();
 128 
 129     private String encoding;    // name of charset used for source files
 130 
 131     private final EnumMap<Location, File> locations;    // where new files go
 132 
 133 
 134     private static final Context.Key<FilerImpl> filerKey =
 135             new Context.Key<FilerImpl>();
 136 
 137     // Set of files opened.
 138     private Collection<Flushable> wc;
 139 
 140     private Bark bark;
 141 
 142     // All created files.
 143     private final Set<File> filesCreated;
 144 
 145     // Names of newly created source files
 146     private HashSet<String> sourceFileNames = new HashSet<String>();
 147 
 148     // Names of newly created class files
 149     private HashSet<String> classFileNames  = new HashSet<String>();
 150 
 151     private boolean roundOver;
 152 
 153     public static FilerImpl instance(Context context) {
 154         FilerImpl instance = context.get(filerKey);
 155         if (instance == null) {
 156             instance = new FilerImpl(context);
 157         }
 158         return instance;
 159     }
 160 
 161     // flush all output streams;
 162     public void flush() {
 163         for(Flushable opendedFile: wc) {
 164             try {
 165                 opendedFile.flush();
 166                 if (opendedFile instanceof FileOutputStream) {
 167                     try {
 168                         ((FileOutputStream) opendedFile).getFD().sync() ;
 169                     } catch (java.io.SyncFailedException sfe) {}
 170                 }
 171             } catch (IOException e) { }
 172         }
 173     }
 174 
 175     private FilerImpl(Context context) {
 176         context.put(filerKey, this);
 177         opts = Options.instance(context);
 178         declMaker = DeclarationMaker.instance(context);
 179         bark = Bark.instance(context);
 180         comp = com.sun.tools.apt.main.JavaCompiler.instance(context);
 181         roundOver = false;
 182         this.filesCreated = comp.getAggregateGenFiles();
 183 
 184         // Encoding
 185         encoding = opts.get("-encoding");
 186         if (encoding == null) {
 187             encoding = DEFAULT_ENCODING;
 188         }
 189 
 190         wc = new HashSet<Flushable>();
 191 
 192         // Locations
 193         locations = new EnumMap<Location, File>(Location.class);
 194         String s = opts.get("-s");      // location for new source files
 195         String d = opts.get("-d");      // location for new class files
 196         locations.put(SOURCE_TREE, new File(s != null ? s : "."));
 197         locations.put(CLASS_TREE,  new File(d != null ? d : "."));
 198     }
 199 
 200 
 201     /**
 202      * {@inheritDoc}
 203      */
 204     public PrintWriter createSourceFile(String name) throws IOException {
 205         String pathname = nameToPath(name, ".java");
 206         File file = new File(locations.get(SOURCE_TREE),
 207                              pathname);
 208         PrintWriter pw = getPrintWriter(file, encoding, name, FileKind.SOURCE);
 209         return pw;
 210     }
 211 
 212     /**
 213      * {@inheritDoc}
 214      */
 215     public OutputStream createClassFile(String name) throws IOException {
 216         String pathname = nameToPath(name, ".class");
 217         File file = new File(locations.get(CLASS_TREE),
 218                              pathname);
 219         OutputStream os = getOutputStream(file, name, FileKind.CLASS);
 220         return os;
 221     }
 222 
 223     /**
 224      * {@inheritDoc}
 225      */
 226     public PrintWriter createTextFile(Location loc,
 227                                       String pkg,
 228                                       File relPath,
 229                                       String charsetName) throws IOException {
 230         File file = (pkg.length() == 0)
 231                         ? relPath
 232                         : new File(nameToPath(pkg), relPath.getPath());
 233         if (charsetName == null) {
 234             charsetName = encoding;
 235         }
 236         return getPrintWriter(loc, file.getPath(), charsetName, null, FileKind.OTHER);
 237     }
 238 
 239     /**
 240      * {@inheritDoc}
 241      */
 242     public OutputStream createBinaryFile(Location loc,
 243                                          String pkg,
 244                                          File relPath) throws IOException {
 245         File file = (pkg.length() == 0)
 246                         ? relPath
 247                         : new File(nameToPath(pkg), relPath.getPath());
 248         return getOutputStream(loc, file.getPath(), null, FileKind.OTHER);
 249     }
 250 
 251 
 252     /**
 253      * Converts the canonical name of a top-level type or package to a
 254      * pathname.  Suffix is ".java" or ".class" or "".
 255      */
 256     private String nameToPath(String name, String suffix) throws IOException {
 257         if (!DeclarationMaker.isJavaIdentifier(name.replace('.', '_'))) {
 258             bark.aptWarning("IllegalFileName", name);
 259             throw new IOException();
 260         }
 261         return name.replace('.', File.separatorChar) + suffix;
 262     }
 263 
 264     private String nameToPath(String name) throws IOException {
 265         return nameToPath(name, "");
 266     }
 267 
 268     /**
 269      * Returns a writer for a text file given its location, its
 270      * pathname relative to that location, and its encoding.
 271      */
 272     private PrintWriter getPrintWriter(Location loc, String pathname,
 273                                        String encoding, String name, FileKind kind) throws IOException {
 274         File file = new File(locations.get(loc), pathname);
 275         return getPrintWriter(file, encoding, name, kind);
 276     }
 277 
 278     /**
 279      * Returns a writer for a text file given its encoding.
 280      */
 281     private PrintWriter getPrintWriter(File file,
 282                                        String encoding, String name, FileKind kind) throws IOException {
 283         prepareFile(file, name, kind);
 284         PrintWriter pw =
 285             new PrintWriter(
 286                     new BufferedWriter(
 287                          new OutputStreamWriter(new FileOutputStream(file),
 288                                                 encoding)));
 289         wc.add(pw);
 290         return pw;
 291     }
 292 
 293     /**
 294      * Returns an output stream for a binary file given its location
 295      * and its pathname relative to that location.
 296      */
 297     private OutputStream getOutputStream(Location loc, String pathname, String name, FileKind kind)
 298                                                         throws IOException {
 299         File file = new File(locations.get(loc), pathname);
 300         return getOutputStream(file, name, kind);
 301     }
 302 
 303     private OutputStream getOutputStream(File file, String name, FileKind kind) throws IOException {
 304         prepareFile(file, name, kind);
 305         OutputStream os = new FileOutputStream(file);
 306         wc.add(os);
 307         return os;
 308 
 309     }
 310 
 311     public Set<String> getSourceFileNames() {
 312         return sourceFileNames;
 313     }
 314 
 315     public Set<String> getClassFileNames() {
 316         return classFileNames;
 317     }
 318 
 319     public void roundOver() {
 320         roundOver = true;
 321     }
 322 
 323     /**
 324      * Checks that the file has not already been created during this
 325      * invocation.  If not, creates intermediate directories, and
 326      * deletes the file if it already exists.
 327      */
 328     private void prepareFile(File file, String name, FileKind kind) throws IOException {
 329         if (roundOver) {
 330             bark.aptWarning("NoNewFilesAfterRound", file.toString());
 331             throw new IOException();
 332         }
 333 
 334         if (filesCreated.contains(file)) {
 335             bark.aptWarning("FileReopening", file.toString());
 336             throw new IOException();
 337         } else {
 338             if (file.exists()) {
 339                 file.delete();
 340             } else {
 341                 File parent = file.getParentFile();
 342                 if (parent != null && !parent.exists()) {
 343                     if(!parent.mkdirs()) {
 344                         bark.aptWarning("BadParentDirectory", file.toString());
 345                         throw new IOException();
 346                     }
 347                 }
 348             }
 349 
 350             kind.register(file, name, this);
 351             filesCreated.add(file);
 352         }
 353     }
 354 }