001package Torello.Java;
002
003import Torello.Java.StrIndent;
004
005import static Torello.Java.C.*;
006
007import java.util.*;
008import java.io.*;
009import java.util.zip.*;
010
011import com.google.cloud.storage.*;
012import com.google.auth.oauth2.*;
013
014/**
015 * <B><CODE>Load File Exception Catch</CODE></B> provides an eloquent way for printing standardized
016 * <I>(consistent-looking)</I> error messages to terminal if a data-file fails to load from the
017 * file-system, and subsequently halting the JVM - immediately.
018 * 
019 * <EMBED CLASS='external-html' DATA-FILE-ID=LFEC>
020 */
021@Torello.JavaDoc.StaticFunctional
022public class LFEC
023{
024    private LFEC() { }
025
026
027    // ********************************************************************************************
028    // ********************************************************************************************
029    // These are error-printing methods.
030    // ********************************************************************************************
031    // ********************************************************************************************
032
033
034    /**
035     * This prints a message and a location and halts the current thread immediately.  (Halts the
036     * program)  The entire rational behind the {@code class LFEC} is to facilitate loading
037     * data-files, and forcing an immediate program halt if this operation fails.  Generally,
038     * during development and debugging, failing to read from a data-file is serious enough to
039     * warrant stopping the software until the issue is fixed.  "Hobbling along without reading
040     * the data-files" makes little sense.
041     *
042     * <BR /><BR />When the software is being released or distributed, the philosophy turns to
043     * loading mission critical data out of data-files in a java jar-file.  If the jar-file is
044     * corrupted and those files are not present, generally <I>this situation would <B>also
045     * warrant</B> halting the program execution immediately until the jar file is fixed.</I>
046     *
047     * @param t An exception, error or other {@code Throwable} that generated a problem when
048     * attempting to read a data-file.
049     * 
050     * @param message This is an internally generated message explaining what has occurred.
051     */
052    protected static void ERROR_EXIT(Throwable t, String message)
053    {
054        System.out.println(
055            '\n' +
056            "There was an error loading a data-file, and program-execution is being halted " +
057            "immediately.\n" +
058            "Problem Encountered With:\n" + StrIndent.indent(message, 4) + "\n" +
059            "Exception or Error Message:\n" +
060            EXCC.toString(t) + "\n\n" +
061            "Exiting Program, Fatal Error Loading Critical Data File."
062        );
063
064        System.exit(1);
065    }
066
067    /**
068     * This prints a message and a location and halts the current thread immediately.  (Halts the
069     * program)  The entire rational behind the {@code class LFEC} is to facilitate loading
070     * data-files, and forcing an immediate program halt if this operation fails.  Generally,
071     * during development and debugging, failing to read from a data-file is serious enough to
072     * warrant stopping the software until the issue is fixed.  "Hobbling along without reading
073     * the data-files" makes little sense.
074     *
075     * <BR /><BR />When the software is being released or distributed, the philosophy turns to
076     * loading mission critical data out of data-files in a java jar-file.  If the jar-file is
077     * corrupted and those files are not present, generally <I>this situation would <B>also
078     * warrant</B> halting the program execution immediately until the jar file is fixed.</I>
079     *
080     * @param message This is an internally generated message explaining what has occurred.
081     */
082    protected static void ERROR_EXIT(String message)
083    {
084        System.out.println(
085            '\n' +
086            "There was an error loading a data-file, and program-execution is being halted " +
087            "immediately.\n" +
088            "Problem Encountered With:\n" + message + "\n\n" +
089            "Exiting Program, Fatal Error Loading Critical Data File."
090        );
091
092        System.exit(1);
093    }
094
095
096    // ********************************************************************************************
097    // ********************************************************************************************
098    // These are the data-loading methods.
099    // ********************************************************************************************
100    // ********************************************************************************************
101
102
103    /**
104     * Loads a file directly into a java-{@code String.}  If this file fails to load, it halts the
105     * run-time environment.
106     * 
107     * @param f This is a {@code java.lang.String} that contains the filename.
108     * 
109     * @return The returned {@code String} contains the entire contents of the file.
110     * 
111     * @see FileRW#loadFileToString(String)
112     * @see #ERROR_EXIT(Throwable, String)
113     */
114    public static String loadFile(String f)
115    {
116        try
117            { return FileRW.loadFileToString(f); }
118
119        catch (Throwable t)
120            { ERROR_EXIT(t, "FileRW.loadFileToString(\"" + f + "\")");  }
121
122        throw new UnreachableError(); // Should not be possible to reach this statement
123    }
124
125    /**
126     * Loads a file directory to a string <I>from a java jar-file</I>.  It halts the program, and
127     * prints a detailed message if any {@code Error's} or {@code Exception's} were thrown.  The
128     * directory inside the jar-file where this file may be located identified by parameter
129     * {@code class 'classLoaderClass'}.
130     *
131     * @param classLoaderClass <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_CLASS_LOAD_C>
132     * <EMBED CLASS='external-html' DATA-FILE-ID=RAWTYPES>
133     *
134     * @param f This is a {@code String} that contains the filename of the data-file that needs
135     * to be loaded.  It is a 'relative file-name' that is relative to the jar-file / directory pair
136     * location that the class loader identifies using the {@code Class} from parameter
137     * {@code 'classLoaderClass'}
138     *
139     * @return The returned string contains the entire contents of the file.
140     * 
141     * @see #ERROR_EXIT(Throwable, String)
142     */
143    public static String loadFile_JAR(Class<?> classLoaderClass, String f)
144    {
145        // These are 'java.lang.AutoCloseable', and java handles them automatically
146        // if there is an exception
147
148        try (
149            InputStream     is  = classLoaderClass.getResourceAsStream(f);
150            BufferedReader  br  = new BufferedReader(new InputStreamReader(is));
151        )
152        {
153            String          s   = "";
154            StringBuffer    sb  = new StringBuffer();
155
156            while ((s = br.readLine()) != null) sb.append(s + "\n");
157
158            return sb.toString();
159        }
160
161        catch (Throwable t)
162        {
163            ERROR_EXIT(
164                t,
165                "Error loading text-file [" + f + "] from jar-file.\n" +
166                "Class loader attempted to use information in class " +
167                "[" + classLoaderClass.getCanonicalName() + "], but failed."
168            );
169        }
170
171        throw new UnreachableError(); // Should NOT be possible to reach this statement...
172    }
173
174    /**
175     * Loads a file <I>from a java jar-file</I> using an {@code InputStream} and copies that file,
176     * byte-for-byte, to {@code 'targetFileName'}.  A detailed message is printed if any
177     * {@code Error's} or {@code Exception's} were thrown.  The directory inside the jar-file
178     * where this file may be located identified by parameter {@code class 'classLoaderClass'}.
179     *
180     * @param classLoaderClass <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_CLASS_LOAD_C>
181     * <EMBED CLASS='external-html' DATA-FILE-ID=RAWTYPES>
182     *
183     * @param fileName This is a {@code String} that contains the filename of the data-file that
184     * needs to be copied.  It is a 'relative file-name' that is relative to the jar-file /
185     * directory pair location that the class loader identifies using the {@code Class} from
186     * parameter {@code 'classLoaderClass'}.
187     * 
188     * @param targetFileName This is the file and directory that contains the target location
189     * (as a {@code String}) to where the file should be copied.
190     * 
191     * @see #ERROR_EXIT(Throwable, String)
192     */
193    public static void copyFile_JAR
194        (Class<?> classLoaderClass, String fileName, String targetFileName)
195    {
196        // These are 'java.lang.AutoCloseable', and java handles them automatically
197        // if there is an exception
198
199        try (
200            InputStream         is  = classLoaderClass.getResourceAsStream(fileName);
201            FileOutputStream    fos = new FileOutputStream(targetFileName);
202        )
203        {
204            byte[]  b       = new byte[5000];
205            int     result  = 0;
206    
207            while ((result = is.read(b)) != -1) fos.write(b, 0, result);
208        }
209
210        catch (Throwable t)
211        {
212            ERROR_EXIT(
213                t,
214                "Error copying file [" + fileName + "] from jar-file to " +
215                "[" + targetFileName + "].\n" +
216                "Class loader attempted to use information in class " +
217                "[" + classLoaderClass.getCanonicalName() + "]."
218            );
219        }
220    }
221
222    /**
223     * This loads a file to a {@code Vector} of {@code String's}.  It halts the program, and prints
224     * a detailed message if any errors or exceptions occur.
225     *
226     * @param f The name of the file to load
227     *
228     * @param includeNewLine States whether the {@code '\n'} (new-line) character should be appended
229     * to each element of the returned {@code Vector<String>}.
230     *
231     * @return a {@code Vector} of {@code String's}, where each element in the {@code Vector} is a
232     * line retrieved from the text-file.
233     *
234     * @see #ERROR_EXIT(Throwable, String)
235     */
236    public static Vector<String> loadFileToVector(String f, boolean includeNewLine)
237    {
238        try
239            { return FileRW.loadFileToVector(f, includeNewLine); }
240
241        catch (Throwable t)
242        {
243            ERROR_EXIT(
244                t, 
245                "Attempting to Invoke this Load-Method with these Arguments:\n" +
246                "FileRW.loadFileToVector(\"" + f + "\", " + includeNewLine + ");"
247            );
248        }
249
250        throw new UnreachableError(); // Should NOT be possible to reach this statement...
251    }
252
253
254    /**
255     * This loads a file to a {@code Vector} of {@code String's}.  It halts the program, and prints
256     * a detailed message if any {@code Error's} or {@code Exception's} occur.  The directory inside
257     * the jar-file where this file may be located identified by parameter
258     * {@code 'classLoaderClass'}.
259     *
260     * @param classLoaderClass <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_CLASS_LOAD_C>
261     * <EMBED CLASS='external-html' DATA-FILE-ID=RAWTYPES>
262     *
263     * @param f This is a {@code String} that contains the filename of the data-file that needs to
264     * be loaded.  It is a 'relative file-name' that is relative to the jar-file / directory pair
265     * location that the class loader identifies using the {@code Class} from parameter
266     * {@code 'classLoaderClass'}.
267     *
268     * @param includeNewLine States whether the {@code '\n'} (newline) character should be appended
269     * to each element of the {@code Vector}.
270     *
271     * @return a {@code Vector} of {@code String's}, where each element in the {@code Vector} is a
272     * line retrieved from the text-file.
273     *
274     * @see #ERROR_EXIT(Throwable, String)
275     */
276    public static Vector<String> loadFileToVector_JAR
277        (Class<?> classLoaderClass, String f, boolean includeNewLine)
278    {
279        // These are 'java.lang.AutoCloseable', and java handles them automatically
280        // if there is an exception
281
282        try (
283            InputStream     is  = classLoaderClass.getResourceAsStream(f);
284            BufferedReader  br  = new BufferedReader(new InputStreamReader(is));
285        )
286        {
287            String          s   = "";
288            Vector<String>  ret = new Vector<>();
289
290            if (includeNewLine)
291                while ((s = br.readLine()) != null)
292                    ret.addElement(s + '\n');
293
294            else
295                while ((s = br.readLine()) != null)
296                    ret.addElement(s);
297
298            return ret;
299        }
300
301        catch (Throwable t)
302        {
303            ERROR_EXIT(
304                t,
305                "Error loading text-file [" + f + "] from jar-file.\n" +
306                "Parameter includeNewLine was: " + includeNewLine + "\n" +
307                "Class loader attempted to use information in class " +
308                "[" + classLoaderClass.getCanonicalName() + "], but failed."
309            );
310        }
311
312        throw new UnreachableError(); // Should NOT be possible to reach this statement...
313    }
314
315    /**
316     * This loads a {@code java.lang.Object} from a data-file.
317     * <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_CONTAINER>
318     *
319     * @param f The name of the file containing a serialized {@code java.lang.Object} to load
320     *
321     * @param zip This should be <I>TRUE</I> if, when serialized, the {@code Object} was compressed
322     * too, and <I>FALSE</I> if compression was not used.
323     *
324     * @param returnClass <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_RET_CLASS>
325     *
326     * @param <T> This type parameter is simply provided for convenience, to allow the user to
327     * specify the return class, without having to cast the object and suppress warnings, or catch
328     * exceptions.
329     *
330     * @return A de-serialized {@code java.lang.Object} present in the data-file passed by-name
331     * through file-name parameter {@code 'f'}, and cast to the type denoted by parameter
332     * {@code returnClass}.
333     *
334     * @see FileRW#readObjectFromFile(String, boolean)
335     * @see #ERROR_EXIT(Throwable, String)
336     */
337    public static <T> T readObjectFromFile(String f, boolean zip, Class<T> returnClass)
338    {
339        try
340        {
341            Object ret = FileRW.readObjectFromFile(f, zip);
342
343            if (! returnClass.isInstance(ret))
344
345                ERROR_EXIT(
346                    "Serialized Object from file: " + f + "\n" +
347                    "Using expected (" + (zip ? "zip-compression" : "no-compression") + ")\n" +
348                    "Didn't have an object with class-name: [" + returnClass + "]\n" +
349                    "but rather with class-name: [" + ret.getClass().getName() + "]"
350                );
351
352            return returnClass.cast(ret);
353        }
354
355        catch (Throwable t)
356        {
357            ERROR_EXIT(
358                t,
359                "Exception reading Serialized Object from file: " + f + "\n" +
360                "With intended read class-name of: " + returnClass + "\n" +
361                "Using expected (" + (zip ? "zip-compression" : "no-compression") + ")\n"
362            );
363        }
364
365        throw new UnreachableError(); // Should NOT be possible to reach this statement...
366    }
367
368    /**
369     * This loads a {@code java.lang.Object} from a data-file located in a JAR File.
370     * <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_CONTAINER>
371     *
372     * @param classLoaderClass <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_CLASS_LOAD_C>
373     * <EMBED CLASS='external-html' DATA-FILE-ID=RAWTYPES>
374     *
375     * @param f The name of the file containing a serialized {@code java.lang.Object} to load
376     *
377     * @param zip This should be <I>TRUE</I> if, when serialized, the {@code Object} was compressed
378     * too, and <I>FALSE</I> if compression was not used.
379     *
380     * @param returnClass <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_RET_CLASS> 
381     *
382     * @param <T> This type parameter is simply provided for convenience, to allow the user to
383     * specify the return class, without having to cast the object and suppress warnings, or catch
384     * exceptions.
385     *
386     * @return a de-serialized {@code java.lang.Object} present in the data-file passed by-name
387     * through file-name parameter {@code 'f'}, and cast to the type denoted by parameter
388     * {@code 'returnClass'}.
389     *
390     * @see #ERROR_EXIT(Throwable, String)
391     */
392    public static <T> T readObjectFromFile_JAR
393        (Class<?> classLoaderClass, String f, boolean zip, Class<T> returnClass)
394    {
395        // These are 'java.lang.AutoCloseable', and java handles them automatically
396        // if there is an exception
397
398        try (
399            InputStream is = classLoaderClass.getResourceAsStream(f);
400
401            // The user may or may not have asked for reading a *COMPRESSED* file
402            ObjectInputStream ois = zip
403                ? new ObjectInputStream(new GZIPInputStream(is))
404                : new ObjectInputStream(is);
405        )
406        {
407            Object ret = ois.readObject();
408
409            if (! returnClass.isInstance(ret)) ERROR_EXIT(
410                "Serialized Object from jar-file loading-using class: " +
411                classLoaderClass.getCanonicalName() + "\n" +
412                "Looking for data-file named: " + f + "\n" +
413                "Using expected (" + (zip ? "zip-compression" : "no-compression") + ")\n" +
414                "Didn't have an object with class-name: [" + returnClass + "]\n" +
415                "but rather with class-name: [" + ret.getClass().getName() + "]"
416            );
417
418            return returnClass.cast(ret);
419        }
420
421        catch (Throwable t)
422        {
423            ERROR_EXIT(
424                t,
425                "Exception reading Serialized Object from jar-file, loading-using class: " +
426                classLoaderClass.getCanonicalName() + "\n" +
427                "Looking for data-file named: " + f + "\n" +
428                "And attempting to retrieve an object having class-name: " + returnClass + "\n" +
429                "Using expected (" + (zip ? "zip-compression" : "no-compression") + ")\n"
430            );
431
432            throw new UnreachableError(); // Should NOT be possible to reach this statement...
433        }
434    }
435
436
437    // ********************************************************************************************
438    // ********************************************************************************************
439    // Google Cloud Server - Public Static Inner Class
440    // ********************************************************************************************
441    // ********************************************************************************************
442
443
444    /**
445     * The Google Cloud Server Storage Bucket extension of "Load File Exception Catch" does the
446     * work that <CODE>LFEC</CODE> does, but for GCS Storage Buckets, rather than operating 
447     * system files.
448     * 
449     * <EMBED CLASS='external-html' DATA-FILE-ID=LFEC_GCSSB>
450     */
451    @Torello.JavaDoc.StaticFunctional
452    public static class GCSSB
453    {
454        private GCSSB() { }
455
456        /**
457         * This will read a Java Serialized {@code java.lang.Object} from a location in a Google
458         * Cloud Server {@code Storage Bucket}.
459         *
460         * @param storage <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_STORAGE>
461         *
462         * @param bucket The {@code bucket} name of the {@code bucket} from a Google Cloud Server
463         * account.
464         *
465         * @param completeFileName <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_CMPL_FNAME>
466         *
467         * @param zip <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_ZIP>
468         *
469         * @param returnClass This is the type expected to be found by Java in the Serialized
470         * {@code Object} Data-File.  If an {@code Object} is read from this location, but it does
471         * not have the type indicated by this parameter, the program will also halt, and an
472         * explanatory exception message will be printed to the console/terminal.
473         *
474         * @param <T> This type parameter is simply provided for convenience, to allow the user
475         * to specify the return class, without having to cast the object and suppress warnings,
476         * or catch exceptions.
477         *
478         * @return A de-serialized java {@code java.lang.Object} that has been read from a GCS
479         * {@code Storage Bucket}, and cast to the type denoted by parameter
480         * {@code 'returnClass'}.
481         *
482         * @see FileRW#readObjectFromFile(String, boolean)
483         * @see FileRW#readObjectFromFileNOCNFE(String, boolean)
484         * @see #readObjectFromFile(String, boolean, Class)
485         * @see #ERROR_EXIT(String)
486         */
487        public static <T> T readObjectFromFile(
488                Storage storage, String bucket, String completeFileName,
489                boolean zip, Class<T> returnClass
490            )
491        {
492            try
493            {
494                // Read Storage Bucket Data into a byte[] array
495                byte[] bArr = storage.get(bucket, completeFileName).getContent();
496
497                // Build an Input Stream, using that byte[] array as input
498                ByteArrayInputStream bis = new ByteArrayInputStream(bArr);
499
500                // Build an Object Input Stream, using the byte-array input-stream as input
501                ObjectInputStream ois = zip
502                    ? new ObjectInputStream(new GZIPInputStream(bis))
503                    : new ObjectInputStream(bis);
504
505                // Use Java's Object Serialization method to read the Object
506                Object ret = ois.readObject();
507
508                if (! returnClass.isInstance(ret)) ERROR_EXIT(
509                    "Serialized Object read from GCS Storage Bucket: " + bucket + "\n" +
510                    "And file-name: " + completeFileName + "\n" +
511                    "Using expected (" + (zip ? "zip-compression" : "no-compression") + ")\n" +
512                    "Didn't have an object with class-name: " + returnClass + "\n" +
513                    "But rather with className: " + ret.getClass().getName()
514                );
515
516                ois.close(); bis.close();
517
518                return returnClass.cast(ret);
519            }
520
521            catch (Throwable t)
522            {
523                ERROR_EXIT(
524                    t,
525                    "Serialized Object read from GCS Storage Bucket: " + bucket + "\n" +
526                    "And file-name: " + completeFileName + "\n" +
527                    "Using expected (" + (zip ? "zip-compression" : "no-compression") + ")\n" +
528                    "And Expected class-name: " + returnClass + "\n"
529                );
530
531                throw new UnreachableError(); // Cannot reach this statement
532            }
533        }
534
535        /**
536         * This merely loads a text-file from Google's Storage Bucket infrastructure into a
537         * {@code String.}  Make sure to check that the file you are loading does indeed have
538         * text-content.
539         *
540         * @param storage <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_STORAGE>
541         *
542         * @param bucket The {@code bucket} name of the {@code bucket} from a Google Cloud Server
543         * account.
544         *
545         * @param completeFileName <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_CMPL_FNAME>
546         *
547         * @return The text file on Google Cloud Server's Storage Bucket file/directory returned as
548         * a {@code java.lang.String}
549         */
550        public static String loadFileToString
551            (Storage storage, String bucket, String completeFileName)
552        { return new String(storage.get(bucket, completeFileName).getContent()); }
553
554        /**
555         * This merely loads a text-file from Google's Storage Bucket infrastructure into a
556         * {@code String}.  Make sure to check that the file you are loading does indeed have
557         * text-content.
558         *
559         * @param storage <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_STORAGE>
560         *
561         * @param bucket The {@code bucket} name of the {@code bucket} from a Google Cloud Server
562         * account.
563         *
564         * @param completeFileName <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_CMPL_FNAME>
565         *
566         * @param includeNewLine This tells the method to include, or not-include, a {@code '\n'}
567         * (newline) character to each {@code String}.
568         *
569         * @return The text file on Google Cloud Server's {@code Storage Bucket} file/directory
570         * stuff as a {@code Vector} of {@code String's}.
571         *
572         * @see #loadFileToString(Storage, String, String)
573         */
574        public static Vector<String> loadFileToVector
575            (Storage storage, String bucket, String completeFileName, boolean includeNewLine)
576        {
577            String          s   = loadFileToString(storage, bucket, completeFileName);
578            Vector<String>  ret = new Vector<>();
579
580            int pos     = 0;
581            int delta   = includeNewLine ? 1 : 0;
582            int lastPos = 0;
583
584            while ((pos = s.indexOf('\n')) != -1)
585            {
586                ret.add(s.substring(lastPos, pos + delta));
587                lastPos = pos + 1;
588            }
589
590            if (lastPos < s.length()) ret.add(s.substring(lastPos));
591
592            return ret;
593        }
594    
595        /**
596         * This will write the contents of a java {@code 'CharSequence'}  - includes
597         * {@code String, StringBuffer & StringBuilder} to a
598         * file on Google Cloud Server's storage bucket system.
599         *
600         * @param storage <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_STORAGE>
601         *
602         * @param bucket The {@code bucket} name of the {@code bucket} from a Google Cloud Server
603         * account.
604         * @param completeFileName <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_CMPL_FNAME>
605         *
606         * @param ASCIIorUTF8  When writing java {@code String's} the file-system, it is generally
607         * not to important to worry about whether java has stored an {@code 'ASCII'} encoded
608         * {@code String}, or a {@code String} encoded using {@code 'UTF-8'}.  Most
609         * foreign-language news-sites require the latter ({@code 'UTF-8'}), but any site that is
610         * strictly English can get by with plain old ASCII.
611         *
612         * <BR /><BR /><B><SPAN STYLE="color: red">IMPORTANT:</B></SPAN> When this boolean is
613         * {@code TRUE}, this method will attempt to presume the character-sequence you have passed
614         * is in ASCII, and write it that way.  When this boolean is set to {@code FALSE},
615         * this method will attempt to write the {@code String} of {@code byte's} as a
616         * {@code 'UTF-8'} encoded character-set.
617         *
618         * <BR /><BR /><B>ALSO:</B> I have not made any allowance for Unicode or Unicode little
619         * endian, because I have never used them with either the Chinese or Spanish sites I
620         * scrape.  UTF-8 has been the only other character set I encounter.
621         */
622        public static void writeFile(
623                CharSequence fileAsStr, Storage storage, String bucket,
624                String completeFileName, boolean ASCIIorUTF8
625            )
626        {
627            BlobInfo blobInfo = BlobInfo.newBuilder
628                (BlobId.of(bucket, completeFileName)).setContentType("text/plain").build();
629
630            byte[] file = ASCIIorUTF8
631                ? fileAsStr.toString().getBytes()
632                : fileAsStr.toString().getBytes(java.nio.charset.Charset.forName("UTF-8"));
633
634            Blob blob = storage.create(blobInfo, file);
635        }
636
637        /**
638         * This will write a Java {@code Serializable Object} to a location in a Google Cloud
639         * Server Storage Bucket.
640         * 
641         * @param storage <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_STORAGE>
642         * 
643         * @param o This may be any {@code Serializable Java Object}.  {@code Serializable Java
644         * Objects} are ones which implement the {@code interface java.io.Serializable}.
645         * 
646         * @param bucket The {@code bucket} name of the {@code bucket} from a Google Cloud Server
647         * account.
648         * 
649         * @param completeFileName <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_CMPL_FNAME>
650         * 
651         * @param zip <EMBED CLASS='external-html' DATA-FILE-ID=GCSSB_ZIP>
652         */
653        public static void writeObjectToFile(
654                Object o, Storage storage, String bucket,
655                String completeFileName, boolean zip
656            )
657            throws IOException
658        {
659            // Retrieves a file-name object using a GCS BUCKET-NAME, and the FILE-NAME (in
660            // the bucket)            
661
662            BlobId blobId = BlobId.of(bucket, completeFileName);
663
664            // This BlobInfo is GCS version of "java.io.File".  It points to a specific file
665            // inside a GCS Bucket (which was specified earlier)
666
667            BlobInfo blobInfo = BlobInfo
668                .newBuilder(blobId)
669                // .setContentType("text/plain")
670                .build();
671
672            // This will save the Serialized Object Data to a Stream (and eventually an array)
673            ByteArrayOutputStream baos = new ByteArrayOutputStream();
674
675            // This stream writes serialized Java-Objects to the Storage Bucket
676            ObjectOutputStream oos = zip
677                ? new ObjectOutputStream(new GZIPOutputStream(baos))
678                : new ObjectOutputStream(baos);
679
680            oos.writeObject(o);
681
682            oos.flush(); baos.flush(); oos.close();
683
684            // Convert that BAOS to a Byte-Array
685            byte[]  bArr = baos.toByteArray();
686
687            // Write the BYTE-ARRAY to the GCS Bucket and file using the "BlobInfo" that was built
688            // a few lines ago.
689
690            Blob blob = storage.create(blobInfo, bArr);
691        }
692    }
693}