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