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 * @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>&nbsp;&nbsp;&nbsp;&nbsp;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) : "") + " &nbsp;&nbsp;&nbsp;&nbsp;-- " + iMessage
641                            + getHtmlTraceLog();
642                case MSGLEVEL_DEBUG:
643                    return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;-- " + 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;'>&gt;&gt;&gt;FATAL: " + iMessage
662                            + " &lt;&lt;&lt;</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}