    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}