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            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    }