📄 logfile.java
字号:
package momelog.listener;import java.io.IOException;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.UnsupportedEncodingException;import java.io.Writer;import java.util.Enumeration;import javax.microedition.io.ConnectionNotFoundException;import javax.microedition.io.Connector;import javax.microedition.io.file.FileConnection;import javax.microedition.io.file.FileSystemListener;import javax.microedition.io.file.FileSystemRegistry;import momelog.Configurable;import momelog.LogEvent;import momelog.LogListener;import momelog.Logger;/** * {@link LogListener} implementation intended to collect logging information in * the destination file. It can be used only on devices or emulators that * support {@code FileConnection API}. * </p> * <p> * {@link LogFile} resembles very much corresponding class in {@code log4J} * framework. All logging events are converted to strings and written to the * destination text file separated by {@code new-line} character ({@code '\n'}). * By using {@link #setPrefix(String)} and/or {@link #setSuffix(String)} setter * methods it is possible to specify strings to be putted at the start and/or at * the end of each logging line respectively. It is also possible to specify * first (<em>header</em>) and last (<em>footer</em>) line of the * destination file by calling {@link #setHeader(String)} and * {@link #setFooter(String)} setter methods respectively. Footer line is * written , when {@link #close()} method is called at the end of the * application's run. Header line is only written, if {@link LogFile} creates or * rewrites the destination file. * </p> * <p> * The location of the destination file can be set by calling * {@link #setOutfile(String)} setter method. It is specified via it's fully * qualified path-name, that includes starting slash ({@code /}) and root's * name. In other words, it is a file's url without protocol and host specifiers * (i.e. {@code file://}). For example file identified by url * {@code file:///SDCard/logs/myapp/log.txt} can be specified by path-name * {@code /SDCard/logs/myapp/log.txt}. By default, the location of the * destination file is set to {@code "momelog"} in first root returned from * {@link FileSystemRegistry#listRoots()} static method. * </p> * <p> * The character encoding used for writing file can be specified by * {@link #setEncoding(String)} setter method. By default {@code UTF-8} encoding * is used. It is also possible to specify whether destination file should be * rewritten on open, if it already exists by using {@link #setAppend(boolean)} * setter method. It is rewritten by default. * </p> * <p> * As J2ME architecture doesn't define {@code finalize()} method in class * {@link Object}, {@link LogFile} class defines {@link #close()} method * intended to explicitly close connection and streams. This method actually * writes {@code footer} line (if there is some) and then closes connection (and * all streams, of course). This method is intended to be called at the end of * application's run (e.g. from {@code MIDlet.destroyApp(boolean unconditional)} * method). Besides it is recommended, users need to do this only, if they are * interesting in {@code footer} line to be written at the end of the file, * because destination file is <em>flushed</em> after every logging line. * </p> * <p> * {@link LogFile} implements {@link FileSystemListener} interface. Root, where * destination file is located, can be unmounted between logging. To eliminate * I/O errors, no logging information is written at that time. After next root * mount, new logging lines will be appended to the destination file * (destination file will be not rewritten and skipped logging lines will be * lost). * </p> * <p> * {@link LogFile} implements {@link Configurable} interface. It can be * configured declaratively from initialization file (recommended) and/or * programmatically by invoking setter methods. {@link LogFile} provides * meaningful default values for all properties and doesn't require * configuration. * </p> * <p> * See <a href="../../../logfile-guide.html">LogFile Guide</a> for more * details. * </p> * * @author Sergio Morozov * @version 1.0 */public class LogFile implements Configurable, LogListener, FileSystemListener{ /** * Default filename of the destination file ({@code "momelog.log"}). */ public static final String DEFAULT_DESTINATION_FILENAME = "momelog.log"; /** * Default character encoding to be used for writing the destination file ({@code UTF-8}). */ public static final String DEFAULT_ENCODING = "UTF-8"; private String outFile = null; private String root = null; private String encoding = DEFAULT_ENCODING; private boolean append = true; private FileConnection con = null; private OutputStream out = null; private Writer writer = null; private boolean canLog = true; private StringBuffer buffer = new StringBuffer(); private String header = null; private String footer = null; private String prefix = null; private String suffix = null; /** * Instantiates {@link LogFile} with default character encoding and no * {@code outfile}. */ public LogFile() { super(); FileSystemRegistry.addFileSystemListener(this); } /** * Instantiates {@link LogFile} initialized with the given location of the * destination file and default encoding ({@code UTF-8}). The location of * destination file is designated by it's fully qualified path-name, that * includes starting slash ({@code /}) and root's name. In other words, it * is a file's url without protocol and host specifiers (i.e. {@code file://}). * For example file identified by url * {@code file:///SDCard/logs/myapp/log.txt} can be specified by path-name * {@code /SDCard/logs/myapp/log.txt}. * * @param outFile * location of the destination file. * @throws NullPointerException * if {@code outFile} is {@code null}. * @throws IllegalArgumentException * if {@code outFile} is empty or doesn't start with a slash ({@code /}). */ public LogFile(String outFile) { this(); setOutfile(outFile); } /** * Instantiates {@link LogFile} initialized with the given location of the * destination file and encoding. The location of destination file is * designated by it's fully qualified path-name, that includes starting slash ({@code /}) * and root's name. In other words, it is a file's url without protocol and * host specifiers (i.e. {@code file://}). For example file identified by url * {@code file:///SDCard/logs/myapp/log.txt} can be specified by path-name * {@code /SDCard/logs/myapp/log.txt}. * * @param outFile * location of the destination file. * @param encoding * character encoding to be used for writing destination file. * @throws NullPointerException * if {@code outFile} or {@code encoding} is {@code null}. * @throws IllegalArgumentException * if {@code outFile} is empty or doesn't start with a slash ({@code /}). */ public LogFile(String outFile, String encoding) { this(outFile); setEncoding(encoding); } /** * Returns {@code append} flag, that indicates whether destination file is * rewritten on open, if it already exists. * * @return {@code false} if the destination file is rewritten on open, if it * already exists, {@code true} otherwise. */ public boolean isAppend() { return this.append; } /** * Sets {@code append} flag, that controls whether destination file should be * rewritten on open, if it already exists. * * @param append * {@code false} if the destination file should be rewritten on open, * if it already exists, {@code true} otherwise. */ public synchronized void setAppend(boolean append) { this.append = append; } /** * Returns character encoding used for writing the destination file. * * @return the character encoding used for writing the destination file. */ public String getEncoding() { return this.encoding; } /** * Sets character encoding to be used for writing the destination file. This * method, to take effect, should be called before opening the destination * file. * * @param encoding * character encoding to be used for writing to the destination file. * @throws NullPointerException * if {@code encoding} is {@code null}. */ public synchronized void setEncoding(String encoding) { if (encoding == null) throw new NullPointerException("encoding"); this.encoding = encoding; } /** * Returns location of the destination file. * * @return file path of the destination file. */ public String getOutFile() { return this.outFile; } /** * Sets location of the destination file. The location of destination file is * designated by it's fully qualified path-name, that includes starting slash ({@code /}) * and root's name. In other words, it is a file's url without protocol and * host specifiers (i.e. {@code file://}). For example file identified by url * {@code file:///SDCard/logs/myapp/log.txt} can be specified by path-name * {@code /SDCard/logs/myapp/log.txt}. * <p> * <strong>Note:</strong> If this method is called, when some other file is * already open, it is closed ({@code footer} line, if there is some, is * written) and connection to a new one is opened on new logging. * </p> * * @param outFile * the location to the destination file. * @throws NullPointerException * if {@code outFile} is {@code null}. * @throws IllegalArgumentException * if {@code outFile} is empty or doesn't start with a slash ({@code /}). */ public synchronized void setOutfile(String outFile) { if (outFile == null) throw new NullPointerException("outFile"); if (outFile.length() == 0) throw new IllegalArgumentException("Empty file path"); if (outFile.charAt(0) != '/') throw new IllegalArgumentException("Malformed file path " + outFile + ". Must start with slash (/)"); if (this.outFile == null || !this.outFile.equals(outFile)) { if (this.con != null) closeConnection(); this.outFile = outFile; int rootEnd = this.outFile.indexOf('/', 1) + 1; if (rootEnd < 2) rootEnd = outFile.length(); this.root = outFile.substring(1, rootEnd); this.canLog = true; } } /** * Returns {@link Writer} to the destination file. Creates * {@link FileConnection} if needed. Returns {@code null} if {@link Writer} * can't be created. * * @return {@link Writer} to the destination file. */ private Writer getWriter() { if (this.writer == null) { if (this.outFile == null) { Enumeration e = FileSystemRegistry.listRoots(); if (e.hasMoreElements()) { this.outFile = '/' + ((String) e.nextElement()) + DEFAULT_DESTINATION_FILENAME; System.out .println("No MoMELog destination file path specified. Setting the default " + this.outFile); } } if (this.outFile == null) { System.err.println("ERROR: No destination file path specified"); this.canLog = false; } else try { this.con = (FileConnection) Connector.open("file://" + this.outFile, Connector.READ_WRITE); long offset = 0L; boolean shouldCreate = !this.con.exists(); if (!shouldCreate) { if (shouldCreate = !this.append) this.con.delete(); else { offset = this.con.fileSize(); if (shouldCreate = offset < 0) { offset = 0L; this.con.delete(); System.err.println("WARNING: Can't obtain size of file " + this.outFile + ". File rewritten."); } } } if (shouldCreate) this.con.create(); try { this.writer = new OutputStreamWriter(this.out = this.con .openOutputStream(offset), this.encoding); } catch (UnsupportedEncodingException e) { System.err.println("ERROR: Unsupported encoding \"" + this.encoding + "\". Fallbacking to UTF-8."); try { this.writer = new OutputStreamWriter(this.out = this.con .openOutputStream(offset), this.encoding = DEFAULT_ENCODING); } catch (UnsupportedEncodingException e1) { System.err .println("ERROR: Impossible UTF-8 encoding not supported. No Logging will be written."); closeConnection(); this.canLog = false; } } if (this.header != null && shouldCreate) { this.writer.write(this.header); this.writer.write('\n'); } } catch (IllegalArgumentException e) { System.err.println("ERROR: Malformed file path " + this.outFile + " : " + e.getMessage()); closeConnection(); this.canLog = false; } catch (ConnectionNotFoundException e) { System.err.println("ERROR: target " + this.outFile + " not found or FileConnection API not supported : " + e.getMessage()); closeConnection(); this.canLog = false; } catch (IOException e) { System.err.println("ERROR: I/O error accessing file " + this.outFile + " : " + e.getMessage()); closeConnection(); this.canLog = false; } catch (SecurityException e) { System.err.println("ERROR: file " + this.outFile + " can't be accessed because of security reasons : " + e.getMessage()); closeConnection(); this.canLog = false; } } return this.writer; } /** * Appends formatted logging lines to the destination file and then flushes the * buffer. Opens file, if it is not already open. * * @see momelog.LogListener#onLog(momelog.LogEvent) */ public synchronized void onLog(LogEvent event) { if (this.canLog) { Writer w = getWriter(); if (w != null) { try { if (this.prefix != null) this.buffer.append(this.prefix); Logger.getFormatter().format(event, this.buffer); if (this.suffix != null) this.buffer.append(this.suffix); this.buffer.append('\n'); w.write(this.buffer.toString()); w.flush(); } catch (IOException e) { System.err.println("ERROR: writing to file " + this.outFile); } finally { this.buffer.setLength(0); } } } } /** * Closes connection to the destination file and all it's streams. */ private void closeConnection() { try { if (this.writer != null) this.writer.close(); if (this.out != null) this.out.close(); if (this.con != null) this.con.close(); } catch (IOException e) {} finally { this.writer = null; this.out = null; this.con = null; } } /** * Writes {@code footer} line (if there is some) and then closes connection to * the destination file and all it's streams. */ public synchronized void close() { if (this.footer != null && this.writer != null) try { this.writer.write(this.footer); this.writer.write('\n'); } catch (IOException e) {} finally
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -