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    package com.chemaxon.calculations.common;
011    
012    import com.chemaxon.common.annotations.PublicAPI;
013    import com.google.common.annotations.Beta;
014    import java.beans.PropertyChangeEvent;
015    import java.beans.PropertyChangeListener;
016    import java.beans.PropertyChangeSupport;
017    
018    /**
019     * Utility functions for the creation of ProgressObservers.
020     *
021     * <p>Please note that this class is marked with @Beta annotation, so it can be subject of incompatible changes or
022     * removal in later releases.</p>
023     *
024     * @author Gabor Imre
025     */
026    @Beta
027    @PublicAPI
028    public final class ProgressObservers {
029    
030        /**
031         * No constructor for utility classes
032         */
033        private ProgressObservers() {}
034    
035        /**
036         * Returns a SubProgressObserver which ignores observed data.
037         *
038         * <p>
039         * API contract is still enforced, eg. {@link IllegalStateException} is thrown on API contract breaches from the
040         * observed code.</p>
041         *
042         * @param   taskName    Name of observed subtask
043         *
044         * @return  A ProgressObserver with no query functionality.
045         */
046        public static SubProgressObserver createNullObserver(String taskName) {
047            return new NullObserver();
048        }
049    
050        /**
051         * Returns a cancel observer which wont cancel execution.
052         *
053         * @return  A canceller which wont cancel
054         */
055        public static CancelObserver createNoCancelObserver() {
056            return new CancelObserver() {
057                public boolean isCancelled() {
058                    return false;
059                }
060    
061                public void addPropertyChangeListener(PropertyChangeListener listener) {
062                    // no event possibly fired, no listener stored
063                }
064    
065                public void removePropertyChangeListener(PropertyChangeListener listener) {
066                    // no event possivly fired, no listener stored
067                }
068            };
069        }
070    
071        /**
072         * Returns a forgiving observer which wont enforce API contracts.
073         *
074         * @return  A {@link SubProgressObserver} which wont enforce API contracts and wont cancel
075         */
076        public static SubProgressObserver createForgivingNullObserver() {
077            return new SubProgressObserver() {
078    
079                public void done() {
080                    // no api contract is enforced; no cancel signaled
081                }
082    
083                public void switchToDeterminate(long totalWork) {
084                    // no api contract is enforced; no cancel signaled
085                }
086    
087                public void worked(long work) {
088                    // no api contract is enforced; no cancel signaled
089                }
090    
091                public SubProgressObserver subTask(String name, long work) {
092                    return this;
093                }
094    
095                public boolean isCancelled() {
096                    return false;
097                }
098    
099                public void addPropertyChangeListener(PropertyChangeListener listener) {
100                    // no api contract is enforced; no cancel signaled
101                }
102    
103                public void removePropertyChangeListener(PropertyChangeListener listener) {
104                    // no api contract is enforced; no cancel signaled
105                }
106            };
107        }
108    
109        /**
110         * Returns a forgiving cancellable observer which wont enforce API contracts.
111         *
112         * <p>
113         * Note that current implementation polls cancelling upon method calls. If an observed code expects cancelling only
114         * through listener callbacks and wont call other methods (progress update) cancel event wont be generated.</p>
115         *
116         * @param canceller Underlying canceller polled at every operation
117         *
118         * @return A {@link SubProgressObserver} which wont enforce API contracts and propagates cancellinkg
119         */
120        public static SubProgressObserver createForgivingNullObserver(PollingCanceller canceller) {
121            final CancelObserverImpl cancelObserver = new CancelObserverImpl(canceller);
122            return new SubProgressObserver() {
123    
124                public void done() {
125                    // no api contract is enforced; no cancel signaled
126                }
127    
128                public void switchToDeterminate(long totalWork) {
129                    isCancelled();
130                }
131    
132                public void worked(long work) {
133                    isCancelled();
134                }
135    
136                public SubProgressObserver subTask(String name, long work) {
137                    isCancelled();
138                    return this;
139                }
140    
141                public boolean isCancelled() {
142                    return cancelObserver.isCancelled();
143                }
144    
145                public void addPropertyChangeListener(PropertyChangeListener listener) {
146                    cancelObserver.addPropertyChangeListener(listener);
147                }
148    
149                public void removePropertyChangeListener(PropertyChangeListener listener) {
150                    cancelObserver.removePropertyChangeListener(listener);
151                }
152            };
153        }
154    
155        /**
156         * A NullObserver which enforces API contracts.
157         */
158        private static final class NullObserver implements SubProgressObserver {
159    
160            /**
161             * States of an Observer.
162             */
163            private enum State {
164                /**
165                 * Initial state when the observer is indefinite, any amount of work can be reported.
166                 */
167                INDEFINITE,
168    
169                /**
170                 * At some point the remaining work is known.
171                 */
172                DEFINITE,
173    
174                /**
175                 * Observation is over, no more work will be reported,
176                 */
177                FINISHED;
178            }
179    
180            /**
181             * Is the total remaining work known.
182             */
183            private boolean totalWorkKnown = false;
184    
185            /**
186             * Current state.
187             */
188            private State state = State.INDEFINITE;
189    
190            /**
191             * Total remaining work when switched to determinate.
192             */
193            private long totalWork = 0;
194    
195            /**
196             * Work logged in {@link State#INDEFINITE} state.
197             */
198            private long workLogged = 0;
199    
200            @Override
201            public void switchToDeterminate(long totalWork) {
202                if (this.state != State.INDEFINITE) {
203                    throw new IllegalStateException(this.state.name());
204                }
205                if (totalWork <= 0) {
206                    throw new IllegalArgumentException("total work must be positive: " + totalWork);
207                }
208                this.state = State.DEFINITE;
209                this.totalWorkKnown = true;
210                this.totalWork = totalWork;
211            }
212    
213    
214            @Override
215            public void done() {
216                if (this.state == State.FINISHED) {
217                    throw new IllegalStateException(this.state.name());
218                }
219                this.state = State.FINISHED;
220            }
221    
222            @Override
223            public void worked(long work) {
224                if (this.state == State.FINISHED) {
225                    throw new IllegalStateException(this.state.name());
226                }
227                if (work <= 0) {
228                    throw new IllegalArgumentException("work must be positive: " + work);
229                }
230                if (this.totalWorkKnown) {
231                    this.workLogged += work;
232                    if (this.workLogged > this.totalWork) {
233                        throw new IllegalStateException("Work logged (" + this.workLogged + ") exceeds total work "
234                                + this.totalWork);
235                    }
236                }
237            }
238    
239            @Override
240            public SubProgressObserver subTask(String name, long work) {
241                if (this.state == State.FINISHED) {
242                    throw new IllegalStateException(this.state.name());
243                }
244                if (work <= 0) {
245                    throw new IllegalArgumentException("work must be positive: " + work);
246                }
247                if (this.totalWorkKnown) {
248                    this.workLogged += work;
249                    if (this.workLogged > this.totalWork) {
250                        throw new IllegalStateException("Work expected to be logged (" + this.workLogged
251                                + ") exceeds total work " + this.totalWork);
252                    }
253                }
254                return ProgressObservers.createNullObserver(name);
255            }
256    
257            @Override
258            public boolean isCancelled() {
259                return false;
260            }
261    
262            @Override
263            public void addPropertyChangeListener(PropertyChangeListener listener) {
264                // no api contract is enforced; no cancel signaled
265            }
266    
267            @Override
268            public void removePropertyChangeListener(PropertyChangeListener listener) {
269                // no api contract is enforced; no cancel signaled
270            }
271        }
272    
273        /**
274         * Interface exposing only a polling method to delegate cancel status events.
275         */
276        public interface PollingCanceller {
277    
278            /**
279             * Returns true when cancelled.
280             *
281             * @return True when cancel signed.
282             */
283            boolean isCancelled();
284        }
285    
286        /**
287         * Implementation of {@link CancelObserver}.
288         */
289        public static class CancelObserverImpl implements CancelObserver {
290    
291            /**
292             * Underlying polling canceller or <code>null</code>.
293             */
294            private final PollingCanceller poll;
295    
296            /**
297             * Represented cancel status.
298             */
299            private boolean cancelled = false;
300    
301            /**
302             * Handler for listeners.
303             */
304            private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
305    
306            /**
307             * Constructor for a cancellable observer implementation.
308             */
309            public CancelObserverImpl() {
310                this.poll = null;
311            }
312    
313            /**
314             * Constructor with an underlying polling source.
315             *
316             * @param sourceCanceller canceller to poll
317             */
318            public CancelObserverImpl(PollingCanceller sourceCanceller) {
319                this.poll = sourceCanceller;
320            }
321    
322            /**
323             * Sign cancelling.
324             *
325             * <p>Note that this implementation is not thread safe, so this method must be invoked from the thread
326             * created/using this instance.</p>
327             */
328            public void cancel() {
329                if (this.cancelled) {
330                    // already cancelled, do nothing
331                    return;
332                }
333                this.cancelled = true;
334                this.pcs.firePropertyChange(CancelObserver.FIRED_PROPERTY_NAME, false, true);
335            }
336    
337    
338            @Override
339            public boolean isCancelled() {
340                if (this.poll != null && this.poll.isCancelled()) {
341                    // store cancel status, fire events
342                    cancel();
343                }
344                return this.cancelled;
345            }
346    
347            @Override
348            public void addPropertyChangeListener(PropertyChangeListener listener) {
349                if (this.isCancelled()) {
350                    listener.propertyChange(new PropertyChangeEvent(this, CancelObserver.FIRED_PROPERTY_NAME, false, true));
351                } else {
352                    this.pcs.addPropertyChangeListener(listener);
353                }
354            }
355    
356            @Override
357            public void removePropertyChangeListener(PropertyChangeListener listener) {
358                this.pcs.removePropertyChangeListener(listener);
359            }
360    
361        }
362    
363    }