PageRenderTime 170ms CodeModel.GetById 143ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/EditBus.java

#
Java | 493 lines | 258 code | 47 blank | 188 comment | 35 complexity | 10005e2b4a3df4a6e95d24447d2d083e MD5 | raw file
  1/*
  2 * EditBus.java - The EditBus
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 1999 Slava Pestov
  7 *
  8 * This program is free software; you can redistribute it and/or
  9 * modify it under the terms of the GNU General Public License
 10 * as published by the Free Software Foundation; either version 2
 11 * of the License, or any later version.
 12 *
 13 * This program is distributed in the hope that it will be useful,
 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 16 * GNU General Public License for more details.
 17 *
 18 * You should have received a copy of the GNU General Public License
 19 * along with this program; if not, write to the Free Software
 20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 21 */
 22
 23package org.gjt.sp.jedit;
 24
 25import java.awt.*;
 26import java.lang.annotation.*;
 27import java.lang.reflect.Method;
 28import java.lang.reflect.InvocationTargetException;
 29import java.util.*;
 30import java.util.List;
 31
 32import org.gjt.sp.util.Log;
 33
 34/**
 35 * jEdit's global event notification mechanism.<p>
 36 *
 37 * Plugins register with the EditBus to receive messages reflecting
 38 * changes in the application's state, including changes in buffers,
 39 * views and edit panes, changes in the set of properties maintained
 40 * by the application, and the closing of the application.<p>
 41 *
 42 * The EditBus maintains a list of objects that have requested to receive
 43 * messages. When a message is sent using this class, all registered
 44 * components receive it in turn. Classes for objects that subscribe to
 45 * the EditBus must implement the {@link EBComponent} interface, which
 46 * defines the single method {@link EBComponent#handleMessage(EBMessage)}.<p>
 47 *
 48 * Alternatively, since jEdit4.3pre19, EditBus components can be any
 49 * object. Handlers for EditBus messages are created by annotating
 50 * methods with the {@link EBHandler} annotation. Such methods should
 51 * expect a single parameter - an edit bus message of any desired type.
 52 * If a message matching the type (or any of its super-types, unless the
 53 * annotation requests exact type matching) is being sent, the annotated
 54 * method will be called instead of the default {@link
 55 * EBComponent#handleMessage(EBMessage)}. If a handler exists for a
 56 * specific message type, the default handler will not be called.<p>
 57 *
 58 * A plugin core class that extends the
 59 * {@link EBPlugin} abstract class (and whose name ends with
 60 * <code>Plugin</code> for identification purposes) will automatically be
 61 * added to the EditBus during jEdit's startup routine.  Any other
 62 * class - for example, a dockable window that needs to receive
 63 * notification of buffer changes - must perform its own registration by calling
 64 * {@link #addToBus(Object)} during its initialization.
 65 * A convenient place to register in a class derived from <code>JComponent</code>
 66 * would be in an implementation of the <code>JComponent</code> method
 67 * <code>addNotify()</code>.<p>
 68 *
 69 * Message types sent by jEdit can be found in the
 70 * {@link org.gjt.sp.jedit.msg} package.<p>
 71 *
 72 * Plugins can also send their own messages - any object can send a message to
 73 * the EditBus by calling the static method {@link #send(EBMessage)}.
 74 * Most plugins, however, only concern themselves with receiving, not
 75 * sending, messages.
 76 *
 77 * @see org.gjt.sp.jedit.EBComponent
 78 * @see org.gjt.sp.jedit.EBMessage
 79 *
 80 * @author Slava Pestov
 81 * @author John Gellene (API documentation)
 82 * @version $Id: EditBus.java 20015 2011-09-24 11:56:48Z kpouer $
 83 *
 84 * @since jEdit 2.2pre6
 85 */
 86public class EditBus
 87{
 88
 89	//{{{ EBHandler annotation
 90	/**
 91	 * This annotation should be used in methods that are to be
 92	 * considered "edit bus message handlers". When registering
 93	 * an object using {@link #addToBus(Object)}, all methods
 94	 * tagged with this annotation will be considered as handlers
 95	 * for specific edit bus messages.<p>
 96	 *
 97	 * Each method should expect a single argument (an object of
 98	 * some type derived from EBMessage, inclusive). When
 99	 * delivering an EBMessage, the bus will search for and invoke
100	 * all handlers matching the outgoing message type.<p>
101	 *
102	 * Since jEdit 4.4pre1, this annotation can also be added to
103	 * classes extending EditPlugin. This will make the plugin
104	 * be added to the bus automatically, similarly to how
105	 * EBPlugin works, but without having to implement the
106	 * EBComponent interface.
107	 *
108	 * @since jEdit 4.3pre19
109	 */
110	@Retention(RetentionPolicy.RUNTIME)
111	@Target({ElementType.TYPE, ElementType.METHOD})
112	public static @interface EBHandler
113	{
114		/**
115		 * Whether the message should match the exact type of
116		 * the parameter, instead of a compatible type.
117		 */
118		boolean exact() default false;
119	} //}}}
120
121	//{{{ addToBus() method
122	/**
123	 * Adds a component to the bus. It will receive all messages sent
124	 * on the bus.
125	 *
126	 * @param comp The component to add
127	 */
128	public static void addToBus(EBComponent comp)
129	{
130		addToBus((Object)comp);
131	} //}}}
132
133	//{{{ addToBus() method
134	/**
135	 * Adds a component to the bus. Methods annotated with the
136	 * {@link EBHandler} annotation found in the component will
137	 * be used as EditBus message handlers if a message of a
138	 * matching type is sent on the bus.<p>
139	 *
140	 * If the component implements {@link EBComponent}, then the
141	 * {@link EBComponent#handleMessage(EBMessage)} method will be
142	 * called for every message sent on the bus.
143	 *
144	 * @param comp The component to add
145	 *
146	 * @since jEdit 4.3pre19
147	 */
148	public static void addToBus(Object comp)
149	{
150		components.addComponent(comp);
151	} //}}}
152
153	//{{{ removeFromBus() method
154	/**
155	 * Removes a component from the bus.
156	 * @param comp The component to remove
157	 */
158	public static void removeFromBus(EBComponent comp)
159	{
160		removeFromBus((Object) comp);
161	} //}}}
162
163	//{{{ removeFromBus() method
164	/**
165	 * Removes a component from the bus.
166	 * @param comp The component to remove
167	 * @since 4.3pre19
168	 */
169	public static void removeFromBus(Object comp)
170	{
171		components.removeComponent(comp);
172	} //}}}
173
174	//{{{ send() method
175	/**
176	 * Sends a message to all components on the bus in turn.
177	 * The message is delivered to components in the AWT thread,
178	 * and this method will wait until all handlers receive the
179	 * message before returning.
180	 *
181	 * <p><b>NOTE:</b>
182	 * If the calling thread is not the AWT thread and the
183	 * thread is interrupted before or while the call of this
184	 * method, this method can return before completion of handlers.
185	 * However, the interruption state is set in this case, so the
186	 * caller can detect the interruption after the call. If you
187	 * really need the completion of handlers, you should make sure
188	 * the call is in the AWT thread or the calling thread is never
189	 * interrupted. If you don't care about the completion of
190	 * handlers, it is recommended to use
191	 * {@link #sendAsync(EBMessage)} instead.
192	 * </p>
193	 *
194	 * @param message The message
195	 */
196	public static void send(EBMessage message)
197	{
198		Runnable sender = new SendMessage(message);
199
200		if (EventQueue.isDispatchThread())
201		{
202			sender.run();
203			return;
204		}
205
206		/*
207		 * We can't throw any checked exceptions from this
208		 * method. It will break all source that currently
209		 * expects this method to not throw them. So we catch
210		 * them and log them instead.
211		 */
212		boolean interrupted = false;
213		try
214		{
215			EventQueue.invokeAndWait(sender);
216		}
217		catch (InterruptedException ie)
218		{
219			interrupted = true;
220			Log.log(Log.ERROR, EditBus.class, ie);
221		}
222		catch (InvocationTargetException ite)
223		{
224			Log.log(Log.ERROR, EditBus.class, ite);
225		}
226		finally
227		{
228			if (interrupted)
229			{
230				Thread.currentThread().interrupt();
231			}
232		}
233	} //}}}
234
235	//{{{ sendAsync() method
236	/**
237	 * Schedules a message to be sent on the edit bus as soon as
238	 * the AWT thread is done processing current events. The
239	 * method returns immediately (i.e., before the message is
240	 * sent).
241	 *
242	 * @param message The message
243	 *
244	 * @since jEdit 4.4pre1
245	 */
246	public static void sendAsync(EBMessage message)
247	{
248		EventQueue.invokeLater(new SendMessage(message));
249	} //}}}
250
251	//{{{ Private members
252	private static final HandlerList components = new HandlerList();
253
254	// can't create new instances
255	private EditBus() {}
256
257	//{{{ dispatch() method
258	private static void dispatch(EBMessageHandler emh,
259				     EBMessage msg)
260		throws Exception
261	{
262		if (emh.handler != null)
263			emh.handler.invoke(emh.comp, msg);
264		else
265		{
266			assert (emh.comp instanceof EBComponent);
267			((EBComponent)emh.comp).handleMessage(msg);
268		}
269	} //}}}
270
271	//{{{ sendImpl() method
272	private static void sendImpl(EBMessage message)
273	{
274		boolean isExact = true;
275		Class<?> type = message.getClass();
276		while (!type.equals(Object.class))
277		{
278			List<EBMessageHandler> handlers = components.get(type);
279			if (handlers != null)
280			{
281				try
282				{
283					for (EBMessageHandler emh : handlers)
284					{
285						if (!isExact &&
286						    emh.source != null &&
287						    emh.source.exact())
288						{
289							continue;
290						}
291						if(Debug.EB_TIMER)
292						{
293							long start = System.nanoTime();
294							dispatch(emh, message);
295							long time = System.nanoTime() - start;
296							if(time >= 1000000)
297							{
298								Log.log(Log.DEBUG,EditBus.class,emh.comp + ": " + time + " ns");
299							}
300						}
301						else
302							dispatch(emh, message);
303					}
304				}
305				catch (InvocationTargetException t)
306				{
307					Log.log(Log.ERROR,EditBus.class,"Exception"
308						+ " while sending message on EditBus:");
309					Log.log(Log.ERROR, EditBus.class, t.getCause());
310				}
311				catch(Throwable t)
312				{
313					Log.log(Log.ERROR,EditBus.class,"Exception"
314						+ " while sending message on EditBus:");
315					Log.log(Log.ERROR,EditBus.class,t);
316				}
317			}
318			type = type.getSuperclass();
319			isExact = false;
320		}
321	} //}}}
322
323	//}}}
324
325	//{{{ EBMessageHandler class
326	private static class EBMessageHandler
327	{
328
329		EBMessageHandler(Object comp,
330				 Method handler,
331				 EBHandler source)
332		{
333			this.comp = comp;
334			this.handler = handler;
335			this.source = source;
336		}
337
338		Object comp;
339		Method handler;
340		EBHandler source;
341	} //}}}
342
343	//{{{ HandlerList class
344	/**
345	 * A "special" hash map that has some optimizations for use by
346	 * the EditBus. Notably, it allows setting a "read only" mode
347	 * where modifications to the map are postponed until the map
348	 * is unlocked.
349	 */
350	private static class HandlerList
351		extends HashMap<Class<?>, List<EBMessageHandler>>
352	{
353
354		public List<EBMessageHandler> safeGet(Class<?> type)
355		{
356			List<EBMessageHandler> lst = super.get(type);
357			if (lst == null) {
358				lst = new LinkedList<EBMessageHandler>();
359				super.put(type, lst);
360			}
361			return lst;
362		}
363
364
365		public synchronized void lock()
366		{
367			lock++;
368		}
369
370
371		public synchronized void unlock()
372		{
373			lock--;
374			if (lock == 0)
375			{
376				for (Object comp : add)
377					addComponent(comp);
378				for (Object comp : remove)
379					removeComponent(comp);
380				add.clear();
381				remove.clear();
382			}
383		}
384
385
386		public synchronized void removeComponent(Object comp)
387		{
388			if (lock != 0)
389			{
390				remove.add(comp);
391				return;
392			}
393
394			for (Map.Entry<Class<?>, List<EBMessageHandler>> entry: entrySet())
395			{
396				Class<?> msg = entry.getKey();
397				List<EBMessageHandler> handlers = entry.getValue();
398				if (handlers == null)
399					continue;
400				for (Iterator<EBMessageHandler> it = handlers.iterator();
401				     it.hasNext(); )
402				{
403					EBMessageHandler emh = it.next();
404					if (emh.comp == comp)
405						it.remove();
406				}
407			}
408		}
409
410
411		public synchronized void addComponent(Object comp)
412		{
413			if (lock != 0)
414			{
415				add.add(comp);
416				return;
417			}
418
419			for (Method m : comp.getClass().getMethods())
420			{
421				EBHandler source = m.getAnnotation(EBHandler.class);
422				if (source == null)
423					continue;
424
425				Class[] params = m.getParameterTypes();
426
427				if (params.length != 1)
428				{
429					Log.log(Log.ERROR, EditBus.class,
430						"Invalid EBHandler method " + m.getName() +
431						" in class " + comp.getClass().getName() +
432						": too many parameters.");
433					continue;
434				}
435
436				if (!EBMessage.class.isAssignableFrom(params[0]))
437				{
438					Log.log(Log.ERROR, EditBus.class,
439						"Invalid parameter " + params[0].getName() +
440						" in method " + m.getName() +
441						" of class " + comp.getClass().getName());
442					continue;
443				}
444
445				synchronized (components)
446				{
447					safeGet(params[0]).add(new EBMessageHandler(comp, m, source));
448				}
449			}
450
451			/*
452			 * If the component implements EBComponent, then add the
453			 * default handler for backwards compatibility.
454			 */
455			if (comp instanceof EBComponent)
456				safeGet(EBMessage.class).add(new EBMessageHandler(comp, null, null));
457		}
458
459
460		private int lock;
461		private List<Object> add = new LinkedList<Object>();
462		private List<Object> remove = new LinkedList<Object>();
463	} //}}}
464
465	//{{{ SendMessage class
466	private static class SendMessage implements Runnable
467	{
468
469		public SendMessage(EBMessage message)
470		{
471			this.message = message;
472		}
473
474
475		public void run()
476		{
477			Log.log(Log.DEBUG,EditBus.class,message.toString());
478
479			components.lock();
480			try
481			{
482				sendImpl(message);
483			}
484			finally
485			{
486				components.unlock();
487			}
488		}
489
490		private EBMessage message;
491	} //}}}
492
493}