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.stream.FileOutputStream; 13 14 import hunt.Exceptions; 15 import hunt.logging; 16 import hunt.stream.Common; 17 18 import std.array; 19 import std.stdio; 20 21 22 /** 23 * A file output stream is an output stream for writing data to a 24 * <code>File</code> or to a <code>FileDescriptor</code>. Whether or not 25 * a file is available or may be created depends upon the underlying 26 * platform. Some platforms, in particular, allow a file to be opened 27 * for writing by only one {@code FileOutputStream} (or other 28 * file-writing object) at a time. In such situations the constructors in 29 * this class will fail if the file involved is already open. 30 * 31 * <p><code>FileOutputStream</code> is meant for writing streams of raw bytes 32 * such as image data. For writing streams of characters, consider using 33 * <code>FileWriter</code>. 34 * 35 * @apiNote 36 * To release resources used by this stream {@link #close} should be called 37 * directly or by try-with-resources. Subclasses are responsible for the cleanup 38 * of resources acquired by the subclass. 39 * Subclasses that override {@link #finalize} in order to perform cleanup 40 * should be modified to use alternative cleanup mechanisms such as 41 * {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method. 42 * 43 * @implSpec 44 * If this FileOutputStream has been subclassed and the {@link #close} 45 * method has been overridden, the {@link #close} method will be 46 * called when the FileInputStream is unreachable. 47 * Otherwise, it is implementation specific how the resource cleanup described in 48 * {@link #close} is performed. 49 * 50 * @author Arthur van Hoff 51 * @see java.io.File 52 * @see java.io.FileDescriptor 53 * @see java.io.FileInputStream 54 * @see java.nio.file.Files#newOutputStream 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 */ 141 this(string name, bool append) { 142 if(name.empty) 143 throw new NullPointerException(); 144 if(append) 145 this(File(name, "a")); 146 else 147 this(File(name, "w")); 148 } 149 150 /** 151 * Creates a file output stream to write to the file represented by 152 * the specified <code>File</code> object. A new 153 * <code>FileDescriptor</code> object is created to represent this 154 * file connection. 155 * <p> 156 * First, if there is a security manager, its <code>checkWrite</code> 157 * method is called with the path represented by the <code>file</code> 158 * argument as its argument. 159 * <p> 160 * If the file exists but is a directory rather than a regular file, does 161 * not exist but cannot be created, or cannot be opened for any other 162 * reason then a <code>FileNotFoundException</code> is thrown. 163 * 164 * @param file the file to be opened for writing. 165 * @exception FileNotFoundException if the file exists but is a directory 166 * rather than a regular file, does not exist but cannot 167 * be created, or cannot be opened for any other reason 168 * @exception SecurityException if a security manager exists and its 169 * <code>checkWrite</code> method denies write access 170 * to the file. 171 */ 172 this(File file) { 173 this.file = file; 174 175 version(HUNT_DEBUG_MORE) { 176 infof("Outputing to file: %s", file.name()); 177 } 178 179 initialize(); 180 } 181 182 private void initialize() { 183 closeLock = new Object(); 184 } 185 186 /** 187 * Opens a file, with the specified name, for overwriting or appending. 188 * @param name name of file to be opened 189 * @param append whether the file is to be opened in append mode 190 */ 191 // private void open0(string name, bool append); 192 193 // wrap call to allow instrumentation 194 /** 195 * Opens a file, with the specified name, for overwriting or appending. 196 * @param name name of file to be opened 197 * @param append whether the file is to be opened in append mode 198 */ 199 // private void open(string name, bool append) { 200 // open0(name, append); 201 // } 202 203 /** 204 * Writes the specified byte to this file output stream. 205 * 206 * @param b the byte to be written. 207 * @param append {@code true} if the write operation first 208 * advances the position to the end of file 209 */ 210 // private void write(int b, bool append); 211 212 /** 213 * Writes the specified byte to this file output stream. Implements 214 * the <code>write</code> method of <code>OutputStream</code>. 215 * 216 * @param b the byte to be written. 217 * @exception IOException if an I/O error occurs. 218 */ 219 override void write(int b) { 220 // write(b, fdAccess.getAppend(fd)); 221 file.rawWrite([cast(byte)b]); 222 } 223 224 /** 225 * Writes a sub array as a sequence of bytes. 226 * @param b the data to be written 227 * @param off the start offset in the data 228 * @param len the number of bytes that are written 229 * @param append {@code true} to first advance the position to the 230 * end of file 231 * @exception IOException If an I/O error has occurred. 232 */ 233 // private void writeBytes(byte b[], int off, int len, bool append); 234 235 /** 236 * Writes <code>b.length</code> bytes from the specified byte array 237 * to this file output stream. 238 * 239 * @param b the data. 240 * @exception IOException if an I/O error occurs. 241 */ 242 override void write(byte[] b) { 243 // writeBytes(b, 0, b.length, fdAccess.getAppend(fd)); 244 file.rawWrite(b); 245 } 246 247 /** 248 * Writes <code>len</code> bytes from the specified byte array 249 * starting at offset <code>off</code> to this file output stream. 250 * 251 * @param b the data. 252 * @param off the start offset in the data. 253 * @param len the number of bytes to write. 254 * @exception IOException if an I/O error occurs. 255 */ 256 override void write(byte[] b, int off, int len) { 257 // writeBytes(b, off, len, fdAccess.getAppend(fd)); 258 file.rawWrite(b[off .. off+len]); 259 } 260 261 /** 262 * Closes this file output stream and releases any system resources 263 * associated with this stream. This file output stream may no longer 264 * be used for writing bytes. 265 * 266 * <p> If this stream has an associated channel then the channel is closed 267 * as well. 268 * 269 * @apiNote 270 * Overriding {@link #close} to perform cleanup actions is reliable 271 * only when called directly or when called by try-with-resources. 272 * Do not depend on finalization to invoke {@code close}; 273 * finalization is not reliable and is deprecated. 274 * If cleanup of resources is needed, other mechanisms such as 275 * {@linkplain java.lang.ref.Cleaner} should be used. 276 * 277 * @exception IOException if an I/O error occurs. 278 * 279 * @revised 1.4 280 * @spec JSR-51 281 */ 282 override void close() { 283 if (closed) 284 return; 285 286 synchronized (closeLock) { 287 if (closed) return; 288 closed = true; 289 } 290 291 this.file.close(); 292 293 // FileChannel fc = channel; 294 // if (fc != null) { 295 // // possible race with getChannel(), benign since 296 // // FileChannel.close is final and idempotent 297 // fc.close(); 298 // } 299 300 // fd.closeAll(new Closeable() { 301 // void close() { 302 // fd.close(); 303 // } 304 // }); 305 } 306 307 /** 308 * Returns the file descriptor associated with this stream. 309 * 310 * @return the <code>FileDescriptor</code> object that represents 311 * the connection to the file in the file system being used 312 * by this <code>FileOutputStream</code> object. 313 * 314 * @exception IOException if an I/O error occurs. 315 * @see java.io.FileDescriptor 316 */ 317 // final FileDescriptor getFD() { 318 // if (fd != null) { 319 // return fd; 320 // } 321 // throw new IOException(); 322 // } 323 324 /** 325 * Returns the unique {@link java.nio.channels.FileChannel FileChannel} 326 * object associated with this file output stream. 327 * 328 * <p> The initial {@link java.nio.channels.FileChannel#position() 329 * position} of the returned channel will be equal to the 330 * number of bytes written to the file so far unless this stream is in 331 * append mode, in which case it will be equal to the size of the file. 332 * Writing bytes to this stream will increment the channel's position 333 * accordingly. Changing the channel's position, either explicitly or by 334 * writing, will change this stream's file position. 335 * 336 * @return the file channel associated with this file output stream 337 * 338 * @spec JSR-51 339 */ 340 // FileChannel getChannel() { 341 // FileChannel fc = this.channel; 342 // if (fc == null) { 343 // synchronized (this) { 344 // fc = this.channel; 345 // if (fc == null) { 346 // this.channel = fc = FileChannelImpl.open(fd, path, false, 347 // true, false, this); 348 // if (closed) { 349 // try { 350 // // possible race with close(), benign since 351 // // FileChannel.close is final and idempotent 352 // fc.close(); 353 // } catch (IOException ioe) { 354 // throw new InternalError(ioe); // should not happen 355 // } 356 // } 357 // } 358 // } 359 // } 360 // return fc; 361 // } 362 363 /** 364 * Cleans up the connection to the file, and ensures that the 365 * {@link #close} method of this file output stream is 366 * called when there are no more references to this stream. 367 * The {@link #finalize} method does not call {@link #close} directly. 368 * 369 * @apiNote 370 * To release resources used by this stream {@link #close} should be called 371 * directly or by try-with-resources. 372 * 373 * @implSpec 374 * If this FileOutputStream has been subclassed and the {@link #close} 375 * method has been overridden, the {@link #close} method will be 376 * called when the FileOutputStream is unreachable. 377 * Otherwise, it is implementation specific how the resource cleanup described in 378 * {@link #close} is performed. 379 * 380 * @deprecated The {@code finalize} method has been deprecated and will be removed. 381 * Subclasses that override {@code finalize} in order to perform cleanup 382 * should be modified to use alternative cleanup mechanisms and 383 * to remove the overriding {@code finalize} method. 384 * When overriding the {@code finalize} method, its implementation must explicitly 385 * ensure that {@code super.finalize()} is invoked as described in {@link Object#finalize}. 386 * See the specification for {@link Object#finalize()} for further 387 * information about migration options. 388 * 389 * @exception IOException if an I/O error occurs. 390 * @see java.io.FileInputStream#close() 391 */ 392 // @Deprecated(since="9", forRemoval = true) 393 // protected void finalize() { 394 // } 395 396 // private static void initIDs(); 397 398 // static { 399 // initIDs(); 400 // } 401 402 /* 403 * Returns a finalizer object if the FOS needs a finalizer; otherwise null. 404 * If the FOS has a close method; it needs an AltFinalizer. 405 */ 406 // private static Object getFinalizer(FileOutputStream fos) { 407 // Class<?> clazz = fos.getClass(); 408 // while (clazz != FileOutputStream.class) { 409 // try { 410 // clazz.getDeclaredMethod("close"); 411 // return new AltFinalizer(fos); 412 // } catch (NoSuchMethodException nsme) { 413 // // ignore 414 // } 415 // clazz = clazz.getSuperclass(); 416 // } 417 // return null; 418 // } 419 420 /** 421 * Class to call {@code FileOutputStream.close} when finalized. 422 * If finalization of the stream is needed, an instance is created 423 * in its constructor(s). When the set of instances 424 * related to the stream is unreachable, the AltFinalizer performs 425 * the needed call to the stream's {@code close} method. 426 */ 427 // static class AltFinalizer { 428 // private final FileOutputStream fos; 429 430 // this(FileOutputStream fos) { 431 // this.fos = fos; 432 // } 433 434 // override 435 // @SuppressWarnings("deprecation") 436 // protected final void finalize() { 437 // try { 438 // if (fos.fd != null) { 439 // if (fos.fd == FileDescriptor.out || fos.fd == FileDescriptor.err) { 440 // // Subclass may override flush; otherwise it is no-op 441 // fos.flush(); 442 // } else { 443 // /* if fd is shared, the references in FileDescriptor 444 // * will ensure that finalizer is only called when 445 // * safe to do so. All references using the fd have 446 // * become unreachable. We can call close() 447 // */ 448 // fos.close(); 449 // } 450 // } 451 // } catch (IOException ioe) { 452 // // ignore 453 // } 454 // } 455 // } 456 457 }