001    package com.softnetConsult.utils.log;
002    
003    import java.io.File;
004    import java.io.FileNotFoundException;
005    import java.io.FileOutputStream;
006    import java.io.UnsupportedEncodingException;
007    
008    import com.softnetConsult.utils.exceptions.ThrowableTools;
009    import com.softnetConsult.utils.streams.MultiPrintStream;
010    
011    
012    /**
013     * This class provides a convenient way to direct text output to the screen (std-out)
014     * and to a file (file stream) at the same time, in addition it provides an easy
015     * way to make sure that all output is indented according to a specifiable level;
016     * this makes this class ideal for simple logging and tracking tasks.
017     * 
018     * <p style="font-size:smaller;">This product includes software developed by the
019     *    <strong>SoftNet-Consult Java Utility Library</strong> project and its contributors.<br />
020     *    (<a href="http://java-tools.sourceforge.net" target="_blank">http://java-tools.sourceforge.net</a>)<br />
021     *    Copyright (c) 2007-2008 SoftNet-Consult.<br />
022     *    Copyright (c) 2007-2008 G. Paperin.<br />
023     *    All rights reserved.
024     * </p>
025     * <p style="font-size:smaller;">File: ScreenFileLogger.java<br />
026     *    Library API version: {@value com.softnetConsult.utils.APIProperties#apiVersion}<br />
027     *    Java compliance version: {@value com.softnetConsult.utils.APIProperties#javaComplianceVersion}
028     * </p>
029     * <p style="font-size:smaller;">Redistribution and use in source and binary forms, with or
030     *    without modification, are permitted provided that the following terms and conditions are met:
031     * </p>
032     * <p style="font-size:smaller;">1. Redistributions of source code must retain the above
033     *    acknowledgement of the SoftNet-Consult Java Utility Library project, the above copyright
034     *    notice, this list of conditions and the following disclaimer.<br />
035     *    2. Redistributions in binary form must reproduce the above acknowledgement of the
036     *    SoftNet-Consult Java Utility Library project, the above copyright notice, this list of
037     *    conditions and the following disclaimer in the documentation and/or other materials
038     *    provided with the distribution.<br />
039     *    3. All advertising materials mentioning features or use of this software or any derived
040     *    software must display the following acknowledgement:<br />
041     *    <em>This product includes software developed by the SoftNet-Consult Java Utility Library
042     *    project and its contributors.</em>
043     * </p>
044     * <p style="font-size:smaller;">THIS SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY
045     *    OF ANY KIND, EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
046     *    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND  NONINFRINGEMENT. IN NO EVENT SHALL
047     *    THE AUTHORS, CONTRIBUTORS OR COPYRIGHT  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
048     *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING  FROM, OUT OF OR
049     *    IN CONNECTION WITH THE SOFTWARE OR THE USE OR  OTHER DEALINGS IN THE SOFTWARE.
050     * </p> 
051     * @author Greg Paperin (<a href="http://www.paperin.org" target="_blank">http://www.paperin.org</a>)
052     * @version {@value com.softnetConsult.utils.APIProperties#apiVersion}
053     */
054    public class ScreenFileLogger {
055    
056    /**
057     * Maximum indentation level permitted.
058     */
059    public static final int MAX_INDENT_LEVEL = 250;
060    
061    /**
062     * Indentation string (one copy per level).
063     */
064    private String indentation;
065    
066    /**
067     * Whether to indent next output. 
068     */
069    private boolean indentNext;
070    
071    /**
072     * Current indentation level.
073     */
074    private int indentLevel;
075    
076    
077    /**
078     * Output file. 
079     */
080    private File logFile;
081    
082    /**
083     * Output file stream.
084     */
085    private FileOutputStream fileStream;
086    
087    /**
088     * Whether to append or overwrite when output file is (re)opened. 
089     */
090    private boolean fileAppend;
091    
092    
093    /**
094     * Aggregating output stream.
095     */
096    private MultiPrintStream outs;
097    
098    
099    /**
100     * Creates a logger that can write all logged output to screen (or whatever is the
101     * current std-out) and/or to a file.
102     * 
103     * @param logToScreen Whether to start logging to std-out (can be changed later).
104     * @param logToFile Whether to start logging to a file (can be changed later if file is specified).
105     * @param logFileName File name to write to.
106     * @param append Append or overwrite the file.
107     * @param indentation Indentation string (one copy per indentation level).
108     * @throws FileNotFoundException If there is a problem when opening the specified file for writing.
109     */
110    public ScreenFileLogger(boolean logToScreen,
111                                                    boolean logToFile, String logFileName, boolean append,
112                                                    String indentation)
113                                                    throws FileNotFoundException {
114            this( logToScreen, logToFile,
115                      (null == logFileName ? (File) null : new File(logFileName)),
116                      append, indentation); 
117    }
118    
119    /**
120     * Creates a logger that can write all logged output to screen (or whatever is the
121     * current std-out) and/or to a file.
122     * 
123     * @param logToScreen Whether to start logging to std-out (can be changed later).
124     * @param logToFile Whether to start logging to a file (can be changed later if file is specified).
125     * @param logFile File to write to.
126     * @param append Append or overwrite the file.
127     * @param indentation Indentation string (one copy per indentation level).
128     * @throws FileNotFoundException If there is a problem when opening the specified file for writing.
129     */
130    public ScreenFileLogger(boolean logToScreen,
131                                                    boolean logToFile, File logFile, boolean append,
132                                                    String indentation)
133                                                    throws FileNotFoundException {
134            
135            if (null == logFile && logToFile)
136                    throw new IllegalArgumentException("logFile cannot be null if logging to file is enabled");
137            
138            this.indentation = (null == indentation ? "" : indentation);
139            this.indentNext = true;
140            this.indentLevel = 0;
141            
142            this.logFile = logFile;
143            this.fileStream = null;
144            this.fileAppend = append;
145            
146            this.outs = new MultiPrintStream();
147            
148            setLogToScreen(logToScreen);
149            setLogToFile(logToFile);
150    }
151    
152    /**
153     * Gets the file associated with this logger (regardless of whether this logger currently
154     * logs to a file).
155     * 
156     * @return File associated with this logger.
157     */
158    public File getFile() {
159            return logFile;
160    }
161    
162    /**
163     * Checks whether this logger currently writes to the screen (std-out).
164     * 
165     * @return Whether this logger currently writes to the screen (std-out).
166     */
167    public boolean getLogToScreen() {
168            return outs.hasOutput(System.out);
169    }
170    
171    /**
172     * Sets whether this logger will write to the screen (std-out) for the subsequent
173     * print-actions.
174     * 
175     * @param logToScreen Whther to write to {@code System.out} or not.
176     */
177    public void setLogToScreen(boolean logToScreen) {
178            if (logToScreen)
179                    outs.addOutputs(System.out);
180            else
181                    outs.removeOutput(System.out, false);
182    }
183    
184    /**
185     * Checks whether this logger currently writes to a file.
186     * 
187     * @return Whether this logger currently writes to a file.
188     */
189    public boolean getLogToFile() {
190            return outs.hasOutput(fileStream);
191    }
192    
193    /**
194     * Sets whether this logger will write to a  for the subsequent print-actions.
195     * The file must be specified in the constructor, if it was set to {@code null},
196     * an attempt to turn file-logging on will raise an {@code IllegalArgumentException}.<br />
197     * If file-writing is being turned on and this logger has not previously written to
198     * a file, the data will be overwritten or appended according to the {@code append}
199     * parameter in the constructor.<br />
200     * If the file-writing was on and is then turned off, the file is closed.<br />
201     * If the file-writing is subsequently turned on again, the file is re-opened
202     * and data is appended at the end.
203     * 
204     * @param logToFile Whether to log to a file or not. 
205     * @throws FileNotFoundException If there is a problem when opening the file.
206     */
207    public void setLogToFile(boolean logToFile) throws FileNotFoundException {
208            
209            if (outs.hasOutput(fileStream) == logToFile)
210                    return;
211            
212            if (logToFile) {
213                    
214                    if (null == logFile)
215                            throw new IllegalArgumentException("Cannot log to file because logFile is null");
216                    
217                    fileStream = new FileOutputStream(logFile, fileAppend);         
218                    try { outs.addOutputs(true, "UTF-8", fileStream); }
219                    catch (UnsupportedEncodingException e) {
220                            throw new RuntimeException("Weird, the encoding \"UTF-8\" is not available", e);
221                    }
222                    fileAppend = true;
223            } else {
224                    outs.removeOutput(fileStream, true);
225                    fileStream = null;
226            }
227    }
228    
229    /**
230     * Closes this logger and a possibly open file stream.
231     */
232    public void close() {
233            if (null != outs) {
234                    outs.flush();
235                    outs.closeExcept(System.out);
236                    outs = null;
237            }
238    }
239    
240    /**
241     * Ensures that all associated streams are closed when this logger is finalised.
242     */
243    @Override
244    protected void finalize() throws Throwable {
245            this.close();
246            super.finalize();
247    }
248    
249    /**
250     * Sets the indentation level to the specicied value.
251     * Each line following a new-line will begin with {@code indentLevel} copies of
252     * the {@code indentation}-string.
253     * 
254     * @param indentLevel The new indentation level.
255     */
256    public void setIndentLevel(int indentLevel) {
257            if (0 > indentLevel)
258                    throw new IllegalArgumentException("0 > indentLevel is not allowed");
259            if (MAX_INDENT_LEVEL < indentLevel)
260                    throw new IllegalArgumentException("MAX_INDENT_LEVEL < indentLevel is not allowed (MAX_INDENT_LEVEL="
261                                                                                     + MAX_INDENT_LEVEL + ")");
262            
263            this.indentLevel = indentLevel;
264    }
265    
266    /**
267     * Gets the current indentation level.
268     * 
269     * @return The current indentation level.
270     */
271    public int getIndentLevel() {
272            return this.indentLevel;
273    }
274    
275    /**
276     * Increments the current indentation level.
277     * Each line following a new-line will begin with {@code indentLevel} copies of
278     * the {@code indentation}-string.
279     */
280    public void incIndentLevel() {
281            if (MAX_INDENT_LEVEL == indentLevel)
282                    throw new IllegalStateException("May not increase indentLevel when it is MAX_INDENT_LEVEL"
283                                                                              + " (MAX_INDENT_LEVEL=" + MAX_INDENT_LEVEL + ")");
284            this.indentLevel++;
285    }
286    
287    /**
288     * Decrements the current indentation level.
289     * Each line following a new-line will begin with {@code indentLevel} copies of
290     * the {@code indentation}-string.
291     */
292    public void decIndentLevel() {
293            if (0 == indentLevel)
294                    throw new IllegalStateException("May not decrease indentLevel when it is zero");
295            this.indentLevel--;
296    }
297    
298    /**
299     * Used internally to generate a correct indentation string.
300     */
301    private void doIndentation() {
302            if (!indentNext)
303                    return;
304            StringBuffer s = new StringBuffer();
305            for (int i = 0; i < indentLevel; i++)
306                    s.append(indentation);
307            indentNext = false;
308            print(s.toString());
309    }
310    
311    /**
312     * Logs a formated string to std-out and/or file following the format specification
313     * defined for {@link java.lang.String#format(String, Object[])}.
314     * 
315     * @param format The format string.
316     * @param args The value arguments.
317     */
318    public void printf(String format, Object ... args) {
319            doIndentation();
320            outs.printf(format, args);
321    }
322    
323    /**
324     * Prints an object to std-out and/or file.
325     * 
326     * @param o Object to print.
327     */
328    public void print(Object o) {
329            doIndentation();
330            outs.print(o);
331    }
332    
333    /**
334     * Prints extended information about a {@code Thowable}
335     * (including stack trace) to std-out and/or file.
336     * 
337     * @param t Object to print.
338     */
339    public void print(Throwable t) {
340            String s = ThrowableTools.stackTraceToString(t);
341            doIndentation();
342            outs.print(s);
343    }
344    
345    /**
346     * Prints a string to std-out and/or file.
347     * 
348     * @param s String to print.
349     */
350    public void print(String s) {
351            doIndentation();
352            outs.print(s);
353    }
354    
355    /**
356     * Prints a string to std-out and/or file.
357     * 
358     * @param s String to print.
359     */
360    public void print(char[] s) {
361            doIndentation();
362            outs.print(s);
363    }
364    
365    /**
366     * Prints the specified value to std-out and/or file.
367     * 
368     * @param val The value to print.
369     */
370    public void print(boolean val) {
371            doIndentation();
372            outs.print(val);
373    }
374    
375    /**
376     * Prints the specified value to std-out and/or file.
377     * 
378     * @param val The value to print.
379     */
380    public void print(char val) {
381            doIndentation();
382            outs.print(val);
383    }
384    
385    /**
386     * Prints the specified value to std-out and/or file.
387     * 
388     * @param val The value to print.
389     */
390    public void print(int val) {
391            doIndentation();
392            outs.print(val);
393    }
394    
395    /**
396     * Prints the specified value to std-out and/or file.
397     * 
398     * @param val The value to print.
399     */
400    public void print(long val) {
401            doIndentation();
402            outs.print(val);
403    }
404    
405    /**
406     * Prints the specified value to std-out and/or file.
407     * 
408     * @param val The value to print.
409     */
410    public void print(float val) {
411            doIndentation();
412            outs.print(val);
413    }
414    
415    /**
416     * Prints the specified value to std-out and/or file.
417     * 
418     * @param val The value to print.
419     */
420    public void print(double val) {
421            doIndentation();
422            outs.print(val);
423    }
424    
425    /**
426     * Initiates a new line by emitting a new-line character to std-out and/or file.
427     * Next output will be indented accoring to the current indentation level.
428     */
429    public void println() {
430            doIndentation();
431            outs.println();
432            indentNext = true;
433    }
434    
435    /**
436     * Prints an object followed by a new line to std-out and/or file.
437     * Next output will be indented accoring to the current indentation level.
438     * 
439     * @param o Object to print.
440     */
441    public void println(Object o) {
442            doIndentation();
443            outs.println(o);
444            indentNext = true;
445    }
446    
447    /**
448     * Prints extended information about a {@code Thowable} (including stack
449     * trace) followed by a new line to std-out and/or file.
450     * Next output will be indented accoring to the current indentation level.
451     * 
452     * @param t Object to print.
453     */
454    public void println(Throwable t) {
455            String s = ThrowableTools.stackTraceToString(t);
456            doIndentation();
457            outs.println(s);
458            indentNext = true;
459    }
460    
461    /**
462     * Prints a string followed by a new line to std-out and/or file.
463     * Next output will be indented accoring to the current indentation level.
464     * 
465     * @param s Object to print.
466     */
467    public void println(String s) {
468            doIndentation();
469            outs.println(s);
470            indentNext = true;
471    }
472    
473    /**
474     * Prints a string followed by a new line to std-out and/or file.
475     * Next output will be indented accoring to the current indentation level.
476     * 
477     * @param s Object to print.
478     */
479    public void println(char[] s) {
480            doIndentation();
481            outs.println(s);
482            indentNext = true;
483    }
484    
485    /**
486     * Prints the specified value followed by a new line to std-out and/or file.
487     * Next output will be indented accoring to the current indentation level.
488     * 
489     * @param val Value to print.
490     */
491    public void println(boolean val) {
492            doIndentation();
493            outs.println(val);
494            indentNext = true;
495    }
496    
497    /**
498     * Prints the specified value followed by a new line to std-out and/or file.
499     * Next output will be indented accoring to the current indentation level.
500     * 
501     * @param val Value to print.
502     */
503    public void println(char val) {
504            doIndentation();
505            outs.println(val);
506            indentNext = true;
507    }
508    
509    /**
510     * Prints the specified value followed by a new line to std-out and/or file.
511     * Next output will be indented accoring to the current indentation level.
512     * 
513     * @param val Value to print.
514     */
515    public void println(int val) {
516            doIndentation();
517            outs.println(val);
518            indentNext = true;
519    }
520    
521    /**
522     * Prints the specified value followed by a new line to std-out and/or file.
523     * Next output will be indented accoring to the current indentation level.
524     * 
525     * @param val Value to print.
526     */
527    public void println(long val) {
528            doIndentation();
529            outs.println(val);
530            indentNext = true;
531    }
532    
533    /**
534     * Prints the specified value followed by a new line to std-out and/or file.
535     * Next output will be indented accoring to the current indentation level.
536     * 
537     * @param val Value to print.
538     */
539    public void println(float val) {
540            doIndentation();
541            outs.println(val);
542            indentNext = true;
543    }
544    
545    /**
546     * Prints the specified value followed by a new line to std-out and/or file.
547     * Next output will be indented accoring to the current indentation level.
548     * 
549     * @param val Value to print.
550     */
551    public void println(double val) {
552            doIndentation();
553            outs.println(val);
554            indentNext = true;
555    }
556    
557    } // public class ScreenFileLogger