View Javadoc

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