    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 }