/* * Copyright 2003-2012 Yusuke Yamamoto * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samurai.tail; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.List; public class LogWatcherThread extends Thread { private List<LogMonitor> logMonitors = new ArrayList<LogMonitor>(0); private boolean killed = false; private boolean debug = false; private List<File> pendingFiles; private String encoding = System.getProperty("file.encoding"); public void setEncoding(String encoding) { this.encoding = encoding; } public void setFiles(File[] files) { pendingFiles = new ArrayList<File>(files.length); for (int i = 0; i < files.length; i++) { pendingFiles.add(files[i]); } } public void appendFiles(File[] files) { for (int i = 0; i < files.length; i++) { pendingFiles.add(files[i]); } } public void setFile(File file) { setFiles(new File[]{file}); } public LogWatcherThread() { init(); } private void init() { super.setName("LogWatcher Thread"); this.setDaemon(true); this.setPriority(Thread.MIN_PRIORITY); encoding = System.getProperty("file.encoding"); } public void setDebug(boolean debug) { this.debug = debug; } public void addLogMonitor(LogMonitor logMonitor) { logMonitors.add(logMonitor); } public synchronized void kill() { if (killed) { throw new IllegalStateException("Thread has already killed."); } this.killed = true; } public boolean isDead() { return killed; } private long filePointer = 0; private boolean hasEnded = false; private boolean hasStarted = false; private File file = null; private ByteArrayOutputStream line = new ByteArrayOutputStream(128); private RandomAccessFile raf; public void run() { while (!this.isDead()) { checkUpdate(); if (this.isCheckingUpdate()) { try { Thread.sleep(50); } catch (InterruptedException ie) { } } } if (debug) System.out.println("dying"); } private final int NOT_OPENED = 0; private final int CHECKING_UPDATE = 1; private final int OPENED = 2; private int state = NOT_OPENED; private long bedtime = 0; /*package*/ final boolean isCheckingUpdate() { return state == CHECKING_UPDATE; } private synchronized void openFile() { try { raf = new RandomAccessFile(file, "r"); raf.seek(filePointer); } catch (IOException ex) { onException(ex); } state = OPENED; } public void checkUpdate() { state = state; switch (state) { case NOT_OPENED: if (pendingFiles.size() != 0) { if (debug) log("found pending file"); file = pendingFiles.remove(0); filePointer = 0; line.reset(); hasEnded = false; if (null != file && file.exists()) { openFile(); break; } } state = CHECKING_UPDATE; case CHECKING_UPDATE: //read pending file if exists if (0 == bedtime) { bedtime = System.currentTimeMillis(); sleeping(); } if (file.exists()) { if (file.length() == filePointer) { if (hasStarted && !hasEnded) { if (1000 < (System.currentTimeMillis() - bedtime)) { if (debug) { log("no new line detected for 1 second"); } logEnded(); bedtime = 0; } } if (pendingFiles.size() != 0) { state = NOT_OPENED; } } else { //file size increased or decreased state = NOT_OPENED; if (file.length() > filePointer) { //assuming lines are added logContinued(); openFile(); } else { //size decreased if (!hasEnded) { logEnded(); } filePointer = 0; logStarted(); } } } else { //file disappeared if (!hasEnded) { logEnded(); } filePointer = 0; } break; case OPENED: try { if (readLine(raf, line)) { if (!hasStarted) { logStarted(); } onLine(line.toByteArray()); line.reset(); } else { state = NOT_OPENED; filePointer = raf.getFilePointer(); raf.close(); } } catch (IOException ex) { onException(ex); } } } private int c = -1; public final boolean readLine(RandomAccessFile raf, ByteArrayOutputStream line) throws IOException { boolean eol = false; while (!eol) { c = raf.read(); switch (c) { case -1: return false; case '\n': eol = true; break; case '\r': eol = true; if ((raf.read()) != '\n') { raf.seek(raf.getFilePointer() - 1); } break; default: line.write(c); break; } } return eol; } private synchronized void onLine(byte[] line) { if (debug) log("onLine(" + line + ")"); for (LogMonitor monitor : logMonitors) { try { monitor.onLine(this.file, new String(line, encoding), this.filePointer); } catch (java.io.UnsupportedEncodingException uee) { uee.printStackTrace(); } catch (RuntimeException re) { re.printStackTrace(); } } } private synchronized void logStarted() { hasStarted = true; hasEnded = false; if (debug) log("logStarted()"); for (LogMonitor monitor : logMonitors) { try { monitor.logStarted(this.file, this.filePointer); } catch (RuntimeException re) { re.printStackTrace(); } } } private synchronized void logEnded() { hasEnded = true; if (debug) log("logEnded()"); for (LogMonitor monitor : logMonitors) { try { monitor.logEnded(this.file, this.filePointer); } catch (RuntimeException re) { re.printStackTrace(); } } } private synchronized void logContinued() { hasEnded = false; if (debug) log("logContinued()"); for (LogMonitor monitor : logMonitors) { try { monitor.logContinued(this.file, this.filePointer); } catch (RuntimeException re) { re.printStackTrace(); } } } private synchronized void onException(IOException ioe) { if (debug) log("onException()"); for (LogMonitor monitor : logMonitors) { try { monitor.onException(this.file, ioe); } catch (RuntimeException re) { re.printStackTrace(); } } } private synchronized void sleeping() { if (!hasEnded && hasStarted) { for (LogMonitor monitor : logMonitors) { try { monitor.logWillEnd(this.file, this.filePointer); } catch (RuntimeException re) { re.printStackTrace(); } } } } private void log(String msg) { System.out.println("logWatcher:" + msg); } }