package vc.util;

import java.io.*;
import java.util.*;

/**
 * Title:        Logger
 * Description:  Logger is a simple class that can offer many features of logging
 *               in a very powerfull manner. This class offers the logging features are
 *               provided in a userfriendly manner. The features are provided by this
 *               class are sufficient for most of the project requirements and it can
 *               be extended for more feture. It's features include :
 *                   1. Cache the log messages and write the log using a background thread
 *                   2. Frequency of the log writing can be set based upon requirements
 *                   3. Logging to multiple I/O devices is supported including Console,
 *                      files and other type of streams
 *                   4. Abstraction for logging the Exceptions, different levels of logging,
 *                      timestamps, different formats of logging etc.
 *               For using the class instantiate it with the required parameters and
 *               access its methods.
 *
 * Copyright:    Copyright (c) 2001
 * Company:      Network Programs Ltd.
 * @author N.PhanindraKumar (nphanindrakumar@yahoo.com)
 * @version 1.0
 */

public class Logger implements Runnable {

    /**
     * This flag/constant is to be used if messages are to be cached/queued and
     * then logged by using abackground thread. This way much time wont be taken
     * for I/O operations.
     */
    public static final int LoggingType_CacheAndLog = 0;
    /**
     * This flag/constant is to be used if messages are to be logged immediatly
     * without queuing. This way much time may be taken for doing I/O operations.
     */
    public static final int LoggingType_LogImmediatly = 1;


    /**
     * This flag/constant is to be used if the log entries should be made in a
     * format as <Timestamp>, <LogLevel>, <Log Message>
     */
    public static final int LogFormat_TimeStampFirst = 0;
    /**
     * This flag/constant is to be used if the log entries should be made in a
     * format as <LogLevel>, <Timestamp>, <Log Message>
     */
    public static final int LogFormat_MsgTypeFirst = 1;

    /**
     * This flag/constant is to be used if the log message is user-defined/non-standard
     */
    public static final int MsgType_UserDef = 0;
    /**
     * This flag/constant is to be used if the log message is an Information
     */
    public static final int MsgType_Info = 1;
    /**
     * This flag/constant is to be used if the log message is a Warning
     */
    public static final int MsgType_Warning = 2;
    /**
     * This flag/constant is to be used if the log message is an Error
     */
    public static final int MsgType_Error = 3;
    /**
     * This flag/constant is to be used if the log message is a Critical Error
     */
    public static final int MsgType_Critical = 4;
    /**
     * This flag/constant is to be used if the log message is an Exception Information
     */
    public static final int MsgType_Exception = 5;

    /**
     * This flag/constant is to be used if the Exception message is to be logged
     * with the information that an exception of a particular type was raised.
     */
    public static final int ExceptionLogLevel_OriginOnly = 0;
    /**
     * This flag/constant is to be used if the Exception message is to be logged
     * with the detailed information.
     */
    public static final int ExceptionLogLevel_WithMessage = 1;
    /**
     * This flag/constant is to be used if the Exception message is to be logged
     * with the detailed information and stacktrace will be logged into the
     * I/O devices like console and streams.
     */
    public static final int ExceptionLogLevel_WithStackTrace = 2;

    /**
     * This flag/constant is to be used if the logging activity is neigther very high
     * nor very low in a particular application. Based upon this value, the Logger
     * will optimize the cycles/time required for logging activities.
     */
    public static final int LoggingFrequency_Medium = Thread.NORM_PRIORITY;
    /**
     * This flag/constant is to be used if the logging activity is not very high
     * in a particular application. Based upon this value, the Logger will optimize
     * the cycles/time required for logging activities.
     */
    public static final int LoggingFrequency_Low = Thread.MIN_PRIORITY;
    /**
     * This flag/constant is to be used if the logging activity is very high
     * in a particular application. Based upon this value, the Logger will optimize
     * the cycles/time required for logging activities.
     */
    public static final int LoggingFrequency_High = Thread.MAX_PRIORITY;


