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&lt;nrVariables;i++) {
027 * &nbsp;&nbsp;&nbsp;&nbsp;//load variable here
028 * &nbsp;&nbsp;&nbsp;&nbsp;Progress.getInstance().incProgress();
029 * }
030 * Progress.getInstance().setPhase("Creating constraints ...", nrConstraints);
031 * for (int i=0;i&lt;nrConstraints;i++) {
032 * &nbsp;&nbsp;&nbsp;&nbsp;//load constraint here
033 * &nbsp;&nbsp;&nbsp;&nbsp;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>&nbsp;&nbsp;&nbsp;&nbsp;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) : "") + " &nbsp;&nbsp;&nbsp;&nbsp;-- " + iMessage
642                            + getHtmlTraceLog();
643                case MSGLEVEL_DEBUG:
644                    return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;-- " + 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;'>&gt;&gt;&gt;FATAL: " + iMessage
663                            + " &lt;&lt;&lt;</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}