001package com.dpillay.tools.tail4j.characters;
002
003import java.io.BufferedInputStream;
004import java.io.BufferedReader;
005import java.io.File;
006import java.io.FileInputStream;
007import java.io.FileReader;
008import java.io.InputStream;
009
010import com.dpillay.tools.tail4j.configuration.TailConfiguration;
011import com.dpillay.tools.tail4j.core.TailListener;
012import com.dpillay.tools.tail4j.core.TailedReader;
013import com.dpillay.tools.tail4j.exception.ApplicationException;
014import com.dpillay.tools.tail4j.exception.ErrorCode;
015import com.dpillay.tools.tail4j.model.TailEvent;
016
017/**
018 * Implements a tailed file reader for string based contents
019 * 
020 * @author dpillay
021 */
022public class StringTailedFileReader implements TailedReader<String, File> {
023        private static char newLine = System.getProperty("line.separator")
024                        .charAt(0);
025
026        private File file = null;
027        private TailListener<String> listener = null;
028        private TailConfiguration configuration = null;
029
030        public StringTailedFileReader(TailConfiguration tc, File file,
031                        TailListener<String> listener) {
032                super();
033                this.file = file;
034                this.listener = listener;
035                this.configuration = tc;
036        }
037
038        public StringTailedFileReader() {
039        }
040
041        @Override
042        public File getSource() {
043                return file;
044        }
045
046        @Override
047        public TailListener<String> getListener() {
048                return listener;
049        }
050
051        @Override
052        public void setSource(File file) {
053                this.file = file;
054        }
055
056        @Override
057        public void setListener(TailListener<String> listener) {
058                this.listener = listener;
059        }
060
061        public TailConfiguration getConfiguration() {
062                return configuration;
063        }
064
065        public void setConfiguration(TailConfiguration configuration) {
066                this.configuration = configuration;
067        }
068
069        @Override
070        public String call() throws Exception {
071                try {
072                        BufferedReader br = new BufferedReader(new FileReader(file));
073                        long showLineCount = this.configuration.getShowLines();
074                        if (showLineCount > 0) {
075                                FileInfo fileInfo = this
076                                                .getSkipLinesLength(file, showLineCount);
077                                long skipLinesLength = fileInfo.getFileSkipLength();
078                                br.skip(skipLinesLength);
079                        } else {
080                                if (this.configuration.isForce()) {
081                                        br.skip(file.length());
082                                } else {
083                                        // showing 10 lines by default
084                                        showLineCount = 10;
085                                        FileInfo fileInfo = this.getSkipLinesLength(file,
086                                                        showLineCount);
087                                        long skipLinesLength = fileInfo.getFileSkipLength();
088                                        br.skip(skipLinesLength);
089                                }
090                        }
091                        while (this.configuration.isForce() || showLineCount-- > 0) {
092                                String line = br.readLine();
093                                if (line == null) {
094                                        if (!this.configuration.isForce())
095                                                break;
096                                        Thread.sleep(200);
097                                        continue;
098                                }
099                                TailEvent<String> event = TailEvent.generateEvent(line, line
100                                                .length());
101                                this.listener.onTail(event);
102                        }
103                } catch (Throwable t) {
104                        throw new ApplicationException(t, ErrorCode.DEFAULT_ERROR,
105                                        "Could not finish tailing file");
106                }
107                return null;
108        }
109
110        private FileInfo getSkipLinesLength(File file, long showLineCount) {
111                InputStream is = null;
112                long count = 0;
113                try {
114                        is = new BufferedInputStream(new FileInputStream(file));
115                        long[] lineChars = new long[(int) showLineCount + 1];
116                        byte[] c = new byte[1024];
117                        int index = 0;
118                        int readChars = 0;
119                        long totalCharsRead = 0;
120                        while ((readChars = is.read(c)) != -1) {
121                                for (int i = 0; i < readChars; ++i) {
122                                        if (c[i] == newLine) {
123                                                ++count;
124                                                if (index == lineChars.length)
125                                                        index = 0;
126                                                lineChars[index++] = totalCharsRead + i + 1;
127                                        }
128                                }
129                                totalCharsRead += readChars;
130                        }
131                        if (count >= showLineCount) {
132                                return new FileInfo(count,
133                                                (index == lineChars.length) ? lineChars[0]
134                                                                : lineChars[index]);
135                        }
136                } catch (Exception e) {
137                }
138                return new FileInfo(count, 0);
139        }
140
141        private static class FileInfo {
142                long fileLineCount = 0;
143                long fileSkipLength = 0;
144
145                public FileInfo(long fileLineCount, long fileSkipLength) {
146                        super();
147                        this.fileLineCount = fileLineCount;
148                        this.fileSkipLength = fileSkipLength;
149                }
150
151                @SuppressWarnings("unused")
152                public long getFileLineCount() {
153                        return fileLineCount;
154                }
155
156                public long getFileSkipLength() {
157                        return fileSkipLength;
158                }
159        }
160}