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 }