    /**
     * This flag/constant is to be used for setting the debugging to off. It is the
     * lowest level of the debugging.
     */
    public static final int DebugLevel_Off = 100;
    /**
     * This flag/constant is to be used for setting the debug level on. It is the
     * lowest level of the debugging.
     */
    public static final int DebugLevel_Lowest = 101;
    /**
     * This flag/constant is to be used for setting the debug level on. It is the
     * low level of the debugging.
     */
    public static final int DebugLevel_Low = 102;
    /**
     * This flag/constant is to be used for setting the debug level on. It is the
     * medium level of the debugging. Also this is the default debugging level.
     */
    public static final int DebugLevel_Medium = 103;
    /**
     * This flag/constant is to be used for setting the debug level on. It is the
     * high level of the debugging.
     */
    public static final int DebugLevel_High = 104;
    /**
     * This flag/constant is to be used for setting the debug level on. It is the
     * highest level of the debugging.
     */
    public static final int DebugLevel_Highest = 105;




    /**
     * Holds the current debug level of the Logger object
     */
    private int DebugLevel;
    /**
     * Holds the current logging type for the Logger object
     */
    private int LoggingType;
    /**
     * Holds the current logging format for the Logger object
     */
    private int LogFormat;
    /**
     * Holds the number of extra log devices registerd for the Logger object
     */
    private int NumExtraLogDevices;
    /**
     * Holds if the Logger object has to write the log to Console or not
     */
    private boolean IsConsoleLog;
    /**
     * The thread object that will be used to obtain the messages from the queue
     * and log them using a diferent thread
     */
    private Thread LogWriterThread = null;
    /**
     * This vector holds all the messages that are to be queued. Here vector is
     * used as a datastructure that acts as a queue based upon its usage.
     */
    private Vector MsgQueue = null;
    /**
     * LogFile is the default log file to which the log will be written along with
     * the console. Log may not be written to it, if it is not provided the filename
     * or stream object while constructing or later.
     */
    private FileOutputStream LogFile = null;
    /**
     * ExtraLogDevices holds the Writer and OutputStream objects that are the
     * extra log devices.
     */
    private Vector ExtraLogDevices = null;


   /**
    * Logger() - This private constructs does most of the initialization like setting
    * the default values, instantiating the variables/object ect. This is used by other
    * constructors.
    *
    * @param LoggingType is to indicate if caching of log messages is required or not.
    *           This parameter takes the value from one of the constants LoggingType_CacheAndLog
    *           and LoggingType_LogImmediatly.
    * @param LoggingFrequency is to indicate the frequency of doing the logging activities.
    *           Value of this parameter will be used only if log messages are cached.
    *           This parameter takes the value from one of the constants LoggingFrequency_Medium,
    *           LoggingFrequency_Low and LoggingFrequency_High.
    */
    private Logger(int LoggingType, int LoggingFrequency) {
           this.DebugLevel = Logger.DebugLevel_Medium;
           this.LoggingType = LoggingType;
           this.LogFormat = LogFormat_MsgTypeFirst;
           this.IsConsoleLog = true;
           if(this.LoggingType == LoggingType_CacheAndLog) {
               this.MsgQueue = new Vector(10,10);
               this.LogWriterThread = new Thread(this);
               this.LogWriterThread.setPriority(LoggingFrequency);
               this.LogWriterThread.start();
           }
    }

   /**
    * Logger() - This constructor is to be used for constructing the object which dont
    * log the messages to a file(atleast initially).
    *
    * @param LoggingType is to tell if caching of log messages is required or not.
    *           Its value is taken from one of the constants LoggingType_CacheAndLog
    *           and LoggingType_LogImmediatly.
    */
    public Logger(int LoggingType) {
        this(LoggingType, -1);
    }

