1 /*
   2  * Copyright (c) 2009, 2014, Oracle and/or its affiliates. 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javac.file;
  27 
  28 import java.io.IOException;
  29 import java.io.InputStream;
  30 import java.io.InputStreamReader;
  31 import java.io.OutputStream;
  32 import java.io.OutputStreamWriter;
  33 import java.io.Reader;
  34 import java.io.Writer;
  35 import java.net.URI;
  36 import java.nio.ByteBuffer;
  37 import java.nio.CharBuffer;
  38 import java.nio.charset.CharsetDecoder;
  39 import java.nio.file.Files;
  40 import java.nio.file.LinkOption;
  41 import java.nio.file.Path;
  42 import java.util.Objects;
  43 
  44 import javax.lang.model.element.Modifier;
  45 import javax.lang.model.element.NestingKind;
  46 import javax.tools.JavaFileObject;
  47 
  48 import com.sun.tools.javac.util.DefinedBy;
  49 import com.sun.tools.javac.util.DefinedBy.Api;
  50 
  51 
  52 /**
  53  *  Implementation of JavaFileObject using java.nio.file API.
  54  *
  55  *  <p>PathFileObjects are, for the most part, straightforward wrappers around
  56  *  Path objects. The primary complexity is the support for "inferBinaryName".
  57  *  This is left as an abstract method, implemented by each of a number of
  58  *  different factory methods, which compute the binary name based on
  59  *  information available at the time the file object is created.
  60  *
  61  *  <p><b>This is NOT part of any supported API.
  62  *  If you write code that depends on this, you do so at your own risk.
  63  *  This code and its internal interfaces are subject to change or
  64  *  deletion without notice.</b>
  65  */
  66 public abstract class PathFileObject implements JavaFileObject {
  67     private final BaseFileManager fileManager;
  68     private final Path path;
  69 
  70     /**
  71      * Create a PathFileObject within a directory, such that the binary name
  72      * can be inferred from the relationship to the parent directory.
  73      */
  74     static PathFileObject createDirectoryPathFileObject(BaseFileManager fileManager,
  75             final Path path, final Path dir) {
  76         return new PathFileObject(fileManager, path) {
  77             @Override
  78             public String inferBinaryName(Iterable<? extends Path> paths) {
  79                 return toBinaryName(dir.relativize(path));
  80             }
  81         };
  82     }
  83 
  84     /**
  85      * Create a PathFileObject in a file system such as a jar file, such that
  86      * the binary name can be inferred from its position within the filesystem.
  87      */
  88     public static PathFileObject createJarPathFileObject(BaseFileManager fileManager,
  89             final Path path) {
  90         return new PathFileObject(fileManager, path) {
  91             @Override
  92             public String inferBinaryName(Iterable<? extends Path> paths) {
  93                 return toBinaryName(path);
  94             }
  95         };
  96     }
  97 
  98     /**
  99      * Create a PathFileObject in a modular file system, such as jrt:, such that
 100      * the binary name can be inferred from its position within the filesystem.
 101      */
 102     public static PathFileObject createJRTPathFileObject(BaseFileManager fileManager,
 103             final Path path) {
 104         return new PathFileObject(fileManager, path) {
 105             @Override
 106             public String inferBinaryName(Iterable<? extends Path> paths) {
 107                 // use subpath to ignore the leading component containing the module name
 108                 return toBinaryName(path.subpath(1, path.getNameCount()));
 109             }
 110         };
 111     }
 112 
 113     /**
 114      * Create a PathFileObject whose binary name can be inferred from the
 115      * relative path to a sibling.
 116      */
 117     static PathFileObject createSiblingPathFileObject(BaseFileManager fileManager,
 118             final Path path, final String relativePath) {
 119         return new PathFileObject(fileManager, path) {
 120             @Override
 121             public String inferBinaryName(Iterable<? extends Path> paths) {
 122                 return toBinaryName(relativePath, "/");
 123             }
 124         };
 125     }
 126 
 127     /**
 128      * Create a PathFileObject whose binary name might be inferred from its
 129      * position on a search path.
 130      */
 131     static PathFileObject createSimplePathFileObject(BaseFileManager fileManager,
 132             final Path path) {
 133         return new PathFileObject(fileManager, path) {
 134             @Override
 135             public String inferBinaryName(Iterable<? extends Path> paths) {
 136                 Path absPath = path.toAbsolutePath();
 137                 for (Path p: paths) {
 138                     Path ap = p.toAbsolutePath();
 139                     if (absPath.startsWith(ap)) {
 140                         try {
 141                             Path rp = ap.relativize(absPath);
 142                             if (rp != null) // maybe null if absPath same as ap
 143                                 return toBinaryName(rp);
 144                         } catch (IllegalArgumentException e) {
 145                             // ignore this p if cannot relativize path to p
 146                         }
 147                     }
 148                 }
 149                 return null;
 150             }
 151         };
 152     }
 153 
 154     protected PathFileObject(BaseFileManager fileManager, Path path) {
 155         this.fileManager = Objects.requireNonNull(fileManager);
 156         this.path = Objects.requireNonNull(path);
 157     }
 158 
 159     public abstract String inferBinaryName(Iterable<? extends Path> paths);
 160 
 161     /**
 162      * Return the Path for this object.
 163      * @return the Path for this object.
 164      */
 165     public Path getPath() {
 166         return path;
 167     }
 168 
 169     @Override @DefinedBy(Api.COMPILER)
 170     public Kind getKind() {
 171         return BaseFileManager.getKind(path.getFileName().toString());
 172     }
 173 
 174     @Override @DefinedBy(Api.COMPILER)
 175     public boolean isNameCompatible(String simpleName, Kind kind) {
 176         Objects.requireNonNull(simpleName);
 177         // null check
 178         if (kind == Kind.OTHER && getKind() != kind) {
 179             return false;
 180         }
 181         String sn = simpleName + kind.extension;
 182         String pn = path.getFileName().toString();
 183         if (pn.equals(sn)) {
 184             return true;
 185         }
 186         if (pn.equalsIgnoreCase(sn)) {
 187             try {
 188                 // allow for Windows
 189                 return path.toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString().equals(sn);
 190             } catch (IOException e) {
 191             }
 192         }
 193         return false;
 194     }
 195 
 196     @Override @DefinedBy(Api.COMPILER)
 197     public NestingKind getNestingKind() {
 198         return null;
 199     }
 200 
 201     @Override @DefinedBy(Api.COMPILER)
 202     public Modifier getAccessLevel() {
 203         return null;
 204     }
 205 
 206     @Override @DefinedBy(Api.COMPILER)
 207     public URI toUri() {
 208         return path.toUri();
 209     }
 210 
 211     @Override @DefinedBy(Api.COMPILER)
 212     public String getName() {
 213         return path.toString();
 214     }
 215 
 216     @Override @DefinedBy(Api.COMPILER)
 217     public InputStream openInputStream() throws IOException {
 218         return Files.newInputStream(path);
 219     }
 220 
 221     @Override @DefinedBy(Api.COMPILER)
 222     public OutputStream openOutputStream() throws IOException {
 223         fileManager.flushCache(this);
 224         ensureParentDirectoriesExist();
 225         return Files.newOutputStream(path);
 226     }
 227 
 228     @Override @DefinedBy(Api.COMPILER)
 229     public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
 230         CharsetDecoder decoder = fileManager.getDecoder(fileManager.getEncodingName(), ignoreEncodingErrors);
 231         return new InputStreamReader(openInputStream(), decoder);
 232     }
 233 
 234     @Override @DefinedBy(Api.COMPILER)
 235     public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
 236         CharBuffer cb = fileManager.getCachedContent(this);
 237         if (cb == null) {
 238             try (InputStream in = openInputStream()) {
 239                 ByteBuffer bb = fileManager.makeByteBuffer(in);
 240                 JavaFileObject prev = fileManager.log.useSource(this);
 241                 try {
 242                     cb = fileManager.decode(bb, ignoreEncodingErrors);
 243                 } finally {
 244                     fileManager.log.useSource(prev);
 245                 }
 246                 fileManager.recycleByteBuffer(bb);
 247                 if (!ignoreEncodingErrors) {
 248                     fileManager.cache(this, cb);
 249                 }
 250             }
 251         }
 252         return cb;
 253     }
 254 
 255     @Override @DefinedBy(Api.COMPILER)
 256     public Writer openWriter() throws IOException {
 257         fileManager.flushCache(this);
 258         ensureParentDirectoriesExist();
 259         return new OutputStreamWriter(Files.newOutputStream(path), fileManager.getEncodingName());
 260     }
 261 
 262     @Override @DefinedBy(Api.COMPILER)
 263     public long getLastModified() {
 264         try {
 265             return Files.getLastModifiedTime(path).toMillis();
 266         } catch (IOException e) {
 267             return -1;
 268         }
 269     }
 270 
 271     @Override @DefinedBy(Api.COMPILER)
 272     public boolean delete() {
 273         try {
 274             Files.delete(path);
 275             return true;
 276         } catch (IOException e) {
 277             return false;
 278         }
 279     }
 280 
 281     public boolean isSameFile(PathFileObject other) {
 282         try {
 283             return Files.isSameFile(path, other.path);
 284         } catch (IOException e) {
 285             return false;
 286         }
 287     }
 288 
 289     @Override
 290     public boolean equals(Object other) {
 291         return (other instanceof PathFileObject && path.equals(((PathFileObject) other).path));
 292     }
 293 
 294     @Override
 295     public int hashCode() {
 296         return path.hashCode();
 297     }
 298 
 299     @Override
 300     public String toString() {
 301         return getClass().getSimpleName() + "[" + path + "]";
 302     }
 303 
 304     private void ensureParentDirectoriesExist() throws IOException {
 305         Path parent = path.getParent();
 306         if (parent != null)
 307             Files.createDirectories(parent);
 308     }
 309 
 310     private long size() {
 311         try {
 312             return Files.size(path);
 313         } catch (IOException e) {
 314             return -1;
 315         }
 316     }
 317 
 318     protected static String toBinaryName(Path relativePath) {
 319         return toBinaryName(relativePath.toString(),
 320                 relativePath.getFileSystem().getSeparator());
 321     }
 322 
 323     protected static String toBinaryName(String relativePath, String sep) {
 324         return removeExtension(relativePath).replace(sep, ".");
 325     }
 326 
 327     protected static String removeExtension(String fileName) {
 328         int lastDot = fileName.lastIndexOf(".");
 329         return (lastDot == -1 ? fileName : fileName.substring(0, lastDot));
 330     }
 331 }