001: /*=============================================================================
002: * Copyright Texas Instruments, Inc., 2001. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package ti.chimera;
020:
021: import java.util.LinkedList;
022: import java.util.Iterator;
023:
024: import javax.swing.Action;
025: import javax.swing.JToolBar;
026:
027: import oscript.data.Value;
028:
029: import ti.exceptions.ProgrammingErrorException;
030:
031: import ti.chimera.registry.Node;
032: import ti.chimera.registry.NodeContract;
033: import ti.chimera.registry.NodeSubscriber;
034: import ti.chimera.registry.NodeCreationSubscriber;
035: import ti.chimera.registry.NodeDeletionSubscriber;
036:
037: /**
038: * The abstract base class for all plugins. This class provides an easy to
039: * use framework for adding modules to <i>chimera</i>, and a mechanism to
040: * manage resources created by this plugin.
041: * <p>
042: * The plugin class itself should be a lightweight container for all the
043: * resources created and/or provided by the plugin. The plugin class will
044: * be instantiated at startup regardless of whether the plugin is actually
045: * started. As much as possible, all resources created by a plugin should
046: * not be created until the the plugin is started. This can be accomplish
047: * by using managed resources.
048: * <p>
049: * The {@link Resource} mechanism is used for managing resources created by
050: * this plugin. A plugin is active (ie. running) whenever it has installed
051: * resources, and it is in-active (ie. stopped) whenever all it's resources
052: * have been uninstalled. There are two types of resources that can be
053: * created (see {@link #addResource}, <i>managed</i>, and <i>unmanaged</i>.
054: * A managed resources is automatically {@link Resource#install}ed when the
055: * plugin is started, and {@link Resource#uninstall}ed when the plugin is
056: * stopped. An unmanaged resource is {@link Resource#install}ed when it
057: * is added ({@link #addResource}), and {@link Resource#uninstall}ed when
058: * it is removed ({@link #removeResource}). Adding an unmanaged resource
059: * will cause the plugin to become active, if it wasn't already active, and
060: * removing an unmanaged resource could cause the plugin to become inactive
061: * if there are no more unmanaged resources.
062: *
063: * @author Rob Clark
064: * @version 0.1
065: */
066: public abstract class Plugin {
067: /**
068: * The main application.
069: */
070: protected Main main;
071:
072: /**
073: * The name of this plugin instance.
074: */
075: private String name;
076:
077: /**
078: * The list of unmanaged resources.
079: */
080: private LinkedList unmanagedResourceList = new LinkedList();
081:
082: /**
083: * The list of managed resources.
084: */
085: private LinkedList managedResourceList = new LinkedList();
086:
087: /**
088: * List of services provided by plugin.
089: */
090: private LinkedList serviceFactoryList = new LinkedList();
091:
092: /**
093: * Class Constructor.
094: *
095: * @param main the main application
096: * @param name the name of this plugin instance
097: */
098: public Plugin(Main main, String name) {
099: this .main = main;
100: this .name = name;
101:
102: main.log("loading plugin " + name);
103: }
104:
105: /**
106: * Get the name of this plugin instance.
107: *
108: * @return the name
109: */
110: public String getName() {
111: return name;
112: }
113:
114: /*=======================================================================*/
115: /**
116: * Because the garbage collection of a service instance (among other
117: * things) is used to determine if a {@link Plugin} should be stopped, the
118: * {@link Registry} cannot hold a reference to the service itself. Instead
119: * the service is registered ({@link Plugin#addServiceFactory}) via a
120: * <code>ServiceFactory</code> which is used by the <code>Registry</code>
121: * to create an instance of the {@link Service} on demand. The service-
122: * factory tracks (via a weak-reference) the lifecycle of the service
123: * instance, and uses a {@link Resource} to prevent the plugin from being
124: * inactive as long as the service instance is in use.
125: * <p>
126: * Conceptually this class is an inner-class of <code>Plugin</code>, but
127: * in order to keep the file sizes managed, all of it's functionality is
128: * actually delegated to {@link ServiceFactoryImpl}. This is all subject
129: * to change, and once the deprecated methods are removed from Plugin,
130: * it is possible that the implementation gets moved into this class and
131: * ServiceFactoryImpl goes away.
132: */
133: public abstract class ServiceFactory extends ServiceFactoryImpl {
134: public ServiceFactory() {
135: super (Plugin.this );
136: }
137: }
138:
139: /**
140: * Register a service provided by this plugin. The serviced provided by
141: * the plugin should be registered by the plugin's constructor, rather
142: * than by the {@link #start} method, so that the registry can determine
143: * which services are provided by which plugin.
144: * <p>
145: * A service is registered via a {@link ServiceFactory}, which should be
146: * the one to actually create an instance of the service. This is done
147: * this way because the registry tracks the instances of a service, and
148: * adds/removes managed resources to when a service instance is created/
149: * GC'd. If the registry, or any other code, where to hold a reference
150: * to the service itself, rather than the service-factory, the service
151: * would never get garbage collected.
152: * <p>
153: * The only reason this method is <code>public</code> is for the benefit
154: * of plugins implemented as script. The way the ObjectScript interpreter
155: * works, script code only has access to public methods of a class. This
156: * method should be treated as if it were <code>protected</code>.
157: *
158: * @param sh a factory to a service provided by this plugin
159: */
160: public synchronized final void registerServiceFactory(
161: ServiceFactory sh) {
162: serviceFactoryList.add(sh);
163: }
164:
165: /**
166: * This is provided to make life easy for script code... script code can
167: * simply pass in a function that returns a service, rather than having
168: * to pass in an instance of {@link ServiceFactory}.
169: *
170: * @param fxn a script function that takes no args and returns
171: * a instance of a service
172: */
173: public final void registerServiceFactory(final Value fxn) {
174: if (fxn.castToJavaObject() instanceof ServiceFactory)
175: registerServiceFactory((ServiceFactory) (fxn
176: .castToJavaObject()));
177: else
178: registerServiceFactory(new ServiceFactory() {
179:
180: public Service createService() {
181: return (Service) (fxn.callAsFunction(new Value[0])
182: .castToJavaObject());
183: }
184:
185: });
186: }
187:
188: /**
189: * @deprecated use <code>registerServiceFactory</code> instead.
190: */
191: public final void registerService(final Service s) {
192: main.warning(getName(), "registerService deprecated");
193: registerServiceFactory(new ServiceFactory() {
194: public Service createService() {
195: return s;
196: }
197: });
198: }
199:
200: /**
201: * Used by registry to determine services provided by plugin.
202: */
203: Iterator getServiceFactories() {
204: return serviceFactoryList.iterator();
205: }
206:
207: /*=======================================================================*/
208: /**
209: * A registry resource handles installing/uninstalling various registry
210: * subscribers when the plugin becomes active/inactive. To use, this
211: * class should be subclassed, and one or more of the following methods
212: * overriden: <code>publish</code>, <code>nodeCreated</code>, or
213: * <code>nodeDeleted</code>.
214: */
215: public abstract class RegistrySubscriberResource extends Resource
216: implements NodeSubscriber, NodeCreationSubscriber,
217: NodeDeletionSubscriber {
218: private String path;
219: private NodeContract contract;
220:
221: /**
222: * Create a <code>RegistrySubscriberResource</code>, which
223: * automatically handles subscribing/unsubscribing from registry
224: * nodes.
225: *
226: * @param path the path to (un)subscribe to/from
227: * @param contract the node-contract, or <code>null</code>;
228: * used for <code>subscribeToValue</code>
229: * @param managed is this a managed resource
230: */
231: public RegistrySubscriberResource(String path,
232: NodeContract contract, boolean managed) {
233: super (managed);
234:
235: this .path = path;
236: this .contract = contract;
237: }
238:
239: /**
240: * Called to publish the new node value to the subscriber.
241: *
242: * @param node the node doing the publishing
243: * @param value the node's new value
244: */
245: public void publish(Node node, Object value) {
246: }
247:
248: /**
249: * Called when the node is created (ie. added to it's parent)
250: *
251: * @param node the node created
252: */
253: public void nodeCreated(Node node) {
254: }
255:
256: /**
257: * Called when the node is deleted (ie. removed from it's parent)
258: *
259: * @param node the node deleted
260: */
261: public void nodeDeleted(Node node) {
262: }
263:
264: /**
265: * install the subscriber
266: */
267: public void install() {
268: main.getRegistry().subscribeToCreation(path, this );
269: main.getRegistry().subscribeToValue(path, contract, this );
270: main.getRegistry().subscribeToDeletion(path, this );
271: }
272:
273: /**
274: * uninstall the subscriber
275: */
276: public void uninstall() {
277: main.getRegistry().unsubscribeFromCreation(this );
278: main.getRegistry().unsubscribeFromValue(this );
279: main.getRegistry().unsubscribeFromDeletion(this );
280: }
281:
282: /**
283: * For debugging.
284: *
285: * @return a string
286: */
287: public String toString() {
288: return "[RegistrySubscriberResource: path=" + path + "]";
289: }
290: }
291:
292: /*=======================================================================*/
293: /**
294: * A view-resource adds a view, when installed, and removes the view
295: * when uninstalled.
296: */
297: public class ViewResource extends Resource {
298: private ViewFactory vh;
299:
300: // this is null as long as this resource isn't installed:
301: private View view = null;
302:
303: // runnable which causes the view-resource to be removed when view
304: // is closed:
305: private Runnable closeRunnable = new Runnable() {
306: public void run() {
307: removeResource(ViewResource.this );
308: }
309: };
310:
311: /**
312: * Create a <code>ViewResource</code>
313: *
314: * @param vh the view-factory
315: * @param managed is this a managed view
316: */
317: public ViewResource(ViewFactory vh, boolean managed) {
318: super (managed);
319: this .vh = vh;
320: }
321:
322: /**
323: * Create a <code>ViewResource</code>. This constructor is provided as
324: * a convenience for script code, so script code can simply pass in a
325: * function that returns a {@link View}, rather than having to pass in
326: * an instance of {@link ViewFactory}.
327: *
328: * @param fxn the script function that takes no args and
329: * returns a view instance
330: * @param managed is this a managed view
331: */
332: public ViewResource(final Value fxn, boolean managed) {
333: this (new ViewFactory() {
334: public View createView() {
335: return (View) (fxn.callAsFunction(new Value[0])
336: .castToJavaObject());
337: }
338: }, managed);
339: }
340:
341: /**
342: * Create a <code>ViewResource</code> for a view. Since a view is
343: * potentially not lightweight, this constructor only allows you
344: * to create an unmanaged resource. To create a managed resouce,
345: * use the constructor that takes a {@link ViewFactory} (or a script
346: * function that returns a view).
347: *
348: * @param view the view
349: */
350: public ViewResource(final View view) {
351: this (new ViewFactory() {
352: public View createView() {
353: return view;
354: }
355: }, false);
356: }
357:
358: /**
359: * install the view
360: */
361: public void install() {
362: if (view != null)
363: throw new ProgrammingErrorException(
364: "ViewResource already install()ed!");
365: view = vh.createView();
366: view.setPlugin(Plugin.this );
367: view.addCloseRunnable(closeRunnable);
368: Dock.showView(view, Dock.getDockName(view));
369: }
370:
371: /**
372: * uninstall the view that was installed
373: */
374: public void uninstall() {
375: if (view == null)
376: throw new ProgrammingErrorException(
377: "ViewResource already uninstall()ed!");
378: Dock dock;
379: if ((dock = view.getDock()) != null)
380: dock.removeView(view);
381: view.setPlugin(null);
382: view.removeCloseRunnable(closeRunnable);
383: view = null;
384: }
385:
386: /**
387: * For debugging.
388: *
389: * @return a string
390: */
391: public String toString() {
392: return "[ViewResource"
393: + ((view != null) ? ": view=" + view : "") + "]";
394: }
395: }
396:
397: /**
398: * In order to create a managed ViewResource, we need to create the
399: * resource without creating the view itself. To accomplish this,
400: * we use a ViewFactory, whose {@link #createView} method is not
401: * called until the resource is installed.
402: */
403: public interface ViewFactory {
404: public View createView();
405: }
406:
407: /*=======================================================================*/
408: /**
409: * A tool-bar-resource adds a tool-bar when installed, and removes when
410: * uninstalled.
411: */
412: public class ToolBarResource extends Resource {
413: private ToolBarFactory tbf;
414: private JToolBar tb;
415:
416: /**
417: * Class Constructor, which can be used to defer creating
418: * the toolbar until this resource is installed.
419: *
420: * @param tbf the tool-bar-factory
421: * @param managed is this a managed resource
422: */
423: public ToolBarResource(ToolBarFactory tbf, boolean managed) {
424: super (managed);
425: this .tbf = tbf;
426: }
427:
428: /**
429: * Class Constructor, which can be used to defer creating
430: * the toolbar until this resource is installed.
431: *
432: * @param fxn the script function that takes no args and
433: * returns a tool-bar instance
434: * @param managed is this a managed resource
435: */
436: public ToolBarResource(final Value fxn, boolean managed) {
437: this (new ToolBarFactory() {
438: public JToolBar createToolBar() {
439: return (JToolBar) (fxn.callAsFunction(new Value[0])
440: .castToJavaObject());
441: }
442: }, managed);
443: }
444:
445: /**
446: * Class Constructor.
447: *
448: * @param tb the tool-bar
449: * @param managed is this a managed view
450: */
451: public ToolBarResource(final JToolBar tb, boolean managed) {
452: this (new ToolBarFactory() {
453: public JToolBar createToolBar() {
454: return tb;
455: }
456: }, managed);
457: }
458:
459: public void install() {
460: if (tb != null)
461: throw new ProgrammingErrorException(
462: "ToolBarResource already install()ed!");
463: tb = tbf.createToolBar();
464: main.getWindowManager().addToolBar(tb);
465: }
466:
467: public void uninstall() {
468: if (tb == null)
469: throw new ProgrammingErrorException(
470: "ToolBarResource already uninstall()ed!");
471: main.getWindowManager().removeToolBar(tb);
472: tb = null;
473: }
474:
475: /**
476: * For debugging.
477: *
478: * @return a string
479: */
480: public String toString() {
481: return "[ToolBarResource]";
482: }
483: }
484:
485: /**
486: * In order to create the ToolBarResource without creating the
487: * JToolBar itself, a ToolBarResource is created with a reference
488: * to a ToolBarFactory which is called on demand to create the
489: * tool-bar.
490: */
491: public interface ToolBarFactory {
492: public JToolBar createToolBar();
493: }
494:
495: /*=======================================================================*/
496: /**
497: * A MenuBarItemResource adds to / removes from the system wide menubar.
498: */
499: public class MenuBarItemResource extends Resource {
500: private String path;
501: private Action a;
502:
503: /**
504: * Class Constructor.
505: *
506: * @param path which sub-menu the item should go under
507: * @param a the menu bar action to add.
508: */
509: public MenuBarItemResource(String path, Action a,
510: boolean managed) {
511: super (managed);
512:
513: this .path = path;
514: this .a = a;
515: }
516:
517: public void install() {
518: main.getWindowManager().addMenuBarItem(path, a);
519: }
520:
521: public void uninstall() {
522: main.getWindowManager().removeMenuBarItem(path, a);
523: }
524:
525: public int hashCode() {
526: return path.hashCode() ^ a.hashCode();
527: }
528:
529: public boolean equals(Object obj) {
530: return ((obj instanceof MenuBarItemResource)
531: && path.equals(((MenuBarItemResource) obj).path) && a
532: .equals(((MenuBarItemResource) obj).a));
533: }
534:
535: /**
536: * For debugging.
537: *
538: * @return a string
539: */
540: public String toString() {
541: return "[MenuBarItemResource: path=" + path + "]";
542: }
543: }
544:
545: /*=======================================================================*/
546: /**
547: * Add a resource. If the resource is unmanaged, this could cause the
548: * plugin to become active if it is not already. This does nothing if
549: * the resource has already been added.
550: *
551: * @param r the resource to add
552: * @see #removeResource
553: */
554: public synchronized void addResource(Resource r) {
555: boolean wasActive = isActive();
556: LinkedList l;
557:
558: if (r.isManaged())
559: l = managedResourceList;
560: else
561: l = unmanagedResourceList;
562:
563: if (!l.contains(r)) {
564: main.log("starting plugin " + getName());
565: main.debug(1, getName() + ": addResource: r=" + r
566: + ", managed=" + r.isManaged());
567:
568: l.add(r);
569:
570: if (!wasActive && isActive()) {
571: main.debug(1, getName() + ": installing resources");
572:
573: // got to copy to array, because list can be modified (indirectly)
574: // from a resource's install() method:
575: Object[] rcrs = managedResourceList.toArray();
576: for (int i = 0; i < rcrs.length; i++)
577: ((Resource) (rcrs[i])).install();
578: }
579:
580: // if it is not managed, the loop above won't install it:
581: if (!r.isManaged() || isActive())
582: r.install();
583: }
584: }
585:
586: /**
587: * Remove a resource. If the resource is unmanged, and there no more
588: * unmanaged resources, this will cause the plugin to become inactive.
589: * This does nothing if the resource has not already been added.
590: *
591: * @param r the resource to add
592: * @see #addResource
593: */
594: public synchronized void removeResource(Resource r) {
595: boolean wasActive = isActive();
596: LinkedList l;
597:
598: if (r.isManaged())
599: l = managedResourceList;
600: else
601: l = unmanagedResourceList;
602:
603: if (l.remove(r)) {
604: main.log("stopping plugin " + getName());
605: main.debug(1, getName() + ": removeResource: r=" + r
606: + ", managed=" + r.isManaged());
607:
608: r.uninstall();
609:
610: if (wasActive && !isActive()) {
611: main.debug(1, getName() + ": uninstalling resources");
612:
613: // got to copy to array, because list can be modified from a
614: // resource's uninstall() method:
615: Object[] rcrs = managedResourceList.toArray();
616: for (int i = 0; i < rcrs.length; i++)
617: ((Resource) (rcrs[i])).uninstall();
618: }
619: }
620: }
621:
622: /*=======================================================================*/
623: /**
624: * Determine if this plugin is currently active. An active plugin is one
625: * for which the {@link #start} method has been called since the last time
626: * {@link #stop} has been called.
627: *
628: * @see #start
629: * @see #stop
630: */
631: public final boolean isActive() {
632: return unmanagedResourceList.size() > 0;
633: }
634:
635: /*=======================================================================*/
636: /**
637: * Overloaded to provide some info about the state of the plugin, which
638: * might be useful while debugging/developing plugins.
639: *
640: * @return a string
641: */
642: public String toString() {
643: StringBuffer sb = new StringBuffer();
644:
645: sb.append('[');
646: sb.append(getName());
647: sb.append(": active=" + isActive());
648: sb.append(", unmanagedResourceList=" + unmanagedResourceList);
649: sb.append(", managedResourceList=" + managedResourceList);
650: sb.append(", serviceFactoryList=" + serviceFactoryList);
651: sb.append(']');
652:
653: return sb.toString();
654: }
655: }
656:
657: /*
658: * Local Variables:
659: * tab-width: 2
660: * indent-tabs-mode: nil
661: * mode: java
662: * c-indentation-style: java
663: * c-basic-offset: 2
664: * eval: (c-set-offset 'substatement-open '0)
665: * eval: (c-set-offset 'case-label '+)
666: * eval: (c-set-offset 'inclass '+)
667: * eval: (c-set-offset 'inline-open '0)
668: * End:
669: */
|