001package Torello.Java;
002
003import java.io.*;
004import java.util.*;
005import java.util.zip.*;
006import java.util.stream.*;
007import java.nio.file.*;
008import java.lang.reflect.InvocationTargetException;
009
010import Torello.Java.Additional.*;
011
012import Torello.JavaDoc.Annotations.StaticFunctional;
013import Torello.JavaDoc.Annotations.StaticFunctional.Excuse;
014
015/**
016 * Operating-System independent File Read & Write utilities that reduce many common Java
017 * File I/O Commands to a single method invocation, focusing heavily on Java's Serialization
018 * feature.
019 * 
020 * <EMBED CLASS='external-html' DATA-FILE-ID=FILE_RW>
021 */
022@StaticFunctional(Excused="TRUNCATE_EOF_CHARS", Excuses=Excuse.FLAG)
023public class FileRW
024{
025    private FileRW() { }
026
027    /**
028     * This is used by method {@link #loadFileToString(String)}.  By default this flag is set to
029     * {@code TRUE}, and when it is, any trailing {@code EOF chars, ASCII-0} found in a file that
030     * is to be interpreted as a Text-File, will be truncated from the {@code String} returned by
031     * that {@code 'reader'} method.
032     */
033    public static boolean TRUNCATE_EOF_CHARS = true;
034
035
036    // ********************************************************************************************
037    // ********************************************************************************************
038    // writeFile
039    // ********************************************************************************************
040    // ********************************************************************************************
041
042
043    /**
044     * Writes the entire contents of a single {@code java.lang.String} to a file on the File-System
045     * named {@code 'fName'}.
046     *
047     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
048     * 
049     * @param s A {@code java.lang.String} which is appended, in entirety, to the file ('fName')
050     *
051     * @param fName The name of the file to which the contents of the {@code java.lang.String}
052     * are appended.  If This file doesn't already exist, it is created here.
053     *
054     * @throws IOException If any I/O errors have occurred with the File-System / disk.
055     */
056    public static void writeFile(CharSequence s, String fName) throws IOException
057    {
058        File outF = new File(fName);
059
060        outF.createNewFile();
061
062        // This writer is 'java.lang.AutoCloseable'.
063        //
064        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
065        //       caught!
066
067        try
068            (FileWriter fw = new FileWriter(outF))
069            { fw.write(s.toString()); }
070    }
071
072    /**
073     * This takes an {@code Iterable<String>}, and a filename, and writes each
074     * {@code java.lang.String} in the {@code Iterator} that it produces to the file 
075     * specified by File-Name parameter {@code 'fName'}
076     *
077     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
078     * 
079     * <BR /><DIV CLASS=JDHintAlt>
080     * <B STYLE='color:red;'>New-Line Characters:</B> A newline {@code ('\n')} is appended to the
081     * end of each {@code java.lang.String} before writing it to the file.
082     * </DIV>
083     *
084     * @param i This is any java {@code Iterable<String>} object.  Each of these will be written,
085     * in succession, to the file named by parameter {@code 'fName'}
086     *
087     * @param fName The name of the file to which the contents of the {@code java.lang.String}
088     * are appended.  If This file doesn't already exist, it is created here.
089     *
090     * @throws IOException If an I/O error has occurred as a result of the File-System or
091     * disk operation.
092     */
093    public static void writeFile(Iterable<String> i, String fName) throws IOException
094    {
095        Iterator<String> iter = i.iterator();
096
097        File outF = new File(fName);
098
099        outF.createNewFile();
100
101        // This writer is 'java.lang.AutoCloseable'.
102        //
103        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
104        //       caught!
105
106        try
107            (FileWriter fw = new FileWriter(outF))
108            { while (iter.hasNext()) fw.write(iter.next() + "\n"); }
109    }
110
111
112    // ********************************************************************************************
113    // ********************************************************************************************
114    // writeFile_NO_NEWLINE
115    // ********************************************************************************************
116    // ********************************************************************************************
117
118
119    /**
120     * This takes an {@code Iterable} of String, and a filename and writes each
121     * {@code java.lang.String} in the {@code Iterator} that it produces to the file specified by
122     * File-Name parameter {@code 'fName'}
123     *
124     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
125     * 
126     * <BR /><DIV CLASS=JDHintAlt>
127     * <B STYLE='color:red;'>New-Line Characters:</B> In this function a newline {@code ('\n')}
128     * character <I>is <B>not</B> appended</I> to the end of each {@code java.lang.String} of the
129     * input {@code Iterator}.
130     * </DIV>
131     *
132     * @param i This is any java {@code 'Iterable'} object that can iterate {@code 'String'}.
133     * Each of these will be written, in succession, to the file named by {@code 'fName'}
134     *
135     * @param fName The name of the file to which the contents of the {@code java.lang.String}
136     * are appended.  If This file doesn't already exist, it is created here.
137     *
138     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
139     * operation.
140     */
141    public static void writeFile_NO_NEWLINE(Iterable<String> i, String fName) throws IOException
142    {
143        Iterator<String> iter = i.iterator();
144
145        File outF = new File(fName);
146
147        outF.createNewFile();
148
149        // This Writer is 'java.lang.AutoCloseable'.
150        //
151        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
152        //       caught!
153
154        try
155            (FileWriter fw = new FileWriter(outF))
156            { while (iter.hasNext()) fw.write(iter.next()); }
157    }
158
159
160    // ********************************************************************************************
161    // ********************************************************************************************
162    // appendToFile
163    // ********************************************************************************************
164    // ********************************************************************************************
165
166
167    /**
168     * Appends the entire input {@code java.lang.String} - actually a
169     * {@code java.lang.CharSequence} to the file on the File-System named {@code 'fName'}
170     * 
171     * <BR /><BR /><DIV CLASS=JDHint>
172     * <B STYLE='color:red;'>Directory Requirements:</B> Though the file does not need to exist
173     * already in order for this file to be written the directory hierarchy needs to exist, or a
174     * Java {@code 'IOException'} will occur.
175     * </DIV>
176     *
177     * @param s A {@code java.lang.CharSequence} (almost identical to {@code 'String'}) which is
178     * appended, in entirety, to the File-Name parameter {@code 'fName'}
179     *
180     * @param fName The name of the file to which the contents of the {@code java.lang.String} are
181     * appended.  If This file doesn't already exist, it is created here.
182     *
183     * @throws IOException If an I/O error has occurred as a result of the File-System or
184     * disk operation.
185     */
186    public static void appendToFile(CharSequence s, String fName) throws IOException
187    {
188        File f = new File(fName);
189
190        if (! f.exists()) f.createNewFile();
191
192        Files.write(Paths.get(fName), s.toString().getBytes(), StandardOpenOption.APPEND);
193    }
194
195    /**
196     * This takes an {@code Iterable<String>}, and a filename, and appends each
197     * {@code java.lang.String} in the {@code Iterator} that it produces to the file 
198     * specified by File-Name parameter {@code 'fName'}
199     *
200     * <BR /><BR /><DIV CLASS=JDHint>
201     * <B STYLE='color:red;'>Directory Requirements:</B> Though the file does not need to exist
202     * already in order for this file to be written the directory hierarchy needs to exist, or a
203     * Java {@code 'IOException'} will occur.
204     * </DIV>
205     *
206     * @param i This is any java {@code Iterable<String>} object.  Each of these will be written,
207     * in succession, to the file named by parameter {@code 'fName'}
208     *
209     * @param fName The name of the file to which the contents of the {@code java.lang.String}
210     * are appended.  If This file doesn't already exist, it is created here.
211     *
212     * @param addNewLines When this parameter is passed {@code TRUE}, a New-Line character will be
213     * appended after each {@code String} that is written.  When {@code FALSE}, only the
214     * {@code String's} produced by the {@code Iterable}, themselves, are appended to the file.
215     * 
216     * @throws IOException If an I/O error has occurred as a result of the File-System or
217     * disk operation.
218     */
219    public static void appendToFile(Iterable<String> i, String fName, boolean addNewLines)
220        throws IOException
221    {
222        File f = new File(fName);
223
224        if (! f.exists()) f.createNewFile();
225
226
227        // Uses the "Ternary Operator" / "Conditional Operator" Syntax, but it's a little hard to
228        // read that.  There is a '?' and a ':' after the boolean 'addNewsLines'
229
230        Iterable<String> passedIterable = addNewLines
231
232
233            // If the user has requested to add newlines, wrap the passed Iterable inside of a new
234            // Iterable that appends a new-line character to the output of the User's 'next()'
235            // method (which is just returning the String's to be written to disk - less the newline)
236
237            ?   new Iterable<>()
238                {
239                    private final Iterator<String> iterInternal = i.iterator();
240
241
242                    // Funny Note: All an "Iterable" is is an Object that returns an Iterator.  The
243                    // interface "Iterable" only has one non-Default Method - iterator().  Re-Write
244                    // that method to return a slightly altered Iterator<String>
245
246                    public Iterator<String> iterator()
247                    {
248                        return new Iterator<String>()
249                        {
250                            public boolean  hasNext()   { return iterInternal.hasNext();        }
251                            public String   next()      { return iterInternal.next() + '\n';    }
252                        };
253                    }
254                }
255
256            // Otherwise, just assign the Users Iterable to the "passedIterable" Variable
257            : i;
258
259        // Now write the Passed Iterable of String, using java.nio.file.Files
260        Files.write(Paths.get(fName), passedIterable, StandardOpenOption.APPEND);
261    }
262
263
264    // ********************************************************************************************
265    // ********************************************************************************************
266    // Load File (as String's}
267    // ********************************************************************************************
268    // ********************************************************************************************
269
270
271    /**
272     * This will load the entire contents of a Text-File on disk to a single
273     * {@code java.lang.String}
274     * 
275     * <BR /><BR /><DIV CLASS=JDHint>
276     * <B STYLE='color:red;'>Trailing Zeros:</B> Some of the ugliest code to see is that which
277     * scans for {@code 'EOF'} characters that have been liberally inserted into a simple
278     * Text-File.  When reading a file (which, regardless of whether it <B>actually is a
279     * Text-File</B>), this method will remove any <B>trailing {@code ASCII-0}</B> characters
280     * (literally, {@code char c == 0}) from the files that are read.
281     * </DIV>
282     * 
283     * <BR />Finding {@code '.html'} or {@code '.java'} files in which some editor (for whatever
284     * reason) has inserted {@code EOF-like} characters to the end of the text can make programming
285     * a headache.
286     * 
287     * <BR /><BR />Suffice it to say, the {@code String} that is returned from this method will
288     * contain the last non-zero character (including CRLF, {@code '\n'} or {@code '\r'}
289     * character that was read from the file.  Operating-systems do not require that a file have a
290     * trailing zero to interpret them.
291     * 
292     * <BR /><BR />Character {@code '0'} is a legacy / older-version of the EOF Marker.
293     * {@code '.java'}-Files certainly don't need them, and they can actually be a problem when a
294     * developer is checking for file's that have equal-{@code String's} in them.
295     *
296     * <BR /><BR /><DIV CLASS=JDHintAlt>
297     * <B STYLE='color:red;'>Static Boolean-Flag:</B> This class (class {@code FileRW}) has a
298     * {@code static, boolean} flag that is able to prevent / shunt this 'Trailing Zero Removing'
299     * behavior.  When {@link #TRUNCATE_EOF_CHARS} is set to {@code FALSE}, reading a file into a
300     * {@code String} using this method will return the {@code String} - <B>including as many 
301     * Trailing Zero-Characters as have been appended to the end of that file</B>.
302     * </DIV>
303     * 
304     * @param fName the File-Name of a valid Text-File in the File-System
305     *
306     * @return The entire contents of the file as a {@code String}.
307     *
308     * @throws IOException If an I/O error has occurred as a result of the File-System or
309     * disk operation.
310     */
311    public static String loadFileToString(String fName) throws IOException
312    {
313        // The reader is  'java.lang.AutoCloseable'.
314        //
315        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
316        //       caught!
317
318        try
319            (FileReader fr = new FileReader(fName))
320        {
321            int     len             = (int) new File(fName).length();
322            char[]  cArr            = new char[len];
323            int     offset          = 0;
324            int     charsRead       = 0;
325            int     charsRemaining  = len;
326
327            while ((offset < len) && ((charsRead = fr.read(cArr, offset, charsRemaining)) != -1))
328            {
329                offset          += charsRead;
330                charsRemaining  -= charsRead;
331            }
332
333            len = cArr.length;
334
335            if (TRUNCATE_EOF_CHARS) while ((len > 0) && (cArr[len-1] == 0)) len--;
336
337            return (len != cArr.length) ? new String(cArr, 0, len) : new String(cArr); 
338        }
339    }
340
341    /**
342     * Convenience Method.
343     * <BR />Invokes: {@link #loadFileToStream(String, boolean)}
344     * <BR />Converts: {@code Stream<String>} into {@code String[]}
345     */
346    public static String[] loadFileToStringArray(String fName, boolean includeNewLine)
347        throws IOException
348    { return loadFileToStream(fName, includeNewLine).toArray(String[]::new); }
349
350    /**
351     * This will load a file to a Java {@code Stream} instance.
352     *
353     * @param fName A File-Name of a valid Text-File in the File-System.
354     * 
355     * @param includeNewLine if this is {@code TRUE}, a {@code '\n'} (newline/CRLF) is appended to
356     * the end of each {@code java.lang.String} read from this file.  If not, the original newline
357     * characters which occur in the file, will be eliminated.
358     * 
359     * <BR /><BR /><DIV CLASS=JDHint>
360     * This method will make one (potentially minor) mistake.  If the final character in the
361     * input-file is, itself, a new-line (if the file ends with a {@code 'CRLF' / 'CR'}), then this
362     * method should return a {@code Stream<String>} that is identical to the original file.
363     * However, <B>if the final character in the file <SPAN STYLE='color:red;'>is not</SPAN> a
364     * new-line {@code '\n'}</B>, then the {@code Stream<String>} that is returned will have an
365     * extra new-line appended to the last {@code String} in the {@code Stream}, and the resultant
366     * {@code Stream<String>} will be longer than the original file by 1 character.
367     * </DIV>
368     *
369     * @return The entire contents of the file as a series of {@code java.lang.String} contained by
370     * a {@code java.util.stream.Stream<String>}.  Converting Java {@code Stream's} to other data
371     * container types is as follows:
372     *
373     * <EMBED CLASS='external-html' DATA-FILE-ID=STRMCNVT>
374     *
375     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
376     * operation.
377     */
378    public static Stream<String> loadFileToStream(String fName, boolean includeNewLine)
379        throws IOException
380    {
381        // These readers's are 'java.lang.AutoCloseable'.
382        //
383        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
384        //       caught!
385        //
386        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
387        //       classes are all (automatically) closed/flushed, in reverse order.
388
389        try (
390            FileReader      fr  = new FileReader(fName);
391            BufferedReader  br  = new BufferedReader(fr);
392        )
393        {
394            Stream.Builder<String>  b = Stream.builder();
395            String                  s = "";
396
397            if (includeNewLine)
398                while ((s = br.readLine()) != null) b.add(s + "\n");
399
400            else
401                while ((s = br.readLine()) != null) b.add(s);
402
403            return b.build();
404        }
405    }
406
407    /**
408     * Convenience Method.
409     * <BR />Invokes: {@link #loadFileToCollection(Collection, String, boolean)}
410     * <BR />Passes: Newly instantiated {@code Vector<String>} to {@code 'collectionChoice'}
411     */
412    public static Vector<String> loadFileToVector(String fName, boolean includeNewLine)
413        throws IOException
414    { return loadFileToCollection(new Vector<String>(), fName, includeNewLine); }
415
416    /**
417     * This method loads the contents of a file to a {@code java.util.Collection<String>} object, 
418     * where each {@code java.lang.String} in the output / returned {@code Collection} is a
419     * different "line of text" from the input-file.  This is identical to invoking:
420     * 
421     * <DIV CLASS=LOC>{@code
422     * Collection<String> myTextFile = FileRW.loadFileToString("someFile.txt").split('\n')
423     * }</DIV>
424     *
425     * <BR /><DIV CLASS=JDHint>
426     * <B STYLE='color:red;'>Variable-Type Parameter:</B> This method uses Java's Variable-Type
427     * Parameter syntax to allow the programmer to decide what type of {@code Collection<String>}
428     * they would like returned from this method.  Common examples would include
429     * {@code Vector<String>, ArrayList<String>, HashSet<String>}, etc...
430     * </DIV>
431     * 
432     * <BR /><B CLASS=JDDescLabel>Loading EOF:</B>
433     * 
434     * <BR />This method will make one small mistake.  If the final character inside the input-file
435     * is, itself, a new-line, then this method will return a Java {@code String-Collection} which
436     * is is identical to the original file.
437     * 
438     * <BR /><BR />However, <I>if the final character in the file <B>is not</B> a new-line
439     * {@code '\n'} character</I>, then the returned {@code Collection} will have an extra new-line
440     * appended immediately after the last {@code String} in the {@code Collection}.  Furthermore,
441     * the resultant {@code Collection<String>} will be longer than the original file by 1 character.
442     *
443     * @param collectionChoice This must be an instance of a class that extends Java's
444     * base {@code class Collection<String>} - <I>using {@code 'String'} as the generic-type
445     * parameter.</I>  It will be populated with the lines from a Text-File using the
446     * {@code Collection.add(String)} method.
447     * 
448     * @param <T> This may be any class which extends {@code java.util.Collection}.  It is
449     * specified as a "Type Parameter" because this collection is returned as a result of this
450     * function.  Perhaps it is superfluous to return the same reference that is provided by the
451     * user as input to this method, but this certainly doesn't change the method signature or
452     * make it more complicated.
453     *
454     * @param fName the File-Name of a valid Text-File on the File-System.
455     *
456     * @param includeNewLine if this is {@code TRUE}, a {@code '\n'} (newline/CRLF) is appended to
457     * the end of each {@code java.lang.String} read from this file.  If not, the original newline
458     * characters which occur in the file, will be eliminated.
459     *
460     * @return An identical reference to the reference passed to parameter
461     * {@code 'collectionChoice'}
462     *
463     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
464     * operation.
465     */
466    public static <T extends Collection<String>> T loadFileToCollection
467        (T collectionChoice, String fName, boolean includeNewLine)
468        throws IOException
469    {
470        // These readers's are 'java.lang.AutoCloseable'.
471        //
472        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
473        //       caught!
474        //
475        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
476        //       classes are all (automatically) closed/flushed, in reverse order.
477
478        try (
479            FileReader      fr  = new FileReader(fName);
480            BufferedReader  br  = new BufferedReader(fr);
481        )
482        {
483            String s = "";
484
485            if (includeNewLine)
486                while ((s = br.readLine()) != null) collectionChoice.add(s + "\n");
487
488            else
489                while ((s = br.readLine()) != null) collectionChoice.add(s);
490        }
491
492        return collectionChoice;
493    }
494
495
496    // ********************************************************************************************
497    // ********************************************************************************************
498    // Write ONE Object To File
499    // ********************************************************************************************
500    // ********************************************************************************************
501
502
503    /**
504     * Convenience Method.
505     * <BR />Invokes: {@link #writeObjectToFile(Object, String, boolean)}
506     * <BR />Catches Exception
507     */
508    public static boolean writeObjectToFileNOCNFE(Object o, String fName, boolean ZIP)
509        throws IOException
510    {
511        try 
512            { writeObjectToFile(o, fName, ZIP); return true; }
513
514        catch (ClassNotFoundException cnfe) { return false; }
515    }
516
517    /**
518     * Writes a {@code java.lang.Object} to a file for storage, and future reference.
519     * 
520     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
521     * 
522     * @param o An {@code Object} to be written to a file as a <I><B>"Serializable"</B></I>
523     * {@code java.lang.Object}
524     *
525     * @param fName The name of the output file
526     *
527     * @param ZIP a boolean that, when {@code TRUE}, will cause the object's data to be compressed
528     * before being written to the output file.
529     *
530     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
531     * operation.
532     *
533     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
534     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
535     * the classpath.
536     */
537    public static void writeObjectToFile(Object o, String fName, boolean ZIP) 
538        throws IOException, ClassNotFoundException
539    {
540        // These stream's are 'java.lang.AutoCloseable'.
541        //
542        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
543        //       method if they occur.  They are not caught!
544        //
545        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
546        //       classes are all (automatically) closed/flushed, in reverse order.
547
548        if (ZIP)
549
550            try (
551                FileOutputStream    fos     = new FileOutputStream(fName);
552                GZIPOutputStream    gzip    = new GZIPOutputStream(fos);
553                ObjectOutputStream  oos     = new ObjectOutputStream(gzip);
554            )
555                { oos.writeObject(o); }
556
557        else
558
559            try (
560                FileOutputStream    fos     = new FileOutputStream(fName);
561                ObjectOutputStream  oos     = new ObjectOutputStream(fos);
562            )
563                { oos.writeObject(o); }
564    }
565
566
567    // ********************************************************************************************
568    // ********************************************************************************************
569    // Write ALL Objects ToFile
570    // ********************************************************************************************
571    // ********************************************************************************************
572
573
574    /**
575     * Convenience Method.
576     * <BR />Invokes: {@link #writeAllObjectsToFile(Iterable, String, boolean)}
577     * <BR />Catches Exception
578     */
579    public static boolean writeAllObjectsToFileNOCNFE(Iterable<?> i, String fName, boolean ZIP)
580        throws IOException
581    {
582        try
583            { writeAllObjectsToFile(i, fName, ZIP); return true; }
584
585        catch (ClassNotFoundException cnfe) { return false; }
586    }
587
588    /**
589     * Writes a series of {@code java.lang.Object} to a file for storage, and future reference.
590     * 
591     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
592     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_WRITABLE_DIR>
593     * 
594     * @param i A series, {@code Collection}, or {@code List} of {@code Object's} to be written
595     * to a file in <I><B>Serializable</B></I> format.
596     *
597     * @param fName The name of the output file
598     *
599     * @param ZIP a {@code boolean} that, when {@code TRUE}, will cause the {@code Object's} 
600     * data to be compressed before being written to the output-file.
601     *
602     * @throws IOException If an I/O error has occurred as a result of the File-System or
603     * disk operation.
604     *
605     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
606     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
607     * the {@code CLASSPATH}.
608     */
609    public static void writeAllObjectsToFile(Iterable<?> i, String fName, boolean ZIP)
610        throws IOException, ClassNotFoundException
611    {
612        Iterator<?> iter = i.iterator();
613
614        // These stream's are 'java.lang.AutoCloseable'.
615        //
616        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
617        //       method if they occur.  They are not caught!
618        //
619        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
620        //       classes are all (automatically) closed/flushed, in reverse order.
621
622        if (ZIP)
623
624            try (
625                FileOutputStream    fos     = new FileOutputStream(fName);
626                GZIPOutputStream    gzip    = new GZIPOutputStream(fos);
627                ObjectOutputStream  oos     = new ObjectOutputStream(gzip);
628            )
629                { while (iter.hasNext()) oos.writeObject(iter.next()); }
630
631        else
632
633            try (
634                FileOutputStream    fos     = new FileOutputStream(fName);
635                ObjectOutputStream  oos     = new ObjectOutputStream(fos);
636            )
637                { while (iter.hasNext()) oos.writeObject(iter.next()); }
638    }
639
640
641    // ********************************************************************************************
642    // ********************************************************************************************
643    // read ONE Object From File
644    // ********************************************************************************************
645    // ********************************************************************************************
646
647
648    /**
649     * Convenience Method.
650     * <BR />Invokes: {@link #readObjectFromFile(String, boolean)}
651     * <BR />Catches Exception
652     */
653    public static Object readObjectFromFileNOCNFE(String fName, boolean ZIP) throws IOException
654    {
655        try 
656            { return readObjectFromFile(fName, ZIP); }
657
658        catch (ClassNotFoundException cnfe) { return null; }
659    }
660
661    /**
662     * Reads an {@code Object} from a Data-File which must contain a serialized
663     * {@code java.lang.Object}.
664     *
665     * <DIV CLASS="EXAMPLE">{@code
666     * // Create some Object for writing to the File-System, using Object Serialization
667     * int[] dataArr = some_data_method();
668     *
669     * // It is always easier to pass 'true' to the compression boolean parameter
670     * FileRW.writeObjectToFile(dataArr, "data/myDataFile.dat", true);
671     *
672     * ...
673     *
674     * // Later on, this file may be read back into the program, using this call:
675     * Object o = FileRW.readObjectFromFile("data/myDataFile.dat", true);
676     *
677     * // This check prevents compiler-time warnings.  The Annotation "SuppressWarnings" 
678     * // would also work.
679     * dataArr = (o instanceof int[]) ? (int[]) o : null;
680     * }</DIV>
681     *
682     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
683     *
684     * @param fName The name of a Data-File that contains a serialized {@code java.lang.Object}
685     *
686     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains a
687     * Zip-Compressed {@code Object}
688     *
689     * @return The {@code Object} that was written to the Data-File.
690     *
691     * @throws IOException If an I/O error has occurred as a result of the File-System or
692     * disk operation.
693     *
694     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
695     * Machine (JVM) tries to load a particular class and the specified class cannot be found
696     * in the {@code CLASSPATH}.
697     */
698    public static Object readObjectFromFile(String fName, boolean ZIP)
699        throws IOException, ClassNotFoundException
700    {
701        // These stream's are 'java.lang.AutoCloseable'.
702        //
703        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
704        //       method if they occur.  They are not caught!
705        //
706        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
707        //       classes are all (automatically) closed/flushed, in reverse order.
708
709        try (
710            FileInputStream     fis = new FileInputStream(fName);
711            ObjectInputStream   ois = ZIP
712                                    ? new ObjectInputStream(new GZIPInputStream(fis))
713                                    : new ObjectInputStream(fis);
714        )
715            { return ois.readObject(); }
716    }
717
718    /**
719     * Convenience Method.
720     * <BR />Invokes: {@link #readObjectFromFile(String, Class, boolean)}
721     * <BR />Catches Exception
722     */
723    public static <T> T readObjectFromFileNOCNFE(String fName, Class<T> c, boolean ZIP)
724        throws IOException
725    {
726        try
727            { return readObjectFromFile(fName, c, ZIP); }
728
729        catch (ClassNotFoundException cnfe) { return null; }
730    }
731
732    /**
733     * Reads an {@code Object} from a Data-File which must contain a serialized
734     * {@code java.lang.Object}.
735     *
736     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
737     *
738     * @param fName The name of a Data-File that contains a serialized {@code java.lang.Object}
739     *
740     * @param c This is the type of the {@code Object} expecting to be read from disk.  A value
741     * for this parameter can always be obtained by referencing the {@code static} field
742     * {@code '.class'} which is attached to <I>every object</I> in java.
743     *
744     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_CLASS_T>
745     * 
746     * @param <T> This should be set to the return type of this method.  By passing the expecred
747     * return-type class, you may conveniently avoid having to cast the returned instance, or worry
748     * about suppressing compiler warnings.
749     * 
750     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains a
751     * Zip-Compressed {@code Object}
752     *
753     * @return The {@code Object} that was read from the Data-File.
754     *
755     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
756     * operation.
757     *
758     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
759     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
760     * the {@code CLASSPATH}.
761     */
762    public static <T> T readObjectFromFile(String fName, Class<T> c, boolean ZIP)
763        throws IOException, ClassNotFoundException
764    {
765        Object o = readObjectFromFile(fName, ZIP);
766
767        if (o == null)          return null;
768        if (c.isInstance(o))    return c.cast(o);
769
770        throw new ClassNotFoundException(
771            "Although an object was indeed read from the file you have named [" + fName + "], " +
772            "that object was not an instance of [" + c.getName() + "], " + 
773            "but rather of [" + o.getClass().getName() + "]"
774        );
775    }
776
777
778    // ********************************************************************************************
779    // ********************************************************************************************
780    // read ALL OBJECTS FromFile
781    // ********************************************************************************************
782    // ********************************************************************************************
783
784
785    /**
786     * Convenience Method.
787     * <BR />Invokes: {@link #readAllObjects(Class, Collection, String, boolean)}
788     * <BR />Passes: {@code Object.class} to {@code 'objType'}
789     * <BR />Passes: Newly instantiated {@code Vector<Object>} to {@code 'collection'}
790     */
791    public static Vector<Object> readAllObjectsToVector(String fName, boolean ZIP)
792        throws IOException, ClassNotFoundException
793    { return readAllObjects(Object.class, new Vector<Object>(), fName, ZIP); }
794
795    /**
796     * Convenience Method.
797     * <BR />Invokes: {@link #readAllObjects(Class, Collection, String, boolean)}
798     * <BR />Passes: Newly instantiated {@code Vector<T>} to {@code 'collection'}
799     */
800    public static <T> Vector<T> readAllObjectsToVector(Class<T> objType, String fName, boolean ZIP)
801        throws IOException, ClassNotFoundException
802    { return readAllObjects(objType, new Vector<T>(), fName, ZIP); }
803
804    /**
805     * Reads all {@code Object's} found inside a Data-File.  This Data-File should contain only
806     * java-serialized {@code java.lang.Object's}.
807     *
808     * @param objType <EMBED CLASS='external-html' DATA-FILE-ID=FRW_OBJTYPE_PARAM>
809     * 
810     * @param <T> This allows the programmer to inform this method what type of {@code Object's}
811     * are stored in the Serialized Object File, specified by {@code 'fName'}.
812     *
813     * @param collection This should be the desired {@code Collection<E>} that the programmer
814     * would like be populated with the instances of type {@code 'objType'} read from file
815     * {@code 'fName'}.  Variable-Type parameter {@code 'E'} needs to be equal-to or an 
816     * ancestor of {@code 'objType'}
817     * 
818     * @param <U> Merely for convenience, this method returns the {@code Collection} instance that
819     * is passed (by parameter {@code 'Collection'}) as a result of this function.  Therefore, the
820     * Type Parameter {@code 'U'} identifies the Return Type of this method.
821     *
822     * @param fName The name of a Data-File that contains serialized {@code Object's}
823     *
824     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains Zip-Compressed
825     * objects
826     *
827     * @return A reference to the {@code Collection} that was passed to this method.
828     *
829     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
830     * operation.
831     */
832    public static <T, U extends Collection<T>> U readAllObjects
833        (Class<T> objType, U collection, String fName, boolean ZIP)
834        throws IOException, ClassNotFoundException
835    {
836        // Temporary Variable, used in both versions of this method
837        Object o;
838
839        // These stream's are 'java.lang.AutoCloseable'.
840        //
841        // NOTE: The IOException and ClassNotFoundException will still be thrown out of this
842        //       method if they occur.  They are not caught!
843        //
844        // ALSO: In try-with-resources blocks, if there is a problem/exception, these
845        //       classes are all (automatically) closed/flushed, in reverse order.
846
847        if (ZIP)
848
849            try (
850                FileInputStream     fis = new FileInputStream(fName);
851                GZIPInputStream     gis = new GZIPInputStream(fis);
852                ObjectInputStream   ois = new ObjectInputStream(gis);
853            )
854            {  
855                while ((o = ois.readObject()) != null)
856
857                    if (objType.isInstance(o))
858                        collection.add(objType.cast(o));
859
860                    else throw new ClassNotFoundException(
861                        "At least one of the objects in the serialized object file " +
862                            "[" + fName + "], " +
863                        "was not an instance of [" + objType.getName() + "], " +
864                        "but rather [" + o.getClass().getName() + "]"
865                    );
866            }
867
868            catch (EOFException eofe) { }
869
870        else
871
872            try (
873                FileInputStream     fis = new FileInputStream(fName);
874                ObjectInputStream   ois = new ObjectInputStream(fis);
875            )
876            {  
877                while ((o = ois.readObject()) != null)
878
879                    if (objType.isInstance(o))
880                        collection.add(objType.cast(o));
881
882                    else throw new ClassNotFoundException(
883                        "At least one of the objects in the serialized object file " +
884                            "[" + fName + "], " +
885                        "was not an instance of [" + objType.getName() + "], " +
886                        "but rather [" + o.getClass().getName() + "]"
887                    );
888            }
889
890            catch (EOFException eofe) { }
891
892        return collection;
893    }
894
895    /**
896     * Convenience Method.
897     * <BR />Invokes: {@link #readAllObjectsToStream(Class, String, boolean)}
898     * <BR />Passes: {@code Object.class} to {@code 'objType'}
899     */
900    public static Stream<Object> readAllObjectsToStream(String fName, boolean ZIP)
901        throws IOException, ClassNotFoundException
902    { return readAllObjectsToStream(Object.class, fName, ZIP); }
903
904    /**
905     * Reads all objects found inside a Data-File.  This Data-File should contain only
906     * java-serialized {@code Object's}.
907     *
908     * @param fName The name of a Data-File that contains the serialized {@code Object's}
909     *
910     * @param objType This is the type of the {@code Object's} expecting to be read from disk.  A
911     * value for this parameter can always be obtained by referencing the static field
912     * {@code '.class'} which is attached to <I>every {@code Object}</I> in Java.  For instance, to
913     * read a Data-File containing a series of {@code Date} instances, use {@code Date.class} as the
914     * value to pass to this parameter.
915     * 
916     * @param <T> This parameter informs this method what type of Serialized Objects are saved
917     * within {@code 'fName'}.  The Java {@code Stream} that is returned as a result of this method
918     * will have the type {@code Stream<T>}.
919     *
920     * @param ZIP if this is {@code TRUE}, it is assumed that the Data-File contains Zip-Compressed
921     * {@code Object's}
922     *
923     * @return A {@code Stream<T>} of all {@code Object's} found in the Data-File.  Converting
924     * Java {@code Stream's} to other Data-Container types is as follows:
925     *
926     * <EMBED CLASS='external-html' DATA-FILE-ID=STREAM_CONVERT_T>
927     *
928     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
929     * operation.
930     *
931     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
932     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
933     * the classpath.
934     */
935    public static <T> Stream<T> readAllObjectsToStream(Class<T> objType, String fName, boolean ZIP)
936        throws IOException, ClassNotFoundException
937    {
938        Stream.Builder<T>   b   = Stream.builder();
939        Object              o   = null;
940
941        try(
942            FileInputStream fis = new FileInputStream(fName);
943
944            ObjectInputStream ois = ZIP
945                ? new ObjectInputStream(new GZIPInputStream(fis))
946                : new ObjectInputStream(fis);
947        )
948        {
949            while ((o = ois.readObject()) != null)
950
951                if (objType.isInstance(o)) b.accept(objType.cast(o));
952
953                else throw new ClassNotFoundException(
954                    "At least one of the objects in the serialized object file [" + fName + "], " +
955                    "was not an instance of [" + objType.getName() + "], " +
956                    "but rather [" + o.getClass().getName() + "]"
957                );
958        }
959
960        catch (EOFException eofe) { }
961
962        return b.build();
963    }
964
965
966    // ********************************************************************************************
967    // ********************************************************************************************
968    // Base-64 Read / Write Stuff (Text File)
969    // ********************************************************************************************
970    // ********************************************************************************************
971
972
973    /**
974     * Uses Java's {@code Object} serialization mechanism to serialize a {@code java.lang.Object},
975     * and then uses the {@code Base64 String-MIME Encoding} system, also provided by java, to
976     * convert the {@code Object} into a text-safe {@code java.lang.String} that may be viewed,
977     * e-mailed, written to a web-page, etc.
978     *
979     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
980     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_EXAMPLE_01>
981     *
982     * @param o This may be any serializable {@code java.lang.Object} instance.  It will be written
983     * to a Text-File after first serializing the {@code Object}, and then next converting the
984     * serialized data-bits to MIME-safe encoded text.
985     *
986     * @param fileName The fileName that will be used to save this {@code Object} as a Text-File.
987     * 
988     * @see #writeFile(CharSequence, String)
989     * @see StringParse#objToB64MimeStr(Object)
990     */
991    public static void writeObjectToTextFile(Object o, String fileName)
992        throws IOException
993    { FileRW.writeFile(StringParse.objToB64MimeStr(o), fileName); }
994
995    /**
996     * This will read a java serialized, and MIME-Converted, MIME-Safe {@code java.lang.String}
997     * from a text file and return the {@code Object} that it represented
998     *
999     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
1000     *
1001     * @param fileName The name of the file containing the MIME-Encoded Serialized
1002     * {@code java.lang.Object}.
1003     *
1004     * @return The {@code Object} that had been encoded into the Text-File.
1005     * 
1006     * @see #loadFileToString(String)
1007     * @see StringParse#b64MimeStrToObj(String)
1008     */
1009    public static Object readObjectFromTextFile(String fileName) throws IOException
1010    { return StringParse.b64MimeStrToObj(loadFileToString(fileName)); }
1011
1012    /**
1013     * Convenience Method.
1014     * <BR />Invokes: {@link #readObjectFromTextFile(String, Class)}
1015     * <BR />Catches Exception
1016     */
1017    public static <T> T readObjectFromTextFileNOCNFE(String fileName, Class<T> c)
1018        throws IOException
1019    {
1020        try
1021            { return readObjectFromTextFile(fileName, c); }
1022
1023        catch (ClassNotFoundException cnfe) { return null; }
1024    }
1025
1026    /**
1027     * This will read a java serialized, and MIME-Converted, MIME-Safe {@code java.lang.String}
1028     * from a text file and return the {@code Object} that it represented
1029     *
1030     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_1OBJ_FILE>
1031     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_EXAMPLE_01>
1032     *
1033     * @param fileName The name of the file containing the MIME-Encoded Serialized
1034     * {@code java.lang.Object}.
1035     *
1036     * @param c This is the type of the {@code Object} expecting to be read from disk.  A value
1037     * for this parameter can always be obtained by referencing the {@code static} field
1038     * {@code '.class'}, which is attached to <I>every {@code Object}</I> in java.
1039     *
1040     * <EMBED CLASS='external-html' DATA-FILE-ID=FRW_CLASS_T>
1041     * 
1042     * @param <T> This Type Parameter informs this method what type of Base-64 Serialized Object
1043     * is saved within Text File {@code 'fileName'}.  The returned result of this method will have
1044     * this type, for convenience to avoid casting the {@code Object} (<I>unless that
1045     * {@code Object} is a Java Generic, and you wish to avoid a "Raw Types" warning</I>.  See 
1046     * further details inside the explanation about parameter {@code 'c'}).
1047     *
1048     * @return The {@code Object} that had been encoded into the Text-File.
1049     *
1050     * @throws IOException If an I/O error has occurred as a result of the File-System or disk
1051     * operation.
1052     *
1053     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
1054     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
1055     * the {@code CLASSPATH}.
1056     * 
1057     * @see StringParse#b64MimeStrToObj(String)
1058     * @see #loadFileToString(String)
1059     */
1060    public static <T> T readObjectFromTextFile(String fileName, Class<T> c)
1061        throws IOException, ClassNotFoundException
1062    { 
1063        Object o = StringParse.b64MimeStrToObj(loadFileToString(fileName));
1064
1065        if (o == null)          return null;
1066        if (c.isInstance(o))    return c.cast(o);
1067
1068        throw new ClassNotFoundException(
1069            "Although an object was indeed read from the file you have named [" + fileName + "], " +
1070            "that object was not an instance of [" + c.getName() + "], " + 
1071            "but rather of [" + o.getClass().getName() + "]"
1072        );
1073    }
1074
1075
1076    // ********************************************************************************************
1077    // ********************************************************************************************
1078    // Object Input & Object Output Streams
1079    // ********************************************************************************************
1080    // ********************************************************************************************
1081
1082
1083    /**
1084     * Creates a simple {@code ObjectInputStream} - usually if multiple {@code Object's} have been
1085     * written to a single file.  It was better practice to put {@code Object's} in a
1086     * {@code java.util.Vector}, and write one {@code java.util.Vector} during serialization.
1087     * 
1088     * <BR /><BR />This, eventually, can became inadequate when downloading large numbers of HTML
1089     * results, where the need to write a large Data-File (intermittently - by saving intermediate
1090     * results) is needed.
1091     *
1092     * @param fName This is the File-Name of the Data-File where the serialized {@code Object's}
1093     * have been stored.
1094     *
1095     * @param ZIP If this is set to {@code TRUE}, the data will be de-compressed.
1096     *
1097     * @return A java {@code ObjectInputStream}
1098     *
1099     * @throws IOException If an I/O error has occurred as a result of the File-System
1100     * or disk operation.
1101     */
1102    public static ObjectInputStream getOIS(String fName, boolean ZIP) throws IOException
1103    {
1104        return ZIP
1105            ? new ObjectInputStream(new GZIPInputStream(new FileInputStream(new File(fName))))
1106            : new ObjectInputStream(new FileInputStream(new File(fName))); 
1107    }
1108
1109    /**
1110     * Creates a simple {@code ObjectOutputStream} - usually if multiple {@code Object's} need
1111     * to be written to a single file.  It was better practice to put {@code Object's} in a
1112     * {@code java.util.Vector}, and write one {@code java.util.Vector} during serialization.
1113     *
1114     * @param fName This is the File-Name of the Data-File where the serialized {@code Object's}
1115     * will be stored.
1116     *
1117     * @param ZIP If this is set to {@code TRUE}, the data will be compressed.
1118     *
1119     * @return A java {@code ObjectInputStream}
1120     *
1121     * @throws IOException If an I/O error has occurred as a result of the File-System or
1122     * disk operation.
1123     */
1124    public static ObjectOutputStream getOOS(String fName, boolean ZIP) throws IOException
1125    {
1126        return ZIP
1127            ? new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(new File(fName))))
1128            : new ObjectOutputStream(new FileOutputStream(new File(fName))); 
1129    }
1130
1131
1132    // ********************************************************************************************
1133    // ********************************************************************************************
1134    // Copy, Move, Delete
1135    // ********************************************************************************************
1136    // ********************************************************************************************
1137
1138
1139    /**
1140     * This method will perform a byte-for-byte copy of a file from one location to another.
1141     *
1142     * @param inFileName The name of the input-file.  It will be byte-for-byte copied to an output
1143     * File-Name.
1144     *
1145     * @param outFileOrDirName The name of the output-file, or the name of the directory where the
1146     * file shall be moved.
1147     * 
1148     * <BR /><BR />If this file already exists <I>and it is a file (not a directory)</I>
1149     * it will be over-written.
1150     * 
1151     * <BR /><BR />If parameter {@code 'outFileOrDirName'} names a directory - <I>due
1152     * to having an ending {@code File.separator}</I>, <B>but</B> does not appear to name a
1153     * directory that exists, this method will throw a {@code FileNotFoundException}.
1154     * 
1155     * <BR /><BR />This can be avoided, however, by passing {@code TRUE} to parameter
1156     * {@code 'createDirsIfNotExist'}.  If {@code 'outFileOrDirName'} specifies a directory (by
1157     * virtue of the fact that it ends with the {@code File.separator String}) - <I>and 
1158     * {@code 'createDirsIfNotExist'} is {@code TRUE}</I>, then this method will first create any
1159     * and all sub-directories needed using the standard Java's {@code File.mkdirs()} method before
1160     * copying the file.
1161     * 
1162     * <BR /><BR /><DIV CLASS=JDHint>
1163     * <B><SPAN STYLE="color: red;">Summary:</SPAN></B> The programmer may provide either a
1164     * Directory-Name or a File-Name to parameter {@code 'outFileOrDirName'}.  If a Directory-Name
1165     * was provided, the moved file will <B>keep its name</B> - having the same name as the
1166     * original file, ({@code 'inFileName'}) had (but have been copied to directory
1167     * {@code 'outFileOrDirName'}).
1168     * </DIV>
1169     *
1170     * <BR /><DIV CLASS=JDHintAlt>
1171     * <B STYLE="color: red;">Behavior Note:</B> The behavior of this copy operation is generally /
1172     * mostly the same as the standard {@code UNIX} or {@code MS-DOS} commands {@code 'cp'} and
1173     * {@code 'copy'} (respectively) - <B>differing only in this method's ability to have
1174     * directories created using {@code File.mkdirs()}</B>
1175     * </DIV>
1176     *
1177     * @param createDirsIfNotExist If the target output-file is situated in a directory-path that
1178     * does not exist, this method will throw an exception.  However, if this boolean parameter is
1179     * set to {@code TRUE} and the aforementioned situation occurs where the complete-directory
1180     * tree does not exist, then this method will first attempt to create the directories using
1181     * {@code java.io.File.mkdirs().}
1182     *
1183     * @throws SecurityException If boolean parameter {@code 'createDirsIfNotExist'} is
1184     * {@code TRUE} <I>and if</I> the directory named by parameter {@code 'outFileName'} does not
1185     * exist, <I>and if</I> attempting to create such a directory is not permitted by the
1186     * Operating-System, then this exception shall throw.
1187     *
1188     * @throws IOException For any number of fail-causes in reading or writing input stream data.
1189     * An explanation of all causes of such an operation is beyond the scope of this
1190     * method-documentation entry.
1191     *
1192     * @throws FileNotFoundException If the {@code 'inFileName'} is not found, or
1193     * {@code 'outFileOrDirName'} uses a directory path that doesn't exist on the File-System,
1194     * <B>and</B> parameter {@code 'createDirsIfNotExist'} is set to {@code FALSE}.
1195     * 
1196     * @throws SameSourceAndTargetException This exception will be thrown if the <CODE>Java Virtual
1197     * Machine</CODE> ascertains that the source and target locations point to the same physical
1198     * disk locations.  The classes utilized for this operation are from package
1199     * {@code java.nio.file.*};
1200     * 
1201     * @throws InvalidPathException If the <I>Java Virtual Machine</I> is unable to instantiate an
1202     * instance of {@code java.nio.files.Path} for either the {@code 'inFileName'} parameter or the
1203     * {@code 'outFileOrDirName'}, then this exception will be thrown.
1204     * 
1205     * @throws NoSuchFileException If, after instantiating an instance of {@code Path} for either
1206     * the {@code source} or the {@code target} locations, the <I>Java Virtual Machine</I> is
1207     * unable to build an instance of {@code Path} using the method {@code Path.toRealPath()}, then
1208     * this exception will throw.
1209     */
1210    public static void copyFile
1211        (String inFileName, String outFileOrDirName, boolean createDirsIfNotExist)
1212        throws IOException
1213    {
1214        File f = new File(outFileOrDirName);
1215
1216        if (createDirsIfNotExist) if (! f.exists()) f.mkdirs();
1217
1218        if (f.isDirectory())
1219        {
1220            if (! outFileOrDirName.endsWith(File.separator))
1221                outFileOrDirName = outFileOrDirName + File.separator;
1222
1223            outFileOrDirName = outFileOrDirName + StringParse.fromLastFileSeparatorPos(inFileName);
1224        }
1225
1226        String inPath = Paths.get(inFileName).toRealPath().toString();
1227        // throws InvalidPathException
1228        // throws NoSuchFileException
1229
1230        try
1231        {
1232            if (Paths.get(outFileOrDirName).toRealPath().toString().equals(inPath))
1233
1234                throw new SameSourceAndTargetException(
1235                    "The Source File Name and the Target Location provided to your copyFile " +
1236                    "request operation appear to point to the same physical-disk location:\n" +
1237                    inPath
1238                );
1239        }
1240
1241        catch (NoSuchFileException e) { }
1242
1243
1244        // NOTE: Mostly (but not always) the output file won't exist yet...  If it does not, 
1245        //       then we really don't need to worry about over-writing the origina file.
1246        // 
1247        // REMEMBER: The only purpose of the above test is to make sure that the source and
1248        //           target are not the same (to avoid clobbering the original file)
1249
1250        FileInputStream     fis     = new FileInputStream(inFileName);
1251        FileOutputStream    fos     = new FileOutputStream(outFileOrDirName);
1252        byte[]              b       = new byte[5000];
1253        int                 result  = 0;
1254
1255        try
1256            { while ((result = fis.read(b)) != -1) fos.write(b, 0, result); }
1257
1258        finally
1259            { fis.close();  fos.flush();  fos.close(); }
1260    }
1261
1262    /**
1263     * Convenience Method.
1264     * <BR />Invokes: {@code java.io.File.delete()}
1265     */
1266    public static void deleteFiles(String... fileNames)
1267    { for (String fileName : fileNames) (new File(fileName)).delete(); }
1268
1269    /**
1270     * Convenience Method.
1271     * <BR />Invokes: {@link #copyFile(String, String, boolean)}
1272     * <BR />And-Then: deletes
1273     */
1274    public static void moveFile
1275        (String inFileName, String outFileName, boolean createDirsIfNotExist)
1276        throws IOException
1277    {
1278        copyFile(inFileName, outFileName, createDirsIfNotExist);
1279        (new File(inFileName)).delete();
1280    }
1281
1282    /**
1283     * This deletes an entire directory, including any sub-directories.  It is like the UNIX
1284     * switch {@code -r} for the command {@code rm}, or the old Microsoft DOS Command
1285     * {@code 'deltree'} for deleting directories.  It simply reuses the class {@code FileTransfer}
1286     *
1287     * <BR /><BR /><B CLASS=JDDescLabel>Platform Independance (WORA):</B>
1288     * 
1289     * <BR />If this method is invoked from a UNIX or LINUX platform, then it will, generally,
1290     * bring about identical results as a call to {@link Shell#RM(String, String)} where the UNIX
1291     * {@code "-r"} (recursive) flag has been included / set.  On such a platform, an entire 
1292     * directory would be eliminated.
1293     * 
1294     * <BR /><BR />However, if executed from Windows, the class {@link Shell} would fail since it
1295     * only works on UNIX, but this method here would still succeed at its task.  It is a
1296     * "Platform-Independent" function.
1297     *
1298     * @param directoryName This should be a valid directory on the File-System.
1299     *
1300     * <BR /><BR /><DIV CLASS=JDHint>
1301     * <B STYLE="color: red">WARNING:</B> This command <B>does indeed delete the entire
1302     * Directory-Tree of the named directory!</B>
1303     * </DIV>
1304     * 
1305     * @param reCreateDirectoryOnExit This parameter allows the user to create an <I>an empty
1306     * directory with the same name</I> as the directory that was just deleted, after all of the
1307     * directory's contents have been deleted.  When this parameter is passed a value of
1308     * {@code TRUE}, the equivalent of the UNIX command {@code mkdir 'directoryName'} will be 
1309     * executed prior to exiting this method.
1310     * 
1311     * <BR /><BR />This can be a small convenience if the user desired that the directory be 
1312     * cleared, rather than deleted completely.
1313     *
1314     * @param log This parameter may be null, and if it is, it will be ignored.  This shall receive
1315     * textual log output from the deletion process.
1316     *
1317     * <EMBED CLASS='external-html' DATA-FILE-ID=APPENDABLE>
1318     *
1319     * @return This shall return a count on the total number of deleted files.  Note that when
1320     * directories are deleted (not files), their deletion <I>shall not count towards</I> the
1321     * total returned in this integer.
1322     *
1323     * @throws IllegalArgumentException Throws if the {@code String} provided to parameter
1324     * {@code directoryName} does not name a valid directory on the File-System.
1325     * 
1326     * @throws IOException May be thrown by the File-System Methods which underly this method.
1327     */
1328    public static int delTree
1329        (String directoryName, boolean reCreateDirectoryOnExit, Appendable log)
1330        throws IOException
1331    {
1332        if (directoryName == null) throw new NullPointerException
1333            ("You have provided null to parameter 'directoryName', but this is not allowed here.");
1334
1335        File f = new File(directoryName);
1336
1337        if (! f.exists()) throw new IllegalArgumentException(
1338            "The directory name you have provided: [" + directoryName + "] was not found on the " +
1339            "File System.  Aborted."
1340        );
1341
1342        if (! f.isDirectory()) throw new IllegalArgumentException(
1343            "The value you have provided to parameter 'directoryName' was: " + 
1344            "[" + directoryName + "], but unfortunately this is not the name of a directory on " +
1345            "the File System, but rather a file.  This is not allowed here."
1346        );
1347
1348        // Uses class FileNode to build the directory into Java Memory.
1349        // It is possibly of interest to note, that if running this java code on a UNIX or
1350        // LINUX platform, this method should perform the exact same operation as an invocation
1351        // of Shell.RM(directoryName, "-r");
1352
1353        FileNode fn = FileNode.createRoot(directoryName).loadTree();
1354
1355        int ret = FileTransfer.deleteFilesRecursive(fn, null, null, log);
1356
1357        if (reCreateDirectoryOnExit) f.mkdirs();
1358
1359        return ret;
1360    }
1361
1362    /**
1363     * This may read a Text-File containing integer data.  If this data is a <B>Comma Separated
1364     * Value</B> {@code 'CSV'} Text-File, please pass {@code TRUE} to the parameter
1365     * {@code 'isCSV'}.  If this file contains integers that have commas between digits in groups
1366     * of three (like {@code '30,000'}) please pass {@code TRUE} to the parameter
1367     * {@code 'hasCommasInInts'}.
1368     * 
1369     * <EMBED CLASS='external-html' DATA-TYPE=int DATA-FILE-ID=FRW_READNUM_FFORMAT>
1370     * 
1371     * <BR /><BR /><DIV CLASS=JDHint>
1372     * <B STYLE='color: red;'>Number-Format:</B> The numbers in this Text-File must be parse-able
1373     * by Java class {@code java.lang.Integer} via its method
1374     * {@code Integer.parseInt(String s, int radix)}
1375     * </DIV>
1376     * 
1377     * @param fileName This should contain the File-Name which itself contains a list of integers.
1378     * These integers may be separated by either a comma ({@code ','}) or a space ({@code ' '}).
1379     * 
1380     * @param hasCommasInInts It is allowed that the file named by {@code 'fileName'} contain
1381     * integers which use the commonly found notation of having a comma between groups of three
1382     * digits within an integer.  For instance the number {@code '98765'}, to a reader, is often
1383     * represented as {@code '98,765'}.  When this parameter is set to {@code TRUE}, this method
1384     * shall simply remove any comma that is found juxtaposed between two digits before 
1385     * processing any text found in the file.
1386     * 
1387     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1388     * file, then please pass {@code TRUE} to this parameter.  If {@code FALSE} is passed here,
1389     * then it is mandatory that the individual numbers inside the Text-File are separated by at
1390     * least one white-space character.
1391     * 
1392     * <BR /><BR /><DIV CLASS=JDHintAlt>
1393     * <B STYLE='color:red;'>Important:</B> If it is decided to set both of the boolean parameters
1394     * to {@code TRUE} - <I>where the integers have commas, <B STYLE="color: red">and</B> the
1395     * integers are separated by commas</I>, it is up to the programmer to ensure that the
1396     * individual numbers, themselves, are <I>not only</I> separated by a comma, <I>but also</I>
1397     * separated by a space as well.
1398     * </DIV>
1399     *
1400     * @param radix This is the {@code 'radix'}, which is also usually called the number's 
1401     * {@code 'base'} that is to be used when parsing the numbers.  Since Java's {@code class
1402     * java.lang.Integer} is used to perform the parse, <I>both</I> the {@code 'radix'}, <I>and</I>
1403     * the data found in the Text-File must conform to the Java method
1404     * {@code Integer.parseInt(String s, int radix)}.
1405     * 
1406     * <BR /><BR /><DIV CLASS=JDHint>
1407     * This parameter may not be ignored.  If the numbers in the Text-File are to be interpreted as
1408     * standard {@code 'decimal'} (<I>Base 10</I>) numbers, then the user should simply pass the
1409     * constant {@code '10'} to this parameter.
1410     * </DIV>
1411     * 
1412     * @return This method shall return a {@code java.util.stream.IntStream} consisting of the
1413     * integers that were found within the Text-File provided by {@code 'fileName'}.
1414     * 
1415     * <BR /><BR /><DIV CLASS=JDHintAlt>
1416     * An instance of {@code IntStream} is easily converted to an {@code int[]} array using the
1417     * method {@code IntStream.toArray()}.
1418     * </DIV>
1419     * 
1420     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1421     * found or not accessible in the File-System, then this exception will throw.
1422     * 
1423     * @throws IOException This exception throws if there are any errors that occur while
1424     * reading this file from the File-System.
1425     * 
1426     * @throws NumberFormatException If any of the numbers read from the Text-File are not
1427     * properly formatted, then this exception shall throw.
1428     * 
1429     * @see StringParse#NUMBER_COMMMA_REGEX
1430     * @see StringParse#COMMA_REGEX
1431     * @see StringParse#WHITE_SPACE_REGEX
1432     */
1433    public static IntStream readIntsFromFile
1434        (String fileName, boolean hasCommasInInts, boolean isCSV, int radix)
1435        throws FileNotFoundException, IOException
1436    {
1437        FileReader          fr  = new FileReader(fileName);
1438        BufferedReader      br  = new BufferedReader(fr);
1439        IntStream.Builder   b   = IntStream.builder();
1440        String              s   = "";
1441
1442        while ((s = br.readLine()) != null)
1443        {
1444            // Skip blank lines.
1445            if ((s = s.trim()).length() == 0) continue;
1446
1447            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1448            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1449            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1450
1451            if (hasCommasInInts)
1452                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1453
1454            String[] numbers = isCSV
1455                ? StringParse.COMMA_REGEX.split(s)
1456                : StringParse.WHITE_SPACE_REGEX.split(s);
1457 
1458            for (String number : numbers)
1459
1460                if ((number = number.trim()).length() > 0)
1461                    b.accept(Integer.parseInt(number, radix));
1462        }
1463
1464        br.close();
1465        fr.close();
1466        return b.build();
1467    }
1468
1469    /**
1470     * This may read a Text-File containing integer data.  If this data is a <B>Comma Separated
1471     * Value</B> {@code 'CSV'} Text-File, please pass {@code TRUE} to the parameter
1472     * {@code 'isCSV'}.  If this file contains integers that have commas between digits in groups
1473     * of three (like {@code '30,000'}) pleas pass {@code TRUE} to the parameter
1474     * {@code 'hasCommasInLongs'}.
1475     * 
1476     * <EMBED CLASS='external-html' DATA-TYPE=long DATA-FILE-ID=FRW_READNUM_FFORMAT>
1477     * 
1478     * <BR /><BR /><DIV CLASS=JDHint>
1479     * <B STYLE='color: red;'>Number-Format:</B> The numbers in this Text-File must be parse-able
1480     * by Java class {@code class java.lang.Long} using the method
1481     * {@code Long.parseLong(String s, int radix)}
1482     * </DIV>
1483     * 
1484     * @param fileName This should contain the File-Name which itself contains a list of 
1485     * {@code 'long'} integers.  These {@code long} integers may be separated by either a comma
1486     * ({@code ','}) or a space ({@code ' '}).
1487     * 
1488     * @param hasCommasInLongs It is allowed that the file named by {@code 'fileName'} contain
1489     * {@code long}-integers which use the commonly found notation of having a comma between groups
1490     * of three digits within a {@code long} integer.  For instance the number {@code '98765'}, to
1491     * a reader, is often represented as {@code '98,765'}.  When this parameter is set to
1492     * {@code TRUE}, this method shall simply remove any comma that is found juxtaposed between
1493     * two digits before processing any text found in the file.
1494     *
1495     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1496     * file, then please pass {@code TRUE} to this parameter.  If {@code FALSE} is passed here,
1497     * then it is mandatory that the individual numbers inside the Text-File are separated by at
1498     * least one white-space character.
1499     * 
1500     * <BR /><BR /><DIV CLASS=JDHintAlt>
1501     * <B STYLE='color: red'>Important:</B> If it is decided to set both of the boolean parameters
1502     * to {@code TRUE} - <I>where the {@code long} integers have commas,
1503     * <B STYLE='color: red'>and</B> the {@code long} integers are separated by commas</I>, it is
1504     * up to the programmer to ensure that the individual numbers, themselves, are <I>not only</I> 
1505     * separated by a comma, <I>but also</I> separated by a space as well.
1506     * </DIV>
1507     * 
1508     * @param radix This is the {@code 'radix'}, which is also usually called the number's 
1509     * {@code 'base'} that is to be used when parsing the numbers.  Since Java's {@code class
1510     * Long} is used to perform the parse, <I>both</I> the {@code 'radix'}, <I>and</I> the data
1511     * found in the Text-File must conform to the Java method
1512     * {@code Long.parseLong(String s, int radix)}.
1513     * 
1514     * <BR /><BR /><DIV CLASS=JDHint>
1515     * This parameter may not be ignored.  If the numbers in the Text-File are to be interpreted as
1516     * standard {@code 'decimal'} (<I>Base 10</I>) numbers, then the user should simply pass the
1517     * constant {@code '10'} to this parameter.
1518     * </DIV>
1519     * 
1520     * @return This method shall return a {@code java.util.stream.LongStream} consisting of the
1521     * {@code long}-integers that were found within the Text-File provided by {@code 'fileName'}.
1522     * 
1523     * <BR /><BR /><DIV CLASS=JDHintAlt>
1524     * An instance of {@code LongStream} is easily converted to a {@code long[]} array using the
1525     * method {@code LongStream.toArray()}.
1526     * </DIV>
1527     * 
1528     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1529     * found or not accessible in the File-System, then this exception will throw.
1530     * 
1531     * @throws IOException This exception throws if there are any errors that occur while
1532     * reading this file from the File-System.
1533     * 
1534     * @throws NumberFormatException If any of the numbers read from the Text-File are not
1535     * properly formatted, then this exception shall throw.
1536     * 
1537     * @see StringParse#NUMBER_COMMMA_REGEX
1538     * @see StringParse#COMMA_REGEX
1539     * @see StringParse#WHITE_SPACE_REGEX
1540     */
1541    public static LongStream readLongsFromFile
1542        (String fileName, boolean hasCommasInLongs, boolean isCSV, int radix)
1543        throws FileNotFoundException, IOException
1544    {
1545        FileReader          fr  = new FileReader(fileName);
1546        BufferedReader      br  = new BufferedReader(fr);
1547        LongStream.Builder  b   = LongStream.builder();
1548        String              s   = "";
1549
1550        while ((s = br.readLine()) != null)
1551        {
1552            // Skip blank lines.
1553            if ((s = s.trim()).length() == 0) continue;
1554
1555            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1556            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1557            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1558
1559            if (hasCommasInLongs)
1560                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1561
1562            String[] numbers = isCSV
1563                ? StringParse.COMMA_REGEX.split(s)
1564                : StringParse.WHITE_SPACE_REGEX.split(s);
1565 
1566            for (String number : numbers)
1567                if ((number = number.trim()).length() > 0)
1568                    b.accept(Long.parseLong(number, radix));
1569        }
1570
1571        br.close();
1572        fr.close();
1573        return b.build();
1574    }
1575
1576    /**
1577     * This may read a Text-File containing floating-point data.  If this data is a <B>Comma
1578     * Separated Value</B> {@code 'CSV'} Text-File, please pass {@code TRUE} to the parameter
1579     * {@code 'isCSV'}.  If this file contains {@code double's} that have commas between digits
1580     * in groups of three (like {@code '30,000,000,00'}) pleas pass {@code TRUE} to the parameter
1581     * {@code 'hasCommasInDoubles'}.
1582     *
1583     * <EMBED CLASS='external-html' DATA-TYPE=long DATA-FILE-ID=FRW_READNUM_FFORMAT>
1584     * 
1585     * <BR /><BR /><DIV CLASS=JDHint>
1586     * <B STYLE='color: red;'>Number-Format:</B> The numbers in this Text-File must be parse-able
1587     * by Java class {@code class java.lang.Double} using the method
1588     * {@code Double.parseDouble(String s)}
1589     * </DIV>
1590     * 
1591     * @param fileName This should contain the File-Name which itself contains a list of 
1592     * {@code 'double'} values.  These {@code double's} may be separated by either a comma
1593     * ({@code ','}) or a space ({@code ' '}).
1594     * 
1595     * @param hasCommasInDoubles It is allowed that the file named by {@code 'fileName'} contain
1596     * {@code double}-values which use the commonly found notation of having a comma between groups 
1597     * of three digits within a {@code double} value.  For instance the number {@code '98765.01'},
1598     * to a reader, can be represented as {@code '98,765.01'}.  When this parameter is set to
1599     * {@code TRUE}, this method shall simply remove any comma that is found juxtaposed between
1600     * two digits before processing any text found in the file.
1601     *
1602     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1603     * file, then please pass {@code TRUE} to this parameter.  If {@code FALSE} is passed here,
1604     * then it is mandatory that the individual numbers inside the Text-File are separated by at
1605     * least one white-space character.
1606     * 
1607     * <BR /><BR /><DIV CLASS=JDHintAlt>
1608     * <B STYLE="color: red">Important:</B> If it is decided to set both of the boolean parameters
1609     * to {@code TRUE} - <I>where the {@code double} values have commas,
1610     * <B STYLE="color: red">and</B> the {@code double} values are separated by commas</I>, it is
1611     * up to the programmer to ensure that the individual numbers, themselves, are <I>not only</I> 
1612     * separated by a comma, <I>but also</I> separated by a space as well.
1613     * </DIV>
1614     * 
1615     * @return This method shall return a {@code java.util.stream.DoubleStream} consisting of the
1616     * {@code double}-values that were found within the Text-File provided by {@code 'fileName'}.
1617     * 
1618     * <BR /><BR /><DIV CLASS=JDHint>
1619     * An instance of {@code DoubleStream} is easily converted to a {@code double[]} array using
1620     * the method {@code DoubleStream.toArray()}.
1621     * </DIV>
1622     * 
1623     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1624     * found or not accessible in the File-System, then this exception will throw.
1625     * 
1626     * @throws IOException This exception throws if there are any errors that occur while
1627     * reading this file from the File-System.
1628     * 
1629     * @throws NumberFormatException If any of the numbers read from the Text-File are not
1630     * properly formatted, then this exception shall throw.
1631     * 
1632     * @see StringParse#NUMBER_COMMMA_REGEX
1633     * @see StringParse#COMMA_REGEX
1634     * @see StringParse#WHITE_SPACE_REGEX
1635     */
1636    public static DoubleStream readDoublesFromFile
1637        (String fileName, boolean hasCommasInDoubles, boolean isCSV)
1638        throws FileNotFoundException, IOException
1639    {
1640        FileReader              fr  = new FileReader(fileName);
1641        BufferedReader          br  = new BufferedReader(fr);
1642        DoubleStream.Builder    b   = DoubleStream.builder();
1643        String                  s   = "";
1644
1645        while ((s = br.readLine()) != null)
1646        {
1647            // Skip blank lines.
1648            if ((s = s.trim()).length() == 0) continue;
1649
1650            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1651            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1652            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1653
1654            if (hasCommasInDoubles)
1655                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1656
1657            String[] numbers = isCSV
1658                ? StringParse.COMMA_REGEX.split(s)
1659                : StringParse.WHITE_SPACE_REGEX.split(s);
1660 
1661            for (String number : numbers)
1662
1663                if ((number = number.trim()).length() > 0)
1664                    b.accept(Double.parseDouble(number));
1665        }
1666
1667        br.close();
1668        fr.close();
1669        return b.build();
1670    }
1671
1672    /**
1673     * Convenience Method.
1674     * <BR />Invokes: {@code java.io.FileOutputStream.write(byte[])}.
1675     * <BR /><B>NOTE:</B> This may throw {@code IOException, FileNotFoundException}, etc...
1676     */
1677    public static void writeBinary(byte[] bArr, String fileName) throws IOException
1678    {
1679        // The input-stream is  'java.lang.AutoCloseable'.
1680        //
1681        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
1682        //       caught!  This also (potentially) throws SecurityException & FileNotFoundException
1683
1684        try
1685            (FileOutputStream fos = new FileOutputStream(fileName))
1686            { fos.write(bArr); }
1687    }
1688
1689
1690    /**
1691     * Convenience Method.
1692     * <BR />Invokes: {@code java.io.FileOutputStream.write(byte[], int, int)}.
1693     * <BR /><B>NOTE:</B> This may throw {@code IOException, IndexOutOfBoundsException}, etc...
1694     */
1695    public static void writeBinary(byte[] bArr, int offset, int len, String fileName)
1696        throws IOException
1697    {
1698        // The input-stream is  'java.lang.AutoCloseable'.
1699        //
1700        // NOTE: The IOException will still be thrown out of this method if it occurs.  It is not
1701        //       caught!  This also (potentially) throws SecurityException, FileNotFoundException,
1702        //       IndexOutOfBoundsExcepton (if 'offset' or 'len' do not adhere to 'bArr' definition)
1703
1704        try
1705            (FileOutputStream fos = new FileOutputStream(fileName))
1706            { fos.write(bArr, offset, len); }
1707    }
1708
1709    /**
1710     * Convenience Method.
1711     * <BR />Invokes: {@link #readBinary(String, int, int)}.
1712     */
1713    public static byte[] readBinary(String fileName)
1714        throws FileNotFoundException, IOException
1715    { return readBinary(fileName, 0, -1); }
1716
1717    /**
1718     * Reads data from a binary file into a Java {@code byte[]} array.
1719     * 
1720     * <BR /><BR />Unlike Java's {@code FileOutputStream.write(...)} method, the {@code read(...)}
1721     * Java provides in that same exact class is more difficult to use.  This method is much longer
1722     * than its corresponding {@link #writeBinary(byte[], String)} method.
1723     * 
1724     * @param fileName The name of the file (on the File-System) to read.
1725     * 
1726     * @param offset The number of {@code byte's} to skip before appending a {@code byte} into the
1727     * output {@code byte[]} array.  If the value provided to parameer {@code 'offset'} is longer
1728     * than the size of the file itself, then a <B>zero-length {@code byte[]} array</B> will be
1729     * returned.
1730     * 
1731     * <BR /><BR /><DIV CLASS=JDHint>
1732     * The meaning of the value in parameter {@code 'offset'} is very different from the meaning of
1733     * a parameter by that exact same name, except in method {@code read(...)} of class
1734     * {@code FileInputStream}.  <B>HERE</B> the offset is the number of <I>bytes to skip inside of
1735     * file {@code 'fileName'}, before saving the values that are read froom disk.</I>  In the
1736     * {@code FileInputStream}, offset is used as an array pointer.
1737     * </DIV>
1738     * 
1739     * @param len Once the internal-loop has begun copying bytes from the Data-File into the
1740     * returned {@code byte[]} array, {@code byte's} will continue to be copied into this array
1741     * until precisely {@code 'len'} bytes have been copied.
1742     * 
1743     * <BR /><BR /><DIV CLASS=JDHintAlt>
1744     * The user may provide any negative number to this parameter, and the read process will simply
1745     * begin at position {@code 'offset'}, and continue reading until the End of the File has been
1746     * reached.
1747     * </DIV>
1748     * 
1749     * @return Returns a {@code byte[]}-array, with a length of (parameter) {@code 'len'} bytes.
1750     * 
1751     * @throws FileNotFoundException Class {@code java.io.FileInputStream} will throw a
1752     * {@code FileNotFoundException} if that class is passed a {@code 'fileName'} that does not
1753     * exist, or is a File-Name that represents a directory not a file.
1754     * 
1755     * @throws SecurityException If a security manager exists and its {@code checkRead} method
1756     * denies read access to the file.
1757     * 
1758     * @throws IOException The {@code FileInputStream} instance, and the {@code java.io.File}
1759     * instance are both capable of throwing {@code IOException}.
1760     * 
1761     * @throws IllegalArgumentException
1762     * 
1763     * <BR /><BR /><UL CLASS=JDUL>
1764     * <LI> The value provided to {@code 'offset'} is negative</LI>
1765     * <LI> The value provided to parameter {@code 'len'} is zero.</LI>
1766     * </UL>
1767     * 
1768     * @throws FileSizeException This exception throws if any of the following are detected:
1769     * 
1770     * <BR /><BR /><UL CLASS=JDUL>
1771     * 
1772     * <LI> The returned {@code byte[]} array cannot have a size larger than
1773     *      {@code Integer.MAX_VALUE}.  If the returned array would have a larger size - <I>based
1774     *      on any combination of provided input values</I>, then this exception will throw.
1775     *      <BR /><BR />
1776     *      </LI>
1777     * 
1778     * <LI> The value provided to {@code 'offset'} is larger than the size of the underlying
1779     *      file that is specified by parameter {@code 'fileName'}
1780     *      <BR /><BR />
1781     *      </LI>
1782     * 
1783     * <LI> The total of {@code offset + len} is larger than the size of the underlying file.
1784     *      <BR /><BR />
1785     *      </LI>
1786     * 
1787     * <LI> An invocation of the method {@code File.length()} returns zero, indicating that the
1788     *      file is either unreadable, non-existant, or possibly empty.
1789     *      </LI>
1790     * 
1791     * </UL>
1792     * 
1793     * @throws EOFException This particular exception should not be expected.  Before any reads are
1794     * done, the size of the Data-File is first checked to see if it is big enough to have the
1795     * amount of data that is requested by input paramters {@code 'offset'} and {@code 'len'}.  If
1796     * however, an error occurrs, and the Operating System returns an {@code EOF} earlier than 
1797     * expected (for unforseen reasons), then {@code EOFException} would throw.
1798     */
1799    public static byte[] readBinary(String fileName, int offset, int len)
1800        throws FileNotFoundException, IOException
1801    {
1802        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1803        // EXCEPTION CHECKS
1804        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1805
1806        if (offset < 0) throw new IllegalArgumentException
1807            ("The value of offset (" + offset + ") is negative.");
1808
1809        if (len == 0) throw new IllegalArgumentException
1810            ("A value of zero was provided to parameter 'len.'  This is not allowed");
1811
1812        File f = new File(fileName);
1813
1814        try
1815            (FileInputStream fis = new FileInputStream(f))
1816        {
1817            long fileLen = f.length();
1818
1819            // A file-name that points to a completely empty file was passed to parameter 'fileName'
1820            // **OR** the operating system returned 0 for some other error-related reason.
1821            // According to the Java-Doc explanation, a '0' returned value might mean there were
1822            // errors when trying to access the file.
1823
1824            if (fileLen == 0) throw new FileSizeException(
1825                "Calling java.io.File.length() returned 0 for the file-name that was " +
1826                "provided to this method:\n[" + fileName + "].  This might mean that the file " +
1827                "does not exist, or has other issues.",
1828                0
1829            );
1830
1831            // The file would end before we have even started reading bytes - after having skipped
1832            // 'offset' nmber of initial bytes.
1833
1834            if (offset > fileLen) throw new FileSizeException(
1835                "The value of offset (" + offset + ") is larger than the size of the binary " +
1836                "file, which only had size (" + fileLen + ").",
1837                fileLen
1838            );
1839
1840            // If 'len' was passed a negative value, that value is actually meaningless - and was
1841            // just used to indicate that reading the entire file starting at byte # 'offset' is
1842            // what the user is requesting.
1843
1844            if (len > 0)
1845
1846                // This simply checks how many bytes the file would need to have to provide for
1847                // reading from 'offset' up until 'offset + len'
1848
1849                if ((offset + len) > fileLen) throw new FileSizeException(
1850                    "The file [" + fileName + "] apparently has a size of [" + fileLen + "], " +
1851                    "but the offset (" + offset + ") and the length (" +  len + ") that was " +
1852                    "requested sum to (" + (offset+len) + "), which is greater than that size.",
1853                    fileLen
1854                );
1855
1856
1857            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1858            // More Exception Checks: Check what the size of the returned byte[] array will be
1859            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1860
1861            // Negative-Length means that bytes are read until an EOF occurrs
1862            if (len < 0)
1863            {
1864                long bytesToRead = fileLen - offset;
1865
1866                if (bytesToRead > Integer.MAX_VALUE) throw new FileSizeException(
1867                    "This file has [" + fileLen + "] bytes of data, and even with an offset of " +
1868                    '[' + offset + "], there are still " + bytesToRead + " bytes of data to " +
1869                    "place into the array.  This value is larger than Integer.MAX_VALUE " +
1870                    "(" + Integer.MAX_VALUE + ").",
1871                    fileLen
1872                );
1873
1874                else len = (int) bytesToRead;
1875            }
1876
1877            // A Positive length means that exactly the value inside input-parameter 'len' need to
1878            // be placed into the returned byte[] array.
1879
1880            else if (len > Integer.MAX_VALUE) throw new FileSizeException(
1881                "This file has [" + fileLen + "] bytes of data, which is larger than " +
1882                "Integer.MAX_VALUE (" + Integer.MAX_VALUE + ").",
1883                fileLen
1884            );
1885
1886
1887            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1888            // FIRST, if there is a NON-ZERO OFFSET, then exactly OFFSET bytes must be skipped
1889            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1890
1891            int     failedReads         = 0;
1892            long    bytesSkipped        = 0;
1893            int     totalBytesSkipped   = 0;
1894            int     bytesRemaining      = offset;
1895
1896            // NOTE: This loop is skipped, immediately / automatically, if indeed 'offset' is zero
1897            while (totalBytesSkipped < offset)
1898
1899                if ((bytesSkipped = fis.skip(bytesRemaining)) == 0)
1900                {
1901                    if (failedReads++ == 10) throw new IOException(
1902                        "There have 10 repeated attempts to read the file's data, but all " +
1903                        "attempts have resulted in empty-reads with zero bytes being  retrieved " +
1904                        "from disk."
1905                    );
1906                }
1907                else
1908                {
1909                    // I don't see why this should happen, but it will be left here, just in
1910                    // case Java screws up.
1911
1912                    if (bytesSkipped > bytesRemaining) throw new InternalError(
1913                        "This error is being thrown because the Java Virtual Machine has " +
1914                        "skipped past the end of the requested offset.  This has occured while " +
1915                        "calling FileInputStream.skip(offset)."
1916                    );
1917
1918                    // NOTE: I am *FULLY AWARE* this is redundant, but the variable names are
1919                    //       the only thing that is very readable about this method.
1920
1921                    totalBytesSkipped += bytesSkipped;
1922                    bytesRemaining -= bytesSkipped;
1923                }
1924
1925
1926            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1927            // THEN, Read the bytes from the file
1928            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1929
1930            byte[]  retArr      = new byte[len];
1931            int     arrPos      = 0;
1932
1933            // This one was already defined / declared in the previous part.  Initialize it, but
1934            // don't re-declare it.
1935            bytesRemaining = len;
1936
1937            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1938            // This loop exits when all (requested) bytes have been read, or EOFException!
1939            // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1940
1941            while (bytesRemaining > 0)
1942            {
1943                int bytesRead = fis.read(retArr, arrPos, bytesRemaining);
1944
1945
1946                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1947                // Case 1: The EOF Marker was reached, before filling up the response-array
1948                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1949
1950                if (bytesRead == -1) if (bytesRemaining > 0) throw new EOFException(
1951                    "The end of file '" + fileName + "' was reached before " + len +
1952                    " bytes were read.  Only " + arrPos + " bytes were read."
1953                );
1954
1955
1956                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1957                // Yes, this seems redundant, but it's just the way it is
1958                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1959                // 
1960                // arrPos           ==>  0 ... retArr.length (retArr.length => input-param 'len')
1961                // bytesRemaining   ==>  'len' ... 0
1962
1963                arrPos          += bytesRead;
1964                bytesRemaining  -= bytesRead;
1965
1966
1967                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1968                // Case 2: Exactly the appropriate number of bytes were read.
1969                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1970
1971                if (arrPos == retArr.length) return retArr;
1972
1973
1974                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1975                // Case 3: This should not be reachable!
1976                // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
1977                //
1978                // Java's FileInputStream.read method is supposed to stop when it has read
1979                // 'bytesRemaining' number of bytes!
1980
1981                if (arrPos > retArr.length) throw new UnreachableError();
1982            }
1983
1984            // One of the cases inside the loop body must fire before the loop ever actually exits,
1985            // unless I'm missing something!
1986            throw new UnreachableError();
1987        }
1988    }
1989
1990    /**
1991     * Attempts to read a {@code java.lang.Class} from a class-file.
1992     * 
1993     * <BR /><BR />It is not neccessarily always knownst to the programmer what the
1994     * <I>Full-Package Class-Name</I>  of the {@code class} that's inside the class-file actually
1995     * is.
1996     * 
1997     * @param classFileName The name of any class-file on the File-System.
1998     * 
1999     * @param possibleClassNames If the exact <B STYLE='color: red;'>Full-Package Name</B> of the
2000     * class being read is known, then that ought to be the only {@code String} passed to this
2001     * Var-Args {@code String}-Parameter.  If there are multiple possibilities, pass all of them,
2002     * and all we be used in an attempt to parse &amp; load this class.
2003     * 
2004     * <BR /><BR /><B>DEVELOPER NOTE:</B> Perhaps I am stupid, but I cannot find any way (using the
2005     * standard JDK) to read a class file, and then ask that class-file either:
2006     * 
2007     * <BR /><BR /><UL CLASS=JDUL>
2008     * <LI>What the name of the {@code Class} in the Class-File is?</LI>
2009     * <LI>What the name of the {@code Package} being used in that Class-File is?</LI>
2010     * </UL>
2011     * 
2012     * <BR />So, for now, a Var-Args {@code String}-Array is required.
2013     * 
2014     * <BR /><BR />To add insult to injury, the standard Java Exception class
2015     * {@code 'TypeNotPresentException'} doesn't have a constructor that accepts a
2016     * {@code 'message'} parameter (it accepts a {@code 'typeName'}) parameter instead.  As a
2017     * result, if this method throws that exception, the error-message printed has a few 
2018     * 'extranneous characters' <B>BOTH</B> before the actual message, <B>AND</B> after it.  It is
2019     * staying this way because Java's Description of that exception matches precisely with its
2020     * use here.
2021     * 
2022     * @return An instance of {@code java.lang.Class} that was contained by the Class-File.
2023     * 
2024     * @throws IOException Java may throw several exceptions while attempting to load
2025     * the {@code '.class'} file into a {@code byte[]} array.  Such exceptions may include
2026     * {@code IOException}, {@code SecurityException}, {@code FileNotFoundException} etc...
2027     * 
2028     * @throws TypeNotPresentException If none of the names listed in Var-Args {@code String[]}
2029     * Array parameter {@code 'possibleClassNames'} are consistent with
2030     * <B STYLE='color:red'><I>BOTH</I></B> the package-name <B STYLE='color:red;'>AND</B> the
2031     * class-name of the actual class that resides inside {@code 'classFileName'}, then this 
2032     * exception will throw.
2033     * 
2034     * <BR /><BR /><DIV CLASS=JDHint>
2035     * <B STYLE='color:red'>Note:</B> Rather than simply returning 'null', this
2036     * {@code RuntimeException} is thrown as a nicely worded error message is provided.
2037     * </DIV>
2038     */
2039    public static Class<?> readClass(String classFileName, String... possibleClassNames)
2040        throws IOException
2041    {
2042        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2043        // Load the entire '.class' file into a byte[] array.
2044        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2045
2046        // Now read the bytes directly into a byte[] array, courtesy of Torello.Java.FileRW
2047        byte[] byteArray = readBinary(classFileName);
2048
2049
2050        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2051        // Create a ClassLoader - and convert "byte[]" into "Class<?> summarySorterClass"
2052        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2053
2054        // NOTE: The commented lines below will not work if there is a "Package Name" given to the
2055        //       class (in the '.java' file).  It is better to just read the raw class-file bytes 
2056        //       into a Java byte[] array.  SPECIFICALLY, if class is not inside of a directory
2057        //       that is PRECISELY-CONSISTENT with the full-path package+class name, then the class
2058        //       loader listed below will FAIL.  (NOTE: If the package+class name is consisten,
2059        //       then a programmer wouldn't need ANY OF THIS, and could simply call the normal
2060        //       "Class.forName(fullPackageClassName)" to get the class!)
2061        //
2062        // ClassLoader cl = new URLClassLoader(new URL[] { new URL("file://" + path) });
2063        // Class<?> ret = cl.loadClass(className);
2064        //
2065        // HOWEVER: Creating a new ClassLoader instance that accepts the byte-array (thereby
2066        //          exporting the 'protected' method defineClass) *DOES* work.
2067
2068        class ByteClassLoader extends ClassLoader
2069        {
2070            public Class<?> defineClass(String name, byte[] classBytes)
2071            { return defineClass(name, classBytes, 0, classBytes.length); }
2072        }
2073
2074        ByteClassLoader cl  = new ByteClassLoader();
2075        Class<?>        ret = null;
2076        StringBuilder   sb  = new StringBuilder();
2077
2078
2079        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2080        // Make attempts to convert the byte[] array into a java.lang.Class
2081        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
2082
2083        for (String fullClassName : possibleClassNames)
2084
2085            try
2086                { return cl.defineClass(fullClassName, byteArray); }
2087
2088            catch (NoClassDefFoundError e)
2089            {
2090                String errorMessage = e.getMessage();
2091
2092                if (errorMessage != null) sb.append(errorMessage + '\n');
2093            }
2094
2095        throw new TypeNotPresentException(
2096            "The Class-File: [" + classFileName  + "] seems to have been successfully read, but " +
2097            "none of the user provided Canonical-Class Names (including a package) were " +
2098            "consistent with the actual name of the Class/Type inside that class file.  Below " +
2099            "are listed the exception-messages received, which include both the actual/complete " +
2100            "canonical class-name, along with your user-provided guesses.  Errors Saved:\n" +
2101            StrIndent.indent(sb.toString(), 4),
2102            null
2103        );
2104    }
2105}