   /**
    * Logger() - This constructor is to be used for constructing the object which will
    * log the messages to a file in addition to other I/O devices like Console.
    *
    * @param LogFileName is the name of the file to which the log is to be generated.
    * @param LoggingType is to indicate if caching of log messages is required or not.
    *           This parameter takes the value from one of the constants LoggingType_CacheAndLog
    *           and LoggingType_LogImmediatly.
    * @param LoggingFrequency is to indicate the frequency of doing the logging activities.
    *           Value of this parameter will be used only if log messages are cached.
    *           This parameter takes the value from one of the constants LoggingFrequency_Medium,
    *           LoggingFrequency_Low and LoggingFrequency_High.
    * @exception IOException
    */
    public Logger(String LogFileName, int LoggingType,  int LoggingFrequency) throws IOException {
           this(LoggingType, LoggingFrequency);
           LogFile = new FileOutputStream(LogFileName, true);
    }

   /**
    * Synchronized method logMessageWithoutCaching() - Writes the messages to the
    * log devices directly without caching the messages in a queue. Also this wont
    * add any additional log information like timestamp, message type etc.
    *
    * @param Message Is the actual string that will be logged into the log devices.
    */
    public synchronized void logMessageWithoutCaching(String Message) {
        try {
            this.writeDirectlyToLogDevices("\n" + Message);
        } catch (Exception ex) {
            System.out.println("Raised the Exception in Logger::logMessageWithoutCaching() : " + ex.getMessage());
            ex.printStackTrace();
        }
    }


   /**
    * Synchronized method logDebugMessage() - Writes the debug messages to the
    * log devices eigther by queueing the message or writing it directly to the
    * log devices, based upon the looggingType parameter passed to the constructor
    * of the object. This method makes a log entry with additional information like
    * timestamp etc.
    *
    * @param MessageType Is the parameter that will write the string information about log level.
    *                Takes its value from one of the constants :
    *                    1. DebugLevel_Off
    *                    2. DebugLevel_Lowest
    *                    3. DebugLevel_Low
    *                    4. DebugLevel_Medium
    *                    5. DebugLevel_High
    *                    6. DebugLevel_High
    *
    * @param Message Is the actual string that will be logged into the log devices.
    * @exception Exception
    */
    public synchronized void logDebugMessage(int DebugLevel, String Message) {
       if(DebugLevel!=Logger.DebugLevel_Off && DebugLevel<=this.DebugLevel ) {
           try {
            writeToLogDevices(this.getMessageHeader(DebugLevel, null) + ", " + Message + "\n");
           } catch (Exception ex) {
            System.out.println("Raised the Exception in Logger::logDebugMessage() : " + ex.getMessage());
            ex.printStackTrace();
           }
       }
    }


   /**
    * Synchronized method logMessage() - Writes the log messages to the
    * log devices eigther by queueing the message or writing it directly to the
    * log devices, based upon the looggingType parameter passed to the constructor
    * of the object. This method makes a log entry with additional information like
    * timestamp etc.
    *
    * @param MessageType Is the parameter that will write the string information about log level.
    *                Takes its value from one of the constants :
    *                    1. MsgType_UserDef
    *                    2. MsgType_Info
    *                    3. MsgType_Warning
    *                    4. MsgType_Error
    *                    5. MsgType_Critical
    *                    6. MsgType_Exception
    * @param Message Is the actual string that will be logged into the log devices.
    * @param CustomMessage Is the parameter that carries the title of the custom log level,
    *                  "MessageType" parameter takes the value MsgType_UserDef, or
    *                  else will be null.
    * @exception Exception
    */
    public synchronized void logMessage(int MessageType, String Message, String CustomMessageTitle) {
       try {
        writeToLogDevices(this.getMessageHeader(MessageType, CustomMessageTitle) + ", " + Message + "\n");
       } catch (Exception ex) {
        System.out.println("Raised the Exception in Logger::logMessage() : " + ex.getMessage());
        ex.printStackTrace();
       }
    }


