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.HTML.Tools.JavaDoc.StaticFunctional;
013import Torello.HTML.Tools.JavaDoc.Excuse;
014
015/**
016 * <CODE>File Read / Write - Documentation.</CODE><BR /><BR />
017 * <EMBED CLASS="external-html" DATA-FILE-ID="FRW">
018 */
019@StaticFunctional(Excused="TRUNCATE_EOF_CHARS", Excuses=Excuse.FLAG)
020public class FileRW
021{
022    private FileRW() { }
023
024    /**
025     * This is used by method {@link #loadFileToString(String)}.  By default this flag is set to
026     * <B>TRUE</B>, and when it is, any trailing {@code EOF chars, ASCII-0} found in a file that
027     * is to be interpreted as a text-file, will be truncated from the {@code String} returned by
028     * that {@code 'reader'} method.
029     */
030    public static boolean TRUNCATE_EOF_CHARS = true;
031
032    // ********************************************************************************************
033    // writeFile
034    // ********************************************************************************************
035
036
037    /**
038     * Writes the entire contents of a single {@code java.lang.String} to a file on the file-system
039     * named {@code 'fName'}.
040     *
041     * <BR /><BR /><B>NOTE:</B> Though the file does not need to exist already in order for this
042     * file to be written (in fact, if it does exist - <I>it will be 'over-written'</I>) the
043     * directory hierarchy needs to exist, or a java {@code 'IOException'} will occur.
044     *
045     * @param s A {@code java.lang.String} which is appended, in entirety, to the file ('fName')
046     *
047     * @param fName The name of the file to which the contents of the {@code java.lang.String}
048     * are appended.  If This file doesn't already exist, it is created here.
049     *
050     * @throws IOException If any I/O errors have occurred with the file-system / disk.
051     */
052    public static void writeFile(CharSequence s, String fName) throws IOException
053    {
054        File outF = new File(fName);
055        outF.createNewFile();
056        FileWriter fw = new FileWriter(outF);
057        fw.write(s.toString());
058        fw.close();
059    }
060
061    /** Convenience Method.  Invokes {@link #writeFile(Iterator, String)} */
062    public static void writeFile(Iterable<String> i, String fName) throws IOException
063    { writeFile(i.iterator(), fName); }
064
065    /** Convenience Method.  Invokes {@link #writeFile(Iterator, String)} */
066    public static void writeFile(Stream<String> stringStream, String fName) throws IOException
067    { writeFile(stringStream.iterator(), fName); }
068
069    /**
070     * This takes an {@code Iterator<String>}, and a filename, and writes each
071     * {@code java.lang.String} in the {@code Iterator} to the file named by parameter
072     * {@code 'fName'}
073     *
074     * <BR /><BR /><B><I>NOTE:</I></B> A newline {@code ('\n')} is appended to the end of each
075     * {@code java.lang.String} before writing it to the file.
076     *
077     * <BR /><BR /><B>ALSO:</B> Though the file does not need to exist already in order for this
078     * file to be written (in fact, if it does exist - <I>it will be 'over-written'</I>) the
079     * directory hierarchy needs to exist, or a java {@code 'IOException'} will occur.
080     *
081     * @param i This is any java {@code Iterable<String>} object.  Each of these will be written,
082     * in succession, to the file named by parameter {@code 'fName'}
083     *
084     * @param fName The name of the file to which the contents of the {@code java.lang.String}
085     * are appended.  If This file doesn't already exist, it is created here.
086     *
087     * @throws IOException If an I/O error has occurred as a result of the file-system or
088     * disk operation.
089     */
090    public static void writeFile(Iterator<String> i, String fName) throws IOException
091    {
092        File outF = new File(fName);
093        outF.createNewFile();
094        FileWriter fw = new FileWriter(outF);
095        while (i.hasNext()) fw.write(i.next() + "\n");
096        fw.close();
097    }
098
099
100    // ********************************************************************************************
101    // writeFile_NO_NEWLINE
102    // ********************************************************************************************
103
104
105    /** Convenience Method.  Invokes {@link #writeFile_NO_NEWLINE(Iterator, String)} */
106    public static void writeFile_NO_NEWLINE(Iterable<String> i, String fName)
107        throws IOException
108    { writeFile_NO_NEWLINE(i.iterator(), fName); }
109
110    /** Convenience Method.  Invokes {@link #writeFile_NO_NEWLINE(Iterator, String)} */
111    public static void writeFile_NO_NEWLINE(Stream<String> stringStream, String fName)
112        throws IOException
113    { writeFile_NO_NEWLINE(stringStream.iterator(), fName); }
114
115    /**
116     * This takes an {@code Iterator} of String, and a filename and writes each
117     * {@code java.lang.String} in the {@code Iterator} to the file named by parameter
118     * {@code 'fName'}
119     *
120     * <BR /><BR /><B>NOTE:</B> in this function a newline {@code ('\n')} character <I>is 
121     * <B>not</B> appended</I> to the end of each {@code java.lang.String} of the input
122     * {@code Iterator}.
123     *
124     * <BR /><BR /><B>ALSO:</B> Though the file does not need to exist already in order for this
125     * file to be written (in fact, if it does exist - <I>it will be 'over-written'</I>) the
126     * directory hierarchy needs to exist, or a java {@code 'IOException'} will occur.
127     *
128     * @param i This is any java {@code 'Iterable'} object that can iterate {@code 'String'}.
129     * Each of these will be written, in succession, to the file named by {@code 'fName'}
130     *
131     * @param fName The name of the file to which the contents of the {@code java.lang.String}
132     * are appended.  If This file doesn't already exist, it is created here.
133     *
134     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
135     * operation.
136     */
137    public static void writeFile_NO_NEWLINE(Iterator<String> i, String fName) throws IOException
138    {
139        File outF = new File(fName);
140        outF.createNewFile();
141        FileWriter fw = new FileWriter(outF);
142        while (i.hasNext()) fw.write(i.next());
143        fw.close();
144    }
145
146
147    // ********************************************************************************************
148    // appendToFile
149    // ********************************************************************************************
150
151
152    /**
153     * Appends the entire input {@code java.lang.String} - actually a
154     * {@code java.lang.CharSequence} to the file on the file-system named {@code 'fName'}
155     *
156     * <BR /><BR /><B>NOTE:</B> Though the file does not need to exist already in order for this
157     * file to be written the directory hierarchy needs to exist, or a java {@code 'IOException'}
158     * will occur.
159     *
160     * @param s A {@code java.lang.CharSequence} (almost identical to {@code 'String'}) which is
161     * appended, in entirety, to the file-name parameter {@code 'fName'}
162     *
163     * @param fName The name of the file to which the contents of the {@code java.lang.String} are
164     * appended.  If This file doesn't already exist, it is created here.
165     *
166     * @throws IOException If an I/O error has occurred as a result of the file-system or
167     * disk operation.
168     */
169    public static void appendToFile(CharSequence s, String fName) throws IOException
170    {
171        File f = new File(fName);
172        if (! f.exists()) f.createNewFile();
173        Files.write(Paths.get(fName), s.toString().getBytes(), StandardOpenOption.APPEND);
174    }
175
176
177    // ********************************************************************************************
178    // load File (as String's}
179    // ********************************************************************************************
180
181
182    /** Convenience Method.  Invokes {@link #loadFileToString(String)} */
183    public static StringBuffer loadFileToStringBuffer(String fName) throws IOException
184    { return (new StringBuffer()).append(loadFileToString(fName)); }
185
186    /** Convenience Method.  Invokes {@link #loadFileToString(String)} */
187    public static StringBuilder loadFileToStringBuilder(String fName) throws IOException
188    { return (new StringBuilder()).append(loadFileToString(fName)); }
189
190    /**
191     * This will load the entire contents of a text-file on disk to a single
192     * {@code java.lang.String}
193     * 
194     * <BR /><BR /><B><SPAN STYLE='color: red;'>TRUNCATES TRAILING ZEROES:</B></SPAN> Some of the
195     * ugliest code to see is that which finds {@code 'EOF'} characters liberally inserted into a
196     * simple text-file.  When reading a file (which, regardless of whether it <I>actually is a
197     * text-file</I>), this method will remove any <I>trailing {@code ASCII-0}</I> characters
198     * (literally, {@code char c == 0}) from the files that are read.  Finding {@code '.html'} or
199     * {@code '.java'} files in which some editor (for whatever reason) has inserted 
200     * {@code EOF-like} characters to the end of the text can make programming a headache.
201     * 
202     * <BR /><BR />Suffice it to say, the {@code String} that is returned from this method will
203     * contain the last non-zero character (including CRLF, {@code '\n'} or {@code '\r'}
204     * character that was read from the file.  Operating-systems do not need to have a trailing
205     * zero (legacy {@code 'End-of-File'} marker) at the end of a text-file in order to interpret
206     * that file.  Java {@code String's} certainly have no use for them, and when worry about
207     * things like {@code equality-tests} inside Java loops, eliminating the {@code EOF}
208     * character altogether is actually best.
209     *
210     * <BR /><BR /><B><SPAN STYLE="color: red;">FINALLY:</B></SPAN> This class has a
211     * {@code static boolean} flat can shunt this behavior, and all {@code String's} returned will
212     * include as many trailing zero characters at the end of the {@code String} returned by this
213     * method as the Java {@code FileReader} class finds.  See {@link #TRUNCATE_EOF_CHARS}.
214     *
215     * @param fName the file-name of a valid text-file in the file-system
216     *
217     * @return The entire contents of the file as a {@code String}.
218     *
219     * @throws IOException If an I/O error has occurred as a result of the file-system or
220     * disk operation.
221     */
222    public static String loadFileToString(String fName) throws IOException
223    {
224        FileReader  fr              = new FileReader(fName);
225        int         len             = (int) new File(fName).length();
226        char[]      cArr            = new char[len];
227        int         offset          = 0;
228        int         charsRead       = 0;
229        int         charsRemaining  = len;
230
231        try
232        {
233            while ((offset < len) && ((charsRead = fr.read(cArr, offset, charsRemaining)) != -1))
234            {
235                offset          += charsRead;
236                charsRemaining  -= charsRead;
237            }
238        }
239        finally
240            { fr.close(); }
241
242        len = cArr.length;
243
244        if (TRUNCATE_EOF_CHARS) while ((len > 0) && (cArr[len-1] == 0)) len--;
245
246        return (len != cArr.length) ? new String(cArr, 0, len) : new String(cArr); 
247    }
248
249    /** Convenience Method.  Invokes {@link #loadFileToStream(String, boolean)} */
250    public static String[] loadFileToStringArray(String fName, boolean includeNewLine)
251        throws IOException
252    { return loadFileToStream(fName, includeNewLine).toArray(String[]::new); }
253
254    /**
255     * This will load a file to a {@code java.lang.StringBuilder} instance.
256     *
257     * @param fName A file-name of a valid text-file in the file-system.
258     * 
259     * @param includeNewLine if this is <B>TRUE</B>, a {@code '\n'} (newline/CRLF) is appended to
260     * the end of each {@code java.lang.String} read from this file.  If not, the original newline
261     * characters which occur in the file, will be eliminated.
262     * 
263     * <BR /><BR /><B><SPAN STYLE="color: red;">MINOR NOTE:</SPAN></B> This method will make one
264     * (potentially minor) mistake.  If the final character in the input-file is, itself, a 
265     * new-line (if the file ends with a {@code 'CRLF' / 'CR'}), then this method should return a
266     * {@code Stream<String>} that is identical to the original file.  However, <I>if the final
267     * character in the file <B>is not</B> a new-line {@code '\n'}</I>, then the
268     * {@code Stream<String>} that is returned will have an extra new-line appended to the last
269     * {@code String} in the {@code Stream}, and the resultant {@code Stream<String>} will
270     * be longer than the original file by 1 character.
271     *
272     * @return The entire contents of the file as a series of {@code java.lang.String} contained by
273     * a {@code java.util.stream.Stream<String>}.  Converting Java {@code Stream's} to other data
274     * container types is as follows:
275     *
276     * <EMBED CLASS="external-html" DATA-FILE-ID="STRMCNVT">
277     *
278     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
279     * operation.
280     */
281    public static Stream<String> loadFileToStream(String fName, boolean includeNewLine)
282        throws IOException
283    {
284        FileReader              fr  = new FileReader(fName);
285        BufferedReader          br  = new BufferedReader(fr);
286        Stream.Builder<String>  b   = Stream.builder();
287        String                  s   = "";
288
289        try
290        {
291            if (includeNewLine) while ((s = br.readLine()) != null) b.add(s + "\n");
292            else                while ((s = br.readLine()) != null) b.add(s);
293        }
294        finally
295            { fr.close(); }
296
297        return b.build();
298    }
299
300    /** Convenience Method.  Invokes {@link #loadFileToCollection(Collection, String, boolean)} */
301    public static Vector<String> loadFileToVector(String fName, boolean includeNewLine)
302        throws IOException
303    { return loadFileToCollection(new Vector<String>(), fName, includeNewLine); }
304
305    /**
306     * This method loads the contents of a file to a {@code java.util.Collection<String>} object, 
307     * where each {@code java.lang.String} in the output / returned {@code Collection} is a
308     * different "line of text" from the input-file.  This is identical to using the method from
309     * {@code public String[] java.lang.String 'split("\n")'} - <I>and then saving each of the
310     * {@code String's} from the {@code String-array} into an element of the returned
311     * {@code Collection<String>}</I>
312     *
313     * <BR /><BR />This method uses Java's variable-type parameter syntax to allow the programmer
314     * to decide what type of {@code Collection<String>} to obtain from this method.  The standard
315     * Java {@code Collection's} include: {@code TreeSet, Vector, ArrayList, HashSet}, among
316     * others.  Provide a {@code new Collection<String>} by simply calling a constructor to one
317     * of these classes like: {@code new Collection<String>()}.
318     * 
319     * <BR /><BR /><B><SPAN STYLE="color: red;">MINOR NOTE:</SPAN></B> This method will make one
320     * (potentially minor) mistake.  If the final character in the input-file is, itself, a 
321     * new-line (if the file ends with a {@code 'CRLF' / 'CR'}), then this method should return a
322     * {@code Collection<String>} that is identical to the original file.  However, <I>if the final
323     * character in the file <B>is not</B> a new-line {@code '\n'}</I>, then the
324     * {@code Collection<String>} that is returned will have an extra new-line appended to the last
325     * {@code String} in the {@code Collection}, and the resultant {@code Collection<String>} will
326     * be longer than the original file by 1 character.
327     *
328     * @param collectionChoice This must be an instance of a class that extends Java's
329     * base {@code class Collection<String>} - <I>using {@code 'String'} as the generic-type
330     * parameter.</I>  It will be populated with the lines from a text-file using the
331     * {@code Collection.add(String)} method.
332     *
333     * @param fName the file-name of a valid text-file on the file-system.
334     *
335     * @param includeNewLine if this is <B>TRUE</B>, a {@code '\n'} (newline/CRLF) is appended to
336     * the end of each {@code java.lang.String} read from this file.  If not, the original newline
337     * characters which occur in the file, will be eliminated.
338     *
339     * @return An identical reference to the reference passed to parameter
340     * {@code 'collectionChoice'}
341     *
342     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
343     * operation.
344     */
345    public static <T extends Collection<String>> T loadFileToCollection
346        (T collectionChoice, String fName, boolean includeNewLine)
347        throws IOException
348    {
349        FileReader      fr  = new FileReader(fName);
350        BufferedReader  br  = new BufferedReader(fr);
351        String          s   = "";
352
353        try
354        {
355            if (includeNewLine) while ((s = br.readLine()) != null) collectionChoice.add(s + "\n");
356            else                while ((s = br.readLine()) != null) collectionChoice.add(s);
357        }
358        finally
359            { fr.close(); }
360
361        return collectionChoice;
362    }
363
364    // ********************************************************************************************
365    // Write ONE Object To File
366    // ********************************************************************************************
367
368
369    /** Convenience Method.  Invokes {@link #writeObjectToFile(Object, String, boolean)}, catches Exception. */
370    public static boolean writeObjectToFileNOCNFE(Object o, String fName, boolean ZIP) throws IOException
371    {
372        try 
373            { writeObjectToFile(o, fName, ZIP); return true; }
374        catch (ClassNotFoundException cnfe)
375            { return false; }
376    }
377
378    /**
379     * Writes a {@code java.lang.Object} to a file for storage, and future reference.
380     * <BR /><BR /><B>ALSO:</B> Though the file does not need to exist already in order for this
381     * file to be written (in fact, if it does exist - <I>it will be 'over-written'</I>) the
382     * directory hierarchy needs to exist, or a java {@code IOException} will occur.
383     *
384     * @param o An {@code Object} to be written to a file as a <I><B>"Serializable"</B></I>
385     * {@code java.lang.Object}
386     *
387     * @param fName The name of the output file
388     *
389     * @param ZIP a boolean that, when <B>TRUE</B>, will cause the object's data to be compressed
390     * before being written to the output file.
391     *
392     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
393     * operation.
394     *
395     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
396     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
397     * the classpath.
398     */
399    public static void writeObjectToFile
400        (Object o, String fName, boolean ZIP) 
401        throws IOException, ClassNotFoundException
402    {
403        FileOutputStream        fos     = new FileOutputStream(fName);
404
405        if (! ZIP)
406        {
407            ObjectOutputStream  oos     = new ObjectOutputStream(fos);
408            oos.writeObject(o); oos.flush(); oos.close(); fos.close();
409        }
410        else
411        {
412            GZIPOutputStream    gzip    = new GZIPOutputStream(fos);
413            ObjectOutputStream  oos     = new ObjectOutputStream(gzip);
414            oos.writeObject(o); oos.flush(); gzip.finish(); oos.close(); fos.close();   
415        }
416    }
417
418
419    // ********************************************************************************************
420    // Write ALL Objects ToFile
421    // ********************************************************************************************
422
423
424    /** Convenience Method.  Invokes {@link #writeAllObjectsToFile(Iterator, String, boolean)} */
425    public static void writeAllObjectsToFile(Iterable<?> i, String fName, boolean ZIP)
426        throws IOException, ClassNotFoundException
427    { writeAllObjectsToFile(i.iterator(), fName, ZIP); }
428
429    /** Convenience Method.  Invokes {@link #writeAllObjectsToFile(Iterator, String, boolean)}, catches Exception. */
430    public static boolean writeAllObjectsToFileNOCNFE(Iterable<?> i, String fName, boolean ZIP)
431        throws IOException
432    {
433        try
434            { writeAllObjectsToFile(i.iterator(), fName, ZIP); return true; }
435        catch (ClassNotFoundException cnfe)
436            { return false; }
437    }
438
439    /** Convenience Method.  Invokes {@link #writeAllObjectsToFile(Iterator, String, boolean)}, catches Exception. */
440    public static boolean writeAllObjectsToFileNOCNFE(Iterator<?> i, String fName, boolean ZIP)
441        throws IOException
442    {
443        try
444            { writeAllObjectsToFile(i, fName, ZIP); return true; }
445        catch (ClassNotFoundException cnfe)
446            { return false; }
447    }
448
449    /**
450     * Writes a series of {@code java.lang.Object} to a file for storage, and future reference.
451     *
452     * <BR /><BR /><B>ALSO:</B> Though the file does not need to exist already in order for this
453     * file to be written (in fact, if it does exist - <I>it will be 'over-written'</I>) the
454     * directory hierarchy needs to exist, or a java {@code 'IOException'} will occur.
455     *
456     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWSOF">
457     *
458     * @param i A series, {@code Collection}, or {@code List} of {@code Object's} to be written
459     * to a file in <I><B>Serializable</B></I> format.
460     *
461     * @param fName The name of the output file
462     *
463     * @param ZIP a {@code boolean} that, when <B>TRUE</B>, will cause the {@code Object's} 
464     * data to be compressed before being written to the output-file.
465     *
466     * @throws IOException If an I/O error has occurred as a result of the file-system or
467     * disk operation.
468     *
469     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
470     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
471     * the {@code CLASSPATH}.
472     */
473    public static void writeAllObjectsToFile(Iterator<?> i, String fName, boolean ZIP)
474        throws IOException, ClassNotFoundException
475    {
476        FileOutputStream        fos     = new FileOutputStream(fName);
477
478        if (! ZIP)
479        {
480            ObjectOutputStream  oos     = new ObjectOutputStream(fos);
481            while (i.hasNext()) oos.writeObject(i.next());
482            oos.flush(); oos.close(); fos.close();
483        } else
484        {
485            GZIPOutputStream    gzip    = new GZIPOutputStream(fos);
486            ObjectOutputStream  oos     = new ObjectOutputStream(gzip);
487            while (i.hasNext()) oos.writeObject(i.next());
488            oos.flush(); gzip.finish(); oos.close(); fos.close();   
489        }
490    }
491
492
493    // ********************************************************************************************
494    // read ONE Object From File
495    // ********************************************************************************************
496
497
498    /** Convenience Method.  Invokes {@link #readObjectFromFile(String, boolean)}.  Catches Exception. */
499    public static Object readObjectFromFileNOCNFE(String fName, boolean ZIP) throws IOException
500    {
501        try 
502            { return readObjectFromFile(fName, ZIP); }
503        catch (ClassNotFoundException cnfe)
504            { return null; }
505    }
506
507    /**
508     * Reads an {@code Object} from a data-file which must contain a serialized
509     * {@code java.lang.Object}.
510     *
511     * <DIV CLASS="EXAMPLE">{@code
512     * // Create some Object for writing to the file-system, using Object Serialization
513     * int[] dataArr = some_data_method();
514     *
515     * // It is always easier to pass 'true' to the compression boolean parameter
516     * FileRW.writeObjectToFile(dataArr, "data/myDataFile.dat", true);
517     *
518     * ...
519     *
520     * // Later on, this file may be read back into the program, using this call:
521     * Object o = FileRW.readObjectFromFile("data/myDataFile.dat", true);
522     *
523     * // This check prevents compiler-time warnings.  The Annotation "SuppressWarnings" 
524     * // would also work.
525     * dataArr = (o instanceof int[]) ? (int[]) o : null;
526     * }</DIV>
527     *
528     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWSOF">
529     *
530     * @param fName The name of a data-file that contains a serialized {@code java.lang.Object}
531     *
532     * @param ZIP if this is <B>TRUE</B>, it is assumed that the data-file contains a
533     * zip-compressed {@code Object}
534     *
535     * @return The {@code Object} that was written to the data-file.
536     *
537     * @throws IOException If an I/O error has occurred as a result of the file-system or
538     * disk operation.
539     *
540     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
541     * Machine (JVM) tries to load a particular class and the specified class cannot be found
542     * in the {@code CLASSPATH}.
543     */
544    public static Object readObjectFromFile(String fName, boolean ZIP)
545        throws IOException, ClassNotFoundException
546    {
547        FileInputStream         fis     = new FileInputStream(fName);
548        ObjectInputStream       ois     = ZIP   ? new ObjectInputStream(new GZIPInputStream(fis))
549                                                : new ObjectInputStream(fis);
550        Object                  o;
551
552        try 
553            { o = ois.readObject(); }
554        finally
555            { fis.close(); }
556
557        return o;
558    }
559
560    /** Convenience Method.  Invokes {@link #readObjectFromFile(String, Class, boolean)}.  Catches Exception. */
561    public static <T> T readObjectFromFileNOCNFE(String fName, Class<T> c, boolean ZIP)
562        throws IOException
563    {
564        try
565            { return readObjectFromFile(fName, c, ZIP); }
566        catch (ClassNotFoundException cnfe)
567            { return null; }
568    }
569
570    /**
571     * Reads an {@code Object} from a data-file which must contain a serialized
572     * {@code java.lang.Object}.
573     *
574     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWSOF">
575     *
576     * @param fName The name of a data-file that contains a serialized {@code java.lang.Object}
577     *
578     * @param c This is the type of the {@code Object} expecting to be read from disk.  A value
579     * for this parameter can always be obtained by referencing the {@code static} field
580     * {@code '.class'} which is attached to <I>every object</I> in java.
581     *
582     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWCT">
583     * 
584     * @param ZIP if this is <B>TRUE</B>, it is assumed that the data-file contains a zip-compressed
585     * {@code Object}
586     *
587     * @return The {@code Object} that was read from the data-file.
588     *
589     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
590     * operation.
591     *
592     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
593     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
594     * the {@code CLASSPATH}.
595     */
596    public static <T> T readObjectFromFile(String fName, Class<T> c, boolean ZIP)
597        throws IOException, ClassNotFoundException
598    {
599        Object o = readObjectFromFile(fName, ZIP);
600
601        if (o == null)          return null;
602        if (c.isInstance(o))    return c.cast(o);
603
604        throw new ClassNotFoundException(
605            "Although an object was indeed read from the file you have named [" + fName + "], " +
606            "that object was not an instance of [" + c.getName() + "], " + 
607            "but rather of [" + o.getClass().getName() + "]"
608        );
609    }
610
611
612    // ********************************************************************************************
613    // read ALL OBJECTS FromFile
614    // ********************************************************************************************
615
616
617    /** Convenience Method.  Invokes {@link #readAllObjects(Class, Collection, String, boolean)}. */
618    public static Vector<Object> readAllObjectsToVector(String fName, boolean ZIP)
619        throws IOException, ClassNotFoundException
620    { return readAllObjects(Object.class, new Vector<Object>(), fName, ZIP); }
621
622    /** Convenience Method.  Invokes {@link #readAllObjects(Class, Collection, String, boolean)}. */
623    public static <T> Vector<T> readAllObjectsToVector(Class<T> objType, String fName, boolean ZIP)
624        throws IOException, ClassNotFoundException
625    { return readAllObjects(objType, new Vector<T>(), fName, ZIP); }
626
627    /**
628     * Reads all {@code Object's} found inside a data-file.  This data-file should contain only
629     * java-serialized {@code java.lang.Object's}.
630     *
631     * @param objType <EMBED CLASS="external-html" DATA-FILE-ID="FRWOBJTYPE">
632     *
633     * @param collection This should be the desired {@code Collection<E>} that the programmer
634     * would like be populated with the instances of type {@code 'objType'} read from file
635     * {@code 'fName'}.  Variable-Type parameter {@code 'E'} needs to be equal-to or an 
636     * ancestor of {@code 'objType'}
637     *
638     * @param fName The name of a data-file that contains serialized {@code Object's}
639     *
640     * @param ZIP if this is <B>TRUE</B>, it is assumed that the data-file contains zip-compressed
641     * objects
642     *
643     * @return A reference to the {@code Collection} that was passed to this method.
644     *
645     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
646     * operation.
647     */
648    public static <T, U extends Collection<T>> U readAllObjects
649        (Class<T> objType, U collection, String fName, boolean ZIP)
650        throws IOException, ClassNotFoundException
651    {
652        Object                  o       = null;
653        FileInputStream         fis     = new FileInputStream(fName);
654        ObjectInputStream       ois     = ZIP   ? new ObjectInputStream(new GZIPInputStream(fis))
655                                                : new ObjectInputStream(fis);
656
657        try
658        {  
659            while ((o = ois.readObject()) != null)
660
661                if (objType.isInstance(o))
662                    collection.add(objType.cast(o));
663
664                else throw new ClassNotFoundException(
665                    "At least one of the objects in the serialized object file [" + fName + "], " +
666                    "was not an instance of [" + objType.getName() + "], " +
667                    "but rather [" + o.getClass().getName() + "]"
668                );
669        } 
670        catch (EOFException eofe)
671            { }
672        finally
673            { fis.close(); }
674
675        return collection;
676    }
677
678    /** Convenience Method.  Invokes {@link #readAllObjectsToStream(Class, String, boolean)}. */
679    public static Stream<Object> readAllObjectsToStream(String fName, boolean ZIP)
680        throws IOException, ClassNotFoundException
681    { return readAllObjectsToStream(Object.class, fName, ZIP); }
682
683    /**
684     * Reads all objects found inside a data-file.  This data-file should contain only
685     * java-serialized {@code Object's}.
686     *
687     * @param fName The name of a data-file that contains the serialized {@code Object's}
688     *
689     * @param objType This is the type of the {@code Object's} expecting to be read from disk.  A value
690     * for this parameter can always be obtained by referencing the static field {@code '.class'}
691     * which is attached to <I>every {@code Object}</I> in java.  For instance, to read a 
692     * data-file containing a series of {@code Date} instances, use {@code Date.class} as the
693     * value to pass to this parameter.
694     *
695     * @param ZIP if this is <B>TRUE</B>, it is assumed that the data-file contains zip-compressed
696     * {@code Object's}
697     *
698     * @return A {@code Stream<T>} of all {@code Object's} found in the data-file.  Converting
699     * Java {@code Stream's} to other data container types is as follows:
700     *
701     * <EMBED CLASS="external-html" DATA-FILE-ID="STRMCNVTT">
702     *
703     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
704     * operation.
705     *
706     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
707     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
708     * the classpath.
709     */
710    public static <T> Stream<T> readAllObjectsToStream(Class<T> objType, String fName, boolean ZIP)
711        throws IOException, ClassNotFoundException
712    {
713        Stream.Builder<T>       b       = Stream.builder();
714        Object                  o       = null;
715        FileInputStream         fis     = new FileInputStream(fName);
716        ObjectInputStream       ois     = ZIP   ? new ObjectInputStream(new GZIPInputStream(fis))
717                                                : new ObjectInputStream(fis);
718
719        try
720        {
721            while ((o = ois.readObject()) != null)
722
723                if (objType.isInstance(o)) b.accept(objType.cast(o));
724
725                else throw new ClassNotFoundException(
726                    "At least one of the objects in the serialized object file [" + fName + "], " +
727                    "was not an instance of [" + objType.getName() + "], " +
728                    "but rather [" + o.getClass().getName() + "]"
729                );
730        }
731        catch (EOFException eofe)
732            { }
733        finally
734            { fis.close(); }
735
736        return b.build();
737    }
738
739
740    // ********************************************************************************************
741    // Base-64 Read / Write Stuff (Text File)
742    // ********************************************************************************************
743
744
745    /**
746     * Uses Java's {@code Object} serialization mechanism to serialize a {@code java.lang.Object},
747     * and then uses the {@code Base64 String-MIME Encoding} system, also provided by java, to
748     * convert the {@code Object} into a text-safe {@code java.lang.String} that may be viewed,
749     * e-mailed, written to a web-page, etc.
750     *
751     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWSOF">
752     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWEX01">
753     *
754     * @param o This may be any serializable {@code java.lang.Object} instance.  It will be written
755     * to a text-file after first serializing the {@code Object}, and then next converting the
756     * serialized data-bits to MIME-safe encoded text.
757     *
758     * @param fileName The fileName that will be used to save this {@code Object} as a text-file.
759     */
760    public static void writeObjectToTextFile(Object o, String fileName)
761        throws IOException
762    { FileRW.writeFile(StringParse.objToB64MimeStr(o), fileName); }
763
764    /**
765     * This will read a java serialized, and MIME-Converted, MIME-Safe {@code java.lang.String}
766     * from a text file and return the {@code Object} that it represented
767     *
768     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWSOF">
769     *
770     * @param fileName The name of the file containing the MIME-Encoded Serialized
771     * {@code java.lang.Object}.
772     *
773     * @return The {@code Object} that had been encoded into the text-file.
774     */
775    public static Object readObjectFromTextFile(String fileName) throws IOException
776    { return StringParse.b64MimeStrToObj(loadFileToString(fileName)); }
777
778    /** Convenience Method.  Invokes {@link #readObjectFromTextFile(String, Class)}.  Catches Exception. */
779    public static <T> T readObjectFromTextFileNOCNFE(String fileName, Class<T> c)
780        throws IOException
781    {
782        try
783            { return readObjectFromTextFile(fileName, c); }
784        catch (ClassNotFoundException cnfe)
785            { return null; }
786    }
787
788    /**
789     * This will read a java serialized, and MIME-Converted, MIME-Safe {@code java.lang.String}
790     * from a text file and return the {@code Object} that it represented
791     *
792     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWSOF">
793     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWEX01">
794     *
795     * @param fileName The name of the file containing the MIME-Encoded Serialized
796     * {@code java.lang.Object}.
797     *
798     * @param c This is the type of the {@code Object} expecting to be read from disk.  A value
799     * for this parameter can always be obtained by referencing the {@code static} field
800     * {@code '.class'}, which is attached to <I>every {@code Object}</I> in java.
801     *
802     * <EMBED CLASS="external-html" DATA-FILE-ID="FRWCT">
803     *
804     * @return The {@code Object} that had been encoded into the text-file.
805     *
806     * @throws IOException If an I/O error has occurred as a result of the file-system or disk
807     * operation.
808     *
809     * @throws ClassNotFoundException This exception is thrown when the Java Virtual
810     * Machine (JVM) tries to load a particular class and the specified class cannot be found in
811     * the {@code CLASSPATH}.
812     */
813    public static <T> T readObjectFromTextFile(String fileName, Class<T> c)
814        throws IOException, ClassNotFoundException
815    { 
816        Object o = StringParse.b64MimeStrToObj(loadFileToString(fileName));
817
818        if (o == null)          return null;
819        if (c.isInstance(o))    return c.cast(o);
820
821        throw new ClassNotFoundException(
822            "Although an object was indeed read from the file you have named [" + fileName + "], " +
823            "that object was not an instance of [" + c.getName() + "], " + 
824            "but rather of [" + o.getClass().getName() + "]"
825        );
826    }
827
828
829    // ********************************************************************************************
830    // Object Input & Object Output Streams
831    // ********************************************************************************************
832
833
834    /**
835     * Creates a simple {@code ObjectInputStream} - usually if multiple {@code Object's} have been
836     * written to a single file.  It was better practice to put {@code Object's} in a
837     * {@code java.util.Vector}, and write one {@code java.util.Vector} during serialization.
838     * This, eventually, can became inadequate when downloading large numbers of HTML results,
839     * where the need to write a large data-file (intermittently - by saving intermediate results)
840     * is needed.
841     *
842     * @param fName This is the file-name of the data-file where the serialized {@code Object's}
843     * have been stored.
844     *
845     * @param ZIP If this is set to <B>TRUE</B>, the data will be de-compressed.
846     *
847     * @return A java {@code ObjectInputStream}
848     *
849     * @throws IOException If an I/O error has occurred as a result of the file-system
850     * or disk operation.
851     */
852    public static ObjectInputStream getOIS(String fName, boolean ZIP) throws IOException
853    {
854        return ZIP
855            ? new ObjectInputStream(new GZIPInputStream(new FileInputStream(new File(fName))))
856            : new ObjectInputStream(new FileInputStream(new File(fName))); 
857    }
858
859    /**
860     * Creates a simple {@code ObjectOutputStream} - usually if multiple {@code Object's} need
861     * to be written to a single file.  It was better practice to put {@code Object's} in a
862     * {@code java.util.Vector}, and write one {@code java.util.Vector} during serialization.
863     *
864     * @param fName This is the file-name of the data-file where the serialized {@code Object's}
865     * will be stored.
866     *
867     * @param ZIP If this is set to <B>TRUE</B>, the data will be compressed.
868     *
869     * @return A java {@code ObjectInputStream}
870     *
871     * @throws IOException If an I/O error has occurred as a result of the file-system or
872     * disk operation.
873     */
874    public static ObjectOutputStream getOOS(String fName, boolean ZIP) throws IOException
875    {
876        return ZIP
877            ? new ObjectOutputStream(new GZIPOutputStream(new FileOutputStream(new File(fName))))
878            : new ObjectOutputStream(new FileOutputStream(new File(fName))); 
879    }
880
881
882    // ********************************************************************************************
883    // Copy, Move, Delete
884    // ********************************************************************************************
885
886
887    /**
888     * This method will perform a byte-for-byte copy of a file from one location to another.
889     *
890     * @param inFileName The name of the input-file.  It will be byte-for-byte copied to an output
891     * file-name.
892     *
893     * @param outFileOrDirName The name of the output-file, or the name of the directory where the
894     * file shall be moved.  If this file already exists <I>and it is a file, not a directory</I>,
895     * it will be over-written.  If parameter {@code 'outFileOrDirName'} names a directory - <I>due
896     * to having an ending {@code File.separator}</I>, <B>but</B> does not appear to name
897     * directorie(s) that exist, this method will throw a {@code FileNotFoundException}.  However,
898     * if parameter {@code 'createDirsIfNotExist'} is set to <B>TRUE</B>, <B>even if</B> parameter
899     * {@code 'outFileOrDirName'} names a directory (by virtue of the fact that it ends with the
900     * {@code File.separator String}) then this method will create the directory tree-structure
901     * using the standard Java's {@code File.mkdirs()} method.
902     *
903     * <BR /><BR /><B><SPAN STYLE="color: red;">NOTE:</SPAN></B> The programmer may provide either
904     * a directory-name or a file-name to parameter {@code 'outFileOrDirName'}.  If the programmer
905     * has provided a directory-name instead of a file-name, the moved file will have the same name
906     * as the original file ({@code 'inFileName'}) had, but it will be located in
907     * {@code 'outFileOrDirName'}.
908     *
909     * <BR /><BR /><B><SPAN STYLE="color: red;">BEHAVIOR NOTE:</SPAN></B> The behavior of this
910     * copy operation is generally / mostly the same as the standard {@code UNIX} or {@code MS-DOS}
911     * commands {@code 'cp'} and {@code 'copy'} (respectively) - <I>differing only in this method's
912     * ability to have directories created using {@code File.mkdirs()}</I>
913     *
914     * @param createDirsIfNotExist If the target output-file is situated in a directory-path that
915     * does not exist, this method will throw an exception.  However, if this boolean parameter is
916     * set to <B>TRUE</B> and the aforementioned situation occurs where the complete-directory tree
917     * does not exist, then this method will first attempt to create the directories using
918     * {@code java.io.File.mkdirs().}
919     *
920     * @throws SecurityException If boolean parameter {@code 'createDirsIfNotExist'} is
921     * <B>TRUE</B> <I>and if</I> the directory named by parameter {@code 'outFileName'} does not
922     * exist, <I>and if</I> attempting to create such a directory is not permitted by the
923     * Operating-System, then this exception shall throw.
924     *
925     * @throws IOException For any number of fail-causes in reading or writing input stream data.
926     * An explanation of all causes of such an operation is beyond the scope of this
927     * method-documentation entry.
928     *
929     * @throws FileNotFoundException If the {@code 'inFileName'} is not found, or
930     * {@code 'outFileOrDirName'} uses a directory path that doesn't exist on the file-system,
931     * <B>and</B> parameter {@code 'createDirsIfNotExist'} is set to <B>FALSE</B>.
932     * 
933     * @throws SameSourceAndTargetException This exception will be thrown if the <CODE>Java Virtual
934     * Machine</CODE> ascertains that the source and target locations point to the same physical
935     * disk locations.  The classes utilized for this operation are from package
936     * {@code java.nio.file.*};
937     * 
938     * @throws InvalidPathException If the <I>Java Virtual Machine</I> is unable to instantiate an
939     * instance of {@code java.nio.files.Path} for either the {@code 'inFileName'} parameter or the
940     * {@code 'outFileOrDirName'}, then this exception will be thrown.
941     * 
942     * @throws NoSuchFileException If, after instantiating an instance of {@code Path} for either
943     * the {@code source} or the {@code target} locations, the <I>Java Virtual Machine</I> is
944     * unable to build an instance of {@code Path} using the method {@code Path.toRealPath()}, then
945     * this exception will throw.
946     */
947    public static void copyFile
948        (String inFileName, String outFileOrDirName, boolean createDirsIfNotExist)
949        throws IOException
950    {
951        File f = new File(outFileOrDirName);
952
953        if (createDirsIfNotExist) f.mkdirs();
954
955        if (f.isDirectory())
956        {
957            if (! outFileOrDirName.endsWith(File.separator))
958                outFileOrDirName = outFileOrDirName + File.separator;
959
960            outFileOrDirName = outFileOrDirName + StringParse.fromLastFileSeparatorPos(inFileName);
961        }
962
963        String inPath = Paths.get(inFileName).toRealPath().toString();
964        // throws InvalidPathException
965        // throws NoSuchFileException
966
967        try
968        {
969            if (Paths.get(outFileOrDirName).toRealPath().toString().equals(inPath))
970                throw new SameSourceAndTargetException(
971                    "The Source File Name and the Target Location provided to your copyFile " +
972                    "request operation appear to point to the same physical-disk location:\n" +
973                    inPath
974                );
975        } catch (NoSuchFileException e) { }
976        // NOTE: Mostly (but not always) the output file won't exist yet...  If it does not, 
977        //       then we really don't need to worry about over-writing the origina file.  
978        // REMEMBER: The only purpose of the above test is to make sure that the source and
979        //           target are not the same (to avoid clobbering the original file)
980
981        FileInputStream     fis     = new FileInputStream(inFileName);
982        FileOutputStream    fos     = new FileOutputStream(outFileOrDirName);
983        byte[]              b       = new byte[5000];
984        int                 result  = 0;
985
986        try
987            { while ((result = fis.read(b)) != -1) fos.write(b, 0, result); }
988        finally
989            { fis.close();  fos.flush();  fos.close(); }
990    }
991
992    /** Convenience Method.  Invokes {@code java.io.File.delete()} */
993    public static void deleteFiles(String... fileNames)
994    { for (String fileName : fileNames) (new File(fileName)).delete(); }
995
996    /** Convenience Method.  Invokes {@link #copyFile(String, String, boolean)}, and deletes. */
997    public static void moveFile
998        (String inFileName, String outFileName, boolean createDirsIfNotExist)
999        throws IOException
1000    {
1001        copyFile(inFileName, outFileName, createDirsIfNotExist);
1002        (new File(inFileName)).delete();
1003    }
1004
1005    /**
1006     * This deletes an entire directory, including any sub-directories.  It is like the UNIX
1007     * switch {@code -r} for the command {@code rm}, or the old Microsoft DOS Command
1008     * {@code 'deltree'} for deleting directories.  It simply reuses the {@code class FileTransfer}
1009     *
1010     * <BR /><BR /><B>NOTE:</B> If this method is used on a UNIX or LINUX platform, then it ought
1011     * to produce reults that are identical to an invocation of
1012     * {@code Shell.RM(directoryName, "-r")}.  However, if running on Windows, that method
1013     * invocation would not function because it is a LINUX native API call.  This method here is
1014     * actually <I><B STYLE="color: red">platform independent</B></I>.
1015     *
1016     * @param directoryName This should be a valid directory on the file-system.
1017     *
1018     * <BR /><BR /><B><SPAN STYLE="color: red">WARNING:</B></SPAN> This command <B><I>does indeed
1019     * delete the entire directory-tree of the named directory!</I></B>
1020     * 
1021     * @param reCreateDirectoryOnExit This parameter allows the user to create an <I>an empty
1022     * directory with the same name</I> as the directory that was just deleted, after all of the
1023     * directory's contents have been deleted.  When this parameter is passed a value of
1024     * <B>TRUE</B>, the equivalent of the UNIX command {@code mkdir 'directoryName'} will be 
1025     * executed prior to exiting this method.
1026     * 
1027     * <BR /><BR />This can be a small convenience if the user desired that the directory be 
1028     * cleared, rather than deleted completely.
1029     *
1030     * @param log This parameter may be null, and if it is, it will be ignored.  This shall receive
1031     * textual log output from the deletion process.
1032     *
1033     * <EMBED CLASS="external-html" DATA-FILE-ID="APPENDABLE">
1034     *
1035     * @return This shall return a count on the total number of deleted files.  Note that when
1036     * directories are deleted (not files), their deletion <I>shall not count towards</I> the
1037     * total returned in this integer.
1038     *
1039     * @throws IllegalArgumentException Throws if the {@code String} provided to parameter
1040     * {@code directoryName} does not name a valid directory on the File-System.
1041     */
1042    public static int delTree
1043        (String directoryName, boolean reCreateDirectoryOnExit, Appendable log)
1044        throws IOException
1045    {
1046        if (directoryName == null) throw new NullPointerException
1047            ("You have provided null to parameter 'directoryName', but this is not allowed here.");
1048
1049        File f = new File(directoryName);
1050
1051        if (! f.exists()) throw new IllegalArgumentException(
1052            "The directory name you have provided: [" + directoryName + "] was not found on the " +
1053            "File System.  Aborted."
1054        );
1055
1056        if (! f.isDirectory()) throw new IllegalArgumentException(
1057            "The value you have provided to parameter 'directoryName' was: " + 
1058            "[" + directoryName + "], but unfortunately this is not the name of a directory on " +
1059            "the File System, but rather a file.  This is not allowed here."
1060        );
1061
1062        // Uses class FileNode to build the directory into Java Memory.
1063        // It is possibly of interest to note, that if running this java code on a UNIX or
1064        // LINUX platform, this method should perform the exact same operation as an invocation
1065        // of Shell.RM(directoryName, "-r");
1066
1067        FileNode fn = FileNode.createRoot(directoryName).loadTree();
1068
1069        int ret = FileTransfer.deleteFilesRecursive(fn, null, null, log);
1070
1071        if (reCreateDirectoryOnExit) f.mkdirs();
1072
1073        return ret;
1074    }
1075
1076    /**
1077     * This may read a text-file containing integer data.  If this data is a <B>Comma Separated
1078     * Value</B> {@code 'CSV'} text-file, please pass <B>TRUE</B> to the parameter {@code 'isCSV'}.
1079     * If this file contains integers that have commas between digits in groups of three (like
1080     * {@code '30,000'}) pleas pass <B>TRUE</B> to the parameter {@code 'hasCommasInInts'}.
1081     * 
1082     * <BR /><BR /><B STYLE="color: red">FILE FORMAT:</B> The formatting of the file is not too
1083     * particular.  The {@code 'flag'} parameter {@code 'isCSV'} merely requires that the
1084     * individual integers be separated by commas.  There may be any number of such {@code int's}
1085     * on a particular line, and any number of lines in the file.  Blank lines are simply ignored.
1086     * If {@code 'isCSV'} is set to <B>FALSE</B>, all numbers must be separated by some amount of
1087     * white-space.  Multiple numbers <I>may still be placed</I> on a single line (as long as
1088     * there is white-space between them) even if {@code 'isCSV'} is <B>FALSE</B>.
1089     * 
1090     * <BR /><BR /><B STYLE="color: red">NUMBER FORMAT:</B> The numbers in this text-file must be
1091     * parse-able by Java's {@code class java.lang.Integer} using the method
1092     * {@code Integer.parseInt(String s, int radix)}
1093     * 
1094     * @param fileName This should contain the file-name which itself contains a list of integers.
1095     * These integers may be separated by either a comma ({@code ','}) or a space ({@code ' '}).
1096     * 
1097     * @param hasCommasInInts It is allowed that the file named by {@code 'fileName'} contain
1098     * integers which use the commonly found notation of having a comma between groups of three
1099     * digits within an integer.  For instance the number {@code '98765'}, to a reader, is often
1100     * represented as {@code '98,765'}.  When this parameter is set to <B>TRUE</B>, this method
1101     * shall simply remove any comma that is found juxtaposed between two digits before 
1102     * processing any text found in the file.
1103     * 
1104     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1105     * file, then please pass <B>TRUE</B> to this parameter.  If <B>FALSE</B> is passed here, then
1106     * it is mandatory that the individual numbers inside the text-file are separated by at least
1107     * one white-space character.
1108     * 
1109     * <BR /><BR /><B STYLE="color: red">IMPORTANT:</B> If it is decided to set both of the boolean
1110     * parameters to <B>TRUE</B> - <I>where the integers have commas, 
1111     * <B STYLE="color: red">and</B> the integers are separated by commas</I>, it is up to the
1112     * programmer to ensure that the individual numbers, themselves, are <I>not only</I> separated
1113     * by a comma, <I>but also</I> separated by a space as well.
1114     *
1115     * @param radix This is the {@code 'radix'}, which is also usually called the number's 
1116     * {@code 'base'} that is to be used when parsing the numbers.  Since Java's {@code class
1117     * java.lang.Integer} is used to perform the parse, <I>both</I> the {@code 'radix'}, <I>and</I>
1118     * the data found in the text-file must conform to the Java method
1119     * {@code Integer.parseInt(String s, int radix)}.
1120     * 
1121     * <BR /><BR /><B>NOTE:</B> This parameter may not be ignored.  If the numbers in the text-file
1122     * are to be interpreted as standard {@code 'decimal'} (<I>Base 10</I>) numbers, then the user
1123     * should simply pass the constant {@code '10'} to this parameter.
1124     * 
1125     * @return This method shall return a {@code java.util.stream.IntStream} consisting of the
1126     * integers that were found within the text-file provided by {@code 'fileName'}.
1127     * 
1128     * <BR /><BR /><B>NOTE:</B> An instance of {@code IntStream} is easily converted to an
1129     * {@code int[]} array using the method {@code IntStream.toArray()}.
1130     * 
1131     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1132     * found or not accessible in the file-system, then this exception will throw.
1133     * 
1134     * @throws IOException This exception throws if there are any errors that occur while
1135     * reading this file from the file-system.
1136     * 
1137     * @throws NumberFormatException If any of the numbers read from the text-file are not
1138     * properly formatted, then this exception shall throw.
1139     * 
1140     * @see StringParse#NUMBER_COMMMA_REGEX
1141     * @see StringParse#COMMA_REGEX
1142     * @see StringParse#WHITE_SPACE_REGEX
1143     */
1144    public static IntStream readIntsFromFile
1145        (String fileName, boolean hasCommasInInts, boolean isCSV, int radix)
1146        throws FileNotFoundException, IOException
1147    {
1148        FileReader              fr  = new FileReader(fileName);
1149        BufferedReader          br  = new BufferedReader(fr);
1150        IntStream.Builder       b   = IntStream.builder();
1151        String                  s   = "";
1152
1153        while ((s = br.readLine()) != null)
1154        {
1155            // Skip blank lines.
1156            if ((s = s.trim()).length() == 0) continue;
1157
1158            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1159            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1160            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1161            if (hasCommasInInts)
1162                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1163
1164            String[] numbers = isCSV
1165                ? StringParse.COMMA_REGEX.split(s)
1166                : StringParse.WHITE_SPACE_REGEX.split(s);
1167 
1168            for (String number : numbers)
1169                if ((number = number.trim()).length() > 0)
1170                    b.accept(Integer.parseInt(number, radix));
1171        }
1172
1173        br.close();
1174        fr.close();
1175        return b.build();
1176    }
1177
1178    /**
1179     * This may read a text-file containing integer data.  If this data is a <B>Comma Separated
1180     * Value</B> {@code 'CSV'} text-file, please pass <B>TRUE</B> to the parameter {@code 'isCSV'}.
1181     * If this file contains integers that have commas between digits in groups of three (like
1182     * {@code '30,000'}) pleas pass <B>TRUE</B> to the parameter {@code 'hasCommasInLongs'}.
1183     * 
1184     * <BR /><BR /><B STYLE="color: red">FILE FORMAT:</B> The formatting of the file is not too
1185     * particular.  The {@code 'flag'} parameter {@code 'isCSV'} merely requires that the
1186     * individual {@code long} integers be separated by commas.  There may be any number of such
1187     * {@code long's} on a particular line, and any number of lines in the file.  Blank lines are
1188     * simply ignored.  If {@code 'isCSV'} is set to <B>FALSE</B>, all numbers must be separated
1189     * by some amount of white-space.  Multiple numbers <I>may still be placed</I> on a single
1190     * line (as long as there is white-space between them) even if {@code 'isCSV'} is <B>FALSE</B>. 
1191     * 
1192     * <BR /><BR /><B STYLE="color: red">NUMBER FORMAT:</B> The numbers in this text-file must be
1193     * parse-able by Java's {@code class java.lang.Long} using the method
1194     * {@code Long.parseLong(String s, int radix)}
1195     * 
1196     * @param fileName This should contain the file-name which itself contains a list of 
1197     * {@code 'long'} integers.  These {@code long} integers may be separated by either a comma
1198     * ({@code ','}) or a space ({@code ' '}).
1199     * 
1200     * @param hasCommasInLongs It is allowed that the file named by {@code 'fileName'} contain
1201     * {@code long}-integers which use the commonly found notation of having a comma between groups 
1202     * of three digits within a {@code long} integer.  For instance the number {@code '98765'}, to
1203     * a reader, is often represented as {@code '98,765'}.  When this parameter is set to
1204     * <B>TRUE</B>, this method shall simply remove any comma that is found juxtaposed between
1205     * two digits before processing any text found in the file.
1206     *
1207     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1208     * file, then please pass <B>TRUE</B> to this parameter.  If <B>FALSE</B> is passed here, then
1209     * it is mandatory that the individual numbers inside the text-file are separated by at least
1210     * one white-space character.
1211     * 
1212     * <BR /><BR /><B STYLE="color: red">IMPORTANT:</B> If it is decided to set both of the boolean
1213     * parameters to <B>TRUE</B> - <I>where the {@code long} integers have commas, 
1214     * <B STYLE="color: red">and</B> the {@code long} integers are separated by commas</I>, it is
1215     * up to the programmer to ensure that the individual numbers, themselves, are <I>not only</I> 
1216     * separated by a comma, <I>but also</I> separated by a space as well.
1217     * 
1218     * @param radix This is the {@code 'radix'}, which is also usually called the number's 
1219     * {@code 'base'} that is to be used when parsing the numbers.  Since Java's {@code class
1220     * Long} is used to perform the parse, <I>both</I> the {@code 'radix'}, <I>and</I> the data
1221     * found in the text-file must conform to the Java method
1222     * {@code Long.parseLong(String s, int radix)}.
1223     * 
1224     * <BR /><BR /><B>NOTE:</B> This parameter may not be ignored.  If the numbers in the text-file
1225     * are to be interpreted as standard {@code 'decimal'} (<I>Base 10</I>) numbers, then the user
1226     * should simply pass the constant {@code '10'} to this parameter.
1227     * 
1228     * @return This method shall return a {@code java.util.stream.LongStream} consisting of the
1229     * {@code long}-integers that were found within the text-file provided by {@code 'fileName'}.
1230     * 
1231     * <BR /><BR /><B>NOTE:</B> An instance of {@code LongStream} is easily converted to a
1232     * {@code long[]} array using the method {@code LongStream.toArray()}.
1233     * 
1234     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1235     * found or not accessible in the file-system, then this exception will throw.
1236     * 
1237     * @throws IOException This exception throws if there are any errors that occur while
1238     * reading this file from the file-system.
1239     * 
1240     * @throws NumberFormatException If any of the numbers read from the text-file are not
1241     * properly formatted, then this exception shall throw.
1242     * 
1243     * @see StringParse#NUMBER_COMMMA_REGEX
1244     * @see StringParse#COMMA_REGEX
1245     * @see StringParse#WHITE_SPACE_REGEX
1246     */
1247    public static LongStream readLongsFromFile
1248        (String fileName, boolean hasCommasInLongs, boolean isCSV, int radix)
1249        throws FileNotFoundException, IOException
1250    {
1251        FileReader              fr  = new FileReader(fileName);
1252        BufferedReader          br  = new BufferedReader(fr);
1253        LongStream.Builder      b   = LongStream.builder();
1254        String                  s   = "";
1255
1256        while ((s = br.readLine()) != null)
1257        {
1258            // Skip blank lines.
1259            if ((s = s.trim()).length() == 0) continue;
1260
1261            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1262            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1263            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1264            if (hasCommasInLongs)
1265                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1266
1267            String[] numbers = isCSV
1268                ? StringParse.COMMA_REGEX.split(s)
1269                : StringParse.WHITE_SPACE_REGEX.split(s);
1270 
1271            for (String number : numbers)
1272                if ((number = number.trim()).length() > 0)
1273                    b.accept(Long.parseLong(number, radix));
1274        }
1275
1276        br.close();
1277        fr.close();
1278        return b.build();
1279    }
1280
1281
1282    /**
1283     * This may read a text-file containing floating-point data.  If this data is a <B>Comma
1284     * Separated Value</B> {@code 'CSV'} text-file, please pass <B>TRUE</B> to the parameter
1285     * {@code 'isCSV'}.  If this file contains {@code double's} that have commas between digits
1286     * in groups of three (like {@code '30,000,000,00'}) pleas pass <B>TRUE</B> to the parameter
1287     * {@code 'hasCommasInDoubles'}.
1288     *
1289     * <BR /><BR /><B STYLE="color: red">FILE FORMAT:</B> The formatting of the file is not too
1290     * particular.  The {@code 'flag'} parameter {@code 'isCSV'} merely requires that the
1291     * individual {@code double} values be separated by commas.  There may be any number of such
1292     * {@code double's} on a particular line, and any number of lines in the file.  Blank lines are
1293     * simply ignored.  If {@code 'isCSV'} is set to <B>FALSE</B>, all numbers must be separated
1294     * by some amount of white-space.  Multiple numbers <I>may still be placed</I> on a single
1295     * line (as long as there is white-space between them) even if {@code 'isCSV'} is <B>FALSE</B>. 
1296     * 
1297     * <BR /><BR /><B STYLE="color: red">NUMBER FORMAT:</B> The numbers in this text-file must be
1298     * parseable by Java's {@code class java.lang.Double} using the method
1299     * {@code Double.parseDouble(String s)}
1300     * 
1301     * @param fileName This should contain the file-name which itself contains a list of 
1302     * {@code 'double'} values.  These {@code double's} may be separated by either a comma
1303     * ({@code ','}) or a space ({@code ' '}).
1304     * 
1305     * @param hasCommasInDoubles It is allowed that the file named by {@code 'fileName'} contain
1306     * {@code double}-values which use the commonly found notation of having a comma between groups 
1307     * of three digits within a {@code double} value.  For instance the number {@code '98765.01'},
1308     * to a reader, can be represented as {@code '98,765.01'}.  When this parameter is set to
1309     * <B>TRUE</B>, this method shall simply remove any comma that is found juxtaposed between
1310     * two digits before processing any text found in the file.
1311     *
1312     * @param isCSV If the text file named by {@code 'fileName'} is a <B>Comma Separated Value</B>
1313     * file, then please pass <B>TRUE</B> to this parameter.  If <B>FALSE</B> is passed here, then
1314     * it is mandatory that the individual numbers inside the text-file are separated by at least
1315     * one white-space character.
1316     * 
1317     * <BR /><BR /><B STYLE="color: red">IMPORTANT:</B> If it is decided to set both of the boolean
1318     * parameters to <B>TRUE</B> - <I>where the {@code double} values have commas, 
1319     * <B STYLE="color: red">and</B> the {@code double} values are separated by commas</I>, it is
1320     * up to the programmer to ensure that the individual numbers, themselves, are <I>not only</I> 
1321     * separated by a comma, <I>but also</I> separated by a space as well.
1322     * 
1323     * @return This method shall return a {@code java.util.stream.DoubleStream} consisting of the
1324     * {@code double}-values that were found within the text-file provided by {@code 'fileName'}.
1325     * 
1326     * <BR /><BR /><B>NOTE:</B> An instance of {@code DoubleStream} is easily converted to a
1327     * {@code double[]} array using the method {@code DoubleStream.toArray()}.
1328     * 
1329     * @throws FileNotFoundException If the file named by parameter {@code 'fileName'} is not
1330     * found or not accessible in the file-system, then this exception will throw.
1331     * 
1332     * @throws IOException This exception throws if there are any errors that occur while
1333     * reading this file from the file-system.
1334     * 
1335     * @throws NumberFormatException If any of the numbers read from the text-file are not
1336     * properly formatted, then this exception shall throw.
1337     * 
1338     * @see StringParse#NUMBER_COMMMA_REGEX
1339     * @see StringParse#COMMA_REGEX
1340     * @see StringParse#WHITE_SPACE_REGEX
1341     */
1342    public static DoubleStream readDoublesFromFile
1343        (String fileName, boolean hasCommasInDoubles, boolean isCSV)
1344        throws FileNotFoundException, IOException
1345    {
1346        FileReader              fr  = new FileReader(fileName);
1347        BufferedReader          br  = new BufferedReader(fr);
1348        DoubleStream.Builder    b   = DoubleStream.builder();
1349        String                  s   = "";
1350
1351        while ((s = br.readLine()) != null)
1352        {
1353            // Skip blank lines.
1354            if ((s = s.trim()).length() == 0) continue;
1355
1356            // This line simply finds String-Matches that match "Digit,Digit" and replaces
1357            // such matches with "DigitDigit".  After this replacement, they are parsed with ease.
1358            // NOTE: NUMBER_COMMMA_REGEX = Pattern.compile("\\d,\\d");
1359            if (hasCommasInDoubles)
1360                s = StringParse.NUMBER_COMMMA_REGEX.matcher(s).replaceAll("$1$2").trim();
1361
1362            String[] numbers = isCSV
1363                ? StringParse.COMMA_REGEX.split(s)
1364                : StringParse.WHITE_SPACE_REGEX.split(s);
1365 
1366            for (String number : numbers)
1367                if ((number = number.trim()).length() > 0)
1368                    b.accept(Double.parseDouble(number));
1369        }
1370
1371        br.close();
1372        fr.close();
1373        return b.build();
1374    }
1375}