001: /* Copyright (C) 2003 Internet Archive.
002: *
003: * This file is part of the Heritrix web crawler (crawler.archive.org).
004: *
005: * Heritrix is free software; you can redistribute it and/or modify
006: * it under the terms of the GNU Lesser Public License as published by
007: * the Free Software Foundation; either version 2.1 of the License, or
008: * any later version.
009: *
010: * Heritrix is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: * GNU Lesser Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser Public License
016: * along with Heritrix; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019: package org.archive.crawler.util;
020:
021: import java.io.BufferedReader;
022: import java.io.File;
023: import java.io.FileNotFoundException;
024: import java.io.FileReader;
025: import java.io.IOException;
026: import java.io.InputStreamReader;
027: import java.io.RandomAccessFile;
028: import java.text.DecimalFormat;
029: import java.text.NumberFormat;
030: import java.util.LinkedList;
031: import java.util.regex.Matcher;
032: import java.util.regex.Pattern;
033: import java.util.regex.PatternSyntaxException;
034:
035: import org.archive.crawler.framework.CrawlController;
036: import org.archive.io.CompositeFileReader;
037: import org.archive.util.ArchiveUtils;
038:
039: /**
040: * This class contains a variety of methods for reading log files (or other text
041: * files containing repeated lines with similar information).
042: * <p>
043: * All methods are static.
044: *
045: * @author Kristinn Sigurdsson
046: */
047:
048: public class LogReader {
049: /**
050: * Returns the entire file. Useful for smaller files.
051: *
052: * @param aFileName a file name
053: * @return The String representation of the entire file.
054: * Null is returned if errors occur (file not found or io exception)
055: */
056: public static String get(String aFileName) {
057: try {
058: return get(new FileReader(aFileName));
059: } catch (FileNotFoundException e) {
060: e.printStackTrace();
061: return null;
062: }
063: }
064:
065: /**
066: * Reads entire contents of reader, returns as string.
067: *
068: * @param reader
069: * @return String of entire contents; null for any error.
070: */
071: public static String get(InputStreamReader reader) {
072: StringBuffer ret = new StringBuffer();
073: try {
074: BufferedReader bf = new BufferedReader(reader, 8192);
075:
076: String line = null;
077: while ((line = bf.readLine()) != null) {
078: ret.append(line);
079: ret.append("\n");
080: }
081: } catch (IOException e) {
082: e.printStackTrace();
083: return null;
084: }
085: return ret.toString();
086: }
087:
088: /**
089: * Gets a portion of a log file. Starting at a given line number and the n-1
090: * lines following that one or until the end of the log if that is reached
091: * first.
092: *
093: * @param aFileName The filename of the log/file
094: * @param lineNumber The number of the first line to get (if larger then the
095: * file an empty string will be returned)
096: * @param n How many lines to return (total, including the one indicated by
097: * lineNumber). If smaller then 1 then an empty string
098: * will be returned.
099: *
100: * @return An array of two strings is returned. At index 0 a portion of the
101: * file starting at lineNumber and reaching lineNumber+n is located.
102: * At index 1 there is an informational string about how large a
103: * segment of the file is being returned.
104: * Null is returned if errors occur (file not found or io exception)
105: */
106: public static String[] get(String aFileName, int lineNumber, int n) {
107: File f = new File(aFileName);
108: long logsize = f.length();
109: try {
110: return get(new FileReader(aFileName), lineNumber, n,
111: logsize);
112: } catch (FileNotFoundException e) {
113: e.printStackTrace();
114: return null;
115: }
116: }
117:
118: /**
119: * Gets a portion of a log spread across a numbered series of files.
120: *
121: * Starting at a given line number and the n-1 lines following that
122: * one or until the end of the log if that is reached
123: * first.
124: *
125: * @param aFileName The filename of the log/file
126: * @param lineNumber The number of the first line to get (if larger then the
127: * file an empty string will be returned)
128: * @param n How many lines to return (total, including the one indicated by
129: * lineNumber). If smaller then 1 then an empty string
130: * will be returned.
131: *
132: * @return An array of two strings is returned. At index 0 a portion of the
133: * file starting at lineNumber and reaching lineNumber+n is located.
134: * At index 1 there is an informational string about how large a
135: * segment of the file is being returned.
136: * Null is returned if errors occur (file not found or io exception)
137: */
138: public static String[] getFromSeries(String aFileName,
139: int lineNumber, int n) {
140: File f = new File(aFileName);
141: long logsize = f.length();
142: try {
143: return get(seriesReader(aFileName), lineNumber, n, logsize);
144: } catch (IOException e) {
145: e.printStackTrace();
146: return null;
147: }
148: }
149:
150: public static String buildDisplayingHeader(int len, long logsize) {
151: double percent = 0.0;
152: if (logsize != 0) {
153: percent = ((double) len / logsize) * 100;
154: }
155: return "Displaying: " + ArchiveUtils.doubleToString(percent, 1)
156: + "% of " + ArchiveUtils.formatBytesForDisplay(logsize);
157: }
158:
159: /**
160: * Gets a portion of a log file. Starting at a given line number and the n-1
161: * lines following that one or until the end of the log if that is reached
162: * first.
163: *
164: * @param reader source to scan for lines
165: * @param lineNumber The number of the first line to get (if larger then the
166: * file an empty string will be returned)
167: * @param n How many lines to return (total, including the one indicated by
168: * lineNumber). If smaller then 1 then an empty string
169: * will be returned.
170: *
171: * @param logsize total size of source
172: * @return An array of two strings is returned. At index 0 a portion of the
173: * file starting at lineNumber and reaching lineNumber+n is located.
174: * At index 1 there is an informational string about how large a
175: * segment of the file is being returned.
176: * Null is returned if errors occur (file not found or io exception)
177: */
178: public static String[] get(InputStreamReader reader,
179: int lineNumber, int n, long logsize) {
180: StringBuffer ret = new StringBuffer();
181: String info = null;
182: try {
183: BufferedReader bf = new BufferedReader(reader, 8192);
184:
185: String line = null;
186: int i = 1;
187: while ((line = bf.readLine()) != null) {
188: if (i >= lineNumber && i < (lineNumber + n)) {
189: ret.append(line);
190: ret.append('\n');
191: } else if (i >= (lineNumber + n)) {
192: break;
193: }
194: i++;
195: }
196: info = buildDisplayingHeader(ret.length(), logsize);
197: } catch (IOException e) {
198: e.printStackTrace();
199: return null;
200: }
201: String[] tmp = { ret.toString(), info };
202: return tmp;
203: }
204:
205: /**
206: * Return the line number of the first line in the
207: * log/file that matches a given regular expression.
208: *
209: * @param aFileName The filename of the log/file
210: * @param regExpr The regular expression that is to be used
211: * @return The line number (counting from 1, not zero) of the first line
212: * that matches the given regular expression. -1 is returned if no
213: * line matches the regular expression. -1 also is returned if
214: * errors occur (file not found, io exception etc.)
215: */
216: public static int findFirstLineContaining(String aFileName,
217: String regExpr) {
218: try {
219: return findFirstLineContaining(new FileReader(aFileName),
220: regExpr);
221: } catch (FileNotFoundException e) {
222: e.printStackTrace();
223: return -1;
224: }
225: }
226:
227: /**
228: * Return the line number of the first line in the
229: * log/file that begins with the given string.
230: *
231: * @param aFileName The filename of the log/file
232: * @param prefix The prefix string to match
233: * @return The line number (counting from 1, not zero) of the first line
234: * that matches the given regular expression. -1 is returned if no
235: * line matches the regular expression. -1 also is returned if
236: * errors occur (file not found, io exception etc.)
237: */
238: public static int findFirstLineBeginningFromSeries(
239: String aFileName, String prefix) {
240: try {
241: return findFirstLineBeginning(seriesReader(aFileName),
242: prefix);
243: } catch (IOException e) {
244: e.printStackTrace();
245: return -1;
246: }
247: }
248:
249: /**
250: * Return the line number of the first line in the
251: * log/file that that begins with the given string.
252: *
253: * @param reader The reader of the log/file
254: * @param prefix The prefix string to match
255: * @return The line number (counting from 1, not zero) of the first line
256: * that matches the given regular expression. -1 is returned if no
257: * line matches the regular expression. -1 also is returned if
258: * errors occur (file not found, io exception etc.)
259: */
260: public static int findFirstLineBeginning(InputStreamReader reader,
261: String prefix) {
262:
263: try {
264: BufferedReader bf = new BufferedReader(reader, 8192);
265:
266: String line = null;
267: int i = 1;
268: while ((line = bf.readLine()) != null) {
269: if (line.startsWith(prefix)) {
270: // Found a match
271: return i;
272: }
273: i++;
274: }
275: } catch (IOException e) {
276: e.printStackTrace();
277: }
278: return -1;
279: }
280:
281: /**
282: * Return the line number of the first line in the
283: * log/file that matches a given regular expression.
284: *
285: * @param aFileName The filename of the log/file
286: * @param regExpr The regular expression that is to be used
287: * @return The line number (counting from 1, not zero) of the first line
288: * that matches the given regular expression. -1 is returned if no
289: * line matches the regular expression. -1 also is returned if
290: * errors occur (file not found, io exception etc.)
291: */
292: public static int findFirstLineContainingFromSeries(
293: String aFileName, String regExpr) {
294: try {
295: return findFirstLineContaining(seriesReader(aFileName),
296: regExpr);
297: } catch (IOException e) {
298: e.printStackTrace();
299: return -1;
300: }
301: }
302:
303: /**
304: * Return the line number of the first line in the
305: * log/file that matches a given regular expression.
306: *
307: * @param reader The reader of the log/file
308: * @param regExpr The regular expression that is to be used
309: * @return The line number (counting from 1, not zero) of the first line
310: * that matches the given regular expression. -1 is returned if no
311: * line matches the regular expression. -1 also is returned if
312: * errors occur (file not found, io exception etc.)
313: */
314: public static int findFirstLineContaining(InputStreamReader reader,
315: String regExpr) {
316: Pattern p = Pattern.compile(regExpr);
317:
318: try {
319: BufferedReader bf = new BufferedReader(reader, 8192);
320:
321: String line = null;
322: int i = 1;
323: while ((line = bf.readLine()) != null) {
324: if (p.matcher(line).matches()) {
325: // Found a match
326: return i;
327: }
328: i++;
329: }
330: } catch (IOException e) {
331: e.printStackTrace();
332: }
333: return -1;
334: }
335:
336: /**
337: * Returns all lines in a log/file matching a given regular expression.
338: * Possible to get lines immediately following the matched line. Also
339: * possible to have each line prepended by it's line number.
340: *
341: * @param aFileName The filename of the log/file
342: * @param regExpr The regular expression that is to be used
343: * @param addLines How many lines (in addition to the matched line) to add.
344: * A value less then 1 will mean that only the matched line
345: * will be included. If another matched line is hit before
346: * we reach this limit it will be included and this counter
347: * effectively reset for it.
348: * @param prependLineNumbers If true, then each line will be prepended by
349: * it's line number in the file.
350: * @param skipFirstMatches The first number of matches up to this value will
351: * be skipped over.
352: * @param numberOfMatches Once past matches that are to be skipped this many
353: * matches will be added to the return value. A
354: * value of 0 will cause all matching lines to be
355: * included.
356: * @return An array of two strings is returned. At index 0 tall lines in a
357: * log/file matching a given regular expression is located.
358: * At index 1 there is an informational string about how large a
359: * segment of the file is being returned.
360: * Null is returned if errors occur (file not found or io exception)
361: * If a PatternSyntaxException occurs, it's error message will be
362: * returned and the informational string will be empty (not null).
363: */
364: public static String[] getByRegExpr(String aFileName,
365: String regExpr, int addLines, boolean prependLineNumbers,
366: int skipFirstMatches, int numberOfMatches) {
367: try {
368: File f = new File(aFileName);
369: return getByRegExpr(new FileReader(f), regExpr, addLines,
370: prependLineNumbers, skipFirstMatches,
371: numberOfMatches, f.length());
372: } catch (FileNotFoundException e) {
373: e.printStackTrace();
374: return null;
375: }
376: }
377:
378: /**
379: * Returns all lines in a log/file matching a given regular expression.
380: * Possible to get lines immediately following the matched line. Also
381: * possible to have each line prepended by it's line number.
382: *
383: * @param aFileName The filename of the log/file
384: * @param regExpr The regular expression that is to be used
385: * @param addLines How many lines (in addition to the matched line) to add.
386: * A value less then 1 will mean that only the matched line
387: * will be included. If another matched line is hit before
388: * we reach this limit it will be included and this counter
389: * effectively reset for it.
390: * @param prependLineNumbers If true, then each line will be prepended by
391: * it's line number in the file.
392: * @param skipFirstMatches The first number of matches up to this value will
393: * be skipped over.
394: * @param numberOfMatches Once past matches that are to be skipped this many
395: * matches will be added to the return value. A
396: * value of 0 will cause all matching lines to be
397: * included.
398: * @return An array of two strings is returned. At index 0 tall lines in a
399: * log/file matching a given regular expression is located.
400: * At index 1 there is an informational string about how large a
401: * segment of the file is being returned.
402: * Null is returned if errors occur (file not found or io exception)
403: * If a PatternSyntaxException occurs, it's error message will be
404: * returned and the informational string will be empty (not null).
405: */
406: public static String[] getByRegExprFromSeries(String aFileName,
407: String regExpr, int addLines, boolean prependLineNumbers,
408: int skipFirstMatches, int numberOfMatches) {
409: try {
410: File f = new File(aFileName);
411: return getByRegExpr(seriesReader(aFileName), regExpr,
412: addLines, prependLineNumbers, skipFirstMatches,
413: numberOfMatches, f.length());
414: } catch (IOException e) {
415: e.printStackTrace();
416: return null;
417: }
418: }
419:
420: /**
421: * Returns all lines in a log/file matching a given regular expression.
422: * Possible to get lines immediately following the matched line. Also
423: * possible to have each line prepended by it's line number.
424: *
425: * @param reader The reader of the log/file
426: * @param regExpr The regular expression that is to be used
427: * @param addLines How many lines (in addition to the matched line) to add.
428: * A value less then 1 will mean that only the matched line
429: * will be included. If another matched line is hit before
430: * we reach this limit it will be included and this counter
431: * effectively reset for it.
432: * @param prependLineNumbers If true, then each line will be prepended by
433: * it's line number in the file.
434: * @param skipFirstMatches The first number of matches up to this value will
435: * be skipped over.
436: * @param numberOfMatches Once past matches that are to be skipped this many
437: * matches will be added to the return value. A
438: * value of 0 will cause all matching lines to be
439: * included.
440: * @param logsize Size of the log in bytes
441: * @return An array of two strings is returned. At index 0 all lines in a
442: * log/file matching a given regular expression is located.
443: * At index 1 there is an informational string about how large a
444: * segment of the file is being returned.
445: * Null is returned if errors occur (file not found or io exception)
446: * If a PatternSyntaxException occurs, it's error message will be
447: * returned and the informational string will be empty (not null).
448: */
449: public static String[] getByRegExpr(InputStreamReader reader,
450: String regExpr, int addLines, boolean prependLineNumbers,
451: int skipFirstMatches, int numberOfMatches, long logsize) {
452: StringBuffer ret = new StringBuffer();
453: String info = "";
454:
455: try {
456: Pattern p = Pattern.compile(regExpr);
457: BufferedReader bf = new BufferedReader(reader, 8192);
458:
459: String line = null;
460: int i = 1;
461: boolean doAdd = false;
462: int addCount = 0;
463: long linesMatched = 0;
464: while ((line = bf.readLine()) != null) {
465: if (p.matcher(line).matches()) {
466: // Found a match
467: if (numberOfMatches > 0
468: && linesMatched >= skipFirstMatches
469: + numberOfMatches) {
470: // Ok, we are done.
471: break;
472: }
473: linesMatched++;
474: if (linesMatched > skipFirstMatches) {
475: if (prependLineNumbers) {
476: ret.append(i);
477: ret.append(". ");
478: }
479: ret.append(line);
480: ret.append("\n");
481: doAdd = true;
482: addCount = 0;
483: }
484: } else if (doAdd) {
485: if (addCount < addLines) {
486: //Ok, still within addLines
487: linesMatched++;
488: if (prependLineNumbers) {
489: ret.append(i);
490: ret.append(". ");
491: }
492: ret.append(line);
493: ret.append("\n");
494: } else {
495: doAdd = false;
496: addCount = 0;
497: }
498: }
499: i++;
500: }
501: info = buildDisplayingHeader(ret.length(), logsize);
502: } catch (FileNotFoundException e) {
503: return null;
504: } catch (IOException e) {
505: e.printStackTrace();
506: return null;
507: } catch (PatternSyntaxException e) {
508: ret = new StringBuffer(e.getMessage());
509: }
510: String[] tmp = { ret.toString(), info };
511: return tmp;
512: }
513:
514: /**
515: * Returns all lines in a log/file matching a given regular expression.
516: * Possible to get lines immediately following the matched line. Also
517: * possible to have each line prepended by it's line number.
518: *
519: * @param aFileName The filename of the log/file
520: * @param regExpr The regular expression that is to be used
521: * @param addLines Any lines following a match that <b>begin</b> with this
522: * string will also be included. We will stop including new
523: * lines once we hit the first that does not match.
524: * @param prependLineNumbers If true, then each line will be prepended by
525: * it's line number in the file.
526: * @param skipFirstMatches The first number of matches up to this value will
527: * be skipped over.
528: * @param numberOfMatches Once past matches that are to be skipped this many
529: * matches will be added to the return value. A
530: * value of 0 will cause all matching lines to be
531: * included.
532: * @return An array of two strings is returned. At index 0 tall lines in a
533: * log/file matching a given regular expression is located.
534: * At index 1 there is an informational string about how large a
535: * segment of the file is being returned.
536: * Null is returned if errors occur (file not found or io exception)
537: * If a PatternSyntaxException occurs, it's error message will be
538: * returned and the informational string will be empty (not null).
539: */
540: public static String[] getByRegExpr(String aFileName,
541: String regExpr, String addLines,
542: boolean prependLineNumbers, int skipFirstMatches,
543: int numberOfMatches) {
544: try {
545: File f = new File(aFileName);
546: return getByRegExpr(new FileReader(f), regExpr, addLines,
547: prependLineNumbers, skipFirstMatches,
548: numberOfMatches, f.length());
549: } catch (FileNotFoundException e) {
550: e.printStackTrace();
551: return null;
552: }
553: }
554:
555: /**
556: * Returns all lines in a log/file matching a given regular expression.
557: * Possible to get lines immediately following the matched line. Also
558: * possible to have each line prepended by it's line number.
559: *
560: * @param aFileName The filename of the log/file
561: * @param regExpr The regular expression that is to be used
562: * @param addLines Any lines following a match that <b>begin</b> with this
563: * string will also be included. We will stop including new
564: * lines once we hit the first that does not match.
565: * @param prependLineNumbers If true, then each line will be prepended by
566: * it's line number in the file.
567: * @param skipFirstMatches The first number of matches up to this value will
568: * be skipped over.
569: * @param numberOfMatches Once past matches that are to be skipped this many
570: * matches will be added to the return value. A
571: * value of 0 will cause all matching lines to be
572: * included.
573: * @return An array of two strings is returned. At index 0 tall lines in a
574: * log/file matching a given regular expression is located.
575: * At index 1 there is an informational string about how large a
576: * segment of the file is being returned.
577: * Null is returned if errors occur (file not found or io exception)
578: * If a PatternSyntaxException occurs, it's error message will be
579: * returned and the informational string will be empty (not null).
580: */
581: public static String[] getByRegExprFromSeries(String aFileName,
582: String regExpr, String addLines,
583: boolean prependLineNumbers, int skipFirstMatches,
584: int numberOfMatches) {
585: try {
586: File f = new File(aFileName);
587: return getByRegExpr(seriesReader(aFileName), regExpr,
588: addLines, prependLineNumbers, skipFirstMatches,
589: numberOfMatches, f.length());
590: } catch (IOException e) {
591: e.printStackTrace();
592: return null;
593: }
594: }
595:
596: /**
597: * Returns all lines in a log/file matching a given regular expression.
598: * Possible to get lines immediately following the matched line. Also
599: * possible to have each line prepended by it's line number.
600: *
601: * @param reader The reader of the log/file
602: * @param regExpr The regular expression that is to be used
603: * @param addLines Any lines following a match that <b>begin</b> with this
604: * string will also be included. We will stop including new
605: * lines once we hit the first that does not match.
606: * @param prependLineNumbers If true, then each line will be prepended by
607: * it's line number in the file.
608: * @param skipFirstMatches The first number of matches up to this value will
609: * be skipped over.
610: * @param numberOfMatches Once past matches that are to be skipped this many
611: * matches will be added to the return value. A
612: * value of 0 will cause all matching lines to be
613: * included.
614: * @param logsize Size of the log in bytes
615: * @return An array of two strings is returned. At index 0 tall lines in a
616: * log/file matching a given regular expression is located.
617: * At index 1 there is an informational string about how large a
618: * segment of the file is being returned.
619: * Null is returned if errors occur (file not found or io exception)
620: * If a PatternSyntaxException occurs, it's error message will be
621: * returned and the informational string will be empty (not null).
622: */
623: public static String[] getByRegExpr(InputStreamReader reader,
624: String regExpr, String addLines,
625: boolean prependLineNumbers, int skipFirstMatches,
626: int numberOfMatches, long logsize) {
627: StringBuffer ret = new StringBuffer();
628: String info = "";
629: try {
630: Matcher m = Pattern.compile(regExpr).matcher("");
631: BufferedReader bf = new BufferedReader(reader, 8192);
632:
633: String line = null;
634: int i = 1;
635: boolean doAdd = false;
636: long linesMatched = 0;
637: while ((line = bf.readLine()) != null) {
638: m.reset(line);
639: if (m.matches()) {
640: // Found a match
641: if (numberOfMatches > 0
642: && linesMatched >= skipFirstMatches
643: + numberOfMatches) {
644: // Ok, we are done.
645: break;
646: }
647: linesMatched++;
648: if (linesMatched > skipFirstMatches) {
649: if (prependLineNumbers) {
650: ret.append(i);
651: ret.append(". ");
652: }
653: ret.append(line);
654: ret.append("\n");
655: doAdd = true;
656: }
657: } else if (doAdd) {
658: if (line.indexOf(addLines) == 0) {
659: linesMatched++;
660: //Ok, line begins with 'addLines'
661: if (prependLineNumbers) {
662: ret.append(i);
663: ret.append(". ");
664: }
665: ret.append(line);
666: ret.append("\n");
667: } else {
668: doAdd = false;
669: }
670: }
671: i++;
672: }
673: info = buildDisplayingHeader(ret.length(), logsize);
674: } catch (FileNotFoundException e) {
675: return null;
676: } catch (IOException e) {
677: e.printStackTrace();
678: return null;
679: } catch (PatternSyntaxException e) {
680: ret = new StringBuffer(e.getMessage());
681: }
682: String[] tmp = { ret.toString(), info };
683: return tmp;
684: }
685:
686: /**
687: * Implementation of a unix-like 'tail' command
688: *
689: * @param aFileName a file name String
690: * @return An array of two strings is returned. At index 0 the String
691: * representation of at most 10 last lines is located.
692: * At index 1 there is an informational string about how large a
693: * segment of the file is being returned.
694: * Null is returned if errors occur (file not found or io exception)
695: */
696: public static String[] tail(String aFileName) {
697: return tail(aFileName, 10);
698: }
699:
700: /**
701: * Implementation of a unix-like 'tail -n' command
702: *
703: * @param aFileName a file name String
704: * @param n int number of lines to be returned
705: * @return An array of two strings is returned. At index 0 the String
706: * representation of at most n last lines is located.
707: * At index 1 there is an informational string about how large a
708: * segment of the file is being returned.
709: * Null is returned if errors occur (file not found or io exception)
710: */
711: public static String[] tail(String aFileName, int n) {
712: try {
713: return tail(new RandomAccessFile(new File(aFileName), "r"),
714: n);
715: } catch (FileNotFoundException e) {
716: e.printStackTrace();
717: return null;
718: }
719: }
720:
721: /**
722: * Implementation of a unix-like 'tail -n' command
723: *
724: * @param raf a RandomAccessFile to tail
725: * @param n int number of lines to be returned
726: * @return An array of two strings is returned. At index 0 the String
727: * representation of at most n last lines is located.
728: * At index 1 there is an informational string about how large a
729: * segment of the file is being returned.
730: * Null is returned if errors occur (file not found or io exception)
731: */
732: public static String[] tail(RandomAccessFile raf, int n) {
733: int BUFFERSIZE = 1024;
734: long pos;
735: long endPos;
736: long lastPos;
737: int numOfLines = 0;
738: String info = null;
739: byte[] buffer = new byte[BUFFERSIZE];
740: StringBuffer sb = new StringBuffer();
741: try {
742: endPos = raf.length();
743: lastPos = endPos;
744:
745: // Check for non-empty file
746: // Check for newline at EOF
747: if (endPos > 0) {
748: byte[] oneByte = new byte[1];
749: raf.seek(endPos - 1);
750: raf.read(oneByte);
751: if ((char) oneByte[0] != '\n') {
752: numOfLines++;
753: }
754: }
755:
756: do {
757: // seek back BUFFERSIZE bytes
758: // if length of the file if less then BUFFERSIZE start from BOF
759: pos = 0;
760: if ((lastPos - BUFFERSIZE) > 0) {
761: pos = lastPos - BUFFERSIZE;
762: }
763: raf.seek(pos);
764: // If less then BUFFERSIZE avaliable read the remaining bytes
765: if ((lastPos - pos) < BUFFERSIZE) {
766: int remainer = (int) (lastPos - pos);
767: buffer = new byte[remainer];
768: }
769: raf.readFully(buffer);
770: // in the buffer seek back for newlines
771: for (int i = buffer.length - 1; i >= 0; i--) {
772: if ((char) buffer[i] == '\n') {
773: numOfLines++;
774: // break if we have last n lines
775: if (numOfLines > n) {
776: pos += (i + 1);
777: break;
778: }
779: }
780: }
781: // reset last postion
782: lastPos = pos;
783: } while ((numOfLines <= n) && (pos != 0));
784:
785: // print last n line starting from last postion
786: for (pos = lastPos; pos < endPos; pos += buffer.length) {
787: raf.seek(pos);
788: if ((endPos - pos) < BUFFERSIZE) {
789: int remainer = (int) (endPos - pos);
790: buffer = new byte[remainer];
791: }
792: raf.readFully(buffer);
793: sb.append(new String(buffer));
794: }
795:
796: info = buildDisplayingHeader(sb.length(), raf.length());
797: } catch (FileNotFoundException e) {
798: sb = null;
799: } catch (IOException e) {
800: e.printStackTrace();
801: sb = null;
802: } finally {
803: try {
804: if (raf != null) {
805: raf.close();
806: }
807: } catch (IOException e) {
808: e.printStackTrace();
809: }
810: }
811: if (sb == null) {
812: return null;
813: }
814: String[] tmp = { sb.toString(), info };
815: return tmp;
816: }
817:
818: /**
819: * @param fileName
820: * @return
821: * @throws IOException
822: */
823: private static CompositeFileReader seriesReader(String fileName)
824: throws IOException {
825: LinkedList<File> filenames = new LinkedList<File>();
826: int seriesNumber = 1;
827: NumberFormat fmt = new DecimalFormat("00000");
828: String predecessorFilename = fileName.substring(0, fileName
829: .length()
830: - CrawlController.CURRENT_LOG_SUFFIX.length())
831: + fmt.format(seriesNumber);
832: while ((new File(predecessorFilename)).exists()) {
833: filenames.add(new File(predecessorFilename));
834: seriesNumber++;
835: predecessorFilename = fileName.substring(0, fileName
836: .length()
837: - CrawlController.CURRENT_LOG_SUFFIX.length())
838: + fmt.format(seriesNumber);
839: }
840: filenames.add(new File(fileName)); // add current file
841: return new CompositeFileReader(filenames);
842: }
843: }
|