   /**
    * Synchronized method logException() - Writes the exception inmformation to the
    * log devices eigther by queueing the message or writing it directly to the
    * log devices, based upon the looggingType parameter passed to the constructor
    * of the object. This method makes a log entry with the information extracted
    * from the exception object passed as the parameter. The information varies
    * according to the parameter ExceptionLogLevel
    *
    * @param CatchedException Is the parameter that carries the exception object whose
    *                     information is to be logged.
    * @param MessageType Is the parameter that will write the string information about
    *                log level for exceptions.Takes its value from one of the constants:
    *                    1. ExceptionLogLevel_OriginOnly
    *                    2. ExceptionLogLevel_WithMessage
    *                    3. ExceptionLogLevel_WithStackTrace
    * @exception Exception
    */
    public synchronized void logException(Exception CatchedException, int ExceptionLogLevel ) {
        try {
            writeToLogDevices(this.getMessageHeader(Logger.MsgType_Exception, null) + ", " + getExceptionMessage(CatchedException,ExceptionLogLevel) + "\n");
        } catch (Exception ex) {
            System.out.println("Raised the Exception in Logger::logMessage() : " + ex.getMessage());
            ex.printStackTrace();
        }
    }

   /**
    * Synchronized method logExceptionWithMessage() - Writes the exception inmformation to the
    * log devices eigther by queueing the message or writing it directly to the
    * log devices, based upon the looggingType parameter passed to the constructor
    * of the object. Along with the exception information, additional information
    * related to the same context can be looged. This method makes a log entry
    * with the information extracted from the exception object passed as the parameter.
    * The information varies according to the parameter ExceptionLogLevel
    *
    * @param CatchedException Is the parameter that carries the exception object whose
    *                     information is to be logged.
    * @param MessageType Is the parameter that will write the string information about
    *                log level for exceptions.Takes its value from one of the constants:
    *                    1. ExceptionLogLevel_OriginOnly
    *                    2. ExceptionLogLevel_WithMessage
    *                    3. ExceptionLogLevel_WithStackTrace
    * @param MessageType Is the parameter that will write the string information about log level.
    *                Takes its value from one of the constants :
    *                    1. MsgType_UserDef
    *                    2. MsgType_Info
    *                    3. MsgType_Warning
    *                    4. MsgType_Error
    *                    5. MsgType_Critical
    *                    6. MsgType_Exception
    * @param Message Is the actual string that will be logged into the log devices.
    * @param CustomMessage Is the parameter that carries the title of the custom log level,
    *                  "MessageType" parameter takes the value MsgType_UserDef, or
    *                  else will be null.
    * @exception Exception
    */
    public synchronized void logExceptionWithMessage(Exception CatchedException, int ExceptionLogLevel, int MessageType, String Message, String CustomMessageTitle) {
        try {
            writeToLogDevices(this.getMessageHeader(MessageType, CustomMessageTitle) + ", " + Message + "Following is the log entry with the exception data.\n");
            writeToLogDevices(this.getMessageHeader(Logger.MsgType_Exception, null) + ", " + getExceptionMessage(CatchedException,ExceptionLogLevel) + "\n");
        } catch (Exception ex) {
            System.out.println("Raised the Exception in Logger::logMessage() : " + ex.getMessage());
            ex.printStackTrace();
        }
    }


    /**
     * This method is for the multithreaded functionality required by the objects
     * of Logger. If Logger object uses the queueing of log messages, then this method
     * will execute in a different thread to extract the log messages from the queue
     * and then write them to the log devices.
     *
     */
    public void run() {
        while(true) {
            try {
               writeDirectlyToLogDevices((String)MsgQueue.remove(0));
            } catch(ArrayIndexOutOfBoundsException aioob) {
                saveDataInStreams(false);
            }
        }
    }


