View Javadoc

1   /*
2    * $Id: OWLDelayedSyntaxChecker.java,v 1.17 2005/05/31 19:37:20 jlerner Exp $
3    *
4    * Copyright (c) 1999-2004, BBN Technologies, LLC.
5    * All rights reserved.
6    * http://www.daml.org/legal/opensource/bbn_license.html
7    */
8   
9   package com.bbn.swede.editor;
10  
11  import java.util.ArrayList;
12  import java.util.Timer;
13  import java.util.TimerTask;
14  
15  import org.eclipse.jface.text.DocumentEvent;
16  
17  /***
18   * Aggregates document events for later handling. The composite event built by
19   * this class is executed if either a certain period if time goes by without a
20   * new event coming in or a new event comes in that is not adjacent to the
21   * current composite event. Composite events are handled by registered delayed
22   * document event listeners.
23   * 
24   * @author rblace
25   * @author jlerner
26   * @see com.bbn.swede.editor.IDelayedDocumentEventListener
27   */
28  class OWLDelayedSyntaxChecker
29  {
30     private DocumentEvent _lastEvent;
31  
32     private Timer _docTimer = new Timer(true);
33  
34     private OWLEditorTimerTask _docTimerTask;
35  
36     private long _delay;
37  
38     private static final int I_DEFAULT_TIMER_DELAY = 2000;
39  
40     /***
41      * Creates a delayed syntax checker with the default timer delay.
42      */
43     public OWLDelayedSyntaxChecker()
44     {
45        _delay = I_DEFAULT_TIMER_DELAY;
46     }
47  
48     /***
49      * Creates a delayed syntax checker.
50      * 
51      * @param timerDelay The delay time for automatic composite event handling,
52      *        in milliseconds.
53      */
54     public OWLDelayedSyntaxChecker(long timerDelay)
55     {
56        _delay = timerDelay;
57     }
58  
59     private ArrayList _alListeners = new ArrayList();
60  
61     /***
62      * Adds an IDelayedDocumentEventListener to the delayed syntax checker.
63      * 
64      * @param ddel The listener to notify when delayed document events are fired
65      */
66     public void addDelayedDocumentEventListener(
67        IDelayedDocumentEventListener ddel)
68     {
69        _alListeners.add(ddel);
70     }
71  
72     /***
73      * Removes an IDelayedDocumentEventListener from the delayed syntax checker.
74      * If <code>ddel</code> is not registered as a listener, nothing happens.
75      * 
76      * @param ddel The listener to stop notifying when delayed document events
77      *        are fired
78      */
79     public void removeDelayedDocumentEventListener(
80        IDelayedDocumentEventListener ddel)
81     {
82        _alListeners.remove(ddel);
83     }
84  
85     /***
86      * Triggers composite document event handling whenever it is scheduled to
87      * run.
88      * 
89      * @see java.util.TimerTask
90      */
91     private class OWLEditorTimerTask extends TimerTask
92     {
93        private boolean _bFinished;
94  
95        public boolean cancel()
96        {
97           _bFinished = true;
98           return super.cancel();
99        }
100 
101       /***
102        * Indicates whether the timer task is has either finished running or
103        * has been cancelled.
104        * @return <code>true</code> if the task is no longer running, 
105        *         <code>false</code> if it is currently running or has not yet
106        *         started.
107        */
108       public boolean isFinished()
109       {
110          return _bFinished;
111       }
112 
113       public void run()
114       {
115          runSyntaxCheck();
116          _bFinished = true;
117       }
118    }
119 
120    private void dumpEvent(DocumentEvent de)
121    {
122       System.out.println("Offset: " + de.fOffset);
123       System.out.println("Deleted: " + de.fLength + " characters");
124       System.out.println("Inserted text: \"" + de.fText + "\"");
125    }
126 
127    /***
128     * Sends the current composite document event to registered listeners for
129     * handling.
130     */
131    private synchronized void runSyntaxCheck()
132    {
133       if(_lastEvent == null)
134       {
135          return;
136       }
137 
138       for(int i = 0; i < _alListeners.size(); i++)
139       {
140          IDelayedDocumentEventListener ddel = (IDelayedDocumentEventListener)_alListeners
141             .get(i);
142          ddel.handleEvent(_lastEvent);
143       }
144       _lastEvent = null;
145    }
146 
147    /***
148     * Resets the timer that controls automatic handling of composite events.
149     * 
150     *  
151     */
152    public void updateTimer()
153    {
154       //cancelTimer();
155       //schedule a new task
156       synchronized (_docTimer)
157       {
158          if((_docTimerTask == null) || _docTimerTask.isFinished())
159          {
160             _docTimerTask = new OWLEditorTimerTask();
161             _docTimer.schedule(_docTimerTask, _delay);
162          }
163       }
164    }
165 
166    /***
167     * Cancels the parse timer.
168     */
169    public void cancelTimer()
170    {
171       synchronized (_docTimer)
172       {
173          // if the timertask already exists, we have to cancel it
174          if(_docTimerTask != null)
175          {
176             _docTimerTask.cancel();
177          }
178       }
179    }
180 
181    /***
182     * Add a regular document event to the delayed syntax checker. If there is
183     * currently no composite event being built, <code>event</code> becomes the
184     * current composite event. If there is already a composite event and
185     * <code>event</code> is a deletion and/or insertion that is adjacent to
186     * it, it is merged into the composite event. If there is already a composite
187     * event and <code>event</code> is not adjacent to it, the composite event
188     * is dispatched immediately for handling and <code>event</code> becomes
189     * the new composite event.
190     * 
191     * @param event The event to be processed by the delayed syntax checker
192     */
193    public void addDocumentEvent(DocumentEvent event)
194    {
195       //new version of the event so our meddling isnt seen
196       DocumentEvent newEvent = new DocumentEvent();
197 
198       //stupid lack of copy constructor
199       newEvent.fDocument = event.fDocument;
200       newEvent.fLength = event.fLength;
201       newEvent.fOffset = event.fOffset;
202       //HACK: Eclipse gives null text for Ctrl+D line deletion events, so
203       //      we need to check for it here and substitute the empty string
204       newEvent.fText = (event.fText == null ? "" : event.fText);
205 
206       //  I would synchronize on _lastEvent, since that's the trouble - if events
207       // are coming
208       //  rapidly, code at the end of this method can alter the _lastEvent used
209       // in the beginning
210       //  and in other places. But since _lastEvent can sometimes legitimately
211       // (sp?) be
212       //  null, you can't always get a monitor on that.
213       synchronized (this)
214       {
215          if(_lastEvent != null)
216          {
217             //figure out if events are adjacent
218             int nStart = newEvent.getOffset();
219             int nEnd = nStart + newEvent.getLength();
220             int lStart = _lastEvent.getOffset();
221             int lEnd = lStart + _lastEvent.getText().length();
222 
223             if(!(nEnd < lStart || nStart > lEnd))
224             {
225                //events adjacent, combine them blickies
226 
227                //if it involves deletion
228                if(newEvent.getLength() > 0)
229                {
230                   //if last event involved inserting text
231                   if(_lastEvent.getText().length() > 0)
232                   {
233                      int newOffset = _lastEvent.fOffset;
234                      String newText = new String();
235                      int newLength = _lastEvent.fLength;
236 
237                      //adjust the offset if need be
238                      if(nStart < lStart)
239                      {
240                         newOffset = nStart;
241                      }
242 
243                      //if text is partially altered
244                      if(nStart < lEnd && nEnd > lStart)
245                      {
246                         //grab any leftover from the start
247                         if(nStart > lStart)
248                         {
249                            newText += _lastEvent.getText().substring(0,
250                               nStart - lStart);
251                         }
252 
253                         //leftovers from the end... mmmmm... string leftovers
254                         if(nEnd < lEnd)
255                         {
256                            newText += _lastEvent.getText().substring(
257                               _lastEvent.getText().length() - (lEnd - nEnd));
258                         }
259                      }
260                      else
261                      {
262                         //text stays intact
263                         newText = _lastEvent.getText();
264                      }
265 
266                      //figure out how much we're deleting
267                      newLength = _lastEvent.getLength() + newEvent.getLength()
268                         - (_lastEvent.getText().length() - newText.length());
269 
270                      //stick it all back
271                      _lastEvent.fLength = newLength;
272                      _lastEvent.fOffset = newOffset;
273                      _lastEvent.fText = newText;
274 
275                   }
276                   else
277                   {
278                      //add delete, adjust the offset if needed
279                      if(nStart < lStart)
280                      {
281                         _lastEvent.fOffset = nStart;
282                      }
283                      _lastEvent.fLength += newEvent.fLength;
284                   }
285                }
286 
287                //if it involves inserting new text, handle it now
288                if(newEvent.getText().length() > 0)
289                {
290                   String lastText = _lastEvent.getText();
291                   String newText;
292 
293                   //gotta refresh these
294                   lStart = _lastEvent.getOffset();
295                   lEnd = lStart + lastText.length();
296 
297                   newText = lastText.substring(0, nStart - lStart)
298                      + newEvent.getText() + lastText.substring(nStart - lStart);
299 
300                   _lastEvent.fText = newText;
301                }
302 
303                //check to see if the event has become empty
304                //and if it has, just get rid of it
305                if(_lastEvent.getText().length() <= 0
306                   && _lastEvent.getLength() <= 0)
307                {
308                   _lastEvent = null;
309                }
310 
311             }
312             else
313             {
314                //events not adjacent, execute current event
315                //and start compiling the next one
316                runSyntaxCheck();
317                _lastEvent = newEvent;
318             }
319 
320          }
321          //if there was no last event, insert this one
322          else
323          {
324             _lastEvent = newEvent;
325          }
326       }
327    }
328 
329    /***
330     * Forces the syntax checker to immediately run the current composite event.
331     */
332    public synchronized void forceRun()
333    {
334       runSyntaxCheck();
335       cancelTimer();
336    }
337 
338 }