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}