1 /* 2 * Hunt - A refined core library for D programming language. 3 * 4 * Copyright (C) 2018-2019 HuntLabs 5 * 6 * Website: https://www.huntlabs.net/ 7 * 8 * Licensed under the Apache-2.0 License. 9 * 10 */ 11 12 module hunt.io.FileOutputStream; 13 14 import hunt.Exceptions; 15 import hunt.io.Common; 16 17 import std.array; 18 import std.stdio; 19 20 21 /** 22 * A file output stream is an output stream for writing data to a 23 * <code>File</code> or to a <code>FileDescriptor</code>. Whether or not 24 * a file is available or may be created depends upon the underlying 25 * platform. Some platforms, in particular, allow a file to be opened 26 * for writing by only one {@code FileOutputStream} (or other 27 * file-writing object) at a time. In such situations the constructors in 28 * this class will fail if the file involved is already open. 29 * 30 * <p><code>FileOutputStream</code> is meant for writing streams of raw bytes 31 * such as image data. For writing streams of characters, consider using 32 * <code>FileWriter</code>. 33 * 34 * @apiNote 35 * To release resources used by this stream {@link #close} should be called 36 * directly or by try-with-resources. Subclasses are responsible for the cleanup 37 * of resources acquired by the subclass. 38 * Subclasses that override {@link #finalize} in order to perform cleanup 39 * should be modified to use alternative cleanup mechanisms such as 40 * {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method. 41 * 42 * @implSpec 43 * If this FileOutputStream has been subclassed and the {@link #close} 44 * method has been overridden, the {@link #close} method will be 45 * called when the FileInputStream is unreachable. 46 * Otherwise, it is implementation specific how the resource cleanup described in 47 * {@link #close} is performed. 48 * 49 * @author Arthur van Hoff 50 * @see java.io.File 51 * @see java.io.FileDescriptor 52 * @see java.io.FileInputStream 53 * @see java.nio.file.Files#newOutputStream 54 * @since 1.0 55 */ 56 class FileOutputStream : OutputStream 57 { 58 /** 59 * Access to FileDescriptor internals. 60 */ 61 // private static final JavaIOFileDescriptorAccess fdAccess = 62 // SharedSecrets.getJavaIOFileDescriptorAccess(); 63 64 /** 65 * The system dependent file descriptor. 66 */ 67 // private FileDescriptor fd; 68 69 /** 70 * The associated channel, initialized lazily. 71 */ 72 // private FileChannel channel; 73 private File file; 74 75 /** 76 * The path of the referenced file 77 * (null if the stream is created with a file descriptor) 78 */ 79 // private string path; 80 81 private Object closeLock; 82 83 private bool closed; 84 85 // private Object altFinalizer; 86 87 /** 88 * Creates a file output stream to write to the file with the 89 * specified name. A new <code>FileDescriptor</code> object is 90 * created to represent this file connection. 91 * <p> 92 * First, if there is a security manager, its <code>checkWrite</code> 93 * method is called with <code>name</code> as its argument. 94 * <p> 95 * If the file exists but is a directory rather than a regular file, does 96 * not exist but cannot be created, or cannot be opened for any other 97 * reason then a <code>FileNotFoundException</code> is thrown. 98 * 99 * @implSpec Invoking this constructor with the parameter {@code name} is 100 * equivalent to invoking {@link #FileOutputStream(string,bool) 101 * new FileOutputStream(name, false)}. 102 * 103 * @param name the system-dependent filename 104 * @exception FileNotFoundException if the file exists but is a directory 105 * rather than a regular file, does not exist but cannot 106 * be created, or cannot be opened for any other reason 107 * @exception SecurityException if a security manager exists and its 108 * <code>checkWrite</code> method denies write access 109 * to the file. 110 * @see java.lang.SecurityManager#checkWrite(java.lang.string) 111 */ 112 this(string name) { 113 this(name, false); 114 } 115 116 /** 117 * Creates a file output stream to write to the file with the specified 118 * name. If the second argument is <code>true</code>, then 119 * bytes will be written to the end of the file rather than the beginning. 120 * A new <code>FileDescriptor</code> object is created to represent this 121 * file connection. 122 * <p> 123 * First, if there is a security manager, its <code>checkWrite</code> 124 * method is called with <code>name</code> as its argument. 125 * <p> 126 * If the file exists but is a directory rather than a regular file, does 127 * not exist but cannot be created, or cannot be opened for any other 128 * reason then a <code>FileNotFoundException</code> is thrown. 129 * 130 * @param name the system-dependent file name 131 * @param append if <code>true</code>, then bytes will be written 132 * to the end of the file rather than the beginning 133 * @exception FileNotFoundException if the file exists but is a directory 134 * rather than a regular file, does not exist but cannot 135 * be created, or cannot be opened for any other reason. 136 * @exception SecurityException if a security manager exists and its 137 * <code>checkWrite</code> method denies write access 138 * to the file. 139 * @see java.lang.SecurityManager#checkWrite(java.lang.string) 140 * @since 1.1 141 */ 142 this(string name, bool append) { 143 if(name.empty) 144 throw new NullPointerException(); 145 if(append) 146 this(File(name, "a")); 147 else 148 this(File(name, "w")); 149 } 150 151 /** 152 * Creates a file output stream to write to the file represented by 153 * the specified <code>File</code> object. A new 154 * <code>FileDescriptor</code> object is created to represent this 155 * file connection. 156 * <p> 157 * First, if there is a security manager, its <code>checkWrite</code> 158 * method is called with the path represented by the <code>file</code> 159 * argument as its argument. 160 * <p> 161 * If the file exists but is a directory rather than a regular file, does 162 * not exist but cannot be created, or cannot be opened for any other 163 * reason then a <code>FileNotFoundException</code> is thrown. 164 * 165 * @param file the file to be opened for writing. 166 * @exception FileNotFoundException if the file exists but is a directory 167 * rather than a regular file, does not exist but cannot 168 * be created, or cannot be opened for any other reason 169 * @exception SecurityException if a security manager exists and its 170 * <code>checkWrite</code> method denies write access 171 * to the file. 172 * @see java.io.File#getPath() 173 * @see java.lang.SecurityException 174 * @see java.lang.SecurityManager#checkWrite(java.lang.string) 175 */ 176 this(File file) { 177 this.file = file; 178 initialize(); 179 } 180 181 private void initialize() { 182 closeLock = new Object(); 183 } 184 185 /** 186 * Creates a file output stream to write to the file represented by 187 * the specified <code>File</code> object. If the second argument is 188 * <code>true</code>, then bytes will be written to the end of the file 189 * rather than the beginning. A new <code>FileDescriptor</code> object is 190 * created to represent this file connection. 191 * <p> 192 * First, if there is a security manager, its <code>checkWrite</code> 193 * method is called with the path represented by the <code>file</code> 194 * argument as its argument. 195 * <p> 196 * If the file exists but is a directory rather than a regular file, does 197 * not exist but cannot be created, or cannot be opened for any other 198 * reason then a <code>FileNotFoundException</code> is thrown. 199 * 200 * @param file the file to be opened for writing. 201 * @param append if <code>true</code>, then bytes will be written 202 * to the end of the file rather than the beginning 203 * @exception FileNotFoundException if the file exists but is a directory 204 * rather than a regular file, does not exist but cannot 205 * be created, or cannot be opened for any other reason 206 * @exception SecurityException if a security manager exists and its 207 * <code>checkWrite</code> method denies write access 208 * to the file. 209 * @see java.io.File#getPath() 210 * @see java.lang.SecurityException 211 * @see java.lang.SecurityManager#checkWrite(java.lang.string) 212 * @since 1.4 213 */ 214 // this(File file, bool append) { 215 // string name = (file != null ? file.getPath() : null); 216 // SecurityManager security = System.getSecurityManager(); 217 // if (security != null) { 218 // security.checkWrite(name); 219 // } 220 // if (name == null) { 221 // throw new NullPointerException(); 222 // } 223 // if (file.isInvalid()) { 224 // throw new FileNotFoundException("Invalid file path"); 225 // } 226 // this.fd = new FileDescriptor(); 227 // fd.attach(this); 228 // this.path = name; 229 230 // open(name, append); 231 // altFinalizer = getFinalizer(this); 232 // if (altFinalizer == null) { 233 // FileCleanable.register(fd); // open sets the fd, register the cleanup 234 // } 235 // } 236 237 /** 238 * Creates a file output stream to write to the specified file 239 * descriptor, which represents an existing connection to an actual 240 * file in the file system. 241 * <p> 242 * First, if there is a security manager, its <code>checkWrite</code> 243 * method is called with the file descriptor <code>fdObj</code> 244 * argument as its argument. 245 * <p> 246 * If <code>fdObj</code> is null then a <code>NullPointerException</code> 247 * is thrown. 248 * <p> 249 * This constructor does not throw an exception if <code>fdObj</code> 250 * is {@link java.io.FileDescriptor#valid() invalid}. 251 * However, if the methods are invoked on the resulting stream to attempt 252 * I/O on the stream, an <code>IOException</code> is thrown. 253 * 254 * @param fdObj the file descriptor to be opened for writing 255 * @exception SecurityException if a security manager exists and its 256 * <code>checkWrite</code> method denies 257 * write access to the file descriptor 258 * @see java.lang.SecurityManager#checkWrite(java.io.FileDescriptor) 259 */ 260 // this(FileDescriptor fdObj) { 261 // SecurityManager security = System.getSecurityManager(); 262 // if (fdObj == null) { 263 // throw new NullPointerException(); 264 // } 265 // if (security != null) { 266 // security.checkWrite(fdObj); 267 // } 268 // this.fd = fdObj; 269 // this.path = null; 270 // this.altFinalizer = null; 271 272 // fd.attach(this); 273 // } 274 275 /** 276 * Opens a file, with the specified name, for overwriting or appending. 277 * @param name name of file to be opened 278 * @param append whether the file is to be opened in append mode 279 */ 280 // private void open0(string name, bool append); 281 282 // wrap call to allow instrumentation 283 /** 284 * Opens a file, with the specified name, for overwriting or appending. 285 * @param name name of file to be opened 286 * @param append whether the file is to be opened in append mode 287 */ 288 // private void open(string name, bool append) { 289 // open0(name, append); 290 // } 291 292 /** 293 * Writes the specified byte to this file output stream. 294 * 295 * @param b the byte to be written. 296 * @param append {@code true} if the write operation first 297 * advances the position to the end of file 298 */ 299 // private void write(int b, bool append); 300 301 /** 302 * Writes the specified byte to this file output stream. Implements 303 * the <code>write</code> method of <code>OutputStream</code>. 304 * 305 * @param b the byte to be written. 306 * @exception IOException if an I/O error occurs. 307 */ 308 override void write(int b) { 309 // write(b, fdAccess.getAppend(fd)); 310 file.rawWrite([cast(byte)b]); 311 } 312 313 /** 314 * Writes a sub array as a sequence of bytes. 315 * @param b the data to be written 316 * @param off the start offset in the data 317 * @param len the number of bytes that are written 318 * @param append {@code true} to first advance the position to the 319 * end of file 320 * @exception IOException If an I/O error has occurred. 321 */ 322 // private void writeBytes(byte b[], int off, int len, bool append); 323 324 /** 325 * Writes <code>b.length</code> bytes from the specified byte array 326 * to this file output stream. 327 * 328 * @param b the data. 329 * @exception IOException if an I/O error occurs. 330 */ 331 override void write(byte[] b) { 332 // writeBytes(b, 0, b.length, fdAccess.getAppend(fd)); 333 file.rawWrite(b); 334 } 335 336 /** 337 * Writes <code>len</code> bytes from the specified byte array 338 * starting at offset <code>off</code> to this file output stream. 339 * 340 * @param b the data. 341 * @param off the start offset in the data. 342 * @param len the number of bytes to write. 343 * @exception IOException if an I/O error occurs. 344 */ 345 override void write(byte[] b, int off, int len) { 346 // writeBytes(b, off, len, fdAccess.getAppend(fd)); 347 file.rawWrite(b[off .. off+len]); 348 } 349 350 /** 351 * Closes this file output stream and releases any system resources 352 * associated with this stream. This file output stream may no longer 353 * be used for writing bytes. 354 * 355 * <p> If this stream has an associated channel then the channel is closed 356 * as well. 357 * 358 * @apiNote 359 * Overriding {@link #close} to perform cleanup actions is reliable 360 * only when called directly or when called by try-with-resources. 361 * Do not depend on finalization to invoke {@code close}; 362 * finalization is not reliable and is deprecated. 363 * If cleanup of resources is needed, other mechanisms such as 364 * {@linkplain java.lang.ref.Cleaner} should be used. 365 * 366 * @exception IOException if an I/O error occurs. 367 * 368 * @revised 1.4 369 * @spec JSR-51 370 */ 371 override void close() { 372 if (closed) 373 return; 374 375 synchronized (closeLock) { 376 if (closed) return; 377 closed = true; 378 } 379 380 this.file.close(); 381 382 // FileChannel fc = channel; 383 // if (fc != null) { 384 // // possible race with getChannel(), benign since 385 // // FileChannel.close is final and idempotent 386 // fc.close(); 387 // } 388 389 // fd.closeAll(new Closeable() { 390 // void close() { 391 // fd.close(); 392 // } 393 // }); 394 } 395 396 /** 397 * Returns the file descriptor associated with this stream. 398 * 399 * @return the <code>FileDescriptor</code> object that represents 400 * the connection to the file in the file system being used 401 * by this <code>FileOutputStream</code> object. 402 * 403 * @exception IOException if an I/O error occurs. 404 * @see java.io.FileDescriptor 405 */ 406 // final FileDescriptor getFD() { 407 // if (fd != null) { 408 // return fd; 409 // } 410 // throw new IOException(); 411 // } 412 413 /** 414 * Returns the unique {@link java.nio.channels.FileChannel FileChannel} 415 * object associated with this file output stream. 416 * 417 * <p> The initial {@link java.nio.channels.FileChannel#position() 418 * position} of the returned channel will be equal to the 419 * number of bytes written to the file so far unless this stream is in 420 * append mode, in which case it will be equal to the size of the file. 421 * Writing bytes to this stream will increment the channel's position 422 * accordingly. Changing the channel's position, either explicitly or by 423 * writing, will change this stream's file position. 424 * 425 * @return the file channel associated with this file output stream 426 * 427 * @since 1.4 428 * @spec JSR-51 429 */ 430 // FileChannel getChannel() { 431 // FileChannel fc = this.channel; 432 // if (fc == null) { 433 // synchronized (this) { 434 // fc = this.channel; 435 // if (fc == null) { 436 // this.channel = fc = FileChannelImpl.open(fd, path, false, 437 // true, false, this); 438 // if (closed) { 439 // try { 440 // // possible race with close(), benign since 441 // // FileChannel.close is final and idempotent 442 // fc.close(); 443 // } catch (IOException ioe) { 444 // throw new InternalError(ioe); // should not happen 445 // } 446 // } 447 // } 448 // } 449 // } 450 // return fc; 451 // } 452 453 /** 454 * Cleans up the connection to the file, and ensures that the 455 * {@link #close} method of this file output stream is 456 * called when there are no more references to this stream. 457 * The {@link #finalize} method does not call {@link #close} directly. 458 * 459 * @apiNote 460 * To release resources used by this stream {@link #close} should be called 461 * directly or by try-with-resources. 462 * 463 * @implSpec 464 * If this FileOutputStream has been subclassed and the {@link #close} 465 * method has been overridden, the {@link #close} method will be 466 * called when the FileOutputStream is unreachable. 467 * Otherwise, it is implementation specific how the resource cleanup described in 468 * {@link #close} is performed. 469 * 470 * @deprecated The {@code finalize} method has been deprecated and will be removed. 471 * Subclasses that override {@code finalize} in order to perform cleanup 472 * should be modified to use alternative cleanup mechanisms and 473 * to remove the overriding {@code finalize} method. 474 * When overriding the {@code finalize} method, its implementation must explicitly 475 * ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}. 476 * See the specification for {@link Object#finalize()} for further 477 * information about migration options. 478 * 479 * @exception IOException if an I/O error occurs. 480 * @see java.io.FileInputStream#close() 481 */ 482 // @Deprecated(since="9", forRemoval = true) 483 // protected void finalize() { 484 // } 485 486 // private static void initIDs(); 487 488 // static { 489 // initIDs(); 490 // } 491 492 /* 493 * Returns a finalizer object if the FOS needs a finalizer; otherwise null. 494 * If the FOS has a close method; it needs an AltFinalizer. 495 */ 496 // private static Object getFinalizer(FileOutputStream fos) { 497 // Class<?> clazz = fos.getClass(); 498 // while (clazz != FileOutputStream.class) { 499 // try { 500 // clazz.getDeclaredMethod("close"); 501 // return new AltFinalizer(fos); 502 // } catch (NoSuchMethodException nsme) { 503 // // ignore 504 // } 505 // clazz = clazz.getSuperclass(); 506 // } 507 // return null; 508 // } 509 510 /** 511 * Class to call {@code FileOutputStream.close} when finalized. 512 * If finalization of the stream is needed, an instance is created 513 * in its constructor(s). When the set of instances 514 * related to the stream is unreachable, the AltFinalizer performs 515 * the needed call to the stream's {@code close} method. 516 */ 517 // static class AltFinalizer { 518 // private final FileOutputStream fos; 519 520 // this(FileOutputStream fos) { 521 // this.fos = fos; 522 // } 523 524 // override 525 // @SuppressWarnings("deprecation") 526 // protected final void finalize() { 527 // try { 528 // if (fos.fd != null) { 529 // if (fos.fd == FileDescriptor.out || fos.fd == FileDescriptor.err) { 530 // // Subclass may override flush; otherwise it is no-op 531 // fos.flush(); 532 // } else { 533 // /* if fd is shared, the references in FileDescriptor 534 // * will ensure that finalizer is only called when 535 // * safe to do so. All references using the fd have 536 // * become unreachable. We can call close() 537 // */ 538 // fos.close(); 539 // } 540 // } 541 // } catch (IOException ioe) { 542 // // ignore 543 // } 544 // } 545 // } 546 547 }