1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2006, 2009, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.logging;
  28 
  29 import java.io.FileNotFoundException;
  30 import java.io.IOException;
  31 import java.io.RandomAccessFile;
  32 import java.text.DateFormat;
  33 import java.text.SimpleDateFormat;
  34 import java.util.ArrayList;
  35 import java.util.Date;
  36 import java.util.LinkedHashMap;
  37 import java.util.Map;
  38 import java.util.logging.Level;
  39 import java.util.logging.Logger;
  40 
  41 public class LogModel {
  42 
  43     public LogModel(ObservedFile logFile, String fileName) {
  44         file = fileName;
  45         records = new ArrayList<LiteLogRecord>();
  46         loggers = new ArrayList<String>();
  47         messageCache = new MessageCache();
  48         setObservedFile(logFile);
  49     }
  50 
  51     public ArrayList<String> getLoggers() {
  52         return loggers;
  53     }
  54 
  55     public ArrayList<LiteLogRecord> getRecords() {
  56         return records;
  57     }
  58 
  59     public void init() {
  60         worker = new Worker("LogViewerWorker");
  61         //worker.setPriority(Thread.MIN_PRIORITY);
  62         worker.start();
  63     }
  64 
  65     boolean jobDone() {
  66         if (worker == null ) {
  67             return false;
  68         }
  69         return !worker.isAlive();
  70     }
  71 
  72     int recordsRead() {
  73         if (records != null) {
  74             return records.size();
  75         } else {
  76             return 0;
  77         }
  78     }
  79 
  80     public int pagesRead() {
  81         int records = recordsRead();
  82         if (records == 0) {
  83             return 0;
  84         } else {
  85             return (records-1) / PAGE_SIZE + 1;
  86         }
  87     }
  88 
  89     private class Worker extends Thread {
  90 
  91         public Worker(String name) {
  92             super(name);
  93         }
  94 
  95         boolean stop = false;
  96 
  97         public void run() {
  98             RandomAccessFile r = null;
  99             int firstRecordOnPage = 0;
 100             int recordCount = 0;
 101             stable = false;
 102             try {
 103                 r =  new RandomAccessFile(file, "r");
 104                 String signature = null;
 105                 while (true) {
 106                     signature = r.readLine();
 107                     if (signature == null) {
 108                         if (stop) {
 109                             return;
 110                         }
 111                         try {
 112                             if (debug) System.out.println("Worker-1 sleep, first loop (empty file)");
 113                             stable = true;
 114                             fireNewPage(0, 0);
 115                             sleep(500);
 116                         } catch (InterruptedException ex) {
 117                             if (debug) ex.printStackTrace();
 118                             // it's ok
 119                         }
 120                     } else {
 121                         break;
 122                     }
 123                 }
 124                 if (!JTFormatter.LOG_SIGNATURE.equals(signature)) {
 125                     throw new IOException("Wrong logfile " + file + " signature=" + signature);
 126                 }
 127 
 128                 stable = false;
 129 
 130                 // read info from index
 131                 synchronized (of) {
 132                     of.readLoggers(loggers);
 133                     of.readRecords(records);
 134                     recordCount = records.size();
 135                     firstRecordOnPage = (pagesRead()-1)*PAGE_SIZE;
 136                     if (debug) System.out.println("Worker-1 read from index " + records.size() + this);
 137                 }
 138 
 139                 for (String logger : loggers) {
 140                     fireNewLoggerFound(logger);
 141                 }
 142 
 143                 if (records.size() > 0 ) {
 144                     recordCount = records.size();
 145                     LogModel.LiteLogRecord llr = records.get(recordCount-1);
 146                     r.seek(llr.endOff);
 147                 }
 148 
 149 
 150                 String readStr = "";
 151                 while (true) {
 152                     while (readStr != null) {
 153                         if (stop) {
 154                             return;
 155                         }
 156 
 157                         String logName = r.readLine();
 158                         if (logName == null) {
 159                             break;
 160                         }
 161 
 162 
 163                         int logID = loggers.indexOf(logName);
 164                         if (logID < 0) {
 165                             logID = loggers.size();
 166                             loggers.add(logID, logName);
 167                             fireNewLoggerFound(logName);
 168                         }
 169                         // 2) Level (int)
 170                         readStr = r.readLine();
 171                         if (readStr == null) {
 172                             throw new IOException("Wrong logfile " + file);
 173                         }
 174                         int level = Integer.parseInt(readStr);
 175                         // 3) Time in mills
 176                         readStr = r.readLine();
 177                         if (readStr == null) {
 178                             throw new IOException("Wrong logfile " + file);
 179                         }
 180                         long mills = Long.parseLong(readStr);
 181                         // 4) Msg length
 182                         readStr = r.readLine();
 183                         if (readStr == null) {
 184                             throw new IOException("Wrong logfile " + file);
 185                         }
 186                         long length = Long.parseLong(readStr);
 187                         // 5) Msg
 188                         long read = 0;
 189                         long start = r.getFilePointer();
 190                         StringBuffer msg = new StringBuffer();
 191 
 192                         // Do not optimize this loop, do not use readLine() !
 193                         while (read <= length) {
 194                             byte ch = r.readByte();
 195                             read++;
 196                             msg.append(ch);
 197                         }
 198 
 199                         LiteLogRecord record = new LiteLogRecord();
 200                         record.loggerID = logID;
 201                         record.startOff = start;
 202                         record.endOff = r.getFilePointer();
 203                         record.time = mills;
 204                         record.severety = level;
 205 
 206                         records.add(record);
 207                         if (recordCount % PAGE_SIZE == 0 && recordCount != 0) {
 208                             fireNewPage(firstRecordOnPage, recordCount);
 209                             firstRecordOnPage = recordCount;
 210                         }
 211                         recordCount++;
 212                         if (debug) System.out.println("Worker-1 - record read");
 213                     }
 214                     try {
 215                         if (stop) {
 216                             return;
 217                         }
 218                         if (firstRecordOnPage  != recordCount) {
 219                             fireNewPage(firstRecordOnPage, recordCount-1);
 220                             if (debug) System.out.println("Worker-1  - fireNewPage(" + firstRecordOnPage + " , "  + (recordCount-1) + " )");
 221                         }
 222                         if (debug) System.out.println("Worker-1  - all records read, sleep");
 223                         sleep(500);
 224                         if (stop) {
 225                             return;
 226                         }
 227                         stable = true;
 228                         readStr = "";
 229                     } catch (InterruptedException ex) {
 230                         // ok
 231                         if (debug) ex.printStackTrace();
 232                     }
 233                 }  // autoupdate loop
 234             } catch (IOException ex) {
 235                 logEx(ex);
 236             } finally {
 237                 try {
 238                     if (r != null ) {
 239                         r.close();
 240                     }
 241                     if (!stop && firstRecordOnPage  != (recordCount-1)) {
 242                         fireNewPage(firstRecordOnPage, recordCount-1);
 243                     }
 244                 } catch (IOException ex) {
 245                     logEx(ex);
 246                 }
 247             }
 248         }
 249     }
 250 
 251     public void addNewLoggerListener(LoggerListener lst) {
 252         loggerListeners.add(lst);
 253     }
 254 
 255     public void removeNewLoggerListeners() {
 256         loggerListeners.clear();
 257     }
 258 
 259     void addNewPageListener(NewPageListener lst) {
 260         pageListeners.add(lst);
 261     }
 262 
 263     boolean isStableState() {
 264         return stable;
 265     }
 266 
 267     void setObservedFile(ObservedFile of) {
 268         if (this.of != null && fileListener != null) {
 269             this.of.removeFileListener(fileListener);
 270         }
 271 
 272         this.of = of;
 273         if (of != null) {
 274             fileListener = new LogFileListener();
 275             of.addFileListener(fileListener);
 276         }
 277     }
 278 
 279     private void fireNewLoggerFound(String loggerName) {
 280         for(LoggerListener lst : loggerListeners) {
 281             lst.onNewLogger(loggerName);
 282         }
 283     }
 284 
 285 
 286     private void fireRemoveAllLoggers() {
 287         for(LoggerListener lst : loggerListeners) {
 288             lst.onRemoveAllLoggers();
 289         }
 290     }
 291 
 292     private void fireNewPage(int from, int to) {
 293         int pageNum = (to-1) / PAGE_SIZE + 1;
 294         for(NewPageListener lst : pageListeners) {
 295             lst.onNewPage(from, to, pageNum);
 296         }
 297     }
 298 
 299 
 300     public synchronized String getRecordMessage(LiteLogRecord rec) {
 301         if (rec == null)
 302             return "";
 303         if (messageCache.containsKey(rec)) {
 304             return messageCache.get(rec);
 305         }
 306 
 307         StringBuffer msg = new StringBuffer();
 308         try {
 309             ensureMirrorFileOpened();
 310             if (rec == null ||  mirrorFile == null) {
 311                 return "";
 312             }
 313             mirrorFile.seek(rec.startOff);
 314             int line = 0;
 315             String readStr = "";
 316             while (readStr != null && mirrorFile.getFilePointer() < rec.endOff) {
 317                 readStr = mirrorFile.readLine();
 318                 if (line > 0) {
 319                     msg.append('\n');
 320                 }
 321                 msg.append(readStr);
 322                 line++;
 323             }
 324         } catch (IOException ex) {
 325             // it can be after log file purge
 326             return "";
 327         }
 328         messageCache.put(rec, msg.toString());
 329         return msg.toString();
 330     }
 331 
 332     synchronized void dispose() {
 333         resetModel();
 334         loggerListeners.clear();
 335         pageListeners.clear();
 336 
 337         if (of != null && fileListener != null) {
 338             of.removeFileListener(fileListener);
 339         }
 340 
 341         //theThread = null;
 342         worker = null;
 343     }
 344 
 345     private synchronized void resetModel() {
 346         if (worker != null && worker.isAlive()) {
 347             worker.stop = true;
 348             worker.interrupt();
 349             if (debug) System.out.println("worker.interrupt()");
 350         }
 351         // wait
 352         if (worker != null) {
 353             try {
 354                 worker.join();
 355                 if (debug) System.out.println("worker.join()");
 356             } catch (InterruptedException ex) {
 357                 if (debug) ex.printStackTrace();
 358             }
 359         }
 360         if (mirrorFile != null) {
 361             try {
 362                 mirrorFile.close();
 363             } catch (IOException ex) {
 364                 logEx(ex);
 365             }
 366         }
 367 
 368         synchronized (of) {
 369             records.clear();
 370             loggers.clear();
 371             fireRemoveAllLoggers();
 372         }
 373         messageCache.clear();
 374         if (mirrorFile != null) {
 375             try {
 376                 mirrorFile.close();
 377             } catch (IOException ex) {
 378                 ex.printStackTrace();
 379             }
 380             mirrorFile = null;
 381         }
 382     }
 383 
 384     private void ensureMirrorFileOpened() throws FileNotFoundException {
 385         if (mirrorFile == null) {
 386             mirrorFile = new RandomAccessFile(file, "r");
 387         }
 388     }
 389 
 390     public int getPageSize() {
 391         return PAGE_SIZE;
 392     }
 393 
 394     public String getLogname(int loggerID) {
 395         if (loggerID < loggers.size())
 396             return loggers.get(loggerID);
 397         else
 398             return "";
 399     }
 400 
 401     public void setLogger(Logger log) {
 402         logger = log;
 403     }
 404 
 405     private void logEx(Throwable th) {
 406         if (logger != null) {
 407             logger.logp(Level.SEVERE, getClass().getName(), null, th.getMessage(), th);
 408         } else {
 409             th.printStackTrace();
 410         }
 411     }
 412 
 413     class LogFileListener implements FileListener {
 414         public void fileModified(FileEvent e) {
 415             synchronized (LogModel.this) {
 416                 if (e.getType().equals(FileEvent.START_ERASING)){
 417                     if (debug) System.out.println("FileEvent.START_ERASING");
 418                     resetModel();
 419                 } else if (e.getType().equals(FileEvent.ERASED)){
 420                     if (debug) System.out.println("FileEvent.ERASED");
 421                     init();
 422                 }
 423 
 424             }
 425         }
 426     }
 427 
 428 
 429     private class MessageCache extends LinkedHashMap<LiteLogRecord, String> {
 430         protected boolean removeEldestEntry(Map.Entry<LiteLogRecord, String> eldest) {
 431             return size() > PAGE_SIZE*2;
 432         }
 433     }
 434 
 435     private boolean stable = false;
 436     private ArrayList<String> loggers;
 437     private ArrayList<LiteLogRecord> records;
 438 
 439     private ArrayList<LoggerListener> loggerListeners = new ArrayList<LoggerListener>();
 440     private ArrayList<NewPageListener> pageListeners = new ArrayList<NewPageListener>();
 441     private MessageCache messageCache = new MessageCache();
 442     private String file;
 443     private RandomAccessFile mirrorFile;
 444     private Worker worker;
 445     private Logger logger;
 446 
 447     private ObservedFile of;
 448     private LogFileListener fileListener;
 449 
 450     static final boolean debug = false;
 451 
 452     public interface LoggerListener {
 453         void onNewLogger(String name);
 454         void onRemoveAllLoggers();
 455     }
 456 
 457     public interface NewPageListener {
 458         void onNewPage(int startRecord, int endRecord, int pageNum);
 459     }
 460 
 461     private static final int PAGE_SIZE = 1000;
 462 
 463 
 464     static public class LiteLogRecord {
 465 
 466         private String getTimeString() {
 467             DateFormat dfISO8601 = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
 468             return dfISO8601.format(new Date(time));
 469         }
 470 
 471         public String getHeader(String logName) {
 472             StringBuffer out = new StringBuffer();
 473             int pos = logName.indexOf("#");
 474             if (pos >= 0) {
 475                 out.append(logName.substring(pos+1));
 476             } else {
 477                 out.append(logName);
 478             }
 479             out.append(", ");
 480             out.append(LoggerFactory.getLocalizedLevelName(Level.parse("" + severety))).append(": ");
 481             out.append(getTimeString());
 482             if (pos > 0) {
 483                 out.append("; ");
 484                 out.append(logName.substring(0, pos));
 485             }
 486             return out.toString();
 487         }
 488         public int loggerID;
 489         public long time;
 490         public int severety;
 491         public long startOff, endOff;
 492     }
 493 
 494 
 495 }
 496 
 497