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