    001package org.cpsolver.ifs.util;
    002
    003import java.io.Serializable;
    004import java.text.SimpleDateFormat;
    005import java.util.ArrayList;
    006import java.util.Date;
    007import java.util.HashMap;
    008import java.util.Iterator;
    009import java.util.List;
    010
    011import org.dom4j.Element;
    012
    013/**
    014 * Progress bar. <br>
    015 * <br>
    016 * Single instance class for recording the current state. It also allows
    017 * recursive storing/restoring of the progress.
    018 *
    019 * <br>
    020 * <br>
    021 * Use:
    022 * <pre>
    023 * <code>
    024 * Progress.getInstance().setStatus("Loading input data");
    025 * Progress.getInstance().setPhase("Creating variables ...", nrVariables);
    026 * for (int i=0;i&lt;nrVariables;i++) {
    027 * &nbsp;&nbsp;&nbsp;&nbsp;//load variable here
    028 * &nbsp;&nbsp;&nbsp;&nbsp;Progress.getInstance().incProgress();
    029 * }
    030 * Progress.getInstance().setPhase("Creating constraints ...", nrConstraints);
    031 * for (int i=0;i&lt;nrConstraints;i++) {
    032 * &nbsp;&nbsp;&nbsp;&nbsp;//load constraint here
    033 * &nbsp;&nbsp;&nbsp;&nbsp;Progress.getInstance().incProgress();
    034 * }
    035 * Progress.getInstance().setStatus("Solving problem");
    036 * ...
    037 * </code>
    038 * </pre>
    039 *
    040 * @author  Tomáš Müller
    041 * @version IFS 1.3 (Iterative Forward Search)<br>
    042 *          Copyright (C) 2006 - 2014 Tomáš Müller<br>
    043 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    044 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    045 * <br>
    046 *          This library is free software; you can redistribute it and/or modify
    047 *          it under the terms of the GNU Lesser General Public License as
    048 *          published by the Free Software Foundation; either version 3 of the
    049 *          License, or (at your option) any later version. <br>
    050 * <br>
    051 *          This library is distributed in the hope that it will be useful, but
    052 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    053 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    054 *          Lesser General Public License for more details. <br>
    055 * <br>
    056 *          You should have received a copy of the GNU Lesser General Public
    057 *          License along with this library; if not see
    058 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
    059 */
    060public class Progress {
    061    public static boolean sTraceEnabled = false;
    062    private static org.apache.logging.log4j.Logger sLogger = org.apache.logging.log4j.LogManager.getLogger(Progress.class);
    063    public static SimpleDateFormat sDF = new SimpleDateFormat("MM/dd/yy HH:mm:ss.SSS");
    064    public static final int MSGLEVEL_TRACE = 0;
    065    public static final int MSGLEVEL_DEBUG = 1;
    066    public static final int MSGLEVEL_PROGRESS = 2;
    067    public static final int MSGLEVEL_INFO = 3;
    068    public static final int MSGLEVEL_STAGE = 4;
    069    public static final int MSGLEVEL_WARN = 5;
    070    public static final int MSGLEVEL_ERROR = 6;
    071    public static final int MSGLEVEL_FATAL = 7;
    072
    073    private String iStatus = "";
    074    private String iPhase = "";
    075    private long iProgressMax = 0;
    076    private long iProgressCurrent = 0;
    077    private List<ProgressListener> iListeners = new ArrayList<ProgressListener>(5);
    078    private List<Object[]> iSave = new ArrayList<Object[]>(5);
    079    private List<Message> iLog = new ArrayList<Message>(1000);
    080    private boolean iDisposed = false;
    081
    082    private static HashMap<Object, Progress> sInstances = new HashMap<Object, Progress>();
    083
    084    private Progress() {
    085    }
    086
    087    /** Progress default instance
    088     * @return progress instance
    089     **/
    090    public static Progress getInstance() {
    091        return getInstance("--DEFAULT--");
    092    }
    093
    094    /** Progress instance
    095     * @param key an object (typically a problem model) for which the progress is to be returned
    096     * @return progress instance
    097     **/
    098    public static Progress getInstance(Object key) {
    099        Progress progress = sInstances.get(key);
    100        if (progress == null) {
    101            progress = new Progress();
    102            sInstances.put(key, progress);
    103        }
    104        return progress;
    105    }
    106
    107    /** Change progress instance for the given key
    108     * @param oldKey old instance
    109     * @param newKey new instance
    110     **/
    111    public static void changeInstance(Object oldKey, Object newKey) {
    112        removeInstance(newKey);
    113        Progress progress = sInstances.get(oldKey);
    114        if (progress != null) {
    115            sInstances.remove(oldKey);
    116            sInstances.put(newKey, progress);
    117        }
    118    }
    119
    120    /** Remove progress instance for the given key
    121     * @param key old instance
    122     **/
    123    public static void removeInstance(Object key) {
    124        Progress progress = sInstances.get(key);
    125        if (progress != null) {
    126            progress.iListeners.clear();
    127            progress.iDisposed = true;
    128            sInstances.remove(key);
    129        }
    130    }
    131
    132    /** Current status
    133     * @return current status
    134     **/
    135    public String getStatus() {
    136        return iStatus;
    137    }
    138
    139    /** Sets current status
    140     * @param status current status
    141     **/
    142    public void setStatus(String status) {
    143        message(MSGLEVEL_STAGE, status);
    144        if (!status.equals(iStatus)) {
    145            iPhase = "";
    146            iProgressMax = 0;
    147            iProgressCurrent = 0;
    148            iStatus = status;
    149            fireStatusChanged();
    150        }
    151    }
    152
    153    /** Current phase
    154     * @return current phase
    155     **/
    156    public String getPhase() {
    157        return iPhase;
    158    }
    159
    160    /**
    161     * Sets current phase
    162     *
    163     * @param phase
    164     *            phase name
    165     * @param progressMax
    166     *            maximum of progress bar
    167     */
    168    public void setPhase(String phase, long progressMax) {
    169        if (iSave.isEmpty() && !phase.equals(iPhase))
    170            message(MSGLEVEL_PROGRESS, phase);
    171        iPhase = phase;
    172        iProgressMax = progressMax;
    173        iProgressCurrent = 0;
    174        firePhaseChanged();
    175    }
    176
    177    /**
    178     * Sets current phase. Maximum of progress bar is set to 100.
    179     *
    180     * @param phase
    181     *            phase name
    182     */
    183    public void setPhase(String phase) {
    184        setPhase(phase, 100);
    185    }
    186
    187    /**
    188     * Update progress bar.
    189     *
    190     * @param progress
    191     *            progress between 0 and progressMax
    192     */
    193    public void setProgress(long progress) {
    194        if (iProgressCurrent != progress) {
    195            iProgressCurrent = progress;
    196            fireProgressChanged();
    197        }
    198    }
    199
    200    /** Current progress
    201     * @return current progress
    202     **/
    203    public long getProgress() {
    204        return iProgressCurrent;
    205    }
    206
    207    /** Maximum of current progress
    208     * @return current progress maximum
    209     **/
    210    public long getProgressMax() {
    211        return iProgressMax;
    212    }
    213
    214    /** Increment current progress */
    215    public void incProgress() {
    216        iProgressCurrent++;
    217        fireProgressChanged();
    218    }
    219
    220    /** Adds progress listener
    221     * @param listener a progress listener
    222     **/
    223    public void addProgressListener(ProgressListener listener) {
    224        iListeners.add(listener);
    225    }
    226
    227    /** Remove progress listener
    228     * @param listener a progress listener
    229     **/
    230    public void removeProgressListener(ProgressListener listener) {
    231        iListeners.remove(listener);
    232    }
    233
    234    /** Remove all progress listeners */
    235    public void clearProgressListeners() {
    236        iListeners.clear();
    237    }
    238
    239    /** Save current progress to the heap memory */
    240    public synchronized void save() {
    241        iSave.add(new Object[] { iStatus, iPhase, Long.valueOf(iProgressMax), Long.valueOf(iProgressCurrent) });
    242        fireProgressSaved();
    243    }
    244
    245    /** Resore the progress from the heap memory */
    246    public synchronized void restore() {
    247        if (iSave.isEmpty())
    248            return;
    249        Object[] o = iSave.get(iSave.size() - 1);
    250        iSave.remove(iSave.size() - 1);
    251        iStatus = (String) o[0];
    252        iPhase = (String) o[1];
    253        iProgressMax = ((Long) o[2]).longValue();
    254        iProgressCurrent = ((Long) o[3]).longValue();
    255        fireProgressRestored();
    256    }
    257
    258    /** Prints a message
    259     * @param level logging level
    260     * @param message message to log
    261     * @param t an exception, if any
    262     **/
    263    public void message(int level, String message, Throwable t) {
    264        if (iDisposed) throw new RuntimeException("This solver is killed.");
    265        Message m = new Message(level, message, t);
    266        switch (level) {
    267            case MSGLEVEL_TRACE:
    268                sLogger.debug("    -- " + message, t);
    269                break;
    270            case MSGLEVEL_DEBUG:
    271                sLogger.debug("  -- " + message, t);
    272                break;
    273            case MSGLEVEL_PROGRESS:
    274                sLogger.debug("[" + message + "]", t);
    275                break;
    276            case MSGLEVEL_INFO:
    277                sLogger.info(message, t);
    278                break;
    279            case MSGLEVEL_STAGE:
    280                sLogger.info("[" + message + "]", t);
    281                break;
    282            case MSGLEVEL_WARN:
    283                sLogger.warn(message, t);
    284                break;
    285            case MSGLEVEL_ERROR:
    286                sLogger.error(message, t);
    287                break;
    288            case MSGLEVEL_FATAL:
    289                sLogger.fatal(message, t);
    290                break;
    291        }
    292        synchronized (iLog) {
    293            iLog.add(m);
    294        }
    295        fireMessagePrinted(m);
    296    }
    297
    298    /** Prints a message
    299     * @param level logging level
    300     * @param message message to log
    301     **/
    302    public void message(int level, String message) {
    303        message(level, message, null);
    304    }
    305
    306    /** Prints a trace message
    307     * @param message trace message
    308     **/
    309    public void trace(String message) {
    310        if (!sTraceEnabled)
    311            return;
    312        message(MSGLEVEL_TRACE, message);
    313    }
    314
    315    /** Prints a debug message
    316     * @param message debug message
    317     **/
    318    public void debug(String message) {
    319        message(MSGLEVEL_DEBUG, message);
    320    }
    321
    322    /** Prints an info message
    323     * @param message info message
    324     **/
    325    public void info(String message) {
    326        message(MSGLEVEL_INFO, message);
    327    }
    328
    329    /** Prints a warning message
    330     * @param message warning message
    331     **/
    332    public void warn(String message) {
    333        message(MSGLEVEL_WARN, message);
    334    }
    335
    336    /** Prints an error message
    337     * @param message error message
    338     **/
    339    public void error(String message) {
    340        message(MSGLEVEL_ERROR, message);
    341    }
    342
    343    /** Prints a fatal message
    344     * @param message fatal message
    345     **/
    346    public void fatal(String message) {
    347        message(MSGLEVEL_FATAL, message);
    348    }
    349
    350    /** Prints a trace message
    351     * @param message trace message
    352     * @param e an exception, if any
    353     **/
    354    public void trace(String message, Throwable e) {
    355        if (!sTraceEnabled)
    356            return;
    357        message(MSGLEVEL_TRACE, message, e);
    358    }
    359
    360    /** Prints a debug message
    361     * @param message debug message
    362     * @param e an exception, if any
    363     **/
    364    public void debug(String message, Throwable e) {
    365        message(MSGLEVEL_DEBUG, message, e);
    366    }
    367
    368    /** Prints an info message
    369     * @param message info message
    370     * @param e an exception, if any
    371     **/
    372    public void info(String message, Throwable e) {
    373        message(MSGLEVEL_INFO, message, e);
    374    }
    375
    376    /** Prints a warning message
    377    * @param message warning message
    378    * @param e an exception, if any
    379    **/
    380    public void warn(String message, Throwable e) {
    381        message(MSGLEVEL_WARN, message, e);
    382    }
    383
    384    /** Prints an error message
    385     * @param message error message
    386     * @param e an exception, if any
    387     **/
    388    public void error(String message, Throwable e) {
    389        message(MSGLEVEL_ERROR, message, e);
    390    }
    391
    392    /** Prints a fatal message
    393     * @param message fatal message
    394     * @param e an exception, if any
    395     **/
    396    public void fatal(String message, Throwable e) {
    397        message(MSGLEVEL_FATAL, message, e);
    398    }
    399
    400    /** Returns log (list of messages)
    401     * @return list of logged messages
    402     **/
    403    public List<Message> getLog() {
    404        return iLog;
    405    }
    406
    407    /**
    408     * Returns log (list of messages). Only messages with the given level or
    409     * higher are included.
    410     * @param level minimum level
    411     * @return list of messages
    412     */
    413    public String getLog(int level) {
    414        StringBuffer sb = new StringBuffer();
    415        synchronized (iLog) {
    416            for (Message m : iLog) {
    417                String s = m.toString(level);
    418                if (s != null)
    419                    sb.append(s + "\n");
    420            }
    421        }
    422        return sb.toString();
    423    }
    424
    425    /** Returns log in HTML format
    426     * @param level minimum level
    427     * @param includeDate include message date and time in the result
    428     * @return html list of messages
    429     */
    430    public String getHtmlLog(int level, boolean includeDate) {
    431        StringBuffer sb = new StringBuffer();
    432        synchronized (iLog) {
    433            for (Message m : iLog) {
    434                String s = m.toHtmlString(level, includeDate);
    435                if (s != null)
    436                    sb.append(s + "<br>");
    437            }
    438        }
    439        return sb.toString();
    440    }
    441
    442    /**
    443     * Returns log in HTML format (only messages with the given level or higher
    444     * are included)
    445     * @param level minimum level
    446     * @param includeDate include message date and time in the result
    447     * @param fromStage last stage from which the log should begin
    448     * @return html list of messages
    449     */
    450    public String getHtmlLog(int level, boolean includeDate, String fromStage) {
    451        StringBuffer sb = new StringBuffer();
    452        synchronized (iLog) {
    453            for (Message m : iLog) {
    454                if (m.getLevel() == MSGLEVEL_STAGE && m.getMessage().equals(fromStage))
    455                    sb = new StringBuffer();
    456                String s = m.toHtmlString(level, includeDate);
    457                if (s != null)
    458                    sb.append(s + "<br>");
    459            }
    460        }
    461        return sb.toString();
    462    }
    463
    464    /** Clear the log */
    465    public void clear() {
    466        synchronized (iLog) {
    467            iLog.clear();
    468        }
    469    }
    470
    471    private void fireStatusChanged() {
    472        for (ProgressListener listener : iListeners) {
    473            listener.statusChanged(iStatus);
    474        }
    475    }
    476
    477    private void firePhaseChanged() {
    478        for (ProgressListener listener : iListeners) {
    479            listener.phaseChanged(iPhase);
    480        }
    481    }
    482
    483    private void fireProgressChanged() {
    484        for (ProgressListener listener : iListeners) {
    485            listener.progressChanged(iProgressCurrent, iProgressMax);
    486        }
    487    }
    488
    489    private void fireProgressSaved() {
    490        for (ProgressListener listener : iListeners) {
    491            listener.progressSaved();
    492        }
    493    }
    494
    495    private void fireProgressRestored() {
    496        for (ProgressListener listener : iListeners) {
    497            listener.progressRestored();
    498        }
    499    }
    500
    501    private void fireMessagePrinted(Message message) {
    502        for (ProgressListener listener : iListeners) {
    503            listener.progressMessagePrinted(message);
    504        }
    505    }
    506
    507    /** Log nessage */
    508    public static class Message implements Serializable {
    509        private static final long serialVersionUID = 1L;
    510        private int iLevel = 0;
    511        private String iMessage;
    512        private Date iDate = null;
    513        private String[] iStakTrace = null;
    514
    515        private Message(int level, String message, Throwable e) {
    516            iLevel = level;
    517            iMessage = message;
    518            iDate = new Date();
    519            if (e != null) {
    520                StackTraceElement trace[] = e.getStackTrace();
    521                if (trace != null) {
    522                    iStakTrace = new String[trace.length + 1];
    523                    iStakTrace[0] = e.getClass().getName() + ": " + e.getMessage();
    524                    for (int i = 0; i < trace.length; i++)
    525                        iStakTrace[i + 1] = trace[i].getClassName()
    526                                + "."
    527                                + trace[i].getMethodName()
    528                                + (trace[i].getFileName() == null ? "" : "(" + trace[i].getFileName()
    529                                        + (trace[i].getLineNumber() >= 0 ? ":" + trace[i].getLineNumber() : "") + ")");
    530                }
    531            }
    532        }
    533
    534        /** Creates message out of XML element
    535         * @param element XML element with the message
    536         **/
    537        public Message(Element element) {
    538            iLevel = Integer.parseInt(element.attributeValue("level", "0"));
    539            iMessage = element.attributeValue("msg");
    540            iDate = new Date(Long.parseLong(element.attributeValue("date", "0")));
    541            java.util.List<?> tr = element.elements("trace");
    542            if (tr != null && !tr.isEmpty()) {
    543                iStakTrace = new String[tr.size()];
    544                for (int i = 0; i < tr.size(); i++)
    545                    iStakTrace[i] = ((Element) tr.get(i)).getText();
    546            }
    547        }
    548
    549        /** Message
    550         * @return message text
    551         **/
    552        public String getMessage() {
    553            return iMessage;
    554        }
    555
    556        /** Debug level
    557         * @return logging level
    558         **/
    559        public int getLevel() {
    560            return iLevel;
    561        }
    562
    563        /** Time stamp
    564         * @return message date and time
    565         **/
    566        public Date getDate() {
    567            return iDate;
    568        }
    569
    570        /** Tracelog */
    571        private String getTraceLog() {
    572            if (iStakTrace == null)
    573                return "";
    574            StringBuffer ret = new StringBuffer("\n" + iStakTrace[0]);
    575            for (int i = 1; i < iStakTrace.length; i++)
    576                ret.append("\n    at " + iStakTrace[i]);
    577            return ret.toString();
    578        }
    579
    580        /** Tracelog as HTML */
    581        private String getHtmlTraceLog() {
    582            if (iStakTrace == null)
    583                return "";
    584            StringBuffer ret = new StringBuffer("<BR>" + iStakTrace[0]);
    585            for (int i = 1; i < iStakTrace.length; i++)
    586                ret.append("<BR>&nbsp;&nbsp;&nbsp;&nbsp;at " + iStakTrace[i]);
    587            return ret.toString();
    588        }
    589
    590        /** Tracelog */
    591        public String[] getTrace() {
    592            return iStakTrace;
    593        }
    594
    595        /**
    596         * String representation of the message (null if the message level is
    597         * below the given level)
    598         * @param level minimum level
    599         * @return message log as string
    600         */
    601        public String toString(int level) {
    602            if (iLevel < level)
    603                return null;
    604            switch (iLevel) {
    605                case MSGLEVEL_TRACE:
    606                    return sDF.format(iDate) + "    -- " + iMessage + getTraceLog();
    607                case MSGLEVEL_DEBUG:
    608                    return sDF.format(iDate) + "  -- " + iMessage + getTraceLog();
    609                case MSGLEVEL_PROGRESS:
    610                    return sDF.format(iDate) + " [" + iMessage + "]" + getTraceLog();
    611                case MSGLEVEL_INFO:
    612                    return sDF.format(iDate) + " " + iMessage + getTraceLog();
    613                case MSGLEVEL_STAGE:
    614                    return sDF.format(iDate) + " >>> " + iMessage + " <<<" + getTraceLog();
    615                case MSGLEVEL_WARN:
    616                    return sDF.format(iDate) + " WARNING: " + iMessage + getTraceLog();
    617                case MSGLEVEL_ERROR:
    618                    return sDF.format(iDate) + " ERROR: " + iMessage + getTraceLog();
    619                case MSGLEVEL_FATAL:
    620                    return sDF.format(iDate) + " >>>FATAL: " + iMessage + " <<<" + getTraceLog();
    621            }
    622            return null;
    623        }
    624
    625        /** String representation of the message */
    626        @Override
    627        public String toString() {
    628            return toString(MSGLEVEL_TRACE);
    629        }
    630
    631        /** HTML representation of the message
    632         * @param level level minimum level
    633         * @param includeDate true if message date and time is to be included in the log
    634         * @return message log as HTML
    635         */
    636        public String toHtmlString(int level, boolean includeDate) {
    637            if (iLevel < level)
    638                return null;
    639            switch (iLevel) {
    640                case MSGLEVEL_TRACE:
    641                    return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;&nbsp;&nbsp;-- " + iMessage
    642                            + getHtmlTraceLog();
    643                case MSGLEVEL_DEBUG:
    644                    return (includeDate ? sDF.format(iDate) : "") + " &nbsp;&nbsp;-- " + iMessage + getHtmlTraceLog();
    645                case MSGLEVEL_PROGRESS:
    646                    return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog();
    647                case MSGLEVEL_INFO:
    648                    return (includeDate ? sDF.format(iDate) : "") + " " + iMessage + getHtmlTraceLog();
    649                case MSGLEVEL_STAGE:
    650                    return "<br>" + (includeDate ? sDF.format(iDate) : "") + " <span style='font-weight:bold;'>"
    651                            + iMessage + "</span>" + getHtmlTraceLog();
    652                case MSGLEVEL_WARN:
    653                    return (includeDate ? sDF.format(iDate) : "")
    654                            + " <span style='color:#b85c00;font-weight:bold;'>WARNING:</span> " + iMessage
    655                            + getHtmlTraceLog();
    656                case MSGLEVEL_ERROR:
    657                    return (includeDate ? sDF.format(iDate) : "")
    658                            + " <span style='color:#ec0000;font-weight:bold;'>ERROR:</span> " + iMessage
    659                            + getHtmlTraceLog();
    660                case MSGLEVEL_FATAL:
    661                    return (includeDate ? sDF.format(iDate) : "")
    662                            + " <span style='color:#ec0000;font-weight:bold;'>&gt;&gt;&gt;FATAL: " + iMessage
    663                            + " &lt;&lt;&lt;</span>" + getHtmlTraceLog();
    664            }
    665            return null;
    666        }
    667
    668        /**
    669         * HTML representation of the message (null if the message level is
    670         * below the given level)
    671         * @param level level minimum level
    672         * @return message log as HTML
    673         */
    674        public String toHtmlString(int level) {
    675            return toHtmlString(level, true);
    676        }
    677
    678        /** HTML representation of the message
    679         * @param includeDate true if message date and time is to be included in the log
    680         * @return message log as HTML
    681         */
    682        public String toHtmlString(boolean includeDate) {
    683            return toHtmlString(MSGLEVEL_TRACE, includeDate);
    684        }
    685
    686        /** HTML representation of the message
    687         * @return message log as HTML
    688         */
    689        public String toHtmlString() {
    690            return toHtmlString(MSGLEVEL_TRACE, true);
    691        }
    692
    693        /** Saves message into an XML element
    694         * @param element an XML element to which the message is to be saved (as attributes)
    695         **/
    696        public void save(Element element) {
    697            element.addAttribute("level", String.valueOf(iLevel));
    698            element.addAttribute("msg", iMessage);
    699            element.addAttribute("date", String.valueOf(iDate.getTime()));
    700            if (iStakTrace != null) {
    701                for (int i = 0; i < iStakTrace.length; i++)
    702                    element.addElement("trace").setText(iStakTrace[i]);
    703            }
    704        }
    705    }
    706
    707    /** Saves the message log into the given XML element
    708     * @param root XML root
    709     **/
    710    public void save(Element root) {
    711        Element log = root.addElement("log");
    712        synchronized (iLog) {
    713            for (Message m : iLog) {
    714                m.save(log.addElement("msg"));
    715            }
    716        }
    717    }
    718
    719    /** Restores the message log from the given XML element
    720     * @param root XML root
    721     * @param clear clear the log first
    722     **/
    723    public void load(Element root, boolean clear) {
    724        synchronized (iLog) {
    725            if (clear)
    726                iLog.clear();
    727            Element log = root.element("log");
    728            if (log != null) {
    729                for (Iterator<?> i = log.elementIterator("msg"); i.hasNext();)
    730                    iLog.add(new Message((Element) i.next()));
    731            }
    732        }
    733    }
    734}