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    
015    /**
016     * Throttles interactive periodic events.
017     *
018     * <p>Invocation counts are represented as longs.</p>
019     *
020     * <p>This class is not thread safe.</p>
021     *
022     * <p>Please note that this class is marked with {@link Beta} annotation, so it can be subject of incompatible changes
023     * or removal in later releases.</p>
024     *
025     * @author Gabor Imre
026     */
027    @Beta
028    public class AutoThrottle {
029    
030        /**
031         * Current invocation interval.
032         */
033        private long invocationInterval;
034    
035        /**
036         * Invocation count since last display.
037         */
038        private long invocationCount;
039    
040        /**
041         * Time since last invocation.
042         */
043        private long lastTime;
044    
045        /**
046         * Target delay in ms.
047         */
048        private final long targetDelay;
049    
050        /**
051         * Construct an autothrottle which targets a time delay.
052         *
053         * @param targetDelayInMs Targeted time delay in ms. When 0 is passed every invocation will result in a suggested
054         * display. For negative numbers passed no display will happen.
055         * @param displayFirst      True when first invocation should display
056         */
057        public AutoThrottle(long targetDelayInMs, boolean displayFirst) {
058            this.targetDelay = targetDelayInMs;
059            this.invocationCount = 0;
060            this.invocationInterval = 1;
061            if (displayFirst) {
062                this.lastTime = -1;
063            } else {
064                this.lastTime = System.currentTimeMillis();
065            }
066        }
067    
068    
069        /**
070         * Sign an invocation to query next throttled event.
071         *
072         * @return  true when display suggested
073         */
074        public boolean invocation() {
075    
076            if (this.targetDelay == 0) {
077                return true;
078            }
079    
080            if (this.lastTime == -1) {
081                this.lastTime = System.currentTimeMillis();
082                this.invocationCount = 0;
083                return true;
084            }
085    
086            if (this.targetDelay < 0) {
087                return false;
088            }
089    
090            this.invocationCount++;
091    
092            if (this.invocationCount >= this.invocationInterval) {
093                final long currentTime = System.currentTimeMillis();
094                final long currentDelay = currentTime - this.lastTime;
095    
096                // Partition parameter space:
097                //  - Can not increase invocation interval
098                //      invocation count is 1 and 2 would run out of time
099                //  - Possible increase/decrease in invocation interval
100                //      invocation count is less than 20
101                //      decrease only if > 1
102                //      increase/decrease by 1
103                //  - Can calculate target invocation interval
104                //      otherwise calculate target delay
105                //      modify invocation interval only by a limited factor (50% - 200%)
106    
107                boolean e = false;
108    
109                if (this.invocationInterval == 1 && 2 * currentDelay > this.targetDelay) {
110                    // Can not increase invocation interval
111                    e = true;
112                } else if (this.invocationInterval < 20) {
113                    // increase/decrease by one
114                    if (currentDelay > this.targetDelay) {
115                        // delay is too big, make it faster if can
116                        e = true;
117                        if (this.invocationInterval > 1) {
118                            this.invocationInterval--;
119                        }
120                    } else {
121                        // deley is small, make it slower if possible
122                        if (currentDelay * (this.invocationInterval + 1.0) / this.invocationInterval <= this.targetDelay) {
123                            // we can make it slower
124                            this.invocationInterval ++;
125                        } else {
126                            // we can not make it slower
127                            e = true;
128                        }
129                    }
130                } else {
131                    // we can target desirable invocation interval, but modify at most 2x factor
132                    double f = (double) currentDelay / this.targetDelay;
133                    if (f > 2.0) {
134                        f = 2.0;
135                    }
136                    if (f < 0.5) {
137                        f = 0.5;
138                    }
139                    this.invocationInterval /= f;
140                }
141    
142                if (e) {
143                    this.invocationCount = 0;
144                    this.lastTime = currentTime;
145                    return true;
146                }
147    
148            }
149    
150            return false;
151        }
152    }