001/*
002 * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
003 * Copyright (C) 2000-2010 The Gridarta Developers.
004 *
005 * This program is free software; you can redistribute it and/or modify
006 * it under the terms of the GNU General Public License as published by
007 * the Free Software Foundation; either version 2 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU General Public License for more details.
014 *
015 * You should have received a copy of the GNU General Public License along
016 * with this program; if not, write to the Free Software Foundation, Inc.,
017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
018 */
019
020package net.sf.gridarta.utils;
021
022import java.util.Timer;
023import java.util.TimerTask;
024import org.jetbrains.annotations.NotNull;
025
026/**
027 * Helper for reducing the number of change events: calls to {@link #change()}
028 * will be forwarded to {@link DelayedChangeListener#change()}. Forwarded calls
029 * are dropped if they would happen very quickly.
030 * @author Andreas Kirschbaum
031 */
032public class DelayedChangeManager {
033
034    /**
035     * The delay in millisecond the first forwarded event is delayed. This
036     * allows combining the first few events of a long series of events without
037     * delaying the delivery for a long time ({@link #notificationDelay}).
038     */
039    private final int initialDelay;
040
041    /**
042     * The interval in milliseconds events are forwarded during a long series of
043     * events.
044     */
045    private final int notificationDelay;
046
047    /**
048     * The {@link DelayedChangeListener} events are forwarded to.
049     */
050    @NotNull
051    private final DelayedChangeListener delayedChangeListener;
052
053    /**
054     * The {@link Timer} for delaying events.
055     */
056    @NotNull
057    private final Timer timer = new Timer();
058
059    /**
060     * A {@link TimerTask} that calls {@link State#timeout()} when the timer
061     * expires.
062     */
063    private class TimeoutTimerTask extends TimerTask {
064
065        @Override
066        public void run() {
067            state = state.timeout();
068        }
069
070    }
071
072    /**
073     * The states of the FSM.
074     */
075    private interface State {
076
077        /**
078         * Executes the event "change".
079         * @return the next state
080         */
081        @NotNull
082        State change();
083
084        /**
085         * Executes the event "finish".
086         * @return the next state
087         */
088        @NotNull
089        State finish();
090
091        /**
092         * Executes the event "timeout".
093         * @return the next state
094         */
095        @NotNull
096        State timeout();
097
098    }
099
100    /**
101     * The state "idle" of the FSM.
102     */
103    @NotNull
104    private final State idle = new State() {
105
106        @NotNull
107        @Override
108        public State change() {
109            timer.schedule(new TimeoutTimerTask(), initialDelay);
110            return pending;
111        }
112
113        @NotNull
114        @Override
115        public State finish() {
116            return idle;
117        }
118
119        @NotNull
120        @Override
121        public State timeout() {
122            assert false;
123            return idle;
124        }
125
126    };
127
128    /**
129     * The state "pending" of the FSM.
130     */
131    @NotNull
132    private final State pending = new State() {
133
134        @NotNull
135        @Override
136        public State change() {
137            return pending;
138        }
139
140        @NotNull
141        @Override
142        public State finish() {
143            delayedChangeListener.change();
144            return wait;
145        }
146
147        @NotNull
148        @Override
149        public State timeout() {
150            timer.schedule(new TimeoutTimerTask(), notificationDelay);
151            delayedChangeListener.change();
152            return wait;
153        }
154
155    };
156
157    /**
158     * The state "wait" of the FSM.
159     */
160    @NotNull
161    private final State wait = new State() {
162
163        @NotNull
164        @Override
165        public State change() {
166            return pending;
167        }
168
169        @NotNull
170        @Override
171        public State finish() {
172            return wait;
173        }
174
175        @NotNull
176        @Override
177        public State timeout() {
178            return idle;
179
180        }
181
182    };
183
184    /**
185     * The FSM's current state.
186     */
187    @NotNull
188    private State state = idle;
189
190    /**
191     * Creates a new instance.
192     * @param initialDelay the initial delay for a series of events in
193     * milliseconds
194     * @param notificationDelay the delay for following events in a series of
195     * events; in milliseconds
196     * @param delayedChangeListener the delayed change listener to forward
197     * events to
198     */
199    public DelayedChangeManager(final int initialDelay, final int notificationDelay, @NotNull final DelayedChangeListener delayedChangeListener) {
200        this.initialDelay = initialDelay;
201        this.notificationDelay = notificationDelay;
202        this.delayedChangeListener = delayedChangeListener;
203    }
204
205    /**
206     * Delivers a change event to the associated {@link DelayedChangeListener}.
207     */
208    public void change() {
209        state = state.change();
210    }
211
212    /**
213     * Finishes a series of {@link #change() change} events. Calling this
214     * function is optional: if it is called, a pending change event is
215     * immediately forwarded; otherwise when the notification timeout has
216     * expired.
217     */
218    public void finish() {
219        state = state.finish();
220    }
221
222}