001    package net.sf.cpsolver.ifs.util;
002    
003    import java.io.Serializable;
004    import java.text.SimpleDateFormat;
005    import java.util.ArrayList;
006    import java.util.Date;
007    import java.util.HashMap;
008    import java.util.Iterator;
009    import java.util.List;
010    
011    import 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 Tomas Muller<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     */
059    public 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            iLog.add(m);
264            fireMessagePrinted(m);
265        }
266    
267        /** Prints a message */
268        public void message(int level, String message) {
269            message(level, message, null);
270        }
271    
272        /** Prints a trace message */
273        public void trace(String message) {
274            if (!sTraceEnabled)
275                return;
276            message(MSGLEVEL_TRACE, message);
277        }
278    
279        /** Prints a debug message */
280        public void debug(String message) {
281            message(MSGLEVEL_DEBUG, message);
282        }
283    
284        /** Prints an info message */
285        public void info(String message) {
286            message(MSGLEVEL_INFO, message);
287        }
288    
289        /** Prints a warning message */
290        public void warn(String message) {
291            message(MSGLEVEL_WARN, message);
292        }
293    
294        /** Prints an error message */
295        public void error(String message) {
296            message(MSGLEVEL_ERROR, message);
297        }
298    
299        /** Prints a fatal message */
300        public void fatal(String message) {
301            message(MSGLEVEL_FATAL, message);
302        }
303    
304        /** Prints a trace message */
305        public void trace(String message, Throwable e) {
306            if (!sTraceEnabled)
307                return;
308            message(MSGLEVEL_TRACE, message, e);
309        }
310    
311        /** Prints a debug message */
312        public void debug(String message, Throwable e) {
313            message(MSGLEVEL_DEBUG, message, e);
314        }
315    
316        /** Prints an info message */
317        public void info(String message, Throwable e) {
318            message(MSGLEVEL_INFO, message, e);
319        }
320    
321        /** Prints a warning message */
322        public void warn(String message, Throwable e) {
323            message(MSGLEVEL_WARN, message, e);
324        }
325    
326        /** Prints an error message */
327        public void error(String message, Throwable e) {
328            message(MSGLEVEL_ERROR, message, e);
329        }
330    
331        /** Prints a fatal message */
332        public void fatal(String message, Throwable e) {
333            message(MSGLEVEL_FATAL, message, e);
334        }
335    
336        /** Returns log (list of messages) */
337        public List<Message> getLog() {
338            return iLog;
339        }
340    
341        /**
342         * Returns log (list of messages). Only messages with the given level or
343         * higher are included.
344         */
345        public String getLog(int level) {
346            StringBuffer sb = new StringBuffer();
347            for (Message m : iLog) {
348                String s = m.toString(level);
349                if (s != null)
350                    sb.append(s + "\n");
351            }
352            return sb.toString();
353        }
354    
355        /** Returns log in HTML format */
356        public String getHtmlLog(int level, boolean includeDate) {
357            StringBuffer sb = new StringBuffer();
358            for (Message m : iLog) {
359                String s = m.toHtmlString(level, includeDate);
360                if (s != null)
361                    sb.append(s + "<br>");
362            }
363            return sb.toString();
364        }
365    
366        /**
367         * Returns log in HTML format (only messages with the given level or higher
368         * are included)
369         */
370        public String getHtmlLog(int level, boolean includeDate, String fromStage) {
371            StringBuffer sb = new StringBuffer();
372            for (Message m : iLog) {
373                if (m.getLevel() == MSGLEVEL_STAGE && m.getMessage().equals(fromStage))
374                    sb = new StringBuffer();
375                String s = m.toHtmlString(level, includeDate);
376                if (s != null)
377                    sb.append(s + "<br>");
378            }
379            return sb.toString();
380        }
381    
382        /** Clear the log */
383        public void clear() {
384            iLog.clear();
385        }
386    
387        private void fireStatusChanged() {
388            for (ProgressListener listener : iListeners) {
389                listener.statusChanged(iStatus);
390            }
391        }
392    
393        private void firePhaseChanged() {
394            for (ProgressListener listener : iListeners) {
395                listener.phaseChanged(iPhase);
396            }
397        }
398    
399        private void fireProgressChanged() {
400            for (ProgressListener listener : iListeners) {
401                listener.progressChanged(iProgressCurrent, iProgressMax);
402            }
403        }
404    
405        private void fireProgressSaved() {
406            for (ProgressListener listener : iListeners) {
407                listener.progressSaved();
408            }
409        }
410    
411        private void fireProgressRestored() {
412            for (ProgressListener listener : iListeners) {
413                listener.progressRestored();
414            }
415        }
416    
417        private void fireMessagePrinted(Message message) {
418            for (ProgressListener listener : iListeners) {
419                listener.progressMessagePrinted(message);
420            }
421        }
422    
423        /** Log nessage */
424        public static class Message implements Serializable {
425            private static final long serialVersionUID = 1L;
426            private int iLevel = 0;
427            private String iMessage;
428            private Date iDate = null;
429            private String[] iStakTrace = null;
430    
431            private Message(int level, String message, Throwable e) {
432                iLevel = level;
433                iMessage = message;
434                iDate = new Date();
435                if (e != null) {
436                    StackTraceElement trace[] = e.getStackTrace();
437                    if (trace != null) {
438                        iStakTrace = new String[trace.length + 1];
439                        iStakTrace[0] = e.getClass().getName() + ": " + e.getMessage();
440                        for (int i = 0; i < trace.length; i++)
441                            iStakTrace[i + 1] = trace[i].getClassName()
442                                    + "."
443                                    + trace[i].getMethodName()
444                                    + (trace[i].getFileName() == null ? "" : "(" + trace[i].getFileName()
445                                            + (trace[i].getLineNumber() >= 0 ? ":" + trace[i].getLineNumber() : "") + ")");
446                    }
447                }
448            }
449    
450            /** Creates message out of XML element */
451            public Message(Element element) {
452                iLevel = Integer.parseInt(element.attributeValue("level", "0"));
453                iMessage = element.attributeValue("msg");
454                iDate = new Date(Long.parseLong(element.attributeValue("date", "0")));
455                java.util.List<?> tr = element.elements("trace");
456                if (tr != null && !tr.isEmpty()) {
457                    iStakTrace = new String[tr.size()];
458                    for (int i = 0; i < tr.size(); i++)
459                        iStakTrace[i] = ((Element) tr.get(i)).getText();
460                }
461            }
462    
463            /** Message */
464            public String getMessage() {
465                return iMessage;
466            }
467    
468            /** Debug level */
469            public int getLevel() {
470                return iLevel;
471            }
472    
473            /** Time stamp */
474            public Date getDate() {
475                return iDate;
476            }
477    
478            /** Tracelog */
479            private String getTraceLog() {
480                if (iStakTrace == null)
481                    return "";
482                StringBuffer ret = new StringBuffer("\n" + iStakTrace[0]);
483                for (int i = 1; i < iStakTrace.length; i++)
484                    ret.append("\n    at " + iStakTrace[i]);
485                return ret.toString();
486            }
487    
488            /** Tracelog as HTML */
489            private String getHtmlTraceLog() {
490                if (iStakTrace == null)
491                    return "";
492                StringBuffer ret = new StringBuffer("<BR>" + iStakTrace[0]);
493                for (int i = 1; i < iStakTrace.length; i++)
494                    ret.append("<BR>&nbsp;&nbsp;&nbsp;&nbsp;at " + iStakTrace[i]);
495                return ret.toString();
496            }
497    
498            /**
499             * String representation of the message (null if the message level is
500             * below the given level)
501             */
502            public String toString(int level) {
503                if (iLevel < level)
504                    return null;
505                switch (iLevel) {
506                    case MSGLEVEL_TRACE:
507                        return sDF.format(iDate) + "    -- " + iMessage + getTraceLog();
508                    case MSGLEVEL_DEBUG:
509                        return sDF.format(iDate) + "  -- " + iMessage + getTraceLog();
510                    case MSGLEVEL_PROGRESS:
511                        return sDF.format(iDate) + " [" + iMessage + "]" + getTraceLog();
512                    case MSGLEVEL_INFO:
513                        return sDF.format(iDate) + " " + iMessage + getTraceLog();
514                    case MSGLEVEL_STAGE:
515                        return sDF.format(iDate) + " >>> " + iMessage + " <<<" + getTraceLog();
516                    case MSGLEVEL_WARN:
517                        return sDF.format(iDate) + " WARNING: " + iMessage + getTraceLog();
518                    case MSGLEVEL_ERROR:
519                        return sDF.format(iDate) + " ERROR: " + iMessage + getTraceLog();
520                    case MSGLEVEL_FATAL:
521                        return sDF.format(iDate) + " >>>FATAL: " + iMessage + " <<<" + getTraceLog();
522                }
523                return null;
524            }
525    
526            /** String representation of the message */
527            @Override
528            public String toString() {
529                return toString(MSGLEVEL_TRACE);
530            }
531    
532            /** HTML representation of the message */
533            public String toHtmlString(int level, boolean includeDate) {
534                if (iLevel < level)
535                    return null;
536                switch (iLevel) {
537                    case MSGLEVEL_TRACE:
538                        return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;&nbsp;&nbsp;-- " + iMessage
539                                + getHtmlTraceLog();
540                    case MSGLEVEL_DEBUG:
541                        return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;-- " + iMessage + getHtmlTraceLog();
542                    case MSGLEVEL_PROGRESS:
543                        return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog();
544                    case MSGLEVEL_INFO:
545                        return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog();
546                    case MSGLEVEL_STAGE:
547                        return "<br>" + (includeDate ? sDF.format(iDate) : "") + " <span style='font-weight:bold;'>"
548                                + iMessage + "</span>" + getHtmlTraceLog();
549                    case MSGLEVEL_WARN:
550                        return (includeDate ? sDF.format(iDate) : "")
551                                + " <span style='color:orange;font-weight:bold;'>WARNING:</span> " + iMessage
552                                + getHtmlTraceLog();
553                    case MSGLEVEL_ERROR:
554                        return (includeDate ? sDF.format(iDate) : "")
555                                + " <span style='color:red;font-weight:bold;'>ERROR:</span> " + iMessage
556                                + getHtmlTraceLog();
557                    case MSGLEVEL_FATAL:
558                        return (includeDate ? sDF.format(iDate) : "")
559                                + " <span style='color:red;font-weight:bold;'>&gt;&gt;&gt;FATAL: " + iMessage
560                                + " &lt;&lt;&lt;</span>" + getHtmlTraceLog();
561                }
562                return null;
563            }
564    
565            /**
566             * HTML representation of the message (null if the message level is
567             * below the given level)
568             */
569            public String toHtmlString(int level) {
570                return toHtmlString(level, true);
571            }
572    
573            /** HTML representation of the message */
574            public String toHtmlString(boolean includeDate) {
575                return toHtmlString(MSGLEVEL_TRACE, includeDate);
576            }
577    
578            /** HTML representation of the message */
579            public String toHtmlString() {
580                return toHtmlString(MSGLEVEL_TRACE, true);
581            }
582    
583            /** Saves message into an XML element */
584            public void save(Element element) {
585                element.addAttribute("level", String.valueOf(iLevel));
586                element.addAttribute("msg", iMessage);
587                element.addAttribute("date", String.valueOf(iDate.getTime()));
588                if (iStakTrace != null) {
589                    for (int i = 0; i < iStakTrace.length; i++)
590                        element.addElement("trace").setText(iStakTrace[i]);
591                }
592            }
593        }
594    
595        /** Saves the message log into the given XML element */
596        public void save(Element root) {
597            Element log = root.addElement("log");
598            for (Message m : iLog) {
599                m.save(log.addElement("msg"));
600            }
601        }
602    
603        /** Restores the message log from the given XML element */
604        public void load(Element root, boolean clear) {
605            if (clear)
606                iLog.clear();
607            Element log = root.element("log");
608            if (log != null) {
609                for (Iterator<?> i = log.elementIterator("msg"); i.hasNext();)
610                    iLog.add(new Message((Element) i.next()));
611            }
612        }
613    }