DocTimeReporter.java
0001 /*
0002  *  DocTimeReporter.java
0003  *
0004  *  Copyright (c) 2008-2009, Intelius, Inc.
0005  *
0006  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0007  *  software, licenced under the GNU Library General Public License,
0008  *  Version 2, June 1991 (in the distribution as file licence.html,
0009  *  and also available at http://gate.ac.uk/gate/licence.html).
0010  *
0011  *  Chirag Viradiya & Andrew Borthwick, 30/Sep/2009
0012  *
0013  *  $Id$
0014  */
0015 package gate.util.reporting;
0016 
0017 import java.io.BufferedReader;
0018 import java.io.BufferedWriter;
0019 import java.io.File;
0020 import java.io.FileReader;
0021 import java.io.FileWriter;
0022 import java.io.IOException;
0023 import java.io.RandomAccessFile;
0024 import java.util.ArrayList;
0025 import java.util.Collections;
0026 import java.util.Date;
0027 import java.util.HashSet;
0028 import java.util.Iterator;
0029 import java.util.LinkedHashMap;
0030 import java.util.List;
0031 import java.util.StringTokenizer;
0032 import java.util.Timer;
0033 import java.util.TimerTask;
0034 import java.util.Vector;
0035 import java.util.regex.Matcher;
0036 import java.util.regex.Pattern;
0037 
0038 import org.apache.commons.io.IOUtils;
0039 
0040 import gate.util.reporting.exceptions.BenchmarkReportExecutionException;
0041 import gate.util.reporting.exceptions.BenchmarkReportFileAccessException;
0042 import gate.util.reporting.exceptions.BenchmarkReportInputFileFormatException;
0043 import gnu.getopt.Getopt;
0044 
0045 /**
0046  * A reporter class to generate a report on time taken by each document within
0047  * given corpus.
0048  */
0049 public class DocTimeReporter implements BenchmarkReportable {
0050 
0051   /** A File handle to input benchmark file. */
0052   private File benchmarkFile = new File("benchmark.txt");
0053   /** Report media. */
0054   private String printMedia = MEDIA_HTML;
0055   /** No of documents to be displayed against matching PRs. */
0056   private int maxDocumentInReport = 10;
0057   /** Search string, could be a PR name. */
0058   private String PRMatchingRegex = MATCH_ALL_PR_REGEX;
0059   /** A marker indicating the start of current logical run. */
0060   private String logicalStart = null;
0061   /** Path where to save the report file. */
0062   private File reportFile;
0063 
0064   /**
0065    * An HashSet containing names of the documents matching the given search
0066    * string.
0067    */
0068   private HashSet<String> allDocs = new HashSet<String>();
0069   /**
0070    * An HashSet containing PR names matching the search string. Used to display
0071    * in report header.
0072    */
0073   private HashSet<String> matchingPRs = new HashSet<String>();
0074   /** Total time taken by the given pipeline for the current logical run. */
0075   private float globalTotal = 0;
0076   /** A LinkedHashMap containing the documents matching the given PRs. */
0077   private LinkedHashMap<String, String> docContainer = new LinkedHashMap<String, String>();
0078   /**
0079    * Folder where the benchmark.txt files are created for specific pipeline log
0080    * entries.
0081    */
0082   private File temporaryDirectory;
0083   /** Name of the given pipeline */
0084   private String pipelineName = "";
0085   /** Status flag for normal exit. */
0086   private static final int STATUS_NORMAL = 0;
0087   /** Status flag for error exit. */
0088   private static final int STATUS_ERROR = 1;
0089   /** Chunk size in which file will be read */
0090   private static final int FILE_CHUNK_SIZE = 2000;
0091   /** An OS independent line separator */
0092   private static final String NL = System.getProperty("line.separator");
0093   /**
0094    * An integer containing the count of total valid log entries present in input
0095    * file provided.
0096    */
0097   public int validEntries = 0;
0098 
0099   /**
0100    * This string constant when set as print media indicates that the report is
0101    * printed in TEXT format.
0102    */
0103   public static final String MEDIA_TEXT = "text";
0104   /**
0105    * This string constant when set as print media indicates that the report is
0106    * printed in HTML format.
0107    */
0108   public static final String MEDIA_HTML = "html";
0109 
0110   /**
0111    * This integer constant when set as No of Docs indicates that the report have
0112    * all the documents matching a given PR.
0113    */
0114   public static final int ALL_DOCS = -1;
0115 
0116   /**
0117    * The default value for search string matching PRs for given run.
0118    */
0119   public static final String MATCH_ALL_PR_REGEX = "all_prs";
0120 
0121   /**
0122    * No argument constructor.
0123    */
0124   public DocTimeReporter() {
0125     // some initialisations
0126     initTmpDir();
0127   }
0128 
0129   /**
0130    * A constructor to be used while executing the tool from the command line.
0131    *
0132    @param args array containing command line arguments.
0133    */
0134   DocTimeReporter(String[] args) {
0135       initTmpDir();
0136       parseArguments(args);
0137   }
0138 
0139   private void initTmpDir() {
0140      try {
0141       temporaryDirectory = File.createTempFile("benchmark-reports"""null);
0142       if (!temporaryDirectory.delete()
0143        || !temporaryDirectory.mkdir()) {
0144         throw new IOException("Unable to create temporary directory.\n"
0145           + temporaryDirectory.getCanonicalPath());
0146       }
0147     catch (IOException e) {
0148       e.printStackTrace();
0149     }
0150   }
0151 
0152 
0153   /**
0154    * Calculates the total of the time taken by processing element at each leaf
0155    * level. Also calculates the difference between the actual time taken by the
0156    * resources and system noted time.
0157    *
0158    @param reportContainer
0159    *          An Object of type LinkedHashMap<String, Object> containing the
0160    *          processing elements (with time in milliseconds) in hierarchical
0161    *          structure.
0162    @return An Object containing modified hierarchical structure of processing
0163    *         elements with totals and All others embedded in it.
0164    */
0165   @SuppressWarnings("unchecked")
0166   @Override
0167   public Object calculate(Object reportContainer) {
0168     return sortHashMapByValues(
0169       doTotal((LinkedHashMap<String, Object>reportContainer));
0170   }
0171 
0172   /**
0173    * Sorts LinkedHashMap by its values(natural descending order). keeps the
0174    * duplicates as it is.
0175    *
0176    @param passedMap
0177    *          An Object of type LinkedHashMap to be sorted by its values.
0178    @return An Object containing the sorted LinkedHashMap.
0179    */
0180   private LinkedHashMap<?,?> sortHashMapByValues(LinkedHashMap<String,String> passedMap) {
0181     List<String> mapKeys = new ArrayList<String>(passedMap.keySet());
0182     List<String> mapValues = new ArrayList<String>(passedMap.values());
0183 
0184     Collections.sort(mapValues, new ValueComparator());
0185     Collections.sort(mapKeys);
0186     // Reversing the collection to sort the values in descending order
0187     Collections.reverse(mapValues);
0188     LinkedHashMap<String,String> sortedMap = new LinkedHashMap<String,String>();
0189 
0190     Iterator<String> valueIt = mapValues.iterator();
0191     while (valueIt.hasNext()) {
0192       String val = valueIt.next();
0193       Iterator<String> keyIt = mapKeys.iterator();
0194       while (keyIt.hasNext()) {
0195         String key = keyIt.next();
0196         String comp1 = passedMap.get(key).toString();
0197         String comp2 = val.toString();
0198 
0199         if (comp1.equals(comp2)) {
0200           passedMap.remove(key);
0201           mapKeys.remove(key);
0202           sortedMap.put(key, val);
0203           break;
0204         }
0205       }
0206     }
0207     return sortedMap;
0208   }
0209 
0210   /**
0211    * Computes the sub totals at each processing level.
0212    *
0213    @param reportContainer
0214    *          An Object of type LinkedHashMap<String, Object> containing the
0215    *          processing elements (with time in milliseconds) in hierarchical
0216    *          structure.
0217    @return An Object containing the LinkedHashMap with the element values
0218    *         totaled.
0219    */
0220   @SuppressWarnings("unchecked")
0221   private LinkedHashMap<String, String> doTotal(
0222     LinkedHashMap<String, Object> reportContainer) {
0223     LinkedHashMap<String, Object> myHash =
0224       reportContainer;
0225     Iterator<String> i = myHash.keySet().iterator();
0226     while (i.hasNext()) {
0227       String key = i.next();
0228       if (myHash.get(keyinstanceof LinkedHashMap) {
0229         docContainer = doTotal((LinkedHashMap<String, Object>) (myHash
0230             .get(key)));
0231       else {
0232         if (docContainer.get(key== null) {
0233           docContainer.put(key, (String)myHash.get(key));
0234         else {
0235           // Do total if value already exists
0236           int val = Integer.parseInt(docContainer.get(key))
0237               + Integer.parseInt((StringmyHash.get(key));
0238           docContainer.put(key, Integer.toString(val));
0239         }
0240       }
0241     }
0242     return docContainer;
0243   }
0244 
0245   /**
0246    * Prints a report as per the value provided for print media option.
0247    *
0248    @param reportSource
0249    *          An Object of type LinkedHashMap<String, Object> containing the
0250    *          processing elements (with time in milliseconds) in hierarchical
0251    *          structure.
0252    @param outputFile
0253    *          Path where to save the report.
0254    */
0255   @SuppressWarnings("unchecked")
0256   @Override
0257   public void printReport(Object reportSource, File outputFile) {
0258     if (printMedia.equalsIgnoreCase(MEDIA_TEXT)) {
0259       printToText(reportSource, outputFile);
0260     else if (printMedia.equalsIgnoreCase(MEDIA_HTML)) {
0261       printToHTML((LinkedHashMap<String, Object>reportSource, outputFile);
0262     }
0263   }
0264 
0265   /**
0266    * Prints benchmark report in text format.
0267    *
0268    @param reportContainer
0269    *          An Object of type LinkedHashMap<String, Object> containing the
0270    *          document names (with time in milliseconds) in hierarchical
0271    *          structure.
0272    @param outputFile
0273    *          An object of type File representing the output report file.
0274    */
0275   private void printToText(Object reportContainer, File outputFile) {
0276     ArrayList<String> printLines = new ArrayList<String>();
0277     @SuppressWarnings("unchecked")
0278     LinkedHashMap<String, Object> rcHash =
0279       (LinkedHashMap<String, Object>reportContainer;
0280     String docs = "";
0281     if (maxDocumentInReport != ALL_DOCS) {
0282       if (allDocs.size() < maxDocumentInReport) {
0283         docs = Integer.toString(allDocs.size());
0284       else {
0285         docs = Integer.toString(maxDocumentInReport);
0286       }
0287 
0288     else {
0289       docs = "All";
0290     }
0291     printLines
0292         .add("============================================================="
0293             + NL);
0294     if (PRMatchingRegex.equals(MATCH_ALL_PR_REGEX)) {
0295       printLines.add("Top " + docs
0296           " expensive documents matching All PRs in " + pipelineName
0297           + NL);
0298     else {
0299       if (matchingPRs.size() 0) {
0300         printLines.add("Top " + docs
0301             " expensive documents matching following PRs in " + pipelineName
0302             + NL);
0303         for (String pr : matchingPRs) {
0304           printLines.add("\t" + pr + NL);
0305         }
0306       else {
0307         printLines.add("No PRs matched to search string \""
0308             + getPRMatchingRegex() "\"" " in " + pipelineName);
0309         printLines.add(NL);
0310         printLines
0311             .add("============================================================="
0312                 + NL);
0313       }
0314 
0315     }
0316     if (allDocs.size() 0) {
0317       printLines
0318           .add("============================================================="
0319               + NL);
0320       printLines.add("Document Name" "\t" "Time (in seconds)" "\t" "%"
0321           + NL);
0322       printLines
0323           .add("-------------------------------------------------------------"
0324               + NL);
0325     }
0326     Iterator<String> i = rcHash.keySet().iterator();
0327     int count = 0;
0328     // Iterating over the report container
0329     while (i.hasNext()) {
0330       Object key = i.next();
0331       if (!((Stringkey).equals("total")) {
0332         int value = Integer.parseInt((StringrcHash.get(key));
0333         if (maxDocumentInReport == ALL_DOCS)
0334           printLines.add(key + "\t" + value / 1000.0 "\t"
0335               + Math.round(((value / globalTotal1001010.0
0336               + NL);
0337         else if (count < maxDocumentInReport)
0338           printLines.add(key + "\t" + value / 1000.0 "\t"
0339               + Math.round(((value / globalTotal1001010.0
0340               + NL);
0341       }
0342       count++;
0343     }
0344     if (allDocs.size() 0) {
0345       printLines
0346           .add("-------------------------------------------------------------"
0347               + NL);
0348       printLines.add("Pipeline Total" "\t" + globalTotal / 1000.0 "\t"
0349           100 + NL + NL + NL);
0350     }
0351     BufferedWriter out = null;
0352     try {
0353       // Writing to report file
0354       out = new BufferedWriter(new FileWriter(outputFile, true));
0355       for (String line : printLines) {
0356         out.write(line);
0357       }
0358 
0359     catch (IOException e) {
0360       e.printStackTrace();
0361 
0362     finally {
0363       try {
0364         if (out != null) { out.close()}
0365       catch (IOException e) {
0366         e.printStackTrace();
0367       }
0368     }
0369   }
0370 
0371   /**
0372    * Stores GATE processing elements and the time taken by them in an in-memory
0373    * data structure for report generation.
0374    *
0375    @param inputFile
0376    *          A handle to the input benchmark file.
0377    *
0378    @return An Object of type LinkedHashMap<String, Object> containing the
0379    *         processing elements (with time in milliseconds) in hierarchical
0380    *         structure. Null if there was an error.
0381    *
0382    @throws BenchmarkReportInputFileFormatException
0383    *           if the input file provided is not a valid benchmark file.
0384    */
0385   @Override
0386   public Object store(File inputFile)
0387       throws BenchmarkReportInputFileFormatException {
0388     String[] temp = inputFile.getAbsolutePath().split("\\" + File.separator);
0389     pipelineName = temp[temp.length - 1].replace("_benchmark.txt""");
0390     LinkedHashMap<String, Object> globalStore =
0391       new LinkedHashMap<String, Object>();
0392     BufferedReader in = null;
0393     try {
0394       in = new BufferedReader(new FileReader(inputFile));
0395       String str;
0396       String docName = null;
0397       String matchedPR = null;
0398       String startToken = null;
0399       // Reading the benchmark.txt one line at a time
0400       Pattern pattern = Pattern.compile("(\\d+) (\\d+) (.*) (.*) \\{(.*)\\}");
0401       // Pattern matching for extracting document name
0402       Pattern patternDocName = Pattern.compile(".*documentName=(.*?)[,|}].*");
0403       while ((str = in.readLine()) != null) {
0404         if (str.matches(".*START.*")) {
0405           String[] splittedStartEntry = str.split("\\s");
0406           if (splittedStartEntry.length > 2) {
0407             startToken = splittedStartEntry[2];
0408           else {
0409             throw new BenchmarkReportInputFileFormatException(
0410                 getBenchmarkFile() " is invalid.");
0411           }
0412         }
0413         Matcher matcher = pattern.matcher(str);
0414         Matcher matcherDocName = patternDocName.matcher(str);
0415         Pattern patternDocEnd = Pattern.compile("(\\d+) (\\d+) " + Pattern.quote(startToken)
0416             " (.*) \\{(.*)\\}.*");
0417         Matcher matcherDocEnd = patternDocEnd.matcher(str);
0418         if (matcherDocName != null) {
0419           if (matcherDocName.matches()) {
0420             docName = matcherDocName.group(1);
0421 
0422           }
0423         }
0424         if (matcherDocEnd != null) {
0425           if (matcherDocEnd.matches()) {
0426 
0427             globalTotal = globalTotal
0428                 + Integer.parseInt(matcherDocEnd.group(2));
0429           }
0430         }
0431         if (matcher != null && matcher.matches()) {
0432           String benchmarkIDs = matcher.group(3).replaceFirst(Pattern.quote(startToken".",
0433               "").replaceFirst("doc_" + Pattern.quote(docName".""");
0434           String[] splittedBenchmarkIDs = benchmarkIDs.split("\\.");
0435           // Getting the exact PR name and storing only entries matching PR name
0436           if (PRMatchingRegex.equals(MATCH_ALL_PR_REGEX)) {
0437             if (splittedBenchmarkIDs.length > 0) {
0438               matchedPR = splittedBenchmarkIDs[0];
0439             }
0440             if (!matchedPR.equalsIgnoreCase(startToken)) {
0441               organizeEntries(globalStore, matchedPR, matcher.group(2), docName);
0442             }
0443           else if (isPRMatched(benchmarkIDs, PRMatchingRegex)) {
0444             if (splittedBenchmarkIDs.length > 0) {
0445               matchedPR = splittedBenchmarkIDs[0];
0446             }
0447             if (matchedPR != null)
0448               matchingPRs.add(matchedPR);
0449             organizeEntries(globalStore, matchedPR, matcher.group(2), docName);
0450           }
0451         }
0452       }
0453 
0454     catch (IOException e) {
0455       e.printStackTrace();
0456       globalStore = null;
0457 
0458     finally {
0459       try {
0460         if (in != null) { in.close()}
0461       catch (IOException e) {
0462         e.printStackTrace();
0463         globalStore = null;
0464       }
0465     }
0466     return globalStore;
0467   }
0468 
0469   /**
0470    * Organizes the valid data extracted from the log entries into LinkedHashMap.
0471    *
0472    @param store
0473    *          A global LinkedHashMap containing the processing elements (with
0474    *          time in milliseconds) in hierarchical structure.
0475    @param matchedPR
0476    *          A PR matching the given search string.
0477    @param bTime
0478    *          Time taken by the specific processing element.
0479    @param docName
0480    *          Name of the document being processed.
0481    */
0482   @SuppressWarnings("unchecked")
0483   private void organizeEntries(LinkedHashMap<String, Object> store,
0484                                String matchedPR, String bTime, String docName) {
0485     allDocs.add(docName);
0486     if (store.containsKey(matchedPR)) {
0487       ((LinkedHashMap<String, Object>store.get(matchedPR))
0488           .put(docName, bTime);
0489     else {
0490       LinkedHashMap<String, Object> tempLHM = new LinkedHashMap<String, Object>();
0491       tempLHM.put(docName, bTime);
0492       store.put(matchedPR, tempLHM);
0493     }
0494   }
0495 
0496   /**
0497    * Prints the document level statistics report in HTML format.
0498    *
0499    @param reportSource
0500    *          An Object of type LinkedHashMap<String, Object> containing the
0501    *          document names (with time in milliseconds).
0502    @param outputFile
0503    *          An object of type File representing the output report file to
0504    *          which the HTML report is to be written.
0505    */
0506   private void printToHTML(LinkedHashMap<String, Object> reportSource,
0507                            File outputFile) {
0508     String htmlReport =
0509       "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"" + NL +
0510       "\"http://www.w3.org/TR/html4/loose.dtd\">" + NL +
0511       "<html><head><title>Benchmarking Report</title>" + NL +
0512       "<meta http-equiv=\"Content-Type\"" +
0513       " content=\"text/html; charset=utf-8\">" + NL +
0514       "<style type=\"text/css\">" + NL +
0515       "div { font-size:12px; margin-top: 4; }" + NL +
0516       "</style>" + NL +
0517       "</head>" + NL +
0518       "<body style=\"font-family:Verdana; color:navy;\">" + NL;
0519     String hTrace =
0520       "<div style=\"right: 0pt; border-top:1px solid #C9D7F1;" +
0521         " font-size:1px;\" ></div>" + NL;
0522     String reportTitle = hTrace;
0523     String docs = "";
0524     if (maxDocumentInReport != ALL_DOCS) {
0525       if (allDocs.size() < maxDocumentInReport) {
0526         docs = Integer.toString(allDocs.size());
0527       else {
0528         docs = Integer.toString(maxDocumentInReport);
0529       }
0530     else {
0531       docs = "All";
0532     }
0533     if (PRMatchingRegex.equals(MATCH_ALL_PR_REGEX)) {
0534       reportTitle = reportTitle
0535         "<div style=\"font-size:15px;font-family:Verdana; color:navy;\">Top "
0536         + docs + " expensive documents matching All PRs in <b>"
0537         + pipelineName + "</b></div>" + NL;
0538     else {
0539       if (matchingPRs.size() 0) {
0540         reportTitle = reportTitle
0541           "<div style=\"font-size:15px;font-family:Verdana; color:navy;\">Top "
0542           + docs + " expensive documents matching following PRs in <b>"
0543           + pipelineName + "</b> <ul>" + NL;
0544         for (String pr : matchingPRs) {
0545           reportTitle = reportTitle + "<li>" + pr + "</li>";
0546         }
0547         reportTitle = reportTitle + "</ul></div>";
0548       else {
0549         reportTitle +=
0550           "<div style=\"font-size:15px;font-family:Verdana; color:navy;\">" +
0551           "No PRs matched to search string \"" +
0552           getPRMatchingRegex() " \" in " + pipelineName + "</div>";
0553       }
0554     }
0555     reportTitle = reportTitle + hTrace;
0556 
0557     if (allDocs.size() 0) {
0558       String htmlReportTitle = reportTitle +
0559         "<table><tr bgcolor=\"#eeeeff\">" +
0560         "<td><b>Document Name</b></td>" +
0561         "<td><b>Time in seconds</b></td>" +
0562         "<td><b>% Time taken</b></td>" +
0563         "</tr><tr>" + NL;
0564       String documentNameHTMLString = "<td rowspan = '112' width = '550'>";
0565       String timeTakenHTMLString = "<td width = '100'>";
0566       String timeInPercentHTMLString = "<td width = '100'>";
0567       LinkedHashMap<String, Object> rcHash =
0568         reportSource;
0569       rcHash.remove("total");
0570       Iterator<String> i = rcHash.keySet().iterator();
0571       int count = 0;
0572       while (i.hasNext()) {
0573         Object key = i.next();
0574         if (!((Stringkey).equals("total")) {
0575           int value = Integer.parseInt((StringrcHash.get(key));
0576           if (maxDocumentInReport == ALL_DOCS) {
0577             documentNameHTMLString += "<div>" + key + "</div>";
0578             timeTakenHTMLString += "<div>" + value / 1000.0 "</div>";
0579             timeInPercentHTMLString += "<div>"
0580                 + Math.round(((value / globalTotal1001010.0
0581                 "</div>" + NL;
0582           else if (count < maxDocumentInReport) {
0583             documentNameHTMLString += "<div>" + key + "</div>";
0584             timeTakenHTMLString += "<div>" + value / 1000.0 "</div>";
0585             timeInPercentHTMLString += "<div>"
0586                 + Math.round(((value / globalTotal1001010.0
0587                 "</div>" + NL;
0588           }
0589         }
0590         count++;
0591       }
0592       documentNameHTMLString +=
0593         "<div bgcolor=\"#eeeeff\" style = \"font-size:15px;margin-left:400px;\">" +
0594         "<b>Total</b></div></td>" + NL;
0595       timeTakenHTMLString +=
0596         "<div bgcolor=\"#eeeeff\" style = \"font-size:15px;\"><b>" +
0597         globalTotal / 1000.0 "</b></div></td>" + NL;
0598       timeInPercentHTMLString +=
0599         "<div bgcolor=\"#eeeeff\" style = \"font-size:15px;\">" +
0600          "<b>100</b></div></td>" + NL;
0601 
0602       if (!outputFile.exists()) {
0603         htmlReport += htmlReportTitle + documentNameHTMLString
0604             + timeTakenHTMLString + timeInPercentHTMLString + "</tr></table>";
0605       else {
0606         htmlReport = "<br/><br/>" + htmlReportTitle + documentNameHTMLString
0607             + timeTakenHTMLString + timeInPercentHTMLString
0608             "</tr></table></body></html>";
0609       }
0610     else {
0611       htmlReport += reportTitle + "</body></html>";
0612     }
0613 
0614     BufferedWriter out = null;
0615     try {
0616       out = new BufferedWriter(new FileWriter(outputFile));
0617       out.write(htmlReport);
0618 
0619     catch (IOException e) {
0620       e.printStackTrace();
0621 
0622     finally {
0623       try {
0624         if (out != null) { out.close()}
0625       catch (IOException e) {
0626         e.printStackTrace();
0627       }
0628     }
0629   }
0630 
0631   /**
0632    * Ignores the inconsistent log entries from the benchmark file. Entries from
0633    * modules like pronominal coreferencer which have not been converted to new
0634    * benchmarking conventions are ignored.
0635    *
0636    @param benchmarkIDChain
0637    *          the chain of benchmark ids. This is the third token in the
0638    *          benchmark file.
0639    @param startTokens
0640    *          an array of first tokens in the benchmark id chain.
0641    *
0642    @return true if valid log entry; false otherwise.
0643    */
0644   private boolean validateLogEntry(String benchmarkIDChain,
0645                                    ArrayList<String> startTokens) {
0646     String startTokenRegExp = "(";
0647     for (int i = 0; i < startTokens.size(); i++) {
0648       if ((benchmarkIDChain.split("\\.")).length == 1
0649           && benchmarkIDChain.equals(startTokens.get(i))) {
0650         validEntries += 1;
0651         return true;
0652       }
0653       startTokenRegExp += startTokens.get(i"|";
0654     }
0655     if (startTokenRegExp.length() 1) {
0656       startTokenRegExp = startTokenRegExp.substring(0, startTokenRegExp
0657           .length() 1);
0658     }
0659     startTokenRegExp += ")";
0660     if (benchmarkIDChain.matches(startTokenRegExp + "\\.doc_.*?\\.pr_.*")) {
0661       validEntries += 1;
0662       return true;
0663     else
0664       return false;
0665   }
0666 
0667   /**
0668    * Parses the report command lime arguments.
0669    *
0670    @param args array containing the command line arguments.
0671    */
0672   @Override
0673   public void parseArguments(String[] args) {
0674     Getopt g = new Getopt("gate.util.reporting.DocTimeReporter", args,
0675         "i:m:d:p:o:l:h");
0676     int c;
0677     String argNoOfDocs = null;
0678     while ((c = g.getopt()) != -1) {
0679       switch (c) {
0680       // -i inputFile
0681       case 'i':
0682         String argInPath = g.getOptarg();
0683         if (argInPath != null) {
0684           setBenchmarkFile(new File(argInPath));
0685         }
0686         break;
0687       // -m printMedia
0688       case 'm':
0689         String argPrintMedia = g.getOptarg();
0690         if (argPrintMedia != null) {
0691           setPrintMedia(argPrintMedia);
0692         }
0693         break;
0694       // -d noOfDocs
0695       case 'd':
0696         argNoOfDocs = g.getOptarg();
0697         if (argNoOfDocs == null) {
0698           setMaxDocumentInReport(maxDocumentInReport);
0699         }
0700         break;
0701       // -p prName
0702       case 'p':
0703         String argPrName = g.getOptarg();
0704         if (argPrName != null) {
0705           setPRMatchingRegex(argPrName);
0706         else {
0707           setPRMatchingRegex(PRMatchingRegex);
0708         }
0709         break;
0710       // -o Report File
0711       case 'o':
0712         String argOutPath = g.getOptarg();
0713         if (argOutPath != null) {
0714           setReportFile(new File(argOutPath));
0715         }
0716         break;
0717       // -l logical start
0718       case 'l':
0719         String argLogicalStart = g.getOptarg();
0720         if (argLogicalStart != null) {
0721           setLogicalStart(argLogicalStart);
0722         }
0723         break;
0724       // -h usage information
0725       case 'h':
0726       case '?':
0727         usage();
0728         System.exit(STATUS_NORMAL);
0729         break;
0730 
0731       default:
0732         usage();
0733         System.exit(STATUS_ERROR);
0734         break;
0735 
0736       // getopt switch
0737     }
0738     if (argNoOfDocs != null) {
0739       try {
0740         setMaxDocumentInReport(Integer.parseInt(argNoOfDocs));
0741       catch (NumberFormatException e) {
0742         e.printStackTrace();
0743         usage();
0744         System.exit(STATUS_ERROR);
0745       }
0746     }
0747   }
0748 
0749   /**
0750    * Returns the name of the media on which report will be generated. e.g. text,
0751    * HTML.
0752    *
0753    @return printMedia A String containing the name of the media on which
0754    *         report will be generated.
0755    */
0756   public String getPrintMedia() {
0757     return printMedia;
0758   }
0759 
0760   /**
0761    * Sets the media on which report will be generated.
0762    *
0763    @param printMedia Type of media on which the report will be generated.
0764    * Must be MEDIA_TEXT or  MEDIA_HTML.
0765    * The default is MEDIA_HTML.
0766    */
0767   public void setPrintMedia(String printMedia) {
0768     if (!printMedia.equals(MEDIA_HTML)
0769      && !printMedia.equals(MEDIA_TEXT)) {
0770       throw new IllegalArgumentException("Illegal argument: " + printMedia);
0771     }
0772     this.printMedia = printMedia.trim();
0773   }
0774 
0775   /**
0776    * Provides the functionality to match a user input string with the PR in the
0777    * given benchmark ids.
0778    *
0779    @param benchmarkIDs
0780    *          A string of benchmarkIDs containing the PR name at the start of
0781    *          string.
0782    @param searchString
0783    *          The string to be matched for PR name.
0784    *
0785    @return boolean true if search string matches PR name; false otherwise.
0786    */
0787   private boolean isPRMatched(String benchmarkIDs, String searchString) {
0788     String prName = benchmarkIDs.split("\\.")[0];
0789     // Remove leading and trailing whitespaces of search string
0790     searchString = searchString.trim();
0791     // Remove "pr" or "pr_" appearing in start of the prName string
0792     searchString = searchString.replaceAll("^(pr|pr_)""");
0793     // Replace underscores with a space in the search string
0794     searchString = searchString.replaceAll("_"" ");
0795     // Replace multiple spaces with a single space
0796     searchString = searchString.replaceAll("\\s+"" ");
0797     searchString = searchString.trim();
0798     // Remove "pr_" appearing in start of the prName string
0799     String processedPRName = prName.replaceAll("^pr_""");
0800     // Replace underscores with a space in the prName
0801     processedPRName = processedPRName.replaceAll("_"" ");
0802     if (prName.startsWith("pr_")) {
0803       return processedPRName.matches("(?i).*" + searchString + ".*");
0804     else {
0805       return false;
0806     }
0807   }
0808 
0809   /**
0810    * A method for deleting a given file.
0811    *
0812    @param fileToBeDeleted
0813    *          A handle of the file to be deleted.
0814    @throws BenchmarkReportFileAccessException
0815    *           if a given file could not be deleted.
0816    */
0817   private void deleteFile(File fileToBeDeleted)
0818       throws BenchmarkReportFileAccessException {
0819     if (fileToBeDeleted.isFile()) {
0820       if (!fileToBeDeleted.delete()) {
0821         throw new BenchmarkReportFileAccessException(
0822           "Could not delete " + fileToBeDeleted.getAbsolutePath());
0823       }
0824     }
0825   }
0826 
0827   /**
0828    * Provides the functionality to separate out pipeline specific benchmark
0829    * entries in separate temporary benchmark files in a temporary folder in the
0830    * current working directory.
0831    *
0832    @param benchmarkFile
0833    *          An object of type File representing the input benchmark file.
0834    @param report
0835    *          A file handle to the report file to be written.
0836    @throws BenchmarkReportFileAccessException
0837    *           if any error occurs while accessing the input benchmark file or
0838    *           while splitting it.
0839    @throws BenchmarkReportExecutionException
0840    *           if the given input benchmark file is modified while generating
0841    *           the report.
0842    */
0843   private void splitBenchmarkFile(File benchmarkFile, File report)
0844       throws BenchmarkReportFileAccessException,
0845              BenchmarkReportInputFileFormatException {
0846     File dir = temporaryDirectory;
0847     // Folder already exists; then delete all files in the temporary folder
0848     if (dir.isDirectory()) {
0849       File files[] = dir.listFiles();
0850       for (int count = 0; count < files.length; count++) {
0851         if (!files[count].delete()) {
0852           throw new BenchmarkReportFileAccessException(
0853             "Could not delete files in the folder \"" +
0854             temporaryDirectory + "\"");
0855         }
0856       }
0857     else if (!dir.mkdir()) {
0858       throw new BenchmarkReportFileAccessException(
0859         "Could not create  temporary folder \"" + temporaryDirectory + "\"");
0860     }
0861 
0862     // delete report2 from the filesystem
0863     if (getPrintMedia().equalsIgnoreCase(MEDIA_TEXT)) {
0864       deleteFile(new File(report.getAbsolutePath() ".txt"));
0865     else if (getPrintMedia().equalsIgnoreCase(MEDIA_HTML)) {
0866       deleteFile(new File(report.getAbsolutePath() ".html"));
0867     }
0868 
0869     RandomAccessFile in = null;
0870     BufferedWriter out = null;
0871     try {
0872       String logEntry = "";
0873       long fromPos = 0;
0874 
0875       // File benchmarkFileName;
0876       if (getLogicalStart() != null) {
0877         fromPos = tail(benchmarkFile, FILE_CHUNK_SIZE);
0878       }
0879       in = new RandomAccessFile(benchmarkFile, "r");
0880 
0881       if (getLogicalStart() != null) {
0882         in.seek(fromPos);
0883       }
0884       ArrayList<String> startTokens = new ArrayList<String>();
0885       String lastStart = "";
0886       Pattern pattern = Pattern.compile("(\\d+) (\\d+) (.*) (.*) \\{(.*)\\}");
0887       Matcher matcher = null;
0888       File benchmarkFileName = null;
0889       while ((logEntry = in.readLine()) != null) {
0890         matcher = pattern.matcher(logEntry);
0891         String startToken = "";
0892         if (logEntry.matches(".*START.*")) {
0893           String[] splittedStartEntry = logEntry.split("\\s");
0894           if (splittedStartEntry.length > 2) {
0895             startToken = splittedStartEntry[2];
0896           else {
0897             throw new BenchmarkReportInputFileFormatException(
0898                 getBenchmarkFile() " is invalid.");
0899           }
0900 
0901           if (startToken.endsWith("Start")) {
0902             continue;
0903           }
0904           if (!startTokens.contains(startToken)) {
0905             // create a new file for the new pipeline
0906             startTokens.add(startToken);
0907             benchmarkFileName = new File(
0908               temporaryDirectory, startToken + "_benchmark.txt");
0909             if (!benchmarkFileName.createNewFile()) {
0910               throw new BenchmarkReportFileAccessException(
0911                   "Could not create \"" + startToken + "_benchmark.txt"
0912                       "\" in directory named \"" + temporaryDirectory + "\"");
0913             }
0914             out = new BufferedWriter(new FileWriter(benchmarkFileName));
0915             out.write(logEntry);
0916             out.newLine();
0917           }
0918         }
0919         // if a valid benchmark entry then write it to the pipeline specific
0920         // file
0921         if (matcher != null
0922             && matcher.matches()
0923             && (validateLogEntry(matcher.group(3), startTokens|| logEntry
0924                 .matches(".*documentLoaded.*"))) {
0925           startToken = matcher.group(3).split("\\.")[0];
0926           if (!(lastStart.equals(startToken))) {
0927             if (out != null) { out.close()}
0928             benchmarkFileName = new File(
0929               temporaryDirectory, startToken + "_benchmark.txt");
0930             out = new BufferedWriter(new FileWriter(benchmarkFileName, true));
0931           }
0932           if (out != null) {
0933             out.write(logEntry);
0934             out.newLine();
0935           }
0936           lastStart = startToken;
0937         }
0938       }
0939 
0940     catch (IOException e) {
0941       e.printStackTrace();
0942 
0943     finally {
0944       try {
0945         if (in != null) { in.close()}
0946         if (out != null) { out.close()}
0947       catch (IOException e) {
0948         e.printStackTrace();
0949       }
0950     }
0951   }
0952 
0953   /**
0954    * A method for reading the file upside down.
0955    *
0956    @param fileToBeRead
0957    *          An object of the file to be read.
0958    @param chunkSize
0959    *          An integer specifying the size of the chunks in which file will be
0960    *          read.
0961    @return A long value pointing to the start position of the given file
0962    *         chunk.
0963    */
0964   private long tail(File fileToBeRead, int chunkSize)
0965       throws BenchmarkReportInputFileFormatException {
0966     RandomAccessFile raf = null;
0967     try {
0968       raf = new RandomAccessFile(fileToBeRead, "r");
0969       Vector<String> lastNlines = new Vector<String>();
0970       int delta = 0;
0971       long curPos = 0;
0972       curPos = raf.length() 1;
0973       long fromPos;
0974       byte[] bytearray;
0975       while (true) {
0976         fromPos = curPos - chunkSize;
0977         if (fromPos <= 0) {
0978           raf.seek(0);
0979           bytearray = new byte[(intcurPos];
0980           raf.readFully(bytearray);
0981           if (parseLinesFromLast(bytearray, lastNlines, fromPos)) {
0982             if (fromPos < 0)
0983               fromPos = 0;
0984           }
0985           break;
0986         else {
0987           raf.seek(fromPos);
0988           bytearray = new byte[chunkSize];
0989           raf.readFully(bytearray);
0990           if (parseLinesFromLast(bytearray, lastNlines, fromPos)) {
0991             break;
0992           }
0993           delta = lastNlines.get(lastNlines.size() 1).length();
0994           lastNlines.remove(lastNlines.size() 1);
0995           curPos = fromPos + delta;
0996         }
0997       }
0998       if (fromPos < 0)
0999         throw new BenchmarkReportInputFileFormatException(getBenchmarkFile()
1000             " does not contain a marker named "
1001             + getLogicalStart()
1002             " indicating logical start of a run.");
1003       return fromPos;
1004 
1005     catch (IOException e) {
1006       e.printStackTrace();
1007       return -1;
1008     }
1009     finally {
1010       IOUtils.closeQuietly(raf);
1011     }
1012   }
1013 
1014   /**
1015    * A method to ensure that the required line is read from the given file part.
1016    *
1017    @param bytearray
1018    *          A part of a file being read upside down.
1019    @param lastNlines
1020    *          A vector containing the lines extracted from file part.
1021    @param fromPos
1022    *          A long value indicating the start of a file part.
1023    *
1024    @return true if marker indicating the logical start of run is found; false
1025    *         otherwise.
1026    */
1027   private boolean parseLinesFromLast(byte[] bytearray,
1028                                      Vector<String> lastNlines, long fromPos) {
1029       String lastNChars = new String(bytearray);
1030       StringBuffer sb = new StringBuffer(lastNChars);
1031       lastNChars = sb.reverse().toString();
1032       StringTokenizer tokens = new StringTokenizer(lastNChars, NL);
1033       while (tokens.hasMoreTokens()) {
1034         StringBuffer sbLine = new StringBuffer(tokens.nextToken());
1035         lastNlines.add(sbLine.reverse().toString());
1036         if ((lastNlines.get(lastNlines.size() 1))
1037             .trim().endsWith(getLogicalStart())) {
1038           return true;
1039         }
1040       }
1041       return false;
1042   }
1043 
1044   /**
1045    * Display a usage message
1046    */
1047   public static void usage() {
1048     System.out.println(
1049     "Usage: java gate.util.reporting.DocTimeReporter [Options]" + NL
1050   "\t Options:" + NL
1051   "\t -i input file path (default: benchmark.txt in the execution directory)" + NL
1052   "\t -m print media - html/text (default: html)" + NL
1053   "\t -d number of docs, use -1 for all docs (default: 10 docs)" + NL
1054   "\t -p processing resource name to be matched (default: all_prs)" + NL
1055   "\t -o output file path (default: report.html/txt in the system temporary directory)" + NL
1056   "\t -l logical start (not set by default)" + NL
1057   "\t -h show help" + NL);
1058   // usage()
1059 
1060   /**
1061    * A main method which acts as a entry point while executing a report via
1062    * command line
1063    *
1064    @param args
1065    *          A string array containing the command line arguments.
1066    @throws BenchmarkReportExecutionException
1067    *           if a given input file is modified while generating the report.
1068    */
1069   public static void main(String[] args)
1070       throws BenchmarkReportInputFileFormatException,
1071              BenchmarkReportFileAccessException {
1072     // process command-line options
1073     DocTimeReporter reportTwo = new DocTimeReporter(args);
1074     reportTwo.generateReport();
1075   }
1076 
1077   /**
1078    * Calls store, calculate and printReport for generating the actual report.
1079    */
1080   private void generateReport() throws BenchmarkReportInputFileFormatException,
1081                                        BenchmarkReportFileAccessException {
1082     Timer timer = null;
1083     try {
1084       TimerTask task = new FileWatcher(getBenchmarkFile()) {
1085         @Override
1086         protected void onChange(File file) {
1087           throw new BenchmarkReportExecutionException(getBenchmarkFile()
1088               " file has been modified while generating the report.");
1089         }
1090       };
1091       timer = new Timer();
1092       // repeat the check every second
1093       timer.schedule(task, new Date()1000);
1094 
1095       if (reportFile == null) {
1096         reportFile = new File(System.getProperty("java.io.tmpdir"),
1097           "report." ((printMedia.equals(MEDIA_HTML)) "html" "txt"));
1098       }
1099       splitBenchmarkFile(getBenchmarkFile(), reportFile);
1100       if (validEntries == 0) {
1101         if (logicalStart != null) {
1102           throw new BenchmarkReportInputFileFormatException(
1103             "No valid log entries present in " + getBenchmarkFile() +
1104             " does not contain a marker named " + logicalStart + ".");
1105         else {
1106           throw new BenchmarkReportInputFileFormatException(
1107             "No valid log entries present in "
1108             + getBenchmarkFile().getAbsolutePath());
1109         }
1110       }
1111       File dir = temporaryDirectory;
1112       // Folder already exists; then delete all files in the temporary folder
1113       if (dir.isDirectory()) {
1114         File files[] = dir.listFiles();
1115         for (int count = 0; count < files.length; count++) {
1116           File inFile = files[count];
1117           Object report2Container1 = store(inFile);
1118           Object report2Container2 = calculate(report2Container1);
1119           printReport(report2Container2, reportFile);
1120         }
1121         if (files.length > && files[0].exists()) {
1122           if (!files[0].delete()) {
1123             System.err.println(files[0" was not possible to delete.");
1124           }
1125         }
1126       }
1127     finally {
1128       if (timer != null) { timer.cancel()}
1129     }
1130   }
1131 
1132   /*
1133    * (non-Javadoc)
1134    *
1135    * @see gate.util.reporting.BenchmarkReportable#executeReport()
1136    */
1137   @Override
1138   public void executeReport() throws BenchmarkReportInputFileFormatException,
1139                                      BenchmarkReportFileAccessException {
1140     generateReport();
1141   }
1142 
1143   /**
1144    * Returns the marker indicating logical start of a run.
1145    *
1146    @return logicalStart A String containing the marker indicating logical
1147    *         start of a run.
1148    */
1149   public String getLogicalStart() {
1150     return logicalStart;
1151   }
1152 
1153   /**
1154    * Sets optionally a string indicating the logical start of a run.
1155    *
1156    @param logicalStart A String indicating the logical start of a run.
1157    * Useful when you you have marked different runs in
1158    * your benchmark file with this string at their start.
1159    * By default the value is null.
1160    */
1161   public void setLogicalStart(String logicalStart) {
1162     this.logicalStart = logicalStart;
1163   }
1164 
1165   /**
1166    @return benchmarkFile path to input benchmark file.
1167    @see #setBenchmarkFile(java.io.File)
1168    */
1169   public File getBenchmarkFile() {
1170     return benchmarkFile;
1171   }
1172 
1173   /**
1174    * Sets the input benchmark file from which the report is generated.
1175    * By default use the file named "benchmark.txt" from the application
1176    * execution directory.
1177    *
1178    @param benchmarkFile Input benchmark file.
1179    */
1180   public void setBenchmarkFile(File benchmarkFile) {
1181     this.benchmarkFile = benchmarkFile;
1182   }
1183 
1184   /**
1185    @return reportFile file path where the report file is written.
1186    @see #setReportFile(java.io.File)
1187    */
1188   public File getReportFile() {
1189     return reportFile;
1190   }
1191 
1192   /**
1193    * If not set, the default is the file name "report.txt/html"
1194    * in the system temporary directory.
1195    *
1196    @param reportFile file path to the report file to write.
1197    */
1198   public void setReportFile(File reportFile) {
1199     this.reportFile = reportFile;
1200   }
1201 
1202   /**
1203    * Returns the maximum no of documents to be shown in the report.
1204    *
1205    @return maxDocumentInReport An integer specifying the maximum no of
1206    *         documents to be shown in the report.
1207    */
1208   public int getMaxDocumentInReport() {
1209     return maxDocumentInReport;
1210   }
1211 
1212   /**
1213    * Maximum number of documents contained in the report.
1214    @param maxDocumentInReport Maximum number of documents contained in
1215    * the report. Use the constant ALL_DOCS for reporting all documents.
1216    * The default is 10.
1217    */
1218   public void setMaxDocumentInReport(int maxDocumentInReport) {
1219     if (!(maxDocumentInReport > || maxDocumentInReport == ALL_DOCS)) {
1220       throw new IllegalArgumentException(
1221         "Illegal argument: " + maxDocumentInReport);
1222     }
1223     this.maxDocumentInReport = maxDocumentInReport;
1224   }
1225 
1226   /**
1227    * Returns the search string to be matched to PR names present in the log
1228    * entries.
1229    *
1230    @return PRMatchingRegex A String to be matched to PR names present in the
1231    *         log entries.
1232    */
1233   public String getPRMatchingRegex() {
1234     return PRMatchingRegex;
1235   }
1236 
1237   /**
1238    * Search string to match PR names present in the benchmark file.
1239    *
1240    @param matchingRegex regular expression to match PR names
1241    * present in the benchmark file. The default is MATCH_ALL_PR_REGEX.
1242    */
1243   public void setPRMatchingRegex(String matchingRegex) {
1244     PRMatchingRegex = matchingRegex;
1245   }
1246 }
1247 
1248 /**
1249  * A FileWather class to check whether the file is modified or not at specified
1250  * interval.
1251  */
1252 abstract class FileWatcher extends TimerTask {
1253   private long timeStamp;
1254   private File file;
1255 
1256   /**
1257    * Creates a FileWatcher on a given file.
1258    *
1259    @param file
1260    *          A handle of the file to be watched.
1261    */
1262   public FileWatcher(File file) {
1263     this.file = file;
1264     timeStamp = file.lastModified();
1265   }
1266 
1267   /*
1268    * (non-Javadoc)
1269    *
1270    * @see java.util.TimerTask#run()
1271    */
1272   @Override
1273   public final void run() {
1274     long oldTimeStamp = file.lastModified();
1275     if (timeStamp != oldTimeStamp) {
1276       cancel();
1277       onChange(file);
1278     }
1279   }
1280 
1281   /**
1282    * Specifies the actions to be taken when a file is modified.
1283    *
1284    @param file
1285    *          A handle of the file to be watched.
1286    */
1287   protected abstract void onChange(File file)
1288     throws BenchmarkReportExecutionException;
1289 }