Source code: org/gjt/sp/jedit/ServiceManager.java
1 /*
2 * ServiceManager.java - Handles services.xml files in plugins
3 * :tabSize=8:indentSize=8:noTabs=false:
4 * :folding=explicit:collapseFolds=1:
5 *
6 * Copyright (C) 2003 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
23 package org.gjt.sp.jedit;
24
25 import com.microstar.xml.*;
26 import java.io.*;
27 import java.net.URL;
28 import java.util.*;
29 import org.gjt.sp.util.Log;
30
31 /**
32 * A generic way for plugins to provide various API extensions.<p>
33 *
34 * Services are loaded from files named <code>services.xml</code> inside the
35 * plugin JAR. A service definition file has the following form:
36 *
37 * <pre><?xml version="1.0"?>
38 *<!DOCTYPE SERVICES SYSTEM "services.dtd">
39 *<SERVICES>
40 * <SERVICE NAME="service name" CLASS="fully qualified class name">
41 * // BeanShell code evaluated when the sevice is first activated
42 * </SERVICE>
43 *</SERVICES></pre>
44 *
45 * The following elements are valid:
46 *
47 * <ul>
48 * <li>
49 * <code>SERVICES</code> is the top-level element and refers
50 * to the set of services offered by the plugin.
51 * </li>
52 * <li>
53 * A <code>SERVICE</code> contains the data for a particular service
54 * activation.
55 * It has two attributes, both required: <code>NAME</code> and
56 * <code>CLASS</code>. The <code>CLASS</code> attribute must be the name of
57 * a known sevice type; see below.
58 * </li>
59 * <li>
60 * A <code>SERVICE</code> element should the BeanShell code that returns a
61 * new instance of the named class. Note that this code can return
62 * <code>null</code>.
63 * </li>
64 * </ul>
65 *
66 * The jEdit core defines the following service types:
67 * <ul>
68 * <li>{@link org.gjt.sp.jedit.buffer.FoldHandler}</li>
69 * <li>{@link org.gjt.sp.jedit.io.VFS}</li>
70 * </ul>
71 *
72 * Plugins may provide more.<p>
73 *
74 * To have your plugin accept services, no extra steps are needed other than
75 * a piece of code somewhere that calls {@link #getServiceNames(String)} and
76 * {@link #getService(String,String)}.
77 *
78 * @see BeanShell
79 * @see PluginJAR
80 *
81 * @since jEdit 4.2pre1
82 * @author Slava Pestov
83 * @version $Id: ServiceManager.java,v 1.7 2003/05/10 00:43:19 spestov Exp $
84 */
85 public class ServiceManager
86 {
87 //{{{ loadServices() method
88 /**
89 * Loads a <code>services.xml</code> file.
90 * @since jEdit 4.2pre1
91 */
92 public static void loadServices(PluginJAR plugin, URL uri,
93 PluginJAR.PluginCacheEntry cache)
94 {
95 Reader in = null;
96
97 try
98 {
99 Log.log(Log.DEBUG,jEdit.class,"Loading services from " + uri);
100
101 ServiceListHandler dh = new ServiceListHandler(plugin,uri);
102 XmlParser parser = new XmlParser();
103 parser.setHandler(dh);
104 in = new BufferedReader(
105 new InputStreamReader(
106 uri.openStream()));
107 parser.parse(null, null, in);
108 if(cache != null)
109 cache.cachedServices = dh.getCachedServices();
110 }
111 catch(XmlException xe)
112 {
113 int line = xe.getLine();
114 String message = xe.getMessage();
115 Log.log(Log.ERROR,ServiceManager.class,uri + ":" + line
116 + ": " + message);
117 }
118 catch(Exception e)
119 {
120 Log.log(Log.ERROR,ServiceManager.class,e);
121 }
122 finally
123 {
124 try
125 {
126 if(in != null)
127 in.close();
128 }
129 catch(IOException io)
130 {
131 Log.log(Log.ERROR,ServiceManager.class,io);
132 }
133 }
134 } //}}}
135
136 //{{{ unloadServices() method
137 /**
138 * Removes all services belonging to the specified plugin.
139 * @param plugin The plugin
140 * @since jEdit 4.2pre1
141 */
142 public static void unloadServices(PluginJAR plugin)
143 {
144 Iterator descriptors = serviceMap.keySet().iterator();
145 while(descriptors.hasNext())
146 {
147 Descriptor d = (Descriptor)descriptors.next();
148 if(d.plugin == plugin)
149 descriptors.remove();
150 }
151 } //}}}
152
153 //{{{ registerService() method
154 /**
155 * Registers a service. Plugins should provide a
156 * <code>services.xml</code> file instead of calling this directly.
157 *
158 * @param clazz The service class
159 * @param name The service name
160 * @param code BeanShell code to create an instance of this
161 * @param plugin The plugin JAR, or null if this is a built-in service
162 *
163 * @since jEdit 4.2pre1
164 */
165 public static void registerService(String clazz, String name,
166 String code, PluginJAR plugin)
167 {
168 Descriptor d = new Descriptor(clazz,name,code,plugin);
169 serviceMap.put(d,d);
170 } //}}}
171
172 //{{{ unregisterService() method
173 /**
174 * Unregisters a service.
175 *
176 * @param clazz The service class
177 * @param name The service name
178 * @param code BeanShell code to create an instance of this
179 *
180 * @since jEdit 4.2pre1
181 */
182 public static void unregisterService(String clazz, String name)
183 {
184 Descriptor d = new Descriptor(clazz,name);
185 serviceMap.remove(d);
186 } //}}}
187
188 //{{{ getServiceTypes() method
189 /**
190 * Returns all known service class types.
191 *
192 * @since jEdit 4.2pre1
193 */
194 public static String[] getServiceTypes()
195 {
196 HashSet returnValue = new HashSet();
197
198 Iterator descriptors = serviceMap.keySet().iterator();
199 while(descriptors.hasNext())
200 {
201 Descriptor d = (Descriptor)descriptors.next();
202 returnValue.add(d.clazz);
203 }
204
205 return (String[])returnValue.toArray(
206 new String[returnValue.size()]);
207 } //}}}
208
209 //{{{ getServiceNames() method
210 /**
211 * Returns the names of all registered services with the given
212 * class. For example, calling this with a parameter of
213 * "org.gjt.sp.jedit.io.VFS" returns all known virtual file
214 * systems.
215 *
216 * @param clazz The class name
217 * @since jEdit 4.2pre1
218 */
219 public static String[] getServiceNames(String clazz)
220 {
221 ArrayList returnValue = new ArrayList();
222
223 Iterator descriptors = serviceMap.keySet().iterator();
224 while(descriptors.hasNext())
225 {
226 Descriptor d = (Descriptor)descriptors.next();
227 if(d.clazz.equals(clazz))
228 returnValue.add(d.name);
229 }
230
231 return (String[])returnValue.toArray(
232 new String[returnValue.size()]);
233 } //}}}
234
235 //{{{ getService() method
236 /**
237 * Returns an instance of the given service. The first time this is
238 * called for a given service, the BeanShell code is evaluated. The
239 * result is cached for future invocations, so in effect services are
240 * singletons.
241 *
242 * @param clazz The service class
243 * @param name The service name
244 * @since jEdit 4.2pre1
245 */
246 public static Object getService(String clazz, String name)
247 {
248 // they never taught you this in undergrad computer science
249 Descriptor key = new Descriptor(clazz,name);
250 Descriptor value = (Descriptor)serviceMap.get(key);
251 if(value == null)
252 {
253 // unknown service - <clazz,name> not in table
254 return null;
255 }
256 else
257 {
258 if(value.code == null)
259 {
260 loadServices(value.plugin,
261 value.plugin.getServicesURI(),
262 null);
263 value = (Descriptor)serviceMap.get(key);
264 }
265 return value.getInstance();
266 }
267 } //}}}
268
269 //{{{ Package-private members
270
271 //{{{ registerService() method
272 /**
273 * Registers a service.
274 *
275 * @since jEdit 4.2pre1
276 */
277 static void registerService(Descriptor d)
278 {
279 serviceMap.put(d,d);
280 } //}}}
281
282 //}}}
283
284 //{{{ Private members
285 private static Map serviceMap = new HashMap();
286 //}}}
287
288 //{{{ Descriptor class
289 static class Descriptor
290 {
291 String clazz;
292 String name;
293 String code;
294 PluginJAR plugin;
295 Object instance;
296 boolean instanceIsNull;
297
298 // this constructor keys the hash table
299 Descriptor(String clazz, String name)
300 {
301 this.clazz = clazz;
302 this.name = name;
303 }
304
305 // this constructor is the value of the hash table
306 Descriptor(String clazz, String name, String code,
307 PluginJAR plugin)
308 {
309 this.clazz = clazz;
310 this.name = name;
311 this.code = code;
312 this.plugin = plugin;
313 }
314
315 Object getInstance()
316 {
317 if(instanceIsNull)
318 return null;
319 else if(instance == null)
320 {
321 // lazy instantiation
322 instance = BeanShell.eval(null,
323 BeanShell.getNameSpace(),
324 code);
325 if(instance == null)
326 {
327 // avoid re-running script if it gives
328 // us null
329 instanceIsNull = true;
330 }
331 }
332
333 return instance;
334 }
335 public int hashCode()
336 {
337 return name.hashCode();
338 }
339
340 public boolean equals(Object o)
341 {
342 if(o instanceof Descriptor)
343 {
344 Descriptor d = (Descriptor)o;
345 return d.clazz.equals(clazz)
346 && d.name.equals(name);
347 }
348 else
349 return false;
350 }
351 } //}}}
352 }