001package org.cpsolver.ifs.util; 002 003import java.io.Serializable; 004import java.text.SimpleDateFormat; 005import java.util.ArrayList; 006import java.util.Date; 007import java.util.HashMap; 008import java.util.Iterator; 009import java.util.List; 010 011import org.dom4j.Element; 012 013/** 014 * Progress bar. <br> 015 * <br> 016 * Single instance class for recording the current state. It also allows 017 * recursive storing/restoring of the progress. 018 * 019 * <br> 020 * <br> 021 * Use: 022 * <pre> 023 * <code> 024 * Progress.getInstance().setStatus("Loading input data"); 025 * Progress.getInstance().setPhase("Creating variables ...", nrVariables); 026 * for (int i=0;i<nrVariables;i++) { 027 * //load variable here 028 * Progress.getInstance().incProgress(); 029 * } 030 * Progress.getInstance().setPhase("Creating constraints ...", nrConstraints); 031 * for (int i=0;i<nrConstraints;i++) { 032 * //load constraint here 033 * Progress.getInstance().incProgress(); 034 * } 035 * Progress.getInstance().setStatus("Solving problem"); 036 * ... 037 * </code> 038 * </pre> 039 * 040 * @version IFS 1.3 (Iterative Forward Search)<br> 041 * Copyright (C) 2006 - 2014 Tomas Muller<br> 042 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 043 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 044 * <br> 045 * This library is free software; you can redistribute it and/or modify 046 * it under the terms of the GNU Lesser General Public License as 047 * published by the Free Software Foundation; either version 3 of the 048 * License, or (at your option) any later version. <br> 049 * <br> 050 * This library is distributed in the hope that it will be useful, but 051 * WITHOUT ANY WARRANTY; without even the implied warranty of 052 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 053 * Lesser General Public License for more details. <br> 054 * <br> 055 * You should have received a copy of the GNU Lesser General Public 056 * License along with this library; if not see 057 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 058 */ 059public class Progress { 060 public static boolean sTraceEnabled = false; 061 private static org.apache.log4j.Logger sLogger = org.apache.log4j.Logger.getLogger(Progress.class); 062 public static SimpleDateFormat sDF = new SimpleDateFormat("MM/dd/yy HH:mm:ss.SSS"); 063 public static final int MSGLEVEL_TRACE = 0; 064 public static final int MSGLEVEL_DEBUG = 1; 065 public static final int MSGLEVEL_PROGRESS = 2; 066 public static final int MSGLEVEL_INFO = 3; 067 public static final int MSGLEVEL_STAGE = 4; 068 public static final int MSGLEVEL_WARN = 5; 069 public static final int MSGLEVEL_ERROR = 6; 070 public static final int MSGLEVEL_FATAL = 7; 071 072 private String iStatus = ""; 073 private String iPhase = ""; 074 private long iProgressMax = 0; 075 private long iProgressCurrent = 0; 076 private List<ProgressListener> iListeners = new ArrayList<ProgressListener>(5); 077 private List<Object[]> iSave = new ArrayList<Object[]>(5); 078 private List<Message> iLog = new ArrayList<Message>(1000); 079 private boolean iDisposed = false; 080 081 private static HashMap<Object, Progress> sInstances = new HashMap<Object, Progress>(); 082 083 private Progress() { 084 } 085 086 /** Progress default instance 087 * @return progress instance 088 **/ 089 public static Progress getInstance() { 090 return getInstance("--DEFAULT--"); 091 } 092 093 /** Progress instance 094 * @param key an object (typically a problem model) for which the progress is to be returned 095 * @return progress instance 096 **/ 097 public static Progress getInstance(Object key) { 098 Progress progress = sInstances.get(key); 099 if (progress == null) { 100 progress = new Progress(); 101 sInstances.put(key, progress); 102 } 103 return progress; 104 } 105 106 /** Change progress instance for the given key 107 * @param oldKey old instance 108 * @param newKey new instance 109 **/ 110 public static void changeInstance(Object oldKey, Object newKey) { 111 removeInstance(newKey); 112 Progress progress = sInstances.get(oldKey); 113 if (progress != null) { 114 sInstances.remove(oldKey); 115 sInstances.put(newKey, progress); 116 } 117 } 118 119 /** Remove progress instance for the given key 120 * @param key old instance 121 **/ 122 public static void removeInstance(Object key) { 123 Progress progress = sInstances.get(key); 124 if (progress != null) { 125 progress.iListeners.clear(); 126 progress.iDisposed = true; 127 sInstances.remove(key); 128 } 129 } 130 131 /** Current status 132 * @return current status 133 **/ 134 public String getStatus() { 135 return iStatus; 136 } 137 138 /** Sets current status 139 * @param status current status 140 **/ 141 public void setStatus(String status) { 142 message(MSGLEVEL_STAGE, status); 143 if (!status.equals(iStatus)) { 144 iPhase = ""; 145 iProgressMax = 0; 146 iProgressCurrent = 0; 147 iStatus = status; 148 fireStatusChanged(); 149 } 150 } 151 152 /** Current phase 153 * @return current phase 154 **/ 155 public String getPhase() { 156 return iPhase; 157 } 158 159 /** 160 * Sets current phase 161 * 162 * @param phase 163 * phase name 164 * @param progressMax 165 * maximum of progress bar 166 */ 167 public void setPhase(String phase, long progressMax) { 168 if (iSave.isEmpty() && !phase.equals(iPhase)) 169 message(MSGLEVEL_PROGRESS, phase); 170 iPhase = phase; 171 iProgressMax = progressMax; 172 iProgressCurrent = 0; 173 firePhaseChanged(); 174 } 175 176 /** 177 * Sets current phase. Maximum of progress bar is set to 100. 178 * 179 * @param phase 180 * phase name 181 */ 182 public void setPhase(String phase) { 183 setPhase(phase, 100); 184 } 185 186 /** 187 * Update progress bar. 188 * 189 * @param progress 190 * progress between 0 and progressMax 191 */ 192 public void setProgress(long progress) { 193 if (iProgressCurrent != progress) { 194 iProgressCurrent = progress; 195 fireProgressChanged(); 196 } 197 } 198 199 /** Current progress 200 * @return current progress 201 **/ 202 public long getProgress() { 203 return iProgressCurrent; 204 } 205 206 /** Maximum of current progress 207 * @return current progress maximum 208 **/ 209 public long getProgressMax() { 210 return iProgressMax; 211 } 212 213 /** Increment current progress */ 214 public void incProgress() { 215 iProgressCurrent++; 216 fireProgressChanged(); 217 } 218 219 /** Adds progress listener 220 * @param listener a progress listener 221 **/ 222 public void addProgressListener(ProgressListener listener) { 223 iListeners.add(listener); 224 } 225 226 /** Remove progress listener 227 * @param listener a progress listener 228 **/ 229 public void removeProgressListener(ProgressListener listener) { 230 iListeners.remove(listener); 231 } 232 233 /** Remove all progress listeners */ 234 public void clearProgressListeners() { 235 iListeners.clear(); 236 } 237 238 /** Save current progress to the heap memory */ 239 public synchronized void save() { 240 iSave.add(new Object[] { iStatus, iPhase, new Long(iProgressMax), new Long(iProgressCurrent) }); 241 fireProgressSaved(); 242 } 243 244 /** Resore the progress from the heap memory */ 245 public synchronized void restore() { 246 if (iSave.isEmpty()) 247 return; 248 Object[] o = iSave.get(iSave.size() - 1); 249 iSave.remove(iSave.size() - 1); 250 iStatus = (String) o[0]; 251 iPhase = (String) o[1]; 252 iProgressMax = ((Long) o[2]).longValue(); 253 iProgressCurrent = ((Long) o[3]).longValue(); 254 fireProgressRestored(); 255 } 256 257 /** Prints a message 258 * @param level logging level 259 * @param message message to log 260 * @param t an exception, if any 261 **/ 262 public void message(int level, String message, Throwable t) { 263 if (iDisposed) throw new RuntimeException("This solver is killed."); 264 Message m = new Message(level, message, t); 265 switch (level) { 266 case MSGLEVEL_TRACE: 267 sLogger.debug(" -- " + message, t); 268 break; 269 case MSGLEVEL_DEBUG: 270 sLogger.debug(" -- " + message, t); 271 break; 272 case MSGLEVEL_PROGRESS: 273 sLogger.debug("[" + message + "]", t); 274 break; 275 case MSGLEVEL_INFO: 276 sLogger.info(message, t); 277 break; 278 case MSGLEVEL_STAGE: 279 sLogger.info("[" + message + "]", t); 280 break; 281 case MSGLEVEL_WARN: 282 sLogger.warn(message, t); 283 break; 284 case MSGLEVEL_ERROR: 285 sLogger.error(message, t); 286 break; 287 case MSGLEVEL_FATAL: 288 sLogger.fatal(message, t); 289 break; 290 } 291 synchronized (iLog) { 292 iLog.add(m); 293 } 294 fireMessagePrinted(m); 295 } 296 297 /** Prints a message 298 * @param level logging level 299 * @param message message to log 300 **/ 301 public void message(int level, String message) { 302 message(level, message, null); 303 } 304 305 /** Prints a trace message 306 * @param message trace message 307 **/ 308 public void trace(String message) { 309 if (!sTraceEnabled) 310 return; 311 message(MSGLEVEL_TRACE, message); 312 } 313 314 /** Prints a debug message 315 * @param message debug message 316 **/ 317 public void debug(String message) { 318 message(MSGLEVEL_DEBUG, message); 319 } 320 321 /** Prints an info message 322 * @param message info message 323 **/ 324 public void info(String message) { 325 message(MSGLEVEL_INFO, message); 326 } 327 328 /** Prints a warning message 329 * @param message warning message 330 **/ 331 public void warn(String message) { 332 message(MSGLEVEL_WARN, message); 333 } 334 335 /** Prints an error message 336 * @param message error message 337 **/ 338 public void error(String message) { 339 message(MSGLEVEL_ERROR, message); 340 } 341 342 /** Prints a fatal message 343 * @param message fatal message 344 **/ 345 public void fatal(String message) { 346 message(MSGLEVEL_FATAL, message); 347 } 348 349 /** Prints a trace message 350 * @param message trace message 351 * @param e an exception, if any 352 **/ 353 public void trace(String message, Throwable e) { 354 if (!sTraceEnabled) 355 return; 356 message(MSGLEVEL_TRACE, message, e); 357 } 358 359 /** Prints a debug message 360 * @param message debug message 361 * @param e an exception, if any 362 **/ 363 public void debug(String message, Throwable e) { 364 message(MSGLEVEL_DEBUG, message, e); 365 } 366 367 /** Prints an info message 368 * @param message info message 369 * @param e an exception, if any 370 **/ 371 public void info(String message, Throwable e) { 372 message(MSGLEVEL_INFO, message, e); 373 } 374 375 /** Prints a warning message 376 * @param message warning message 377 * @param e an exception, if any 378 **/ 379 public void warn(String message, Throwable e) { 380 message(MSGLEVEL_WARN, message, e); 381 } 382 383 /** Prints an error message 384 * @param message error message 385 * @param e an exception, if any 386 **/ 387 public void error(String message, Throwable e) { 388 message(MSGLEVEL_ERROR, message, e); 389 } 390 391 /** Prints a fatal message 392 * @param message fatal message 393 * @param e an exception, if any 394 **/ 395 public void fatal(String message, Throwable e) { 396 message(MSGLEVEL_FATAL, message, e); 397 } 398 399 /** Returns log (list of messages) 400 * @return list of logged messages 401 **/ 402 public List<Message> getLog() { 403 return iLog; 404 } 405 406 /** 407 * Returns log (list of messages). Only messages with the given level or 408 * higher are included. 409 * @param level minimum level 410 * @return list of messages 411 */ 412 public String getLog(int level) { 413 StringBuffer sb = new StringBuffer(); 414 synchronized (iLog) { 415 for (Message m : iLog) { 416 String s = m.toString(level); 417 if (s != null) 418 sb.append(s + "\n"); 419 } 420 } 421 return sb.toString(); 422 } 423 424 /** Returns log in HTML format 425 * @param level minimum level 426 * @param includeDate include message date and time in the result 427 * @return html list of messages 428 */ 429 public String getHtmlLog(int level, boolean includeDate) { 430 StringBuffer sb = new StringBuffer(); 431 synchronized (iLog) { 432 for (Message m : iLog) { 433 String s = m.toHtmlString(level, includeDate); 434 if (s != null) 435 sb.append(s + "<br>"); 436 } 437 } 438 return sb.toString(); 439 } 440 441 /** 442 * Returns log in HTML format (only messages with the given level or higher 443 * are included) 444 * @param level minimum level 445 * @param includeDate include message date and time in the result 446 * @param fromStage last stage from which the log should begin 447 * @return html list of messages 448 */ 449 public String getHtmlLog(int level, boolean includeDate, String fromStage) { 450 StringBuffer sb = new StringBuffer(); 451 synchronized (iLog) { 452 for (Message m : iLog) { 453 if (m.getLevel() == MSGLEVEL_STAGE && m.getMessage().equals(fromStage)) 454 sb = new StringBuffer(); 455 String s = m.toHtmlString(level, includeDate); 456 if (s != null) 457 sb.append(s + "<br>"); 458 } 459 } 460 return sb.toString(); 461 } 462 463 /** Clear the log */ 464 public void clear() { 465 synchronized (iLog) { 466 iLog.clear(); 467 } 468 } 469 470 private void fireStatusChanged() { 471 for (ProgressListener listener : iListeners) { 472 listener.statusChanged(iStatus); 473 } 474 } 475 476 private void firePhaseChanged() { 477 for (ProgressListener listener : iListeners) { 478 listener.phaseChanged(iPhase); 479 } 480 } 481 482 private void fireProgressChanged() { 483 for (ProgressListener listener : iListeners) { 484 listener.progressChanged(iProgressCurrent, iProgressMax); 485 } 486 } 487 488 private void fireProgressSaved() { 489 for (ProgressListener listener : iListeners) { 490 listener.progressSaved(); 491 } 492 } 493 494 private void fireProgressRestored() { 495 for (ProgressListener listener : iListeners) { 496 listener.progressRestored(); 497 } 498 } 499 500 private void fireMessagePrinted(Message message) { 501 for (ProgressListener listener : iListeners) { 502 listener.progressMessagePrinted(message); 503 } 504 } 505 506 /** Log nessage */ 507 public static class Message implements Serializable { 508 private static final long serialVersionUID = 1L; 509 private int iLevel = 0; 510 private String iMessage; 511 private Date iDate = null; 512 private String[] iStakTrace = null; 513 514 private Message(int level, String message, Throwable e) { 515 iLevel = level; 516 iMessage = message; 517 iDate = new Date(); 518 if (e != null) { 519 StackTraceElement trace[] = e.getStackTrace(); 520 if (trace != null) { 521 iStakTrace = new String[trace.length + 1]; 522 iStakTrace[0] = e.getClass().getName() + ": " + e.getMessage(); 523 for (int i = 0; i < trace.length; i++) 524 iStakTrace[i + 1] = trace[i].getClassName() 525 + "." 526 + trace[i].getMethodName() 527 + (trace[i].getFileName() == null ? "" : "(" + trace[i].getFileName() 528 + (trace[i].getLineNumber() >= 0 ? ":" + trace[i].getLineNumber() : "") + ")"); 529 } 530 } 531 } 532 533 /** Creates message out of XML element 534 * @param element XML element with the message 535 **/ 536 public Message(Element element) { 537 iLevel = Integer.parseInt(element.attributeValue("level", "0")); 538 iMessage = element.attributeValue("msg"); 539 iDate = new Date(Long.parseLong(element.attributeValue("date", "0"))); 540 java.util.List<?> tr = element.elements("trace"); 541 if (tr != null && !tr.isEmpty()) { 542 iStakTrace = new String[tr.size()]; 543 for (int i = 0; i < tr.size(); i++) 544 iStakTrace[i] = ((Element) tr.get(i)).getText(); 545 } 546 } 547 548 /** Message 549 * @return message text 550 **/ 551 public String getMessage() { 552 return iMessage; 553 } 554 555 /** Debug level 556 * @return logging level 557 **/ 558 public int getLevel() { 559 return iLevel; 560 } 561 562 /** Time stamp 563 * @return message date and time 564 **/ 565 public Date getDate() { 566 return iDate; 567 } 568 569 /** Tracelog */ 570 private String getTraceLog() { 571 if (iStakTrace == null) 572 return ""; 573 StringBuffer ret = new StringBuffer("\n" + iStakTrace[0]); 574 for (int i = 1; i < iStakTrace.length; i++) 575 ret.append("\n at " + iStakTrace[i]); 576 return ret.toString(); 577 } 578 579 /** Tracelog as HTML */ 580 private String getHtmlTraceLog() { 581 if (iStakTrace == null) 582 return ""; 583 StringBuffer ret = new StringBuffer("<BR>" + iStakTrace[0]); 584 for (int i = 1; i < iStakTrace.length; i++) 585 ret.append("<BR> at " + iStakTrace[i]); 586 return ret.toString(); 587 } 588 589 /** 590 * String representation of the message (null if the message level is 591 * below the given level) 592 * @param level minimum level 593 * @return message log as string 594 */ 595 public String toString(int level) { 596 if (iLevel < level) 597 return null; 598 switch (iLevel) { 599 case MSGLEVEL_TRACE: 600 return sDF.format(iDate) + " -- " + iMessage + getTraceLog(); 601 case MSGLEVEL_DEBUG: 602 return sDF.format(iDate) + " -- " + iMessage + getTraceLog(); 603 case MSGLEVEL_PROGRESS: 604 return sDF.format(iDate) + " [" + iMessage + "]" + getTraceLog(); 605 case MSGLEVEL_INFO: 606 return sDF.format(iDate) + " " + iMessage + getTraceLog(); 607 case MSGLEVEL_STAGE: 608 return sDF.format(iDate) + " >>> " + iMessage + " <<<" + getTraceLog(); 609 case MSGLEVEL_WARN: 610 return sDF.format(iDate) + " WARNING: " + iMessage + getTraceLog(); 611 case MSGLEVEL_ERROR: 612 return sDF.format(iDate) + " ERROR: " + iMessage + getTraceLog(); 613 case MSGLEVEL_FATAL: 614 return sDF.format(iDate) + " >>>FATAL: " + iMessage + " <<<" + getTraceLog(); 615 } 616 return null; 617 } 618 619 /** String representation of the message */ 620 @Override 621 public String toString() { 622 return toString(MSGLEVEL_TRACE); 623 } 624 625 /** HTML representation of the message 626 * @param level level minimum level 627 * @param includeDate true if message date and time is to be included in the log 628 * @return message log as HTML 629 */ 630 public String toHtmlString(int level, boolean includeDate) { 631 if (iLevel < level) 632 return null; 633 switch (iLevel) { 634 case MSGLEVEL_TRACE: 635 return (includeDate ? sDF.format(iDate) : "") + " -- " + iMessage 636 + getHtmlTraceLog(); 637 case MSGLEVEL_DEBUG: 638 return (includeDate ? sDF.format(iDate) : "") + " -- " + iMessage + getHtmlTraceLog(); 639 case MSGLEVEL_PROGRESS: 640 return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog(); 641 case MSGLEVEL_INFO: 642 return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog(); 643 case MSGLEVEL_STAGE: 644 return "<br>" + (includeDate ? sDF.format(iDate) : "") + " <span style='font-weight:bold;'>" 645 + iMessage + "</span>" + getHtmlTraceLog(); 646 case MSGLEVEL_WARN: 647 return (includeDate ? sDF.format(iDate) : "") 648 + " <span style='color:orange;font-weight:bold;'>WARNING:</span> " + iMessage 649 + getHtmlTraceLog(); 650 case MSGLEVEL_ERROR: 651 return (includeDate ? sDF.format(iDate) : "") 652 + " <span style='color:red;font-weight:bold;'>ERROR:</span> " + iMessage 653 + getHtmlTraceLog(); 654 case MSGLEVEL_FATAL: 655 return (includeDate ? sDF.format(iDate) : "") 656 + " <span style='color:red;font-weight:bold;'>>>>FATAL: " + iMessage 657 + " <<<</span>" + getHtmlTraceLog(); 658 } 659 return null; 660 } 661 662 /** 663 * HTML representation of the message (null if the message level is 664 * below the given level) 665 * @param level level minimum level 666 * @return message log as HTML 667 */ 668 public String toHtmlString(int level) { 669 return toHtmlString(level, true); 670 } 671 672 /** HTML representation of the message 673 * @param includeDate true if message date and time is to be included in the log 674 * @return message log as HTML 675 */ 676 public String toHtmlString(boolean includeDate) { 677 return toHtmlString(MSGLEVEL_TRACE, includeDate); 678 } 679 680 /** HTML representation of the message 681 * @return message log as HTML 682 */ 683 public String toHtmlString() { 684 return toHtmlString(MSGLEVEL_TRACE, true); 685 } 686 687 /** Saves message into an XML element 688 * @param element an XML element to which the message is to be saved (as attributes) 689 **/ 690 public void save(Element element) { 691 element.addAttribute("level", String.valueOf(iLevel)); 692 element.addAttribute("msg", iMessage); 693 element.addAttribute("date", String.valueOf(iDate.getTime())); 694 if (iStakTrace != null) { 695 for (int i = 0; i < iStakTrace.length; i++) 696 element.addElement("trace").setText(iStakTrace[i]); 697 } 698 } 699 } 700 701 /** Saves the message log into the given XML element 702 * @param root XML root 703 **/ 704 public void save(Element root) { 705 Element log = root.addElement("log"); 706 synchronized (iLog) { 707 for (Message m : iLog) { 708 m.save(log.addElement("msg")); 709 } 710 } 711 } 712 713 /** Restores the message log from the given XML element 714 * @param root XML root 715 * @param clear clear the log first 716 **/ 717 public void load(Element root, boolean clear) { 718 synchronized (iLog) { 719 if (clear) 720 iLog.clear(); 721 Element log = root.element("log"); 722 if (log != null) { 723 for (Iterator<?> i = log.elementIterator("msg"); i.hasNext();) 724 iLog.add(new Message((Element) i.next())); 725 } 726 } 727 } 728}