1 /*
   2  * Copyright (c) 1998, 2015, 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.File;
  32 import java.io.IOException;
  33 import java.io.InputStream;
  34 import java.io.OutputStream;
  35 import java.io.OutputStreamWriter;
  36 import java.io.UnsupportedEncodingException;
  37 import java.io.Writer;
  38 import java.util.ArrayList;
  39 import java.util.Arrays;
  40 import java.util.LinkedHashSet;
  41 import java.util.List;
  42 import java.util.Set;
  43 
  44 import javax.tools.DocumentationTool;
  45 import javax.tools.FileObject;
  46 import javax.tools.JavaFileManager.Location;
  47 import javax.tools.JavaFileObject;
  48 import javax.tools.StandardJavaFileManager;
  49 import javax.tools.StandardLocation;
  50 
  51 import jdk.javadoc.internal.doclets.toolkit.Configuration;
  52 
  53 /**
  54  * Implementation of DocFileFactory using a {@link StandardJavaFileManager}.
  55  *
  56  *  <p><b>This is NOT part of any supported API.
  57  *  If you write code that depends on this, you do so at your own risk.
  58  *  This code and its internal interfaces are subject to change or
  59  *  deletion without notice.</b>
  60  *
  61  * @since 1.8
  62  */
  63 class StandardDocFileFactory extends DocFileFactory {
  64     private final StandardJavaFileManager fileManager;
  65     private File destDir;
  66 
  67     public StandardDocFileFactory(Configuration configuration) {
  68         super(configuration);
  69         fileManager = (StandardJavaFileManager) configuration.getFileManager();
  70     }
  71 
  72     private File getDestDir() {
  73         if (destDir == null) {
  74             if (!configuration.destDirName.isEmpty()
  75                     || !fileManager.hasLocation(DocumentationTool.Location.DOCUMENTATION_OUTPUT)) {
  76                 try {
  77                     String dirName = configuration.destDirName.isEmpty() ? "." : configuration.destDirName;
  78                     File dir = new File(dirName);
  79                     fileManager.setLocation(DocumentationTool.Location.DOCUMENTATION_OUTPUT, Arrays.asList(dir));
  80                 } catch (IOException e) {
  81                     throw new DocletAbortException(e);
  82                 }
  83             }
  84 
  85             destDir = fileManager.getLocation(DocumentationTool.Location.DOCUMENTATION_OUTPUT).iterator().next();
  86         }
  87         return destDir;
  88     }
  89 
  90     public DocFile createFileForDirectory(String file) {
  91         return new StandardDocFile(new File(file));
  92     }
  93 
  94     public DocFile createFileForInput(String file) {
  95         return new StandardDocFile(new File(file));
  96     }
  97 
  98     public DocFile createFileForOutput(DocPath path) {
  99         return new StandardDocFile(DocumentationTool.Location.DOCUMENTATION_OUTPUT, path);
 100     }
 101 
 102     @Override
 103     Iterable<DocFile> list(Location location, DocPath path) {
 104         if (location != StandardLocation.SOURCE_PATH)
 105             throw new IllegalArgumentException();
 106 
 107         Set<DocFile> files = new LinkedHashSet<>();
 108         Location l = fileManager.hasLocation(StandardLocation.SOURCE_PATH)
 109                 ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
 110         for (File f: fileManager.getLocation(l)) {
 111             if (f.isDirectory()) {
 112                 f = new File(f, path.getPath());
 113                 if (f.exists())
 114                     files.add(new StandardDocFile(f));
 115             }
 116         }
 117         return files;
 118     }
 119 
 120     private static File newFile(File dir, String path) {
 121         return (dir == null) ? new File(path) : new File(dir, path);
 122     }
 123 
 124     class StandardDocFile extends DocFile {
 125         private File file;
 126 
 127 
 128         /** Create a StandardDocFile for a given file. */
 129         private StandardDocFile(File file) {
 130             super(configuration);
 131             this.file = file;
 132         }
 133 
 134         /** Create a StandardDocFile for a given location and relative path. */
 135         private StandardDocFile(Location location, DocPath path) {
 136             super(configuration, location, path);
 137             if (location != DocumentationTool.Location.DOCUMENTATION_OUTPUT) {
 138                 throw new AssertionError("invalid location output");
 139             }
 140             this.file = newFile(getDestDir(), path.getPath());
 141         }
 142 
 143         /** Open an input stream for the file. */
 144         public InputStream openInputStream() throws IOException {
 145             JavaFileObject fo = getJavaFileObjectForInput(file);
 146             return new BufferedInputStream(fo.openInputStream());
 147         }
 148 
 149         /**
 150          * Open an output stream for the file.
 151          * The file must have been created with a location of
 152          * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} and a corresponding relative path.
 153          */
 154         public OutputStream openOutputStream() throws IOException, UnsupportedEncodingException {
 155             if (location != DocumentationTool.Location.DOCUMENTATION_OUTPUT)
 156                 throw new IllegalStateException();
 157 
 158             OutputStream out = getFileObjectForOutput(path).openOutputStream();
 159             return new BufferedOutputStream(out);
 160         }
 161 
 162         /**
 163          * Open an writer for the file, using the encoding (if any) given in the
 164          * doclet configuration.
 165          * The file must have been created with a location of
 166          * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} and a corresponding relative path.
 167          */
 168         public Writer openWriter() throws IOException, UnsupportedEncodingException {
 169             if (location != DocumentationTool.Location.DOCUMENTATION_OUTPUT)
 170                 throw new IllegalStateException();
 171 
 172             OutputStream out = getFileObjectForOutput(path).openOutputStream();
 173             if (configuration.docencoding == null) {
 174                 return new BufferedWriter(new OutputStreamWriter(out));
 175             } else {
 176                 return new BufferedWriter(new OutputStreamWriter(out, configuration.docencoding));
 177             }
 178         }
 179 
 180         /** Return true if the file can be read. */
 181         public boolean canRead() {
 182             return file.canRead();
 183         }
 184 
 185         /** Return true if the file can be written. */
 186         public boolean canWrite() {
 187             return file.canWrite();
 188         }
 189 
 190         /** Return true if the file exists. */
 191         public boolean exists() {
 192             return file.exists();
 193         }
 194 
 195         /** Return the base name (last component) of the file name. */
 196         public String getName() {
 197             return file.getName();
 198         }
 199 
 200         /** Return the file system path for this file. */
 201         public String getPath() {
 202             return file.getPath();
 203         }
 204 
 205         /** Return true is file has an absolute path name. */
 206         public boolean isAbsolute() {
 207             return file.isAbsolute();
 208         }
 209 
 210         /** Return true is file identifies a directory. */
 211         public boolean isDirectory() {
 212             return file.isDirectory();
 213         }
 214 
 215         /** Return true is file identifies a file. */
 216         public boolean isFile() {
 217             return file.isFile();
 218         }
 219 
 220         /** Return true if this file is the same as another. */
 221         public boolean isSameFile(DocFile other) {
 222             if (!(other instanceof StandardDocFile))
 223                 return false;
 224 
 225             try {
 226                 return file.exists()
 227                         && file.getCanonicalFile().equals(((StandardDocFile) other).file.getCanonicalFile());
 228             } catch (IOException e) {
 229                 return false;
 230             }
 231         }
 232 
 233         /** If the file is a directory, list its contents. */
 234         public Iterable<DocFile> list() {
 235             List<DocFile> files = new ArrayList<>();
 236             for (File f: file.listFiles()) {
 237                 files.add(new StandardDocFile(f));
 238             }
 239             return files;
 240         }
 241 
 242         /** Create the file as a directory, including any parent directories. */
 243         public boolean mkdirs() {
 244             return file.mkdirs();
 245         }
 246 
 247         /**
 248          * Derive a new file by resolving a relative path against this file.
 249          * The new file will inherit the configuration and location of this file
 250          * If this file has a path set, the new file will have a corresponding
 251          * new path.
 252          */
 253         public DocFile resolve(DocPath p) {
 254             return resolve(p.getPath());
 255         }
 256 
 257         /**
 258          * Derive a new file by resolving a relative path against this file.
 259          * The new file will inherit the configuration and location of this file
 260          * If this file has a path set, the new file will have a corresponding
 261          * new path.
 262          */
 263         public DocFile resolve(String p) {
 264             if (location == null && path == null) {
 265                 return new StandardDocFile(new File(file, p));
 266             } else {
 267                 return new StandardDocFile(location, path.resolve(p));
 268             }
 269         }
 270 
 271         /**
 272          * Resolve a relative file against the given output location.
 273          * @param locn Currently, only
 274          * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} is supported.
 275          */
 276         public DocFile resolveAgainst(Location locn) {
 277             if (locn != DocumentationTool.Location.DOCUMENTATION_OUTPUT)
 278                 throw new IllegalArgumentException();
 279             return new StandardDocFile(newFile(getDestDir(), file.getPath()));
 280         }
 281 
 282         /** Return a string to identify the contents of this object,
 283          * for debugging purposes.
 284          */
 285         @Override
 286         public String toString() {
 287             StringBuilder sb = new StringBuilder();
 288             sb.append("StandardDocFile[");
 289             if (location != null)
 290                 sb.append("locn:").append(location).append(",");
 291             if (path != null)
 292                 sb.append("path:").append(path.getPath()).append(",");
 293             sb.append("file:").append(file);
 294             sb.append("]");
 295             return sb.toString();
 296         }
 297 
 298         private JavaFileObject getJavaFileObjectForInput(File file) {
 299             return fileManager.getJavaFileObjects(file).iterator().next();
 300         }
 301 
 302         private FileObject getFileObjectForOutput(DocPath path) throws IOException {
 303             // break the path into a package-part and the rest, by finding
 304             // the position of the last '/' before an invalid character for a
 305             // package name, such as the "." before an extension or the "-"
 306             // in filenames like package-summary.html, doc-files or src-html.
 307             String p = path.getPath();
 308             int lastSep = -1;
 309             for (int i = 0; i < p.length(); i++) {
 310                 char ch = p.charAt(i);
 311                 if (ch == '/') {
 312                     lastSep = i;
 313                 } else if (i == lastSep + 1 && !Character.isJavaIdentifierStart(ch)
 314                         || !Character.isJavaIdentifierPart(ch)) {
 315                     break;
 316                 }
 317             }
 318             String pkg = (lastSep == -1) ? "" : p.substring(0, lastSep);
 319             String rest = p.substring(lastSep + 1);
 320             return fileManager.getFileForOutput(location, pkg, rest, null);
 321         }
 322     }
 323 }