1 /*
   2  * Copyright (c) 1998, 2017, 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 jdk.javadoc.internal.doclets.toolkit.util;
  27 
  28 import java.io.BufferedInputStream;
  29 import java.io.BufferedOutputStream;
  30 import java.io.BufferedWriter;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.OutputStream;
  34 import java.io.OutputStreamWriter;
  35 import java.io.UnsupportedEncodingException;
  36 import java.io.Writer;
  37 import java.nio.file.DirectoryStream;
  38 import java.nio.file.Files;
  39 import java.nio.file.Path;
  40 import java.nio.file.Paths;
  41 import java.util.ArrayList;
  42 import java.util.Arrays;
  43 import java.util.LinkedHashSet;
  44 import java.util.List;
  45 import java.util.Objects;
  46 import java.util.Set;
  47 
  48 import javax.tools.DocumentationTool;
  49 import javax.tools.FileObject;
  50 import javax.tools.JavaFileManager.Location;
  51 import javax.tools.JavaFileObject;
  52 import javax.tools.StandardJavaFileManager;
  53 import javax.tools.StandardLocation;
  54 
  55 import com.sun.tools.javac.util.Assert;
  56 import jdk.javadoc.internal.doclets.toolkit.BaseConfiguration;
  57 
  58 /**
  59  * Implementation of DocFileFactory using a {@link StandardJavaFileManager}.
  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  */
  67 class StandardDocFileFactory extends DocFileFactory {
  68     private final StandardJavaFileManager fileManager;
  69     private Path destDir;
  70 
  71     public StandardDocFileFactory(BaseConfiguration configuration) {
  72         super(configuration);
  73         fileManager = (StandardJavaFileManager) configuration.getFileManager();
  74     }
  75 
  76     @Override
  77     public void setDestDir(String destDirName) throws SimpleDocletException {
  78         if (destDir != null)
  79             throw new AssertionError("destDir already initialized: " + destDir);
  80 
  81         if (!destDirName.isEmpty()
  82                 || !fileManager.hasLocation(DocumentationTool.Location.DOCUMENTATION_OUTPUT)) {
  83             try {
  84                 String dirName = destDirName.isEmpty() ? "." : destDirName;
  85                 Path dir = Paths.get(dirName);
  86                 fileManager.setLocationFromPaths(DocumentationTool.Location.DOCUMENTATION_OUTPUT, Arrays.asList(dir));
  87             } catch (IOException e) {
  88                 // generic IOException from file manager, setting location, e.g. file not a directory
  89                 String message = configuration.getResources().getText("doclet.error.initializing.dest.dir", e);
  90                 throw new SimpleDocletException(message, e);
  91             }
  92         }
  93 
  94         destDir = fileManager.getLocationAsPaths(DocumentationTool.Location.DOCUMENTATION_OUTPUT).iterator().next();
  95     }
  96 
  97     private Path getDestDir() {
  98         Objects.requireNonNull(destDir, "destDir not initialized");
  99         return destDir;
 100     }
 101 
 102     @Override
 103     public DocFile createFileForDirectory(String file) {
 104         return new StandardDocFile(Paths.get(file));
 105     }
 106 
 107     @Override
 108     public DocFile createFileForInput(String file) {
 109         return new StandardDocFile(Paths.get(file));
 110     }
 111 
 112     @Override
 113     public DocFile createFileForOutput(DocPath path) {
 114         return new StandardDocFile(DocumentationTool.Location.DOCUMENTATION_OUTPUT, path);
 115     }
 116 
 117     @Override
 118     Iterable<DocFile> list(Location location, DocPath path) {
 119         Location l = ((location == StandardLocation.SOURCE_PATH)
 120                 && !fileManager.hasLocation(StandardLocation.SOURCE_PATH))
 121                 ? StandardLocation.CLASS_PATH
 122                 : location;
 123 
 124         Set<DocFile> files = new LinkedHashSet<>();
 125         for (Path f: fileManager.getLocationAsPaths(l)) {
 126             if (Files.isDirectory(f)) {
 127                 f = f.resolve(path.getPath());
 128                 if (Files.exists(f))
 129                     files.add(new StandardDocFile(f));
 130             }
 131         }
 132         return files;
 133     }
 134 
 135     private static Path newFile(Path dir, String path) {
 136         return (dir == null) ? Paths.get(path) : dir.resolve(path);
 137     }
 138 
 139     class StandardDocFile extends DocFile {
 140         private final Path file;
 141 
 142         /** Create a StandardDocFile for a given file. */
 143         private StandardDocFile(Path file) {
 144             this.file = file;
 145         }
 146 
 147         /** Create a StandardDocFile for a given location and relative path. */
 148         private StandardDocFile(Location location, DocPath path) {
 149             super(location, path);
 150             Assert.check(location == DocumentationTool.Location.DOCUMENTATION_OUTPUT);
 151             this.file = newFile(getDestDir(), path.getPath());
 152         }
 153 
 154         @Override
 155         public FileObject getFileObject()  {
 156             return getJavaFileObjectForInput(file);
 157         }
 158 
 159         /**
 160          * Open an input stream for the file.
 161          *
 162          * @throws DocFileIOException if there is a problem while opening stream
 163          */
 164         @Override
 165         public InputStream openInputStream() throws DocFileIOException {
 166             try {
 167                 JavaFileObject fo = getJavaFileObjectForInput(file);
 168                 return new BufferedInputStream(fo.openInputStream());
 169             } catch (IOException e) {
 170                 throw new DocFileIOException(this, DocFileIOException.Mode.READ, e);
 171             }
 172         }
 173 
 174         /**
 175          * Open an output stream for the file.
 176          * The file must have been created with a location of
 177          * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} and a corresponding relative path.
 178          *
 179          * @throws DocFileIOException if there is a problem while opening stream
 180          */
 181         @Override
 182         public OutputStream openOutputStream() throws DocFileIOException {
 183             if (location != DocumentationTool.Location.DOCUMENTATION_OUTPUT)
 184                 throw new IllegalStateException();
 185 
 186             try {
 187                 OutputStream out = getFileObjectForOutput(path).openOutputStream();
 188                 return new BufferedOutputStream(out);
 189             } catch (IOException e) {
 190                 throw new DocFileIOException(this, DocFileIOException.Mode.WRITE, e);
 191             }
 192         }
 193 
 194         /**
 195          * Open an writer for the file, using the encoding (if any) given in the
 196          * doclet configuration.
 197          * The file must have been created with a location of
 198          * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} and a corresponding relative path.
 199          *
 200          * @throws DocFileIOException if there is a problem while opening stream
 201          * @throws UnsupportedEncodingException if the configured encoding is not supported
 202          */
 203         @Override
 204         public Writer openWriter() throws DocFileIOException, UnsupportedEncodingException {
 205             if (location != DocumentationTool.Location.DOCUMENTATION_OUTPUT)
 206                 throw new IllegalStateException();
 207 
 208             try {
 209                 OutputStream out = getFileObjectForOutput(path).openOutputStream();
 210                 String docencoding = configuration.getOptions().docEncoding;
 211                 return new BufferedWriter(new OutputStreamWriter(out, docencoding));
 212             } catch (IOException e) {
 213                 throw new DocFileIOException(this, DocFileIOException.Mode.WRITE, e);
 214             }
 215         }
 216 
 217         /** Return true if the file can be read. */
 218         @Override
 219         public boolean canRead() {
 220             return Files.isReadable(file);
 221         }
 222 
 223         /** Return true if the file can be written. */
 224         @Override
 225         public boolean canWrite() {
 226             return Files.isWritable(file);
 227         }
 228 
 229         /** Return true if the file exists. */
 230         @Override
 231         public boolean exists() {
 232             return Files.exists(file);
 233         }
 234 
 235         /** Return the base name (last component) of the file name. */
 236         @Override
 237         public String getName() {
 238             return file.getFileName().toString();
 239         }
 240 
 241         /** Return the file system path for this file. */
 242         @Override
 243         public String getPath() {
 244             return file.toString();
 245         }
 246 
 247         /** Return true is file has an absolute path name. */
 248         @Override
 249         public boolean isAbsolute() {
 250             return file.isAbsolute();
 251         }
 252 
 253         /** Return true is file identifies a directory. */
 254         @Override
 255         public boolean isDirectory() {
 256             return Files.isDirectory(file);
 257         }
 258 
 259         /** Return true is file identifies a file. */
 260         @Override
 261         public boolean isFile() {
 262             return Files.isRegularFile(file);
 263         }
 264 
 265         /** Return true if this file is the same as another. */
 266         @Override
 267         public boolean isSameFile(DocFile other) {
 268             if (!(other instanceof StandardDocFile))
 269                 return false;
 270 
 271             try {
 272                 return Files.isSameFile(file, ((StandardDocFile) other).file);
 273             } catch (IOException e) {
 274                 return false;
 275             }
 276         }
 277 
 278         /** If the file is a directory, list its contents. */
 279         @Override
 280         public Iterable<DocFile> list() throws DocFileIOException {
 281             List<DocFile> files = new ArrayList<>();
 282             try (DirectoryStream<Path> ds = Files.newDirectoryStream(file)) {
 283                 for (Path f: ds) {
 284                     files.add(new StandardDocFile(f));
 285                 }
 286             } catch (IOException e) {
 287                 throw new DocFileIOException(this, DocFileIOException.Mode.READ, e);
 288             }
 289             return files;
 290         }
 291 
 292         /** Create the file as a directory, including any parent directories. */
 293         @Override
 294         public boolean mkdirs() {
 295             try {
 296                 Files.createDirectories(file);
 297                 return true;
 298             } catch (IOException e) {
 299                 return false;
 300             }
 301         }
 302 
 303         /**
 304          * Derive a new file by resolving a relative path against this file.
 305          * The new file will inherit the configuration and location of this file
 306          * If this file has a path set, the new file will have a corresponding
 307          * new path.
 308          */
 309         @Override
 310         public DocFile resolve(DocPath p) {
 311             return resolve(p.getPath());
 312         }
 313 
 314         /**
 315          * Derive a new file by resolving a relative path against this file.
 316          * The new file will inherit the configuration and location of this file
 317          * If this file has a path set, the new file will have a corresponding
 318          * new path.
 319          */
 320         @Override
 321         public DocFile resolve(String p) {
 322             if (location == null && path == null) {
 323                 return new StandardDocFile(file.resolve(p));
 324             } else {
 325                 return new StandardDocFile(location, path.resolve(p));
 326             }
 327         }
 328 
 329         /**
 330          * Resolve a relative file against the given output location.
 331          * @param locn Currently, only
 332          * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} is supported.
 333          */
 334         @Override
 335         public DocFile resolveAgainst(Location locn) {
 336             if (locn != DocumentationTool.Location.DOCUMENTATION_OUTPUT)
 337                 throw new IllegalArgumentException();
 338             return new StandardDocFile(getDestDir().resolve(file));
 339         }
 340 
 341         /** Return a string to identify the contents of this object,
 342          * for debugging purposes.
 343          */
 344         @Override
 345         public String toString() {
 346             StringBuilder sb = new StringBuilder();
 347             sb.append("StandardDocFile[");
 348             if (location != null)
 349                 sb.append("locn:").append(location).append(",");
 350             if (path != null)
 351                 sb.append("path:").append(path.getPath()).append(",");
 352             sb.append("file:").append(file);
 353             sb.append("]");
 354             return sb.toString();
 355         }
 356 
 357         private JavaFileObject getJavaFileObjectForInput(Path file) {
 358             return fileManager.getJavaFileObjects(file).iterator().next();
 359         }
 360 
 361         private FileObject getFileObjectForOutput(DocPath path) throws IOException {
 362             // break the path into a package-part and the rest, by finding
 363             // the position of the last '/' before an invalid character for a
 364             // package name, such as the "." before an extension or the "-"
 365             // in filenames like package-summary.html, doc-files or src-html.
 366             String p = path.getPath();
 367             int lastSep = -1;
 368             for (int i = 0; i < p.length(); i++) {
 369                 char ch = p.charAt(i);
 370                 if (ch == '/') {
 371                     lastSep = i;
 372                 } else if (i == lastSep + 1 && !Character.isJavaIdentifierStart(ch)
 373                         || !Character.isJavaIdentifierPart(ch)) {
 374                     break;
 375                 }
 376             }
 377             String pkg = (lastSep == -1) ? "" : p.substring(0, lastSep);
 378             String rest = p.substring(lastSep + 1);
 379             return fileManager.getFileForOutput(location, pkg, rest, null);
 380         }
 381     }
 382 }