001    /*
002     * Copyright (c) 1998-2014 ChemAxon Ltd. All Rights Reserved.
003     *
004     * This software is the confidential and proprietary information of
005     * ChemAxon. You shall not disclose such Confidential Information
006     * and shall use it only in accordance with the terms of the agreements
007     * you entered into with ChemAxon.
008     *
009     */
010    
011    package com.chemaxon.calculations.common;
012    
013    import com.google.common.annotations.Beta;
014    import java.beans.PropertyChangeListener;
015    import java.io.PrintStream;
016    import java.util.ArrayList;
017    import java.util.List;
018    
019    /**
020     * Stupid progressObserver to write to console.
021     *
022     * <p>Note that this implementation is incomplete, <b>subtasks are not supported</b>. Also API contracts are not
023     * enforced, thus a client code which works with this implementation might fail with a stricter implementation.</p>
024     *
025     * <p>Please note that this class is marked with {@link Beta} annotation, so it can be subject of incompatible changes
026     * or removal in later releases.</p>
027     *
028     * @author Gabor Imre
029     */
030    @Beta
031    public class ConsolePO implements SubProgressObserver {
032    
033        /**
034         * Underlying timer, measuring work unit completion speed.
035         */
036        private final Timer timer;
037    
038        /**
039         * Underlying autothrottle displaying time statistics.
040         */
041        private final AutoThrottle at;
042    
043        ///**
044        // * There is reported work unit without printout.
045        // */
046        //private boolean dirty;
047    
048        /**
049         * Total estimated work if determinate.
050         *
051         * <p>
052         * Negative value indicates indeterminate observer.</p>
053         */
054        private long totalWork = -1;
055    
056        /**
057         * Active sub progress observers.
058         */
059        private final List<ConsolePO> activeSubs = new ArrayList<ConsolePO>();
060    
061        /**
062         * Associate work units for active sub progress observers.
063         */
064        private final List<Long> activeSubWorks = new ArrayList<Long>();
065    
066        /**
067         * Parent progress observer or null for top level one.
068         */
069        private final ConsolePO parent;
070    
071        /**
072         * Sub observer was requested.
073         */
074        private boolean subRequested = false;
075    
076        /**
077         * Target delay.
078         */
079        private final long targetDelay;
080    
081        /**
082         * Console to print to.
083         */
084        private final PrintStream console;
085    
086        /**
087         * Construct a new instance.
088         *
089         * @param name          Name to display
090         * @param targetDelay   Delay for console update
091         */
092    
093        public ConsolePO(String name, long targetDelay) {
094            this(null, System.err, name, targetDelay);
095        }
096    
097        /**
098         * Construct a new instance.
099         *
100         * @param parent Parent observer or null for top level
101         * @param name Name to display
102         * @param targetDelay Delay for console update
103         */
104        ConsolePO(ConsolePO parent, PrintStream console, String name, long targetDelay) {
105            this.targetDelay = targetDelay;
106            this.at = new AutoThrottle(targetDelay, true);
107            this.parent = parent;
108            this.timer = new Timer(name);
109            //this.dirty = false;
110            this.console = console;
111        }
112    
113        @Override
114        public void switchToDeterminate(long totalWork) {
115            this.totalWork = totalWork;
116        }
117    
118        /**
119         * Report work unit completion.
120         *
121         * @param work Work units completed
122         * @param force If true then printout is forced regardless of underlying autothrottle status
123         */
124        public void worked(long work, boolean force) {
125            this.timer.invoke(work);
126            if (this.at.invocation() || force) {
127                printFromRoot();
128                //this.dirty = false;
129            } //else {
130            //    this.dirty = true;
131            // }
132        }
133    
134        @Override
135        public void worked(long work) {
136            worked(work, false);
137        }
138    
139        @Override
140        public SubProgressObserver subTask(String name, long work) {
141            final ConsolePO ret = new ConsolePO(this, this.console, name, this.targetDelay);
142            this.activeSubs.add(ret);
143            this.activeSubWorks.add(work);
144            this.subRequested = true;
145            return ret;
146        }
147    
148        /**
149         * Deregister sub progress observer.
150         *
151         * @param sub Sub which is finished
152         */
153        private void subFinished(ConsolePO sub) {
154            final int i = this.activeSubs.indexOf(sub);
155            if (i < 0) {
156                throw new IllegalStateException("Not found in active list " + sub.toString());
157            }
158            final long w = this.activeSubWorks.remove(i);
159            this.worked(w, true);
160            this.activeSubs.remove(i);
161        }
162    
163        @Override
164        public boolean isCancelled() {
165            return false;
166        }
167    
168        @Override
169        public void addPropertyChangeListener(PropertyChangeListener listener) {
170            // Empty
171        }
172    
173        @Override
174        public void removePropertyChangeListener(PropertyChangeListener listener) {
175            // Empty
176        }
177    
178        @Override
179        public void done() {
180            this.timer.stop();
181            //if (this.parent == null && this.dirty) {
182            if (this.parent == null) {
183                printFromRoot();
184            }
185            if (this.parent != null) {
186                // parent will print
187                this.parent.subFinished(this);
188            }
189        }
190    
191        /**
192         * Print line to console.
193         *
194         * <p>
195         * Print request will be propagated to the root observer.</p>
196         */
197        private void printFromRoot() {
198            if (this.parent == null) {
199                this.console.println(this.toString());
200            } else {
201                this.parent.printFromRoot();
202            }
203    
204        }
205    
206        @Override
207        public String toString() {
208            final StringBuilder ret = new StringBuilder();
209    
210            // Current timer as String
211            final String ts = this.timer.toString(!this.subRequested);
212    
213            ret.append(ts);
214    
215            if (this.totalWork > 0) {
216                final String invs = Timer.toSiPrefixedString(this.timer.getInvocationCount());
217                final String tots = Timer.toSiPrefixedString(this.totalWork);
218                ret.append(" (")
219                        .append(invs).append(" of ").append(tots).append("; ")
220                        .append(100 * this.timer.getInvocationCount() / this.totalWork).append(" %)");
221            }
222    
223            for (ConsolePO sub : activeSubs) {
224                ret.append(" | ");
225                ret.append(sub.toString());
226            }
227    
228            return ret.toString();
229        }
230    
231    }