001/* 002 * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package org.jdrupes.mdoclet.internal.doclets.toolkit.util; 027 028import java.io.BufferedReader; 029import java.io.IOException; 030import java.io.InputStream; 031import java.io.InputStreamReader; 032import java.io.OutputStream; 033import java.io.UnsupportedEncodingException; 034import java.io.Writer; 035import java.nio.file.Path; 036import java.util.MissingResourceException; 037import java.util.regex.Matcher; 038import java.util.regex.Pattern; 039 040import javax.tools.DocumentationTool; 041import javax.tools.FileObject; 042import javax.tools.JavaFileManager.Location; 043 044import org.jdrupes.mdoclet.internal.doclets.toolkit.BaseConfiguration; 045import org.jdrupes.mdoclet.internal.doclets.toolkit.Resources; 046 047import javax.tools.StandardLocation; 048 049/** 050 * Abstraction for handling files, which may be specified directly 051 * (e.g. via a path on the command line) or relative to a Location. 052 */ 053public abstract class DocFile { 054 055 /** 056 * The line separator for the current platform. 057 * Use this when writing to external files. 058 */ 059 public static final String PLATFORM_LINE_SEPARATOR 060 = System.getProperty("line.separator"); 061 062 /** Create a DocFile for a directory. */ 063 public static DocFile createFileForDirectory( 064 BaseConfiguration configuration, String file) { 065 return DocFileFactory.getFactory(configuration) 066 .createFileForDirectory(file); 067 } 068 069 /** Create a DocFile for a file that will be opened for reading. */ 070 public static DocFile createFileForInput(BaseConfiguration configuration, 071 String file) { 072 return DocFileFactory.getFactory(configuration) 073 .createFileForInput(file); 074 } 075 076 /** Create a DocFile for a file that will be opened for reading. */ 077 public static DocFile createFileForInput(BaseConfiguration configuration, 078 Path file) { 079 return DocFileFactory.getFactory(configuration) 080 .createFileForInput(file); 081 } 082 083 /** Create a DocFile for a file that will be opened for writing. */ 084 public static DocFile createFileForOutput(BaseConfiguration configuration, 085 DocPath path) { 086 return DocFileFactory.getFactory(configuration) 087 .createFileForOutput(path); 088 } 089 090 /** 091 * The location for this file. Maybe null if the file was created without 092 * a location or path. 093 */ 094 protected final Location location; 095 096 /** 097 * The path relative to the (output) location. Maybe null if the file was 098 * created without a location or path. 099 */ 100 protected final DocPath path; 101 102 /** 103 * List the directories and files found in subdirectories along the 104 * elements of the given location. 105 * @param configuration the doclet configuration 106 * @param location currently, only {@link StandardLocation#SOURCE_PATH} is supported. 107 * @param path the subdirectory of the directories of the location for which to 108 * list files 109 */ 110 public static Iterable<DocFile> list(BaseConfiguration configuration, 111 Location location, DocPath path) { 112 return DocFileFactory.getFactory(configuration).list(location, path); 113 } 114 115 /** Create a DocFile without a location or path */ 116 protected DocFile() { 117 this.location = null; 118 this.path = null; 119 } 120 121 /** Create a DocFile for a given location and relative path. */ 122 protected DocFile(Location location, DocPath path) { 123 this.location = location; 124 this.path = path; 125 } 126 127 /** 128 * Returns a file object for the file. 129 * @return a file object 130 */ 131 public abstract FileObject getFileObject(); 132 133 /** 134 * Open an input stream for the file. 135 * 136 * @return an open input stream for the file 137 * @throws DocFileIOException if there is a problem opening the stream 138 */ 139 public abstract InputStream openInputStream() throws DocFileIOException; 140 141 /** 142 * Open an output stream for the file. 143 * The file must have been created with a location of 144 * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} 145 * and a corresponding relative path. 146 * 147 * @return an open output stream for the file 148 * @throws DocFileIOException if there is a problem opening the stream 149 * @throws UnsupportedEncodingException if the configured encoding is not supported 150 */ 151 public abstract OutputStream openOutputStream() 152 throws DocFileIOException, UnsupportedEncodingException; 153 154 /** 155 * Open an writer for the file, using the encoding (if any) given in the 156 * doclet configuration. 157 * The file must have been created with a location of 158 * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} and a corresponding relative path. 159 * 160 * @return an open output stream for the file 161 * @throws DocFileIOException if there is a problem opening the stream 162 * @throws UnsupportedEncodingException if the configured encoding is not supported 163 */ 164 public abstract Writer openWriter() 165 throws DocFileIOException, UnsupportedEncodingException; 166 167 /** 168 * Copy the contents of another file directly to this file. 169 * 170 * @param fromFile the file to be copied 171 * @throws DocFileIOException if there is a problem file copying the file 172 */ 173 public void copyFile(DocFile fromFile) throws DocFileIOException { 174 try (OutputStream output = openOutputStream()) { 175 try (InputStream input = fromFile.openInputStream()) { 176 byte[] bytearr = new byte[1024]; 177 int len; 178 while ((len = read(fromFile, input, bytearr)) != -1) { 179 write(this, output, bytearr, len); 180 } 181 } catch (IOException e) { 182 throw new DocFileIOException(fromFile, 183 DocFileIOException.Mode.READ, e); 184 } 185 } catch (IOException e) { 186 throw new DocFileIOException(this, DocFileIOException.Mode.WRITE, 187 e); 188 } 189 } 190 191 /** 192 * Copy the contents of a resource file to this file. 193 * 194 * @param resource the path of the resource, relative to the package of this class 195 * @param overwrite whether or not to overwrite the file if it already exists 196 * @param replaceNewLine if false, the file is copied as a binary file; 197 * if true, the file is written line by line, using the platform line 198 * separator 199 * 200 * @throws DocFileIOException if there is a problem while writing the copy 201 * @throws ResourceIOException if there is a problem while reading the resource 202 */ 203 public void copyResource(DocPath resource, boolean overwrite, 204 boolean replaceNewLine) 205 throws DocFileIOException, ResourceIOException { 206 if (exists() && !overwrite) 207 return; 208 209 copyResource(resource, replaceNewLine, null); 210 } 211 212 /** 213 * Copy the contents of a resource file to this file. 214 * 215 * @param resource the path of the resource, relative to the package of this class 216 * @param resources if not {@code null}, substitute occurrences of {@code ##REPLACE:key##} 217 * 218 * @throws DocFileIOException if there is a problem while writing the copy 219 * @throws ResourceIOException if there is a problem while reading the resource 220 */ 221 public void copyResource(DocPath resource, Resources resources) 222 throws DocFileIOException, ResourceIOException { 223 copyResource(resource, true, resources); 224 } 225 226 private void copyResource(DocPath resource, boolean replaceNewLine, 227 Resources resources) 228 throws DocFileIOException, ResourceIOException { 229 try { 230 InputStream in = BaseConfiguration.class 231 .getResourceAsStream(resource.getPath()); 232 if (in == null) 233 return; 234 235 try { 236 if (replaceNewLine) { 237 try (BufferedReader reader 238 = new BufferedReader(new InputStreamReader(in))) { 239 try (Writer writer = openWriter()) { 240 String line; 241 while ((line 242 = readResourceLine(resource, reader)) != null) { 243 write(this, writer, resources == null ? line 244 : localize(line, resources)); 245 write(this, writer, PLATFORM_LINE_SEPARATOR); 246 } 247 } catch (IOException e) { 248 throw new DocFileIOException(this, 249 DocFileIOException.Mode.WRITE, e); 250 } 251 } 252 } else { 253 try (OutputStream out = openOutputStream()) { 254 byte[] buf = new byte[2048]; 255 int n; 256 while ((n = readResource(resource, in, buf)) > 0) { 257 write(this, out, buf, n); 258 } 259 } catch (IOException e) { 260 throw new DocFileIOException(this, 261 DocFileIOException.Mode.WRITE, e); 262 } 263 } 264 } finally { 265 in.close(); 266 } 267 } catch (IOException e) { 268 throw new ResourceIOException(resource, e); 269 } 270 } 271 272 private static final Pattern replacePtn 273 = Pattern.compile("##REPLACE:(?<key>[A-Za-z0-9._]+)##"); 274 275 private String localize(String line, Resources resources) { 276 Matcher m = replacePtn.matcher(line); 277 StringBuilder sb = null; 278 int start = 0; 279 while (m.find()) { 280 if (sb == null) { 281 sb = new StringBuilder(); 282 } 283 sb.append(line, start, m.start()); 284 try { 285 sb.append(resources.getText(m.group("key"))); 286 } catch (MissingResourceException e) { 287 sb.append(m.group()); 288 } 289 start = m.end(); 290 } 291 if (sb == null) { 292 return line; 293 } else { 294 sb.append(line.substring(start)); 295 return sb.toString(); 296 } 297 } 298 299 /** Return true if the file can be read. */ 300 public abstract boolean canRead(); 301 302 /** Return true if the file can be written. */ 303 public abstract boolean canWrite(); 304 305 /** Return true if the file exists. */ 306 public abstract boolean exists(); 307 308 /** Return the base name (last component) of the file name. */ 309 public abstract String getName(); 310 311 /** Return the file system path for this file. */ 312 public abstract String getPath(); 313 314 /** Return true if file has an absolute path name. */ 315 public abstract boolean isAbsolute(); 316 317 /** Return true if file identifies a directory. */ 318 public abstract boolean isDirectory(); 319 320 /** Return true if file identifies a file. */ 321 public abstract boolean isFile(); 322 323 /** Return true if this file is the same as another. */ 324 public abstract boolean isSameFile(DocFile other); 325 326 /** If the file is a directory, list its contents. 327 * 328 * @return the contents of the directory 329 * @throws DocFileIOException if there is a problem while listing the directory 330 */ 331 public abstract Iterable<DocFile> list() throws DocFileIOException; 332 333 /** Create the file as a directory, including any parent directories. */ 334 public abstract boolean mkdirs(); 335 336 /** 337 * Derive a new file by resolving a relative path against this file. 338 * The new file will inherit the configuration and location of this file 339 * If this file has a path set, the new file will have a corresponding 340 * new path. 341 */ 342 public abstract DocFile resolve(DocPath p); 343 344 /** 345 * Derive a new file by resolving a relative path against this file. 346 * The new file will inherit the configuration and location of this file 347 * If this file has a path set, the new file will have a corresponding 348 * new path. 349 */ 350 public abstract DocFile resolve(String p); 351 352 /** 353 * Resolve a relative file against the given output location. 354 * @param locn Currently, only 355 * {@link DocumentationTool.Location#DOCUMENTATION_OUTPUT} is supported. 356 */ 357 public abstract DocFile resolveAgainst(Location locn); 358 359 /** 360 * Reads from an input stream opened from a given file into a given buffer. 361 * If an {@code IOException} occurs, it is wrapped in a {@code DocFileIOException}. 362 * 363 * @param inFile the file for the stream 364 * @param input the stream 365 * @param buf the buffer 366 * 367 * @return the number of bytes read, or -1 if at end of file 368 * @throws DocFileIOException if an exception occurred while reading the stream 369 */ 370 private static int read(DocFile inFile, InputStream input, byte[] buf) 371 throws DocFileIOException { 372 try { 373 return input.read(buf); 374 } catch (IOException e) { 375 throw new DocFileIOException(inFile, DocFileIOException.Mode.READ, 376 e); 377 } 378 } 379 380 /** 381 * Writes to an output stream for a given file from a given buffer. 382 * If an {@code IOException} occurs, it is wrapped in a {@code DocFileIOException}. 383 * 384 * @param outFile the file for the stream 385 * @param out the stream 386 * @param buf the buffer 387 * 388 * @throws DocFileIOException if an exception occurred while writing the stream 389 */ 390 private static void write(DocFile outFile, OutputStream out, byte[] buf, 391 int len) throws DocFileIOException { 392 try { 393 out.write(buf, 0, len); 394 } catch (IOException e) { 395 throw new DocFileIOException(outFile, DocFileIOException.Mode.WRITE, 396 e); 397 } 398 } 399 400 /** 401 * Writes text to an output stream for a given file from a given buffer. 402 * If an {@code IOException} occurs, it is wrapped in a {@code DocFileIOException}. 403 * 404 * @param outFile the file for the stream 405 * @param out the stream 406 * @param text the text to be written 407 * 408 * @throws DocFileIOException if an exception occurred while writing the stream 409 */ 410 private static void write(DocFile outFile, Writer out, String text) 411 throws DocFileIOException { 412 try { 413 out.write(text); 414 } catch (IOException e) { 415 throw new DocFileIOException(outFile, DocFileIOException.Mode.WRITE, 416 e); 417 } 418 } 419 420 /** 421 * Reads from an input stream opened from a given resource into a given buffer. 422 * If an {@code IOException} occurs, it is wrapped in a {@code ResourceIOException}. 423 * 424 * @param docPath the resource for the stream 425 * @param in the stream 426 * @param buf the buffer 427 * 428 * @return the number of bytes read, or -1 if at end of file 429 * @throws ResourceIOException if an exception occurred while reading the stream 430 */ 431 private static int readResource(DocPath docPath, InputStream in, byte[] buf) 432 throws ResourceIOException { 433 try { 434 return in.read(buf); 435 } catch (IOException e) { 436 throw new ResourceIOException(docPath, e); 437 } 438 } 439 440 /** 441 * Reads a line of characters from an input stream opened from a given resource. 442 * If an {@code IOException} occurs, it is wrapped in a {@code ResourceIOException}. 443 * 444 * @param docPath the resource for the stream 445 * @param in the stream 446 * 447 * @return the line of text, or {@code null} if at end of stream 448 * @throws ResourceIOException if an exception occurred while reading the stream 449 */ 450 private static String readResourceLine(DocPath docPath, BufferedReader in) 451 throws ResourceIOException { 452 try { 453 return in.readLine(); 454 } catch (IOException e) { 455 throw new ResourceIOException(docPath, e); 456 } 457 } 458}