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 it = lfc.getContentList().iterator(); it.hasNext();) {
175 LogEntry le = (LogEntry)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 }