    001package net.sf.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 * <ul>
    023 * <code>
    024 * Progress.getInstance().setStatus("Loading input data");<br>
    025 * Progress.getInstance().setPhase("Creating variables ...", nrVariables);<br>
    026 * for (int i=0;i&lt;nrVariables;i++) {<br>
    027 * &nbsp;&nbsp;&nbsp;&nbsp;//load variable here<br>
    028 * &nbsp;&nbsp;&nbsp;&nbsp;Progress.getInstance().incProgress();<br>
    029 * }<br>
    030 * Progress.getInstance().setPhase("Creating constraints ...", nrConstraints);<br>
    031 * for (int i=0;i&lt;nrConstraints;i++) {<br>
    032 * &nbsp;&nbsp;&nbsp;&nbsp;//load constraint here<br>
    033 * &nbsp;&nbsp;&nbsp;&nbsp;Progress.getInstance().incProgress();<br>
    034 * }<br>
    035 * Progress.getInstance().setStatus("Solving problem");<br>
    036 * ...<br>
    037 * </code>
    038 * </ul>
    039 *
    040 * @version IFS 1.2 (Iterative Forward Search)<br>
    041 *          Copyright (C) 2006 - 2010 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    public static Progress getInstance() {
    088        return getInstance("--DEFAULT--");
    089    }
    090
    091    /** Progress instance */
    092    public static Progress getInstance(Object key) {
    093        Progress progress = sInstances.get(key);
    094        if (progress == null) {
    095            progress = new Progress();
    096            sInstances.put(key, progress);
    097        }
    098        return progress;
    099    }
    100
    101    /** Change progress instance for the given key */
    102    public static void changeInstance(Object oldKey, Object newKey) {
    103        removeInstance(newKey);
    104        Progress progress = sInstances.get(oldKey);
    105        if (progress != null) {
    106            sInstances.remove(oldKey);
    107            sInstances.put(newKey, progress);
    108        }
    109    }
    110
    111    /** Remove progress instance for the given key */
    112    public static void removeInstance(Object key) {
    113        Progress progress = sInstances.get(key);
    114        if (progress != null) {
    115            progress.iListeners.clear();
    116            progress.iDisposed = true;
    117            sInstances.remove(key);
    118        }
    119    }
    120
    121    /** Current status */
    122    public String getStatus() {
    123        return iStatus;
    124    }
    125
    126    /** Sets current status */
    127    public void setStatus(String status) {
    128        message(MSGLEVEL_STAGE, status);
    129        if (!status.equals(iStatus)) {
    130            iPhase = "";
    131            iProgressMax = 0;
    132            iProgressCurrent = 0;
    133            iStatus = status;
    134            fireStatusChanged();
    135        }
    136    }
    137
    138    /** Current phase */
    139    public String getPhase() {
    140        return iPhase;
    141    }
    142
    143    /**
    144     * Sets current phase
    145     *
    146     * @param phase
    147     *            phase name
    148     * @param progressMax
    149     *            maximum of progress bar
    150     */
    151    public void setPhase(String phase, long progressMax) {
    152        if (iSave.isEmpty() && !phase.equals(iPhase))
    153            message(MSGLEVEL_PROGRESS, phase);
    154        iPhase = phase;
    155        iProgressMax = progressMax;
    156        iProgressCurrent = 0;
    157        firePhaseChanged();
    158    }
    159
    160    /**
    161     * Sets current phase. Maximum of progress bar is set to 100.
    162     *
    163     * @param phase
    164     *            phase name
    165     */
    166    public void setPhase(String phase) {
    167        setPhase(phase, 100);
    168    }
    169
    170    /**
    171     * Update progress bar.
    172     *
    173     * @param progress
    174     *            progress between 0 and progressMax
    175     */
    176    public void setProgress(long progress) {
    177        if (iProgressCurrent != progress) {
    178            iProgressCurrent = progress;
    179            fireProgressChanged();
    180        }
    181    }
    182
    183    /** Current progress */
    184    public long getProgress() {
    185        return iProgressCurrent;
    186    }
    187
    188    /** Maximum of current progress */
    189    public long getProgressMax() {
    190        return iProgressMax;
    191    }
    192
    193    /** Increment current progress */
    194    public void incProgress() {
    195        iProgressCurrent++;
    196        fireProgressChanged();
    197    }
    198
    199    /** Adds progress listener */
    200    public void addProgressListener(ProgressListener listener) {
    201        iListeners.add(listener);
    202    }
    203
    204    /** Remove progress listener */
    205    public void removeProgressListener(ProgressListener listener) {
    206        iListeners.remove(listener);
    207    }
    208
    209    /** Remove all progress listeners */
    210    public void clearProgressListeners() {
    211        iListeners.clear();
    212    }
    213
    214    /** Save current progress to the heap memory */
    215    public synchronized void save() {
    216        iSave.add(new Object[] { iStatus, iPhase, new Long(iProgressMax), new Long(iProgressCurrent) });
    217        fireProgressSaved();
    218    }
    219
    220    /** Resore the progress from the heap memory */
    221    public synchronized void restore() {
    222        if (iSave.isEmpty())
    223            return;
    224        Object[] o = iSave.get(iSave.size() - 1);
    225        iSave.remove(iSave.size() - 1);
    226        iStatus = (String) o[0];
    227        iPhase = (String) o[1];
    228        iProgressMax = ((Long) o[2]).longValue();
    229        iProgressCurrent = ((Long) o[3]).longValue();
    230        fireProgressRestored();
    231    }
    232
    233    /** Prints a message */
    234    public void message(int level, String message, Throwable t) {
    235        if (iDisposed) throw new RuntimeException("This solver is killed.");
    236        Message m = new Message(level, message, t);
    237        switch (level) {
    238            case MSGLEVEL_TRACE:
    239                sLogger.debug("    -- " + message, t);
    240                break;
    241            case MSGLEVEL_DEBUG:
    242                sLogger.debug("  -- " + message, t);
    243                break;
    244            case MSGLEVEL_PROGRESS:
    245                sLogger.debug("[" + message + "]", t);
    246                break;
    247            case MSGLEVEL_INFO:
    248                sLogger.info(message, t);
    249                break;
    250            case MSGLEVEL_STAGE:
    251                sLogger.info("[" + message + "]", t);
    252                break;
    253            case MSGLEVEL_WARN:
    254                sLogger.warn(message, t);
    255                break;
    256            case MSGLEVEL_ERROR:
    257                sLogger.error(message, t);
    258                break;
    259            case MSGLEVEL_FATAL:
    260                sLogger.fatal(message, t);
    261                break;
    262        }
    263        synchronized (iLog) {
    264            iLog.add(m);
    265        }
    266        fireMessagePrinted(m);
    267    }
    268
    269    /** Prints a message */
    270    public void message(int level, String message) {
    271        message(level, message, null);
    272    }
    273
    274    /** Prints a trace message */
    275    public void trace(String message) {
    276        if (!sTraceEnabled)
    277            return;
    278        message(MSGLEVEL_TRACE, message);
    279    }
    280
    281    /** Prints a debug message */
    282    public void debug(String message) {
    283        message(MSGLEVEL_DEBUG, message);
    284    }
    285
    286    /** Prints an info message */
    287    public void info(String message) {
    288        message(MSGLEVEL_INFO, message);
    289    }
    290
    291    /** Prints a warning message */
    292    public void warn(String message) {
    293        message(MSGLEVEL_WARN, message);
    294    }
    295
    296    /** Prints an error message */
    297    public void error(String message) {
    298        message(MSGLEVEL_ERROR, message);
    299    }
    300
    301    /** Prints a fatal message */
    302    public void fatal(String message) {
    303        message(MSGLEVEL_FATAL, message);
    304    }
    305
    306    /** Prints a trace message */
    307    public void trace(String message, Throwable e) {
    308        if (!sTraceEnabled)
    309            return;
    310        message(MSGLEVEL_TRACE, message, e);
    311    }
    312
    313    /** Prints a debug message */
    314    public void debug(String message, Throwable e) {
    315        message(MSGLEVEL_DEBUG, message, e);
    316    }
    317
    318    /** Prints an info message */
    319    public void info(String message, Throwable e) {
    320        message(MSGLEVEL_INFO, message, e);
    321    }
    322
    323    /** Prints a warning message */
    324    public void warn(String message, Throwable e) {
    325        message(MSGLEVEL_WARN, message, e);
    326    }
    327
    328    /** Prints an error message */
    329    public void error(String message, Throwable e) {
    330        message(MSGLEVEL_ERROR, message, e);
    331    }
    332
    333    /** Prints a fatal message */
    334    public void fatal(String message, Throwable e) {
    335        message(MSGLEVEL_FATAL, message, e);
    336    }
    337
    338    /** Returns log (list of messages) */
    339    public List<Message> getLog() {
    340        return iLog;
    341    }
    342
    343    /**
    344     * Returns log (list of messages). Only messages with the given level or
    345     * higher are included.
    346     */
    347    public String getLog(int level) {
    348        StringBuffer sb = new StringBuffer();
    349        synchronized (iLog) {
    350            for (Message m : iLog) {
    351                String s = m.toString(level);
    352                if (s != null)
    353                    sb.append(s + "\n");
    354            }
    355        }
    356        return sb.toString();
    357    }
    358
    359    /** Returns log in HTML format */
    360    public String getHtmlLog(int level, boolean includeDate) {
    361        StringBuffer sb = new StringBuffer();
    362        synchronized (iLog) {
    363            for (Message m : iLog) {
    364                String s = m.toHtmlString(level, includeDate);
    365                if (s != null)
    366                    sb.append(s + "<br>");
    367            }
    368        }
    369        return sb.toString();
    370    }
    371
    372    /**
    373     * Returns log in HTML format (only messages with the given level or higher
    374     * are included)
    375     */
    376    public String getHtmlLog(int level, boolean includeDate, String fromStage) {
    377        StringBuffer sb = new StringBuffer();
    378        synchronized (iLog) {
    379            for (Message m : iLog) {
    380                if (m.getLevel() == MSGLEVEL_STAGE && m.getMessage().equals(fromStage))
    381                    sb = new StringBuffer();
    382                String s = m.toHtmlString(level, includeDate);
    383                if (s != null)
    384                    sb.append(s + "<br>");
    385            }
    386        }
    387        return sb.toString();
    388    }
    389
    390    /** Clear the log */
    391    public void clear() {
    392        synchronized (iLog) {
    393            iLog.clear();
    394        }
    395    }
    396
    397    private void fireStatusChanged() {
    398        for (ProgressListener listener : iListeners) {
    399            listener.statusChanged(iStatus);
    400        }
    401    }
    402
    403    private void firePhaseChanged() {
    404        for (ProgressListener listener : iListeners) {
    405            listener.phaseChanged(iPhase);
    406        }
    407    }
    408
    409    private void fireProgressChanged() {
    410        for (ProgressListener listener : iListeners) {
    411            listener.progressChanged(iProgressCurrent, iProgressMax);
    412        }
    413    }
    414
    415    private void fireProgressSaved() {
    416        for (ProgressListener listener : iListeners) {
    417            listener.progressSaved();
    418        }
    419    }
    420
    421    private void fireProgressRestored() {
    422        for (ProgressListener listener : iListeners) {
    423            listener.progressRestored();
    424        }
    425    }
    426
    427    private void fireMessagePrinted(Message message) {
    428        for (ProgressListener listener : iListeners) {
    429            listener.progressMessagePrinted(message);
    430        }
    431    }
    432
    433    /** Log nessage */
    434    public static class Message implements Serializable {
    435        private static final long serialVersionUID = 1L;
    436        private int iLevel = 0;
    437        private String iMessage;
    438        private Date iDate = null;
    439        private String[] iStakTrace = null;
    440
    441        private Message(int level, String message, Throwable e) {
    442            iLevel = level;
    443            iMessage = message;
    444            iDate = new Date();
    445            if (e != null) {
    446                StackTraceElement trace[] = e.getStackTrace();
    447                if (trace != null) {
    448                    iStakTrace = new String[trace.length + 1];
    449                    iStakTrace[0] = e.getClass().getName() + ": " + e.getMessage();
    450                    for (int i = 0; i < trace.length; i++)
    451                        iStakTrace[i + 1] = trace[i].getClassName()
    452                                + "."
    453                                + trace[i].getMethodName()
    454                                + (trace[i].getFileName() == null ? "" : "(" + trace[i].getFileName()
    455                                        + (trace[i].getLineNumber() >= 0 ? ":" + trace[i].getLineNumber() : "") + ")");
    456                }
    457            }
    458        }
    459
    460        /** Creates message out of XML element */
    461        public Message(Element element) {
    462            iLevel = Integer.parseInt(element.attributeValue("level", "0"));
    463            iMessage = element.attributeValue("msg");
    464            iDate = new Date(Long.parseLong(element.attributeValue("date", "0")));
    465            java.util.List<?> tr = element.elements("trace");
    466            if (tr != null && !tr.isEmpty()) {
    467                iStakTrace = new String[tr.size()];
    468                for (int i = 0; i < tr.size(); i++)
    469                    iStakTrace[i] = ((Element) tr.get(i)).getText();
    470            }
    471        }
    472
    473        /** Message */
    474        public String getMessage() {
    475            return iMessage;
    476        }
    477
    478        /** Debug level */
    479        public int getLevel() {
    480            return iLevel;
    481        }
    482
    483        /** Time stamp */
    484        public Date getDate() {
    485            return iDate;
    486        }
    487
    488        /** Tracelog */
    489        private String getTraceLog() {
    490            if (iStakTrace == null)
    491                return "";
    492            StringBuffer ret = new StringBuffer("\n" + iStakTrace[0]);
    493            for (int i = 1; i < iStakTrace.length; i++)
    494                ret.append("\n    at " + iStakTrace[i]);
    495            return ret.toString();
    496        }
    497
    498        /** Tracelog as HTML */
    499        private String getHtmlTraceLog() {
    500            if (iStakTrace == null)
    501                return "";
    502            StringBuffer ret = new StringBuffer("<BR>" + iStakTrace[0]);
    503            for (int i = 1; i < iStakTrace.length; i++)
    504                ret.append("<BR>&nbsp;&nbsp;&nbsp;&nbsp;at " + iStakTrace[i]);
    505            return ret.toString();
    506        }
    507
    508        /**
    509         * String representation of the message (null if the message level is
    510         * below the given level)
    511         */
    512        public String toString(int level) {
    513            if (iLevel < level)
    514                return null;
    515            switch (iLevel) {
    516                case MSGLEVEL_TRACE:
    517                    return sDF.format(iDate) + "    -- " + iMessage + getTraceLog();
    518                case MSGLEVEL_DEBUG:
    519                    return sDF.format(iDate) + "  -- " + iMessage + getTraceLog();
    520                case MSGLEVEL_PROGRESS:
    521                    return sDF.format(iDate) + " [" + iMessage + "]" + getTraceLog();
    522                case MSGLEVEL_INFO:
    523                    return sDF.format(iDate) + " " + iMessage + getTraceLog();
    524                case MSGLEVEL_STAGE:
    525                    return sDF.format(iDate) + " >>> " + iMessage + " <<<" + getTraceLog();
    526                case MSGLEVEL_WARN:
    527                    return sDF.format(iDate) + " WARNING: " + iMessage + getTraceLog();
    528                case MSGLEVEL_ERROR:
    529                    return sDF.format(iDate) + " ERROR: " + iMessage + getTraceLog();
    530                case MSGLEVEL_FATAL:
    531                    return sDF.format(iDate) + " >>>FATAL: " + iMessage + " <<<" + getTraceLog();
    532            }
    533            return null;
    534        }
    535
    536        /** String representation of the message */
    537        @Override
    538        public String toString() {
    539            return toString(MSGLEVEL_TRACE);
    540        }
    541
    542        /** HTML representation of the message */
    543        public String toHtmlString(int level, boolean includeDate) {
    544            if (iLevel < level)
    545                return null;
    546            switch (iLevel) {
    547                case MSGLEVEL_TRACE:
    548                    return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;&nbsp;&nbsp;-- " + iMessage
    549                            + getHtmlTraceLog();
    550                case MSGLEVEL_DEBUG:
    551                    return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;-- " + iMessage + getHtmlTraceLog();
    552                case MSGLEVEL_PROGRESS:
    553                    return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog();
    554                case MSGLEVEL_INFO:
    555                    return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog();
    556                case MSGLEVEL_STAGE:
    557                    return "<br>" + (includeDate ? sDF.format(iDate) : "") + " <span style='font-weight:bold;'>"
    558                            + iMessage + "</span>" + getHtmlTraceLog();
    559                case MSGLEVEL_WARN:
    560                    return (includeDate ? sDF.format(iDate) : "")
    561                            + " <span style='color:orange;font-weight:bold;'>WARNING:</span> " + iMessage
    562                            + getHtmlTraceLog();
    563                case MSGLEVEL_ERROR:
    564                    return (includeDate ? sDF.format(iDate) : "")
    565                            + " <span style='color:red;font-weight:bold;'>ERROR:</span> " + iMessage
    566                            + getHtmlTraceLog();
    567                case MSGLEVEL_FATAL:
    568                    return (includeDate ? sDF.format(iDate) : "")
    569                            + " <span style='color:red;font-weight:bold;'>&gt;&gt;&gt;FATAL: " + iMessage
    570                            + " &lt;&lt;&lt;</span>" + getHtmlTraceLog();
    571            }
    572            return null;
    573        }
    574
    575        /**
    576         * HTML representation of the message (null if the message level is
    577         * below the given level)
    578         */
    579        public String toHtmlString(int level) {
    580            return toHtmlString(level, true);
    581        }
    582
    583        /** HTML representation of the message */
    584        public String toHtmlString(boolean includeDate) {
    585            return toHtmlString(MSGLEVEL_TRACE, includeDate);
    586        }
    587
    588        /** HTML representation of the message */
    589        public String toHtmlString() {
    590            return toHtmlString(MSGLEVEL_TRACE, true);
    591        }
    592
    593        /** Saves message into an XML element */
    594        public void save(Element element) {
    595            element.addAttribute("level", String.valueOf(iLevel));
    596            element.addAttribute("msg", iMessage);
    597            element.addAttribute("date", String.valueOf(iDate.getTime()));
    598            if (iStakTrace != null) {
    599                for (int i = 0; i < iStakTrace.length; i++)
    600                    element.addElement("trace").setText(iStakTrace[i]);
    601            }
    602        }
    603    }
    604
    605    /** Saves the message log into the given XML element */
    606    public void save(Element root) {
    607        Element log = root.addElement("log");
    608        synchronized (iLog) {
    609            for (Message m : iLog) {
    610                m.save(log.addElement("msg"));
    611            }
    612        }
    613    }
    614
    615    /** Restores the message log from the given XML element */
    616    public void load(Element root, boolean clear) {
    617        synchronized (iLog) {
    618            if (clear)
    619                iLog.clear();
    620            Element log = root.element("log");
    621            if (log != null) {
    622                for (Iterator<?> i = log.elementIterator("msg"); i.hasNext();)
    623                    iLog.add(new Message((Element) i.next()));
    624            }
    625        }
    626    }
    627}