    /**
     * Sets/Resets the Logger object's data memeber LogFile  to the file named
     * with the value of the parameter LogFileName. If alredy a file is associated
     * with the object it will be closed and a new one will be opened.
     *
     * @param LogFileName Is to carry the file name that should be set as the log file.
     * @exception  IOException
     */
    public void setLogFile(String LogFileName) throws IOException {
           if(this.LogFile != null) {
                   this.LogFile.close();
           }
           LogFile = new FileOutputStream(LogFileName, true);
    }


    /**
     * Sets/Resets the Logger object's data memeber LogFile  to the parameter LogFile.
     * If alredy a file is associated with the object it will be closed and a
     * new one will be set.
     *
     * @param LogFileName Is to carry the file name that should be set as the log file.
     * @exception  IOException
     */
    public void setLogFile(FileOutputStream LogFile) throws IOException {
           if(this.LogFile != null) {
                   this.LogFile.close();
           }
           this.LogFile = LogFile;
    }


    /**
     * Returns the default log file associated with the Logger object
     *
     * @return Returns the data memeber of the object LogFile
     */
    public FileOutputStream getLogFile() {
           return(this.LogFile);
    }


    /**
     * Console Logging can be turned on and off with this method. This will
     * immediatly take affect, and the next comming messages(even those in the queue)
     * will be logged accordingly.
     *
     * @param IsConsoleLog If carries a value "true" then the next messages will be
     *        logged into the console also, or else they wont be logged to the console.
     */
    public void setConsoleLogging(boolean IsConsoleLog) {
           this.IsConsoleLog = IsConsoleLog;
    }


    /**
     * Returns the current console logging status of the Logger object.
     *
     * @return Returns current console logging status of the Logger object using
     *         the data memeber IsConsoleLog.
     */
    public boolean getConsoleLogging() {
           return(this.IsConsoleLog);
    }


    /**
     * sets the current debug level of the Logger object.
     *
     * @@param DebugLevel is to set the dubug level and it takes effect immediatly
     */
    public void setDebugLevel(int DebugLevel) {
           this.DebugLevel = DebugLevel;
    }


    /**
     * Returns the current debugging level of the Logger object.
     *
     * @return Returns current debugging level of the Logger object using
     *         the data memeber DebugLevel.
     */
    public int getDebugLevel() {
           return(this.DebugLevel);
    }


    /**
     * Adds OutputStream object to the list of existing log devices. This will
     * immediatly take affect, and the next comming messages(even those in the queue)
     * will be logged accordingly. This log device is an extra log device(i.e one
     * other than console and file log devices)
     *
     * @param LogStream Is the extra log device to which the log is to be written.
     */
    public void addExtraLogDevice(OutputStream LogStream) {
        if(this.ExtraLogDevices == null) {
            this.ExtraLogDevices = new Vector(3,3);
        }
        this.ExtraLogDevices.add(LogStream);
        NumExtraLogDevices = this.ExtraLogDevices.size();
    }


    /**
     * Adds LogWriter object to the list of existing log devices. This will
     * immediatly take affect, and the next comming messages(even those in the queue)
     * will be logged accordingly. This log device is an extra log device(i.e one
     * other than console and file log devices)
     *
     * @param LogWriter Is the extra log device to which the log is to be written.
     */
    public void addExtraLogDevice(Writer LogWriter) {
        if(this.ExtraLogDevices == null) {
            this.ExtraLogDevices = new Vector(3,3);
        }
        this.ExtraLogDevices.add(LogWriter);
        NumExtraLogDevices = this.ExtraLogDevices.size();
    }


    /**
     * This will remove all the extra log devices that existwith this object. This will
     * immediatly take affect, and the next comming messages(even those in the queue)
     * will be logged accordingly. After this new log devices can be added ad needed.
     */
    public void removeAllExtraLogDevices() {
        this.ExtraLogDevices.clear();
        this.ExtraLogDevices = null;
        NumExtraLogDevices = 0;
    }


    /**
     * Sets the Logger object's logging format with the parameter LogFormat.
     *
     * @param LogFormat Is to set the Logger object's logging format. It can take
     *        the value from one of the following.
     *          1. LogFormat_TimeStampFirst
     *          2. LogFormat_MsgTypeFirst
     */
    public void setLogFormat(int LogFormat) {
        this.LogFormat = LogFormat;
    }


    /**
     * Returns the current logging format of this object
     *
     * @return Returns the data memeber of the object LogFormat that holds the
     *         objects current logging format.
     */
    public int getLogFormat() {
        return(this.LogFormat);
    }


    /**
     * This method is to write the log messages in a normalized way irrespective
     * of the current logging type. If the logging type is to cache/queue the
     * messages then the message will be added to a vector, or else it will be
     * written directly to the devices.
     *
     * @param Message carries the message that is to be logged.
     */
    private void writeToLogDevices(String Message) {
        if(this.LoggingType == Logger.LoggingType_CacheAndLog) {
            this.MsgQueue.add(Message);
        }
        else {
            writeDirectlyToLogDevices(Message);
        }
    }


    /**
     * This method is to write the log messages directly to the log devices. It
     * will check if Console and file devices are ready to write the log and
     * then writes the log. Also it will check if any extra devices are there that
     * are loggable and log will be written to them.
     *
     * @param Message carries the message that is to be logged.
     */
    private void writeDirectlyToLogDevices(String Message) {
        Object extraLogDevice = null;
        if(this.IsConsoleLog == true) {
            System.out.println(Message);
        }
        try {
            if(this.LogFile != null) {
                this.LogFile.write(Message.getBytes());
            }
            for(int i=0; i<NumExtraLogDevices; ++i) {
                extraLogDevice = ExtraLogDevices.get(i);
                if(Class.forName("Writer").isInstance(extraLogDevice) == true) {
                    ( (Writer)extraLogDevice).write(Message);
                }
                if(Class.forName("OutputStream").isInstance(extraLogDevice) == true) {
                    ( (OutputStream)extraLogDevice).write(Message.getBytes());
                }
            }
        } catch (Exception ex) {
            System.out.println(" An exception is trown while doing the I/O operations. Here are the details of the exception : ");
            ex.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * This method prepares the header for log entry related to the message. It is
     * prepared using the current timestamp and then the message type or log level.
     * Its format will be determined using the memeber "LogFormat".
     *
     * @param MessageType carries the message type or log level of the message
     * @param CustomMessageTitle carries the title for custom log lvel if it the
     *        "MessageType" parameter takes a value MsgType_UserDef. Or else it carries null.
     * @param exception Exception
     */
    private String getMessageHeader(int MessageType, String CustomMessageTitle) throws Exception {
        String MessageTitle = "";
        String TimeStamp = (new Date()).toString() + " -- " + System.currentTimeMillis();
        switch(MessageType) {
            case MsgType_UserDef : if(CustomMessageTitle == null) {
                                       throw new Exception("Invalid/Null String for \"CustomMessageTitle\" parameter ");
                                   }
                                   MessageTitle = CustomMessageTitle;
                                   break;
            case MsgType_Info : MessageTitle = "Information" ;
                                   break;
            case MsgType_Warning : MessageTitle = "Warning";
                                   break;
            case MsgType_Error : MessageTitle = "Error";
                                   break;
            case MsgType_Critical : MessageTitle = "Critical";
                                   break;
            case MsgType_Exception : MessageTitle = "Exception";
                                   break;
            case DebugLevel_Off : MessageTitle = "Debug";
                                   break;
            case DebugLevel_Lowest : MessageTitle = "Debug";
                                   break;
            case DebugLevel_Low : MessageTitle = "Debug";
                                   break;
            case DebugLevel_Medium : MessageTitle = "Debug";
                                   break;
            case DebugLevel_High : MessageTitle = "Debug";
                                   break;
            case DebugLevel_Highest : MessageTitle = "Debug";
                                   break;
            default : throw new Exception("Invalid Message Type with a value : " + MessageType);
        }

        switch(LogFormat) {
            case LogFormat_TimeStampFirst : return(TimeStamp + ", " + MessageTitle);
            case LogFormat_MsgTypeFirst : return(MessageTitle + ", " + TimeStamp);
            default : throw new Exception("Invalid LogFormat Type with a value : " + LogFormat);
        }
    }


    /**
     * This method prepares the header for log entry related to the Exception. It is
     * prepared using the current timestamp and then the exception information.
     * Its format will be determined using the memeber "LogFormat".
     *
     * @param CatchedException carries the exception object from which the exception
     *        information will be obtained.
     * @param ExceptionLogLevel will be used to determine the level of information
     *        that is to be used for logging the exception information.
     * @param exception Exception
     */
    private String getExceptionMessage(Exception CatchedException, int ExceptionLogLevel )  throws Exception {
        Object extraLogDevice = null;
        String ExceptionMessage = "Catched/Recieved an exception \"" + CatchedException.getClass() + "\".";
        if(ExceptionLogLevel == Logger.ExceptionLogLevel_WithMessage || ExceptionLogLevel == Logger.ExceptionLogLevel_WithStackTrace) {
            ExceptionMessage = ExceptionMessage + "\t\t" + CatchedException.getMessage() + ".\n\t\t" + CatchedException.toString() + ".";
        }
        if(ExceptionLogLevel == Logger.ExceptionLogLevel_WithStackTrace) {
            CatchedException.printStackTrace();
            for(int i=0; i<NumExtraLogDevices; ++i) {
                extraLogDevice = ExtraLogDevices.get(i);
                if(Class.forName("PrintWriter").isInstance(extraLogDevice) == true) {
                    CatchedException.printStackTrace((PrintWriter)extraLogDevice);
                }
                if(Class.forName("PrintStream").isInstance(extraLogDevice) == true) {
                    CatchedException.printStackTrace((PrintStream)extraLogDevice);
                }
            }

        }
        return(ExceptionMessage);
    }


    /**
     * This method is used to save the information that is written to different
     * log devices like File, Streams, Writers etc. Optionally all these resources
     * can be closed.
     *
     * @param AlsoCloseStreams If this takes the value "true" all the devices will
     * be flushed or else they will vbe closed(i.e save+close).
     */
    private void saveDataInStreams(boolean AlsoCloseStreams) {
        Object extraLogDevice = null;
        String DeviceType = "";
        try {
            if(this.LogFile != null) {
                if(AlsoCloseStreams == true) {
                    this.LogFile.close();
                }
                else {
                    this.LogFile.flush();
                }
            }

            for(int i=0; i<NumExtraLogDevices; ++i) {
                extraLogDevice = ExtraLogDevices.get(i);
                if(Class.forName("Writer").isInstance(extraLogDevice) == true) {
                    if(AlsoCloseStreams == true) {
                        ( (Writer)extraLogDevice).close();
                        ExtraLogDevices.remove(extraLogDevice);
                    }
                    else {
                        ( (Writer)extraLogDevice).flush();
                    }
                }
                if(Class.forName("OutputStream").isInstance(extraLogDevice) == true) {
                    if(AlsoCloseStreams == true) {
                        ( (OutputStream)extraLogDevice).close();
                        ExtraLogDevices.remove(extraLogDevice);
                    }
                    else {
                        ( (OutputStream)extraLogDevice).flush();
                    }
                }
            } //end for
        } catch (Exception ex) {
            System.out.println(" An exception is thrown while doing the I/O operations. Here are the details of the exception : ");
            ex.printStackTrace();
            System.exit(-1);
        }
    }

    /**
     * This method does the clenup for the object before it dies. It closes the
     * IODevices held by this object, so that the information in them becomes persistent.
     */
    protected void finalize() {
        writeDirectlyToLogDevices("\n****************************************** Logger Terminates Here ******************************************");
        saveDataInStreams(true);
    }

}

