001    package log;
002    
003    import java.io.*;
004    import java.util.Iterator;
005    
006    /**
007     * Represents a log file.
008     *
009     * <p>There is one global writable log file in the whole system. An instance
010     * of {@link LogCreator} is used to create the global log file as well as any local
011     * log file.</p>
012     *
013     * <p>Anything loggable must suit the {@link Loggable} interface and must be able to
014     * generate an instance of {@link LogEntry}</p>
015     *
016     * <p>To read in log files see {@link LogInputStream} and {@link LogFileContent}.</p>
017     *
018     * @author Steffen Zschaler
019     * @version 1.0
020     * @since v1.0
021     */
022    public class Log extends Object implements LogContext {
023    
024        /**
025         * The log file's output stream.
026         *
027         * @see Log#changeOutputStream
028         * @see Log#log
029         */
030        protected ObjectOutputStream ooOutput = null;
031    
032        /**
033         * Save log file to persistence file or not.
034         */
035        protected boolean saveToPersistence = false;
036    
037        /**
038         * Construct a new log file.
039         *
040         * @param os the outputstream to write to.
041         */
042        public Log(OutputStream os) {
043            super();
044            try {
045                changeOutputStream(os);
046            }
047            catch (IOException ioe) {}
048        }
049    
050        /**
051         * Closes this Log file.
052         *
053         * @exception IOException if an error occurred while closing the underlying stream.
054         *
055         * @see Log#closeGlobalLog
056         *
057         * @override Never
058         */
059        public synchronized void closeLog() throws IOException {
060            changeOutputStream(null);
061        }
062    
063        /**
064         * Called by the garbage collector on an object when garbage collection
065         * determines that there are no more references to the object.
066         *
067         * <p>Disposes this log file. If this is the global log file, calls
068         * {@link #closeGlobalLog()}, else calls {@link #closeLog()}.</p>
069         *
070         * @exception IOException if an error occurs while closing the underlying stream.
071         *
072         * @see Log#closeLog
073         * @see Log#closeGlobalLog
074         *
075         * @override Never
076         */
077        protected void finalize() throws IOException {
078            if (this == theGlobalLog) {
079                closeGlobalLog();
080            } else {
081                closeLog();
082            }
083        }
084    
085        /**
086         * Change this log's outputstream.
087         *
088         * <p>If an outputstream exists it is closed prior to setting the new outputstream.</p>
089         *
090         * @param os the new output stream.
091         * @exception IOException if an error occured while closing the old stream.
092         *
093         * @see Log#setGlobalOutputStream
094         *
095         * @override Never
096         */
097        public synchronized void changeOutputStream(OutputStream os) throws IOException {
098            if (ooOutput != null) {
099                logCloseLog();
100                ooOutput.close();
101            }
102    
103            if (os != null) {
104                os.write(0); // provide for appended logs
105                ooOutput = new ObjectOutputStream(os);
106            } else {
107                ooOutput = null;
108            }
109    
110            if (ooOutput != null) {
111                logOpenLog();
112            }
113        }
114    
115        /**
116         * Add a log entry when closing the log file.
117         *
118         * <p>Currently does nothing. You can override this to write a log entry when
119         * the log file is being closed.</p>
120         *
121         * @see Log#closeLog
122         *
123         * @override Sometimes Override this method if you want to add a log entry when the log file is closed.
124         */
125        protected void logCloseLog() {}
126    
127        /**
128         * Add a log entry when opening the log file.
129         *
130         * <p>Currently does nothing. You can override this to write a log entry when
131         * the log file is being opened.</p>
132         *
133         * @see Log#changeOutputStream
134         *
135         * @override Sometimes Override this method if you want to add a log entry when the log file is opened.
136         */
137        protected void logOpenLog() {}
138    
139        /**
140         * Adds one entry to the log file. Calls l.getLogData().
141         *
142         * @param l the loggable event to be logged.
143         * @see Loggable
144         *
145         * @exception LogNoOutputStreamException if no OutputStream has been
146         * specified.
147         *
148         * @exception IOException if an IOException occurs when writing to the
149         * stream.
150         *
151         * @override Never
152         */
153        public synchronized void log(Loggable l) throws LogNoOutputStreamException, IOException {
154            if (ooOutput == null) {
155                throw new LogNoOutputStreamException("on Log.log ( " + l + " )");
156            }
157    
158            LogEntry le = l.getLogData();
159            ooOutput.writeObject(le);
160        }
161    
162        /**
163         * Appends all LogEntries that are saved in <code>lfc</code> to the current log. This method is used
164         * when restoring a log file from a persistence file.
165         *
166         * @param lfc contains the LogEntries to be added
167         * @throws LogNoOutputStreamException if no OutputStream is specified
168         * @throws IOException if a log entry cannot be written zu the output stream
169         */
170        public void addLogEntries(LogFileContent lfc) throws LogNoOutputStreamException, IOException {
171            if (ooOutput == null) {
172                throw new LogNoOutputStreamException("on Log.addLogEntries");
173            }
174            for (Iterator<LogEntry> it = lfc.getContentList().iterator(); it.hasNext();) {
175                LogEntry le = it.next();
176                ooOutput.writeObject(le);
177            }
178        }
179    
180        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
181        // STATIC PART
182        ////////////////////////////////////////////////////////////////////////////////////////////////////////////
183    
184        /**
185         * The global log to create a <i>Singleton</i>.
186         */
187        private static Log theGlobalLog = null;
188    
189        /**
190         * The global log file. This is the acutual file object to be written to.
191         */
192        private static File globalLogFile = null;
193    
194        /**
195         * The global Log creator. It creates a log and returns it. Used by {@link createLog}.
196         */
197        private static LogCreator theLogCreator = null;
198        static {
199            theLogCreator = new LogCreator() {
200                public Log createLog(OutputStream os) {
201                    return new Log(os);
202                }
203            };
204        }
205    
206        /**
207         * Reference to the global output stream.
208         *
209         * <p><STRONG>Read Only</STRONG></p>
210         *
211         * @see Log#setGlobalOutputStream
212         */
213        protected static OutputStream theGlobalOutputStream = null;
214    
215        /**
216         * Returns the current global log.
217         *
218         * <p>If no log exists, one is created using the Outputstream as specified
219         * by {@link #setGlobalOutputStream}</p>
220         *
221         * @see #setGlobalOutputStream
222         * @see #closeGlobalLog
223         *
224         * @exception LogNoOutputStreamException if <code>setGlobalOutputStream()</code>
225         * has not been called yet.
226         */
227        public synchronized static Log getGlobalLog() throws LogNoOutputStreamException {
228            if (theGlobalOutputStream == null) {
229                throw new LogNoOutputStreamException("On Log.getGlobalLog()");
230            }
231    
232            if (theGlobalLog == null) {
233                theGlobalLog = createLog(theGlobalOutputStream);
234    
235            }
236            return theGlobalLog;
237        }
238    
239        /**
240         * Create a new Log file using the current Log creator.
241         *
242         * <p>You should prefer calling this method to directly creating a new Log
243         * file as this method will provide an easy interface for adapting to new
244         * log classes.</p>
245         *
246         * @param os the OutputStream to be used.
247         */
248        public static Log createLog(OutputStream os) {
249            return theLogCreator.createLog(os);
250        }
251    
252        /**
253         * Change the Log creator.
254         *
255         * <p>Call to provide support for descended Log classes.</p>
256         *
257         * @param lc the log creator to be used when creating log files.
258         *
259         * @see Log#getGlobalLog
260         */
261        public static void setLogCreator(LogCreator lc) {
262            theLogCreator = lc;
263        }
264    
265        /**
266         * Closes the global log file if any log file was open.
267         *
268         * <p>If no log file exists no exception is thrown.
269         * Closes the log file <B>and</B> it's OutputStream.</p>
270         *
271         * @exception IOException if an error occurs while closing the underlying stream.
272         *
273         * @see Log#getGlobalLog
274         * @see Log#setGlobalOutputStream
275         */
276        public synchronized static void closeGlobalLog() throws IOException {
277            if (theGlobalLog != null) {
278                theGlobalLog.closeLog();
279    
280                theGlobalLog = null;
281                theGlobalOutputStream = null;
282            }
283        }
284    
285        /**
286         * Changes the current OutputStream of the global log file.
287         *
288         * <p>This method <strong>must</strong> be called at least once before any global log
289         * operation takes place.</p>
290         *
291         * <p>If an OutputStream exists it will be closed automatically. To close
292         * the entire global log file use {@link #closeGlobalLog()}.</p>
293         *
294         * @param newOutputStream the new global output stream
295         *
296         * @exception IOException if an error occurs while closing the original stream.
297         *
298         * @see #getGlobalLog
299         * @see #closeGlobalLog
300         * @see #changeOutputStream
301         */
302        public synchronized static void setGlobalOutputStream(OutputStream newOutputStream) throws IOException {
303            OutputStream os = theGlobalOutputStream;
304            theGlobalOutputStream = newOutputStream;
305            if (theGlobalLog != null) {
306                theGlobalLog.changeOutputStream(newOutputStream);
307            } else {
308                if (os != null) {
309                    os.close();
310                }
311            }
312        }
313    
314        /**
315         * Sets the global log file and assigns a FileOutputStream to it.
316         *
317         * @param filename the file to be used as global log file.
318         * @param overwrite if <code>true</code>, an existing file with the name <code>fileName</code> will be
319         * overwritten, if <code>false</code>, the LogEntries will be appended to that existing file.<br>
320         * With this flag one can decide if consecutive runs of the application should be logged in one file or if the old log
321         * file should always be overwritten.
322         * @param save if <code>true</code>, the global log file will be written to the persistence file when
323         * the Shop is saved and accordingly loaded when the Shop's state is loaded. When that happens, the
324         * current log file will be deleted, even if <code>overwrite</code> is set to <code>false</code>.
325         */
326        public synchronized static void setGlobalLogFile(String filename, boolean overwrite, boolean save)
327                throws IOException {
328    
329            if (overwrite) {
330                File f = new File(filename);
331                if (f.exists()) {
332                    //set to null to allow the existing file to be deleted
333                    setGlobalOutputStream(null);
334                    f.delete();
335                }
336            }
337            setGlobalOutputStream(new FileOutputStream(filename, true));
338            globalLogFile = new File(filename);
339            try {
340                getGlobalLog().saveToPersistence = save;
341            }
342            catch (LogNoOutputStreamException ex) {
343            }
344        }
345    
346        /**
347         * Sets the global log file and assigns a FileOutputStream to it.<br>
348         * This method calls <code>setGlobalLogFile(filename, false, false)</code>, that is, Log entries will
349         * be append to older log files, if they exist. The log files will not be saved/restored when the Shop
350         * is saved/restored.
351         * @param filename the file to be used as global log file.
352         */
353        public synchronized static void setGlobalLogFile(String filename) throws IOException {
354            setGlobalLogFile(filename, false, false);
355        }
356    
357        /**
358         * @return the global log file, if set. Otherwise <code>null</null>
359         */
360        public static File getGlobalLogFile() {
361            return globalLogFile;
362        }
363    
364        /**
365         * States if the log file should be saved to the persistence file.
366         * @return <code>true</code> if the log file should be saved to the persistence file, otherwise
367         * <code>false</code>.
368         */
369        public static boolean getSaveToPersistence() {
370            try {
371                return getGlobalLog().saveToPersistence;
372            }
373            catch (Exception ex) {
374                return false;
375            }
376        }
377    
378    }