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