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 }