001: /*
002: * ServiceManager.java - Handles services.xml files in plugins
003: * :tabSize=8:indentSize=8:noTabs=false:
004: * :folding=explicit:collapseFolds=1:
005: *
006: * Copyright (C) 2003 Slava Pestov
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public License
010: * as published by the Free Software Foundation; either version 2
011: * of the License, or any later version.
012: *
013: * This program is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
016: * GNU General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public License
019: * along with this program; if not, write to the Free Software
020: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
021: */
022:
023: package org.gjt.sp.jedit;
024:
025: import java.io.*;
026: import java.net.URL;
027: import java.util.*;
028: import org.gjt.sp.util.Log;
029: import org.gjt.sp.util.XMLUtilities;
030: import org.gjt.sp.util.StandardUtilities;
031: import org.gjt.sp.jedit.buffer.FoldHandlerProvider;
032: import org.gjt.sp.jedit.buffer.FoldHandler;
033:
034: /**
035: * A generic way for plugins to provide various API extensions.<p>
036: *
037: * Services are loaded from files named <code>services.xml</code> inside the
038: * plugin JAR. A service definition file has the following form:
039: *
040: * <pre><?xml version="1.0"?>
041: *<!DOCTYPE SERVICES SYSTEM "services.dtd">
042: *<SERVICES>
043: * <SERVICE NAME="service name" CLASS="fully qualified class name">
044: * // BeanShell code evaluated when the sevice is first activated
045: * </SERVICE>
046: *</SERVICES></pre>
047: *
048: * The following elements are valid:
049: *
050: * <ul>
051: * <li>
052: * <code>SERVICES</code> is the top-level element and refers
053: * to the set of services offered by the plugin.
054: * </li>
055: * <li>
056: * A <code>SERVICE</code> contains the data for a particular service
057: * activation.
058: * It has two attributes, both required: <code>NAME</code> and
059: * <code>CLASS</code>. The <code>CLASS</code> attribute must be the name of
060: * a known sevice type; see below.
061: * </li>
062: * <li>
063: * A <code>SERVICE</code> element should the BeanShell code that returns a
064: * new instance of the named class. Note that this code can return
065: * <code>null</code>.
066: * </li>
067: * </ul>
068: *
069: * The jEdit core defines the following service types:
070: * <ul>
071: * <li>{@link org.gjt.sp.jedit.buffer.FoldHandler}</li>
072: * <li>{@link org.gjt.sp.jedit.io.VFS}</li>
073: * <li>{@link org.gjt.sp.jedit.io.Encoding}</li>
074: * <li>{@link org.gjt.sp.jedit.io.EncodingDetector}</li>
075: * </ul>
076: *
077: * Plugins may provide more.<p>
078: *
079: * To have your plugin accept services, no extra steps are needed other than
080: * a piece of code somewhere that calls {@link #getServiceNames(String)} and
081: * {@link #getService(String,String)}.
082: *
083: * @see BeanShell
084: * @see PluginJAR
085: *
086: * @since jEdit 4.2pre1
087: * @author Slava Pestov
088: * @version $Id: ServiceManager.java 9860 2007-06-27 20:58:22Z kpouer $
089: */
090: public class ServiceManager {
091: //{{{ loadServices() method
092: /**
093: * Loads a <code>services.xml</code> file.
094: * @since jEdit 4.2pre1
095: */
096: public static void loadServices(PluginJAR plugin, URL uri,
097: PluginJAR.PluginCacheEntry cache) {
098: ServiceListHandler dh = new ServiceListHandler(plugin, uri);
099: try {
100: if (!XMLUtilities.parseXML(uri.openStream(), dh)
101: && cache != null) {
102: cache.cachedServices = dh.getCachedServices();
103: }
104: } catch (IOException ioe) {
105: Log.log(Log.ERROR, ServiceManager.class, ioe);
106: }
107: } //}}}
108:
109: //{{{ unloadServices() method
110: /**
111: * Removes all services belonging to the specified plugin.
112: * @param plugin The plugin
113: * @since jEdit 4.2pre1
114: */
115: public static void unloadServices(PluginJAR plugin) {
116: Iterator<Descriptor> descriptors = serviceMap.keySet()
117: .iterator();
118: while (descriptors.hasNext()) {
119: Descriptor d = descriptors.next();
120: if (d.plugin == plugin)
121: descriptors.remove();
122: }
123: } //}}}
124:
125: //{{{ registerService() method
126: /**
127: * Registers a service. Plugins should provide a
128: * <code>services.xml</code> file instead of calling this directly.
129: *
130: * @param clazz The service class
131: * @param name The service name
132: * @param code BeanShell code to create an instance of this
133: * @param plugin The plugin JAR, or null if this is a built-in service
134: *
135: * @since jEdit 4.2pre1
136: */
137: public static void registerService(String clazz, String name,
138: String code, PluginJAR plugin) {
139: Descriptor d = new Descriptor(clazz, name, code, plugin);
140: serviceMap.put(d, d);
141: } //}}}
142:
143: //{{{ unregisterService() method
144: /**
145: * Unregisters a service.
146: *
147: * @param clazz The service class
148: * @param name The service name
149: *
150: * @since jEdit 4.2pre1
151: */
152: public static void unregisterService(String clazz, String name) {
153: Descriptor d = new Descriptor(clazz, name);
154: serviceMap.remove(d);
155: } //}}}
156:
157: //{{{ getServiceTypes() method
158: /**
159: * Returns all known service class types.
160: *
161: * @since jEdit 4.2pre1
162: */
163: public static String[] getServiceTypes() {
164: Set<String> returnValue = new HashSet<String>();
165:
166: Set<Descriptor> keySet = serviceMap.keySet();
167: for (Descriptor d : keySet)
168: returnValue.add(d.clazz);
169:
170: return returnValue.toArray(new String[returnValue.size()]);
171: } //}}}
172:
173: //{{{ getServiceNames() method
174: /**
175: * Returns the names of all registered services with the given
176: * class. For example, calling this with a parameter of
177: * "org.gjt.sp.jedit.io.VFS" returns all known virtual file
178: * systems.
179: *
180: * @param clazz The class name
181: * @since jEdit 4.2pre1
182: */
183: public static String[] getServiceNames(String clazz) {
184: List<String> returnValue = new ArrayList<String>();
185:
186: Set<Descriptor> keySet = serviceMap.keySet();
187: for (Descriptor d : keySet)
188: if (d.clazz.equals(clazz))
189: returnValue.add(d.name);
190:
191: return returnValue.toArray(new String[returnValue.size()]);
192: } //}}}
193:
194: //{{{ getService() method
195: /**
196: * Returns an instance of the given service. The first time this is
197: * called for a given service, the BeanShell code is evaluated. The
198: * result is cached for future invocations, so in effect services are
199: * singletons.
200: *
201: * @param clazz The service class
202: * @param name The service name
203: * @since jEdit 4.2pre1
204: */
205: public static Object getService(String clazz, String name) {
206: // they never taught you this in undergrad computer science
207: Descriptor key = new Descriptor(clazz, name);
208: Descriptor value = serviceMap.get(key);
209: if (value == null) {
210: // unknown service - <clazz,name> not in table
211: return null;
212: } else {
213: if (value.code == null) {
214: loadServices(value.plugin, value.plugin
215: .getServicesURI(), null);
216: value = serviceMap.get(key);
217: }
218: return value.getInstance();
219: }
220: } //}}}
221:
222: //{{{ Package-private members
223:
224: //{{{ registerService() method
225: /**
226: * Registers a service.
227: *
228: * @since jEdit 4.2pre1
229: */
230: static void registerService(Descriptor d) {
231: serviceMap.put(d, d);
232: } //}}}
233:
234: //}}}
235:
236: //{{{ Private members
237: private static final Map<Descriptor, Descriptor> serviceMap = new HashMap<Descriptor, Descriptor>();
238:
239: //}}}
240:
241: //{{{ Descriptor class
242: static class Descriptor {
243: final String clazz;
244: final String name;
245: String code;
246: PluginJAR plugin;
247: Object instance;
248: boolean instanceIsNull;
249:
250: // this constructor keys the hash table
251: Descriptor(String clazz, String name) {
252: this .clazz = clazz;
253: this .name = name;
254: }
255:
256: // this constructor is the value of the hash table
257: Descriptor(String clazz, String name, String code,
258: PluginJAR plugin) {
259: this .clazz = clazz;
260: this .name = name;
261: this .code = code;
262: this .plugin = plugin;
263: }
264:
265: Object getInstance() {
266: if (instanceIsNull)
267: return null;
268: else if (instance == null) {
269: // lazy instantiation
270: instance = BeanShell.eval(null, BeanShell
271: .getNameSpace(), code);
272: if (instance == null) {
273: // avoid re-running script if it gives
274: // us null
275: instanceIsNull = true;
276: }
277: }
278:
279: return instance;
280: }
281:
282: public int hashCode() {
283: return name.hashCode();
284: }
285:
286: public boolean equals(Object o) {
287: if (o instanceof Descriptor) {
288: Descriptor d = (Descriptor) o;
289: return d.clazz.equals(clazz) && d.name.equals(name);
290: } else
291: return false;
292: }
293: } //}}}
294:
295: /**
296: * A FoldHandler based on the ServiceManager
297: * @author Matthieu Casanova
298: * @since jEdit 4.3pre10
299: */
300: public static class ServiceFoldHandlerProvider implements
301: FoldHandlerProvider {
302: /**
303: * The service type. See {@link org.gjt.sp.jedit.ServiceManager}.
304: * @since jEdit 4.3pre10
305: */
306: public static final String SERVICE = "org.gjt.sp.jedit.buffer.FoldHandler";
307:
308: /**
309: * Returns the fold handler with the specified name, or null if
310: * there is no registered handler with that name.
311: * @param name The name of the desired fold handler
312: * @return the FoldHandler or null if it doesn't exists
313: * @since jEdit 4.3pre10
314: */
315: public FoldHandler getFoldHandler(String name) {
316: FoldHandler handler = (FoldHandler) getService(SERVICE,
317: name);
318: return handler;
319: }
320:
321: /**
322: * Returns an array containing the names of all registered fold
323: * handlers.
324: *
325: * @since jEdit 4.3pre10
326: */
327: public String[] getFoldModes() {
328: String[] handlers = getServiceNames(SERVICE);
329: Arrays
330: .sort(handlers,
331: new StandardUtilities.StringCompare());
332: return handlers;
333: }
334: }
335: }
|