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}