/*
* 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 SingleLogWatcher {
private List<LogMonitor> logMonitors = new ArrayList<LogMonitor>(0);
private boolean killed = false;
private boolean debug = false;
private String encoding = System.getProperty("file.encoding");
public SingleLogWatcher(File file,String encoding) {
this.file = file;
this.encoding = encoding;
if (null != file && file.exists()) {
openFile();
}
}
public SingleLogWatcher(File file) {
this(file,System.getProperty("file.encoding"));
}
public synchronized void setEncoding(String encoding) {
this.encoding = encoding;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public synchronized 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;
private boolean checking = true;
private long bedtime = 0;
/*package*/boolean isCheckingUpdate() {
return checking;
}
private synchronized void openFile() {
try {
raf = new RandomAccessFile(file, "r");
raf.seek(filePointer);
} catch (IOException ex) {
onException(ex);
}
checking = false;
}
private boolean started = false;
public synchronized void start() {
if (!started) {
Tailer.getTailer().addLogWatcher(this);
started = true;
} else {
throw new IllegalStateException("already started.");
}
}
/*package*/
synchronized void checkUpdate() {
if (!checking) {
try {
if (readLine(raf, line)) {
if (!hasStarted) {
logStarted();
}
onLine(line.toByteArray());
line.reset();
} else {
checking = true;
filePointer = raf.getFilePointer();
raf.close();
bedtime = System.currentTimeMillis();
sleeping();
}
} catch (IOException ex) {
onException(ex);
}
} else {
//read pending file if exists
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;
}
}
} else {
//file size increased or decreased
if (file.length() > filePointer) {
//assuming lines are added
logContinued();
} else {
//size decreased
if (!hasEnded) {
logEnded();
}
filePointer = 0;
logStarted();
}
openFile();
}
} else {
//file disappeared
if (!hasEnded) {
logEnded();
}
filePointer = 0;
}
}
}
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 void onLine(byte[] line) {
if (debug)
log("onLine(" + new String(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 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 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 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 void onException(IOException ioe) {
if (debug)
log("onException()");
for (LogMonitor monitor : logMonitors) {
try {
monitor.onException(this.file, ioe);
} catch (RuntimeException re) {
re.printStackTrace();
}
}
}
private void sleeping() {
if (!hasEnded && hasStarted) {
if (debug)
log("logWillEnd()");
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);
}
}