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