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 Tomáš Müller<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 /** Tracelog */ 590 public String[] getTrace() { 591 return iStakTrace; 592 } 593 594 /** 595 * String representation of the message (null if the message level is 596 * below the given level) 597 * @param level minimum level 598 * @return message log as string 599 */ 600 public String toString(int level) { 601 if (iLevel < level) 602 return null; 603 switch (iLevel) { 604 case MSGLEVEL_TRACE: 605 return sDF.format(iDate) + " -- " + iMessage + getTraceLog(); 606 case MSGLEVEL_DEBUG: 607 return sDF.format(iDate) + " -- " + iMessage + getTraceLog(); 608 case MSGLEVEL_PROGRESS: 609 return sDF.format(iDate) + " [" + iMessage + "]" + getTraceLog(); 610 case MSGLEVEL_INFO: 611 return sDF.format(iDate) + " " + iMessage + getTraceLog(); 612 case MSGLEVEL_STAGE: 613 return sDF.format(iDate) + " >>> " + iMessage + " <<<" + getTraceLog(); 614 case MSGLEVEL_WARN: 615 return sDF.format(iDate) + " WARNING: " + iMessage + getTraceLog(); 616 case MSGLEVEL_ERROR: 617 return sDF.format(iDate) + " ERROR: " + iMessage + getTraceLog(); 618 case MSGLEVEL_FATAL: 619 return sDF.format(iDate) + " >>>FATAL: " + iMessage + " <<<" + getTraceLog(); 620 } 621 return null; 622 } 623 624 /** String representation of the message */ 625 @Override 626 public String toString() { 627 return toString(MSGLEVEL_TRACE); 628 } 629 630 /** HTML representation of the message 631 * @param level level minimum level 632 * @param includeDate true if message date and time is to be included in the log 633 * @return message log as HTML 634 */ 635 public String toHtmlString(int level, boolean includeDate) { 636 if (iLevel < level) 637 return null; 638 switch (iLevel) { 639 case MSGLEVEL_TRACE: 640 return (includeDate ? sDF.format(iDate) : "") + " -- " + iMessage 641 + getHtmlTraceLog(); 642 case MSGLEVEL_DEBUG: 643 return (includeDate ? sDF.format(iDate) : "") + " -- " + iMessage + getHtmlTraceLog(); 644 case MSGLEVEL_PROGRESS: 645 return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog(); 646 case MSGLEVEL_INFO: 647 return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog(); 648 case MSGLEVEL_STAGE: 649 return "<br>" + (includeDate ? sDF.format(iDate) : "") + " <span style='font-weight:bold;'>" 650 + iMessage + "</span>" + getHtmlTraceLog(); 651 case MSGLEVEL_WARN: 652 return (includeDate ? sDF.format(iDate) : "") 653 + " <span style='color:orange;font-weight:bold;'>WARNING:</span> " + iMessage 654 + getHtmlTraceLog(); 655 case MSGLEVEL_ERROR: 656 return (includeDate ? sDF.format(iDate) : "") 657 + " <span style='color:red;font-weight:bold;'>ERROR:</span> " + iMessage 658 + getHtmlTraceLog(); 659 case MSGLEVEL_FATAL: 660 return (includeDate ? sDF.format(iDate) : "") 661 + " <span style='color:red;font-weight:bold;'>>>>FATAL: " + iMessage 662 + " <<<</span>" + getHtmlTraceLog(); 663 } 664 return null; 665 } 666 667 /** 668 * HTML representation of the message (null if the message level is 669 * below the given level) 670 * @param level level minimum level 671 * @return message log as HTML 672 */ 673 public String toHtmlString(int level) { 674 return toHtmlString(level, true); 675 } 676 677 /** HTML representation of the message 678 * @param includeDate true if message date and time is to be included in the log 679 * @return message log as HTML 680 */ 681 public String toHtmlString(boolean includeDate) { 682 return toHtmlString(MSGLEVEL_TRACE, includeDate); 683 } 684 685 /** HTML representation of the message 686 * @return message log as HTML 687 */ 688 public String toHtmlString() { 689 return toHtmlString(MSGLEVEL_TRACE, true); 690 } 691 692 /** Saves message into an XML element 693 * @param element an XML element to which the message is to be saved (as attributes) 694 **/ 695 public void save(Element element) { 696 element.addAttribute("level", String.valueOf(iLevel)); 697 element.addAttribute("msg", iMessage); 698 element.addAttribute("date", String.valueOf(iDate.getTime())); 699 if (iStakTrace != null) { 700 for (int i = 0; i < iStakTrace.length; i++) 701 element.addElement("trace").setText(iStakTrace[i]); 702 } 703 } 704 } 705 706 /** Saves the message log into the given XML element 707 * @param root XML root 708 **/ 709 public void save(Element root) { 710 Element log = root.addElement("log"); 711 synchronized (iLog) { 712 for (Message m : iLog) { 713 m.save(log.addElement("msg")); 714 } 715 } 716 } 717 718 /** Restores the message log from the given XML element 719 * @param root XML root 720 * @param clear clear the log first 721 **/ 722 public void load(Element root, boolean clear) { 723 synchronized (iLog) { 724 if (clear) 725 iLog.clear(); 726 Element log = root.element("log"); 727 if (log != null) { 728 for (Iterator<?> i = log.elementIterator("msg"); i.hasNext();) 729 iLog.add(new Message((Element) i.next())); 730 } 731 } 732 } 733}