1 module java.util.Timer;
2 
3 import java.lang.all;
4 import java.util.TimerTask;
5 import java.lang.Thread;
6 
7 version(Tango){
8     import tango.core.sync.Mutex;
9     import tango.core.sync.Condition;
10     import tango.text.convert.Format;
11 } else { // Phobos
12 }
13 
14 
15 class Timer {
16     private static final class TaskQueue {
17         version(Tango){
18             private Mutex mutex;
19             private Condition cond;
20         } else { // Phobos
21         }
22 
23 
24         private static const int DEFAULT_SIZE = 32;
25         private bool nullOnEmpty;
26         private TimerTask[] heap;
27         private int elements;
28         public this() {
29             version(Tango){
30                 mutex = new Mutex();
31                 cond = new Condition( mutex );
32                 heap = new TimerTask[DEFAULT_SIZE];
33                 elements = 0;
34                 nullOnEmpty = false;
35             } else { // Phobos
36                 implMissingInPhobos();
37             }
38         }
39 
40         private void add(TimerTask task) {
41             elements++;
42             if (elements is heap.length) {
43                 TimerTask[] new_heap = new TimerTask[heap.length * 2];
44                 System.arraycopy(heap, 0, new_heap, 0, cast(int)heap.length);
45                 heap = new_heap;
46             }
47             heap[elements] = task;
48         }
49 
50         private void remove() {
51             // clear the entry first
52             heap[elements] = null;
53             elements--;
54             if (elements + DEFAULT_SIZE / 2 <= (heap.length / 4)) {
55                 TimerTask[] new_heap = new TimerTask[heap.length / 2];
56                 System.arraycopy(heap, 0, new_heap, 0, elements + 1);
57                 heap = new_heap;
58             }
59         }
60 
61         public void enqueue(TimerTask task) {
62             version(Tango){
63                 synchronized( mutex ){
64                     if (heap is null) {
65                         throw new IllegalStateException("cannot enqueue when stop() has been called on queue");
66                     }
67 
68                     heap[0] = task;
69                     add(task);
70                     int child = elements;
71                     int parent = child / 2;
72                     while (heap[parent].scheduled > task.scheduled) {
73                         heap[child] = heap[parent];
74                         child = parent;
75                         parent = child / 2;
76                     }
77                     heap[child] = task;
78                     heap[0] = null;
79                     cond.notify();
80                 }
81             } else { // Phobos
82                 implMissingInPhobos();
83             }
84         }
85 
86         private TimerTask top() {
87             if (elements is 0) {
88                 return null;
89             }
90             else {
91                 return heap[1];
92             }
93         }
94 
95         public TimerTask serve() {
96             version(Tango){
97                 synchronized( mutex ){
98                     TimerTask task = null;
99                     while (task is null) {
100                         task = top();
101 
102                         if ((heap is null) || (task is null && nullOnEmpty)) {
103                             return null;
104                         }
105 
106                         if (task !is null) {
107                             // The time to wait until the task should be served
108                             long time = task.scheduled - System.currentTimeMillis();
109                             if (time > 0) {
110                                 // This task should not yet be served
111                                 // So wait until this task is ready
112                                 // or something else happens to the queue
113                                 task = null;  // set to null to make sure we call top()
114                                 try {
115                                     cond.wait(time);
116                                 }
117                                 catch (InterruptedException _) {
118                                 }
119                             }
120                         }
121                         else {
122                             // wait until a task is added
123                             // or something else happens to the queue
124                             try {
125                                 cond.wait();
126                             }
127                             catch (InterruptedException _) {
128                             }
129                         }
130                     }
131 
132                     TimerTask lastTask = heap[elements];
133                     remove();
134 
135                     int parent = 1;
136                     int child = 2;
137                     heap[1] = lastTask;
138                     while (child <= elements) {
139                         if (child < elements) {
140                             if (heap[child].scheduled > heap[child + 1].scheduled) {
141                                 child++;
142                             }
143                         }
144 
145                         if (lastTask.scheduled <= heap[child].scheduled)
146                             break;
147 
148                         heap[parent] = heap[child];
149                         parent = child;
150                         child = parent * 2;
151                     }
152 
153                     heap[parent] = lastTask;
154                     return task;
155                 }
156             } else { // Phobos
157                 implMissingInPhobos();
158                 return null;
159             }
160         }
161 
162         public void setNullOnEmpty(bool nullOnEmpty) {
163             version(Tango){
164                 synchronized( mutex ){
165                     this.nullOnEmpty = nullOnEmpty;
166                     cond.notify();
167                 }
168             } else { // Phobos
169                 implMissingInPhobos();
170             }
171         }
172 
173         public void stop() {
174             version(Tango){
175                 synchronized( mutex ){
176                     this.heap = null;
177                     this.elements = 0;
178                     cond.notify();
179                 }
180             } else { // Phobos
181                 implMissingInPhobos();
182             }
183         }
184 
185     }
186 
187     private static final class Scheduler : Runnable {
188         private TaskQueue queue;
189 
190         public this(TaskQueue queue) {
191             this.queue = queue;
192         }
193 
194         public void run() {
195             TimerTask task;
196             while ((task = queue.serve()) !is null) {
197                 if (task.scheduled >= 0) {
198                     task.lastExecutionTime = task.scheduled;
199                     if (task.period < 0) {
200                         task.scheduled = -1;
201                     }
202                     try {
203                         task.run();
204                     }
205                     //                     catch (ThreadDeath death) {
206                     //                         // If an exception escapes, the Timer becomes invalid.
207                     //                         queue.stop();
208                     //                         throw death;
209                     //                     }
210                     catch (Exception t) {
211                         queue.stop();
212                     }
213                 }
214                 if (task.scheduled >= 0) {
215                     if (task.fixed) {
216                         task.scheduled += task.period;
217                     }
218                     else {
219                         task.scheduled = task.period + System.currentTimeMillis();
220                     }
221 
222                     try {
223                         queue.enqueue(task);
224                     }
225                     catch (IllegalStateException ise) {
226                         // Ignore. Apparently the Timer queue has been stopped.
227                     }
228                 }
229             }
230         }
231     }
232 
233     private static int nr;
234     private TaskQueue queue;
235     private Scheduler scheduler;
236     private Thread thread;
237     private bool canceled;
238 
239     public this() {
240         this(false);
241     }
242 
243     public this(bool daemon) {
244         this(daemon, Thread.NORM_PRIORITY);
245     }
246 
247     private this(bool daemon, int priority) {
248         this(daemon, priority, Format( "Timer-{}", ++nr));
249     }
250 
251     private this(bool daemon, int priority, String name) {
252         canceled = false;
253         queue = new TaskQueue();
254         scheduler = new Scheduler(queue);
255         thread = new Thread(scheduler, name);
256         thread.setDaemon(daemon);
257         thread.setPriority(priority);
258         thread.start();
259     }
260 
261     public void cancel() {
262         canceled = true;
263         queue.stop();
264     }
265 
266     private void schedule(TimerTask task, long time, long period, bool fixed) {
267         if (time < 0)
268             throw new IllegalArgumentException("negative time");
269 
270         if (task.scheduled is 0 && task.lastExecutionTime is -1) {
271             task.scheduled = time;
272             task.period = period;
273             task.fixed = fixed;
274         }
275         else {
276             throw new IllegalStateException("task was already scheduled or canceled");
277         }
278 
279         if (!this.canceled && this.thread !is null) {
280             queue.enqueue(task);
281         }
282         else {
283             throw new IllegalStateException("timer was canceled or scheduler thread has died");
284         }
285     }
286 
287     private static void positiveDelay(long delay) {
288         if (delay < 0) {
289             throw new IllegalArgumentException("delay is negative");
290         }
291     }
292 
293     private static void positivePeriod(long period) {
294         if (period < 0) {
295             throw new IllegalArgumentException("period is negative");
296         }
297     }
298 
299     //     public void schedule(TimerTask task, Date date) {
300     //         long time = date.getTime();
301     //         schedule(task, time, -1, false);
302     //     }
303 
304     //     public void schedule(TimerTask task, Date date, long period) {
305     //         positivePeriod(period);
306     //         long time = date.getTime();
307     //         schedule(task, time, period, false);
308     //     }
309 
310     public void schedule(TimerTask task, long delay) {
311         positiveDelay(delay);
312         long time = System.currentTimeMillis() + delay;
313         schedule(task, time, -1, false);
314     }
315 
316     public void schedule(TimerTask task, long delay, long period)  {
317         positiveDelay(delay);
318         positivePeriod(period);
319         long time = System.currentTimeMillis() + delay;
320         schedule(task, time, period, false);
321     }
322 
323     //     public void scheduleAtFixedRate(TimerTask task, Date date, long period)  {
324     //         positivePeriod(period);
325     //         long time = date.getTime();
326     //         schedule(task, time, period, true);
327     //     }
328 
329     public void scheduleAtFixedRate(TimerTask task, long delay, long period)  {
330         positiveDelay(delay);
331         positivePeriod(period);
332         long time = System.currentTimeMillis() + delay;
333         schedule(task, time, period, true);
334     }
335 
336     protected void finalize() {
337         queue.setNullOnEmpty(true);
338     }
339 
340 
341     ///////////////////////////////////////////////////
342     /+    alias CircularList!( TimerTask ) ListType;
343 
344     private Thread thread;
345     private ListType schedules;
346     private Mutex mutex;
347     private Condition cond;
348     private bool isCanceled = false;
349 
350     this(){
351         this(false);
352     }
353     this(bool isDaemon){
354         mutex = new Mutex();
355         cond = new Condition( mutex );
356 
357         schedules = new ListType();
358         thread = new Thread( &run );
359         thread.setDaemon( isDaemon );
360         thread.start();
361     }
362     private void run(){
363 
364         while( !isCanceled ){
365             TimerTask timerTask = null;
366             synchronized(mutex){
367                 bool isReady = false;
368                 do{
369                     if( isCanceled ){
370                         return;
371                     }
372 
373                     if( schedules.size() is 0 ){
374                         cond.wait();
375                     }
376                     else{
377                         timerTask = schedules.head();
378                         TimeSpan toWait = timerTask.executionTime - Clock.now();
379                         if( toWait.interval() > 0 ){
380                             cond.wait( toWait.interval() );
381                         }
382                         else{
383                             schedules.removeHead();
384                             isReady = true;
385                         }
386                     }
387                 }while( !isReady );
388             }
389             if( timerTask ){
390                 timerTask.run();
391                 if( timerTask.period.millis > 0 ){
392                     timerTask.executionTime += timerTask.period;
393                     synchronized(mutex){
394                         int index = 0;
395                         foreach( tt; schedules ){
396                             if( tt.executionTime > timerTask.executionTime ){
397                                 break;
398                             }
399                             index++;
400                         }
401                         schedules.addAt( index, timerTask );
402                     }
403                 }
404             }
405         }
406     }
407     void   cancel(){
408         synchronized(mutex){
409             isCanceled = true;
410             cond.notifyAll();
411         }
412     }
413     void   schedule(TimerTask task, long delay){
414         scheduleAtFixedRate( task, delay, 0 );
415     }
416     void   scheduleAtFixedRate(TimerTask task, long delay, long period){
417         assert( task );
418         version(TANGOSVN){
419             task.executionTime = Clock.now + TimeSpan.fromMillis(delay);
420         } else {
421             task.executionTime = Clock.now + TimeSpan.millis(delay);
422         }
423         task.timer = this;
424         synchronized(mutex){
425             int index = 0;
426             if( schedules.size() > 0 )
427                 foreach( tt; schedules ){
428                     if( tt.executionTime > task.executionTime ){
429                         break;
430                     }
431                     index++;
432                 }
433             schedules.addAt( index, task );
434             cond.notifyAll();
435         }
436     }
437 
438     //     void   schedule(TimerTask task, Date time){}
439     //     void   schedule(TimerTask task, Date firstTime, long period){}
440     //     void   schedule(TimerTask task, long delay, long period){}
441     //     void   scheduleAtFixedRate(TimerTask task, Date firstTime, long period){}
442     +/
443 }
444 
445