/* * ServiceManager.java - Handles services.xml files in plugins * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2003 Slava Pestov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit; import java.io.*; import java.net.URL; import java.util.*; import org.gjt.sp.util.Log; import org.gjt.sp.util.XMLUtilities; import org.gjt.sp.util.StandardUtilities; import org.gjt.sp.jedit.buffer.FoldHandlerProvider; import org.gjt.sp.jedit.buffer.FoldHandler; /** * A generic way for plugins (and core) to provide various API extensions.<p> * * Services are loaded from files named <code>services.xml</code> inside the * plugin JAR. A service definition file has the following form: * * <pre><?xml version="1.0"?> *<!DOCTYPE SERVICES SYSTEM "services.dtd"> *<SERVICES> * <SERVICE NAME="service name" CLASS="fully qualified class name"> * // BeanShell code evaluated when the sevice is first activated * </SERVICE> *</SERVICES></pre> * * The following elements are valid: * * <ul> * <li> * <code>SERVICES</code> is the top-level element and refers * to the set of services offered by the plugin. * </li> * <li> * A <code>SERVICE</code> contains the factory method for this * service singleton. The ServiceManager manages named singletons * created from these factory methods. * It has two attributes, both required: <code>NAME</code> and * <code>CLASS</code>. The <code>CLASS</code> attribute must be the name of * a known sevice type; see below. * </li> * <li> * A <code>SERVICE</code> element should the BeanShell code that returns a * new instance of the named class. Note that this code can return * <code>null</code>. * </li> * </ul> * * To see all of the services offered by jEdit core, see * jEdit's <tt>services.xml</tt> file. * Some core services are listed below: * <ul> * <li>{@link org.gjt.sp.jedit.buffer.FoldHandler}</li> * <li>{@link org.gjt.sp.jedit.textarea.FoldPainter}</li> * <li>{@link org.gjt.sp.jedit.io.VFS}</li> * <li>{@link org.gjt.sp.jedit.io.Encoding}</li> * <li>{@link org.gjt.sp.jedit.io.EncodingDetector}</li> * <li>{@link org.gjt.sp.jedit.gui.statusbar.StatusWidgetFactory}</li> * <li>{@link org.gjt.sp.jedit.gui.DockingFrameworkProvider}</li> * <li>{@link org.gjt.sp.jedit.gui.tray.JEditTrayIcon}</li> * </ul> * * Plugins may define/provide more, so the only way to see a * complete list of service types currently in use is by calling * {@link #getServiceTypes()}. * <br> * To use a service from a plugin, add a piece of code somewhere that calls * {@link #getServiceNames(String)} and {@link #getService(String,String)}. * * * @see BeanShell * @see PluginJAR * * @since jEdit 4.2pre1 * @author Slava Pestov * @version $Id$ */ public class ServiceManager { //{{{ loadServices() method /** * Loads a <code>services.xml</code> file. * @since jEdit 4.2pre1 */ public static void loadServices(PluginJAR plugin, URL uri, PluginJAR.PluginCacheEntry cache) { ServiceListHandler dh = new ServiceListHandler(plugin,uri); try { InputStream in; try { in = uri.openStream(); } catch(FileNotFoundException e) { in = null; // this happened when calling generateCache() in the context of 'find orphan jars' // in org.gjt.sp.jedit.pluginmgr.ManagePanel.FindOrphan.actionPerformed(ActionEvent) // because for not loaded plugins, the plugin will not be added to the list of pluginJars // so the org.gjt.sp.jedit.proto.jeditresource.PluginResURLConnection will not find the plugin // to read the resource from. // Better log a small error message than a big stack trace Log.log(Log.WARNING, ServiceManager.class, "Unable to open: " + uri); } if (in!=null && !XMLUtilities.parseXML(uri.openStream(), dh) && cache != null) { cache.cachedServices = dh.getCachedServices(); } } catch (IOException ioe) { Log.log(Log.ERROR, ServiceManager.class, ioe); } } //}}} //{{{ unloadServices() method /** * Removes all services belonging to the specified plugin. * @param plugin The plugin * @since jEdit 4.2pre1 */ public static void unloadServices(PluginJAR plugin) { Iterator<Descriptor> descriptors = serviceMap.keySet().iterator(); while(descriptors.hasNext()) { Descriptor d = descriptors.next(); if(d.plugin == plugin) descriptors.remove(); } } //}}} //{{{ registerService() method /** * Registers a service. Plugins should provide a * <code>services.xml</code> file instead of calling this directly. * * @param clazz The service class * @param name The service name * @param code BeanShell code to create an instance of this * @param plugin The plugin JAR, or null if this is a built-in service * * @since jEdit 4.2pre1 */ public static void registerService(String clazz, String name, String code, PluginJAR plugin) { Descriptor d = new Descriptor(clazz,name,code,plugin); serviceMap.put(d,d); } //}}} //{{{ unregisterService() method /** * Unregisters a service. * * @param clazz The service class * @param name The service name * * @since jEdit 4.2pre1 */ public static void unregisterService(String clazz, String name) { Descriptor d = new Descriptor(clazz,name); serviceMap.remove(d); } //}}} //{{{ getServiceTypes() method /** * Returns all known service class types. * * @since jEdit 4.2pre1 */ public static String[] getServiceTypes() { Set<String> returnValue = new HashSet<String>(); Set<Descriptor> keySet = serviceMap.keySet(); for (Descriptor d : keySet) returnValue.add(d.clazz); return returnValue.toArray( new String[returnValue.size()]); } //}}} //{{{ getServiceNames() method /** * Returns the names of all registered services with the given * class. For example, calling this with a parameter of * "org.gjt.sp.jedit.io.VFS" returns all known virtual file * systems. * * @param clazz The class name * @since jEdit 4.2pre1 */ public static String[] getServiceNames(String clazz) { List<String> returnValue = new ArrayList<String>(); Set<Descriptor> keySet = serviceMap.keySet(); for (Descriptor d : keySet) if(d.clazz.equals(clazz)) returnValue.add(d.name); return returnValue.toArray( new String[returnValue.size()]); } //}}} //{{{ getServiceNames() method public static String[] getServiceNames(Class clazz) { return getServiceNames(clazz.getName()); } //}}} //{{{ getService() methods /** * Returns an instance of the given service. The first time this is * called for a given service, the BeanShell code is evaluated. The * result is cached for future invocations, so in effect services are * singletons. * * @param clazz The service class * @param name The service name * @since jEdit 4.2pre1 */ public static Object getService(String clazz, String name) { Descriptor key = new Descriptor(clazz,name); Descriptor value = serviceMap.get(key); if(value == null) { // unknown service - <clazz,name> not in table return null; } else { if(value.code == null) { loadServices(value.plugin, value.plugin.getServicesURI(), null); value = serviceMap.get(key); } return value.getInstance(); } } /** * Returns an instance of the given service. The first time this is * called for a given service, the BeanShell code is evaluated. The * result is cached for future invocations, so in effect services are * singletons. * * @param clazz The service class * @param name The service name * @return the service instance * @since jEdit 4.4pre1 */ @SuppressWarnings({"unchecked"}) public static <E> E getService(Class<E> clazz, String name) { return (E) getService(clazz.getName(), name); } //}}} //{{{ Package-private members //{{{ registerService() method /** * Registers a service. * * @param d the service descriptor * @since jEdit 4.2pre1 */ static void registerService(Descriptor d) { serviceMap.put(d,d); } //}}} //}}} //{{{ Private members private static final Map<Descriptor, Descriptor> serviceMap = new HashMap<Descriptor, Descriptor>(); //}}} //{{{ Descriptor class static class Descriptor { final String clazz; final String name; String code; PluginJAR plugin; Object instance; boolean instanceIsNull; // this constructor keys the hash table Descriptor(String clazz, String name) { this.clazz = clazz; this.name = name; } // this constructor is the value of the hash table Descriptor(String clazz, String name, String code, PluginJAR plugin) { this.clazz = clazz; this.name = name; this.code = code; this.plugin = plugin; } Object getInstance() { if(instanceIsNull) return null; else if(instance == null) { // lazy instantiation instance = BeanShell.eval(null, BeanShell.getNameSpace(), code); if(instance == null) { // avoid re-running script if it gives // us null instanceIsNull = true; } } return instance; } @Override public int hashCode() { int result = 31 * clazz.hashCode() + name.hashCode(); return result; } public boolean equals(Object o) { if(o instanceof Descriptor) { Descriptor d = (Descriptor)o; return d.clazz.equals(clazz) && d.name.equals(name); } else return false; } } //}}} /** * A FoldHandler based on the ServiceManager * @author Matthieu Casanova * @since jEdit 4.3pre10 */ public static class ServiceFoldHandlerProvider implements FoldHandlerProvider { /** * The service type. See {@link org.gjt.sp.jedit.ServiceManager}. * @since jEdit 4.3pre10 */ public static final String SERVICE = "org.gjt.sp.jedit.buffer.FoldHandler"; /** * Returns the fold handler with the specified name, or null if * there is no registered handler with that name. * @param name The name of the desired fold handler * @return the FoldHandler or null if it doesn't exist * @since jEdit 4.3pre10 */ @Override public FoldHandler getFoldHandler(String name) { FoldHandler handler = (FoldHandler) getService(SERVICE,name); return handler; } /** * Returns an array containing the names of all registered fold * handlers. * * @since jEdit 4.3pre10 */ @Override public String[] getFoldModes() { String[] handlers = getServiceNames(SERVICE); Arrays.sort(handlers,new StandardUtilities.StringCompare<String>()); return handlers; } } }