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 26package jdk.javadoc.internal.doclets.toolkit.util; 27 28import java.io.BufferedInputStream; 29import java.io.BufferedOutputStream; 30import java.io.BufferedWriter; 31import java.io.IOException; 32import java.io.InputStream; 33import java.io.OutputStream; 34import java.io.OutputStreamWriter; 35import java.io.UnsupportedEncodingException; 36import java.io.Writer; 37import java.nio.file.DirectoryStream; 38import java.nio.file.Files; 39import java.nio.file.Path; 40import java.nio.file.Paths; 41import java.util.ArrayList; 42import java.util.Arrays; 43import java.util.LinkedHashSet; 44import java.util.List; 45import java.util.Objects; 46import java.util.Set; 47 48import javax.tools.DocumentationTool; 49import javax.tools.FileObject; 50import javax.tools.JavaFileManager.Location; 51import javax.tools.JavaFileObject; 52import javax.tools.StandardJavaFileManager; 53import javax.tools.StandardLocation; 54 55import com.sun.tools.javac.util.Assert; 56import 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 */ 67class 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 /** 155 * Open an input stream for the file. 156 * 157 * @throws DocFileIOException if there is a problem while opening stream 158 */ 159 @Override 160 public InputStream openInputStream() throws DocFileIOException { 161 try { 162 JavaFileObject fo = getJavaFileObjectForInput(file); 163 return new BufferedInputStream(fo.openInputStream()); 164 } catch (IOException e) { 165 throw new DocFileIOException(this, DocFileIOException.Mode.READ, e); 166 } 167 } 168 169 /** 170 * Open an output stream for the file. 171 * The file must have been created with a location of 172 * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} and a corresponding relative path. 173 * 174 * @throws DocFileIOException if there is a problem while opening stream 175 */ 176 @Override 177 public OutputStream openOutputStream() throws DocFileIOException { 178 if (location != DocumentationTool.Location.DOCUMENTATION_OUTPUT) 179 throw new IllegalStateException(); 180 181 try { 182 OutputStream out = getFileObjectForOutput(path).openOutputStream(); 183 return new BufferedOutputStream(out); 184 } catch (IOException e) { 185 throw new DocFileIOException(this, DocFileIOException.Mode.WRITE, e); 186 } 187 } 188 189 /** 190 * Open an writer for the file, using the encoding (if any) given in the 191 * doclet configuration. 192 * The file must have been created with a location of 193 * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} and a corresponding relative path. 194 * 195 * @throws DocFileIOException if there is a problem while opening stream 196 * @throws UnsupportedEncodingException if the configured encoding is not supported 197 */ 198 @Override 199 public Writer openWriter() throws DocFileIOException, UnsupportedEncodingException { 200 if (location != DocumentationTool.Location.DOCUMENTATION_OUTPUT) 201 throw new IllegalStateException(); 202 203 try { 204 OutputStream out = getFileObjectForOutput(path).openOutputStream(); 205 return new BufferedWriter(new OutputStreamWriter(out, configuration.docencoding)); 206 } catch (IOException e) { 207 throw new DocFileIOException(this, DocFileIOException.Mode.WRITE, e); 208 } 209 } 210 211 /** Return true if the file can be read. */ 212 @Override 213 public boolean canRead() { 214 return Files.isReadable(file); 215 } 216 217 /** Return true if the file can be written. */ 218 @Override 219 public boolean canWrite() { 220 return Files.isWritable(file); 221 } 222 223 /** Return true if the file exists. */ 224 @Override 225 public boolean exists() { 226 return Files.exists(file); 227 } 228 229 /** Return the base name (last component) of the file name. */ 230 @Override 231 public String getName() { 232 return file.getFileName().toString(); 233 } 234 235 /** Return the file system path for this file. */ 236 @Override 237 public String getPath() { 238 return file.toString(); 239 } 240 241 /** Return true is file has an absolute path name. */ 242 @Override 243 public boolean isAbsolute() { 244 return file.isAbsolute(); 245 } 246 247 /** Return true is file identifies a directory. */ 248 @Override 249 public boolean isDirectory() { 250 return Files.isDirectory(file); 251 } 252 253 /** Return true is file identifies a file. */ 254 @Override 255 public boolean isFile() { 256 return Files.isRegularFile(file); 257 } 258 259 /** Return true if this file is the same as another. */ 260 @Override 261 public boolean isSameFile(DocFile other) { 262 if (!(other instanceof StandardDocFile)) 263 return false; 264 265 try { 266 return Files.isSameFile(file, ((StandardDocFile) other).file); 267 } catch (IOException e) { 268 return false; 269 } 270 } 271 272 /** If the file is a directory, list its contents. */ 273 @Override 274 public Iterable<DocFile> list() throws DocFileIOException { 275 List<DocFile> files = new ArrayList<>(); 276 try (DirectoryStream<Path> ds = Files.newDirectoryStream(file)) { 277 for (Path f: ds) { 278 files.add(new StandardDocFile(f)); 279 } 280 } catch (IOException e) { 281 throw new DocFileIOException(this, DocFileIOException.Mode.READ, e); 282 } 283 return files; 284 } 285 286 /** Create the file as a directory, including any parent directories. */ 287 @Override 288 public boolean mkdirs() { 289 try { 290 Files.createDirectories(file); 291 return true; 292 } catch (IOException e) { 293 return false; 294 } 295 } 296 297 /** 298 * Derive a new file by resolving a relative path against this file. 299 * The new file will inherit the configuration and location of this file 300 * If this file has a path set, the new file will have a corresponding 301 * new path. 302 */ 303 @Override 304 public DocFile resolve(DocPath p) { 305 return resolve(p.getPath()); 306 } 307 308 /** 309 * Derive a new file by resolving a relative path against this file. 310 * The new file will inherit the configuration and location of this file 311 * If this file has a path set, the new file will have a corresponding 312 * new path. 313 */ 314 @Override 315 public DocFile resolve(String p) { 316 if (location == null && path == null) { 317 return new StandardDocFile(file.resolve(p)); 318 } else { 319 return new StandardDocFile(location, path.resolve(p)); 320 } 321 } 322 323 /** 324 * Resolve a relative file against the given output location. 325 * @param locn Currently, only 326 * {@link DocumentationTool.Location.DOCUMENTATION_OUTPUT} is supported. 327 */ 328 @Override 329 public DocFile resolveAgainst(Location locn) { 330 if (locn != DocumentationTool.Location.DOCUMENTATION_OUTPUT) 331 throw new IllegalArgumentException(); 332 return new StandardDocFile(getDestDir().resolve(file)); 333 } 334 335 /** Return a string to identify the contents of this object, 336 * for debugging purposes. 337 */ 338 @Override 339 public String toString() { 340 StringBuilder sb = new StringBuilder(); 341 sb.append("StandardDocFile["); 342 if (location != null) 343 sb.append("locn:").append(location).append(","); 344 if (path != null) 345 sb.append("path:").append(path.getPath()).append(","); 346 sb.append("file:").append(file); 347 sb.append("]"); 348 return sb.toString(); 349 } 350 351 private JavaFileObject getJavaFileObjectForInput(Path file) { 352 return fileManager.getJavaFileObjects(file).iterator().next(); 353 } 354 355 private FileObject getFileObjectForOutput(DocPath path) throws IOException { 356 // break the path into a package-part and the rest, by finding 357 // the position of the last '/' before an invalid character for a 358 // package name, such as the "." before an extension or the "-" 359 // in filenames like package-summary.html, doc-files or src-html. 360 String p = path.getPath(); 361 int lastSep = -1; 362 for (int i = 0; i < p.length(); i++) { 363 char ch = p.charAt(i); 364 if (ch == '/') { 365 lastSep = i; 366 } else if (i == lastSep + 1 && !Character.isJavaIdentifierStart(ch) 367 || !Character.isJavaIdentifierPart(ch)) { 368 break; 369 } 370 } 371 String pkg = (lastSep == -1) ? "" : p.substring(0, lastSep); 372 String rest = p.substring(lastSep + 1); 373 return fileManager.getFileForOutput(location, pkg, rest, null); 374 } 375 } 376} 377