Source code: javax/ide/extension/ExtensionRegistry.java
1 package javax.ide.extension;
2
3 import java.io.IOException;
4 import java.io.InputStream;
5
6 import java.net.URI;
7
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.Collections;
11 import java.util.Iterator;
12 import java.util.LinkedHashMap;
13 import java.util.Map;
14 import java.util.logging.Level;
15 import java.util.logging.Logger;
16
17 import javax.ide.Service;
18 import javax.ide.extension.spi.DefaultElementContext;
19 import javax.ide.extension.spi.DefaultHookVisitorFactory;
20 import javax.ide.extension.spi.DependencyTree;
21 import javax.ide.extension.spi.ExtensionSource;
22 import javax.ide.extension.spi.ExtensionVisitor;
23 import javax.ide.extension.spi.SAXManifestParser;
24 import javax.ide.spi.ProviderNotFoundException;
25
26 import javax.ide.util.Version;
27 import javax.xml.parsers.ParserConfigurationException;
28
29 import org.xml.sax.InputSource;
30 import org.xml.sax.SAXException;
31
32 /**
33 * The extension registry provides access to information about installed
34 * extensions.
35 */
36 public abstract class ExtensionRegistry extends Service
37 {
38 private Map _extensions;
39 private ElementVisitorFactory _hookFactory;
40
41 /**
42 * Find the {@link Extension} identified by the given <code>id</code>.
43 *
44 * @param id the id of an extension.
45 * @return the specified extension, or null if no such extension is
46 * registered.
47 */
48 public final Extension findExtension( String id )
49 {
50 if ( _extensions == null )
51 {
52 throw new IllegalStateException(
53 "ExtensionRegistry has not been initialized.");
54 }
55 return (Extension) _extensions.get( id );
56 }
57
58 /**
59 * Get a collection of registered extensions.
60 *
61 * @return a collection of all registered extensions.
62 */
63 public final Collection getExtensions()
64 {
65 return Collections.unmodifiableCollection( _extensions.values() );
66 }
67
68
69 /**
70 * Get the hook for the specified element name.
71 *
72 * @param hookElement the element name of a hook to retrieve. Must not be
73 * null.
74 * @return the hook for the specified element, or null if no such hook
75 * is defined.
76 */
77 public ExtensionHook getHook( ElementName hookElement )
78 {
79 if ( hookElement == null )
80 {
81 throw new NullPointerException( "Null hookElement" );
82 }
83 return (ExtensionHook) _hookFactory.getVisitor( hookElement );
84 }
85
86 /**
87 * Find all valid extension sources. A source is typically a JAR file
88 * containing an extension manifest in its META-INF directory. However, this
89 * implementation is not limited to processing extensions bundled that way.
90 *
91 * @return a collection of ExtensionSource instances, one for every potential
92 * extension.
93 */
94 protected abstract Collection findAllExtensionSources();
95
96 /**
97 * Create the initial parsing context. For the SAXManifestParser, this must
98 * be an instance of DefaultElementContext.
99 *
100 * @return the initial parsing context.
101 */
102 protected ElementContext createInitialContext()
103 {
104 return new DefaultElementContext();
105 }
106
107 /**
108 * Loads all extensions. <p>
109 *
110 * This implementation obtains the collection of extension sources by calling
111 * {@link #findAllExtensionSources()}. It then refines this list to only
112 * sources which are valid (have a non-null URI).<p>
113 *
114 * This implementation performs dependency analysis of extensions to determine
115 * the correct load order. Given multiple extensions with the same ID but
116 * different versions, it will always choose the latest version (even if that
117 * causes a dependency from another extension to be unsatisfied).<p>
118 *
119 * When the list of extensions to load in order is determined, this
120 * implementation calls {@link #loadExtensions( Collection )}. It also calls
121 * {@link #cycleEncountered( Collection )} if any cyclic dependencies were
122 * found and {@link #unsatisfiedExtensionDependencies( Extension, Collection )}
123 * if unsatisfied dependencies were found.
124 *
125 * @return a collection of all available extensions.
126 */
127 protected Collection loadExtensions()
128 {
129 Collection sources = findAllExtensionSources();
130
131 Collection validSources = new ArrayList();
132 for ( Iterator i = sources.iterator(); i.hasNext(); )
133 {
134 ExtensionSource source = (ExtensionSource) i.next();
135 if ( source.getManifestURI() != null )
136 {
137 validSources.add( source );
138 }
139 }
140
141 DependencyTree dt = DependencyTree.buildTree( validSources,
142 new DependencyTree.EnabledExtensionLookup() {
143 public boolean isExtensionEnabled( Extension e )
144 {
145 return ExtensionRegistry.this.isExtensionEnabled( e.getID(), e.getVersion() );
146 }
147 }, createExtensionLogger(), (DefaultElementContext)createInitialContext()
148 );
149
150 ArrayList orderedSources = new ArrayList();
151 for ( Iterator i = dt.getSortedExtensionIDs().iterator(); i.hasNext(); )
152 {
153 String id = (String) i.next();
154 orderedSources.add( dt.getSource( id ) );
155 }
156
157 Collection extensions = loadExtensions( orderedSources );
158
159 for ( Iterator i = dt.getCycles().iterator(); i.hasNext(); )
160 {
161 cycleEncountered( (Collection) i.next() );
162 }
163
164 for ( Iterator i = dt.getUnsatisfiedExtensions().iterator(); i.hasNext(); )
165 {
166 Extension unsatisfied = (Extension) i.next();
167 Collection deps = dt.getUnsatisfiedDependencies( unsatisfied );
168 unsatisfiedExtensionDependencies( unsatisfied, deps );
169 }
170
171 return extensions;
172 }
173
174 /**
175 * Gets whether the specified extension is enabled in this IDE. <p>
176 *
177 * This implementation always returns true.
178 *
179 * @param id the id of an extension
180 * @param version the version of an extension
181 * @return true if the specified extension is enabled in this IDE.
182 */
183 protected boolean isExtensionEnabled( String id, Version version )
184 {
185 return true;
186 }
187
188 /**
189 * A cycle was encountered. This is called after all extensions have been loaded to
190 * notify the extension registry that there were cyclic dependencies between
191 * extensions. The extension loading implementation in this class will
192 * load extensions even if there are cycles. cycleEncountered() is called
193 * to give IDEs a chance to report cycles to users.<p>
194 *
195 * The returned collection contains partially populated Extension instances in
196 * dependency order. The last extension in the collection is a duplicate of
197 * one of the previous extensions in the collection and is the first extension
198 * on which a cycle was detected.<p>
199 *
200 * This implementation does nothing.
201 *
202 * @param cycle a collection of Extensions that have a cyclic dependency.
203 */
204 protected void cycleEncountered( Collection cycle )
205 {
206
207 }
208
209 /**
210 * An extension with unsatisfied dependencies was encountered. This is called
211 * after all extensions have been loaded to notify the extension registry that
212 * there were extensions which had unsatisfied dependencies. The extension
213 * loading implementation in this class will not load extensions which have
214 * unsatisfied dependencies. unsatisfiedExtensionDependencies() is called to
215 * give IDEs a chance to report unsatisfied dependencies to users.<p>
216 *
217 * This implementation does nothing.
218 *
219 * @param ext an extension which was not loaded due to unresolved
220 * dependencies on other extensions.
221 * @param deps a collection of <tt>ExtensionDependency</tt> objects, one for
222 * each dependency that was not satisfied.
223 */
224 protected void unsatisfiedExtensionDependencies( Extension ext,
225 Collection deps )
226 {
227
228 }
229
230 /**
231 * Load extensions in the specified order.<p>
232 *
233 * This implementation uses SAXManifestParser to load each extension in
234 * turn.
235 *
236 * @param orderedSources a collection of ExtensionSource instances. The
237 * iterator order of this collection is the correct order to load the
238 * extensions based on their dependencies.
239 * @return a collection of fully loaded Extensions.
240 */
241 protected Collection loadExtensions( Collection orderedSources )
242 {
243 if ( _hookFactory == null )
244 {
245 _hookFactory = createHookVisitorFactory();
246 }
247
248 SAXManifestParser parser = new SAXManifestParser(
249 (DefaultElementContext) createInitialContext()
250 );
251 Logger logger = createExtensionLogger();
252 ((DefaultElementContext)parser.getContext()).setMessageReporter( logger );
253 ExtensionVisitor visitor = createExtensionVisitor( _hookFactory );
254 parser.getContext().registerChildVisitor( ExtensionVisitor.ELEMENT,
255 visitor );
256
257 for ( Iterator i = orderedSources.iterator(); i.hasNext(); )
258 {
259 ExtensionSource source = (ExtensionSource) i.next();
260
261 loadExtension(parser, logger, source);
262 }
263 return visitor.getExtensions();
264 }
265
266 protected void loadExtension(SAXManifestParser parser,
267 Logger logger, ExtensionSource source)
268 {
269 parser.getContext().getScopeData().put(
270 ExtensionVisitor.KEY_EXTENSION_SOURCE,
271 source
272 );
273
274 URI uri = source.getManifestURI();
275 InputStream inStream = null;
276 try
277 {
278 inStream = source.getInputStream();
279 InputSource inSource = new InputSource( inStream );
280 inSource.setSystemId( uri.toString() );
281 addToClassPath( source );
282 parser.parse( inSource );
283 }
284 catch ( IOException ioe )
285 {
286 logger.log( Level.SEVERE, "Error loading manifest from "+ source.getName(), ioe );
287 }
288 catch ( ParserConfigurationException pce )
289 {
290 throw new IllegalStateException( "Badly configured jaxb" );
291 }
292 catch ( SAXException saxe )
293 {
294 logger.log( Level.SEVERE, "Failed to parse manifest from "+source.getName(),
295 saxe );
296 }
297 catch ( RuntimeException re )
298 {
299 logger.log( Level.SEVERE, "RuntimeException parsing manifest from "+source.getName()+": "+re );
300 re.printStackTrace();
301 }
302 finally
303 {
304 try { if ( inStream!=null) inStream.close(); } catch ( IOException ioe )
305 {
306 logger.log( Level.SEVERE, "Error closing stream for "+source.getName(), ioe );
307 }
308 }
309 }
310
311 /**
312 * Create a logger which will log validation and error messages while
313 * parsing manifest files.<p>
314 *
315 * This implementation returns a default logger created using the log
316 * manager.
317 *
318 * @return a logger implementation which will log validation and error
319 * messages.
320 */
321 protected Logger createExtensionLogger()
322 {
323 return Logger.getLogger( ExtensionRegistry.class.getName() );
324 }
325
326
327 /**
328 * Create the element visitor that is responsible for visiting the root
329 * element in extension manifests.
330 *
331 * @param hookVisitorFactory the visitor factory for the hooks section
332 * of the manifest.
333 * @return an element visitor implementation for visiting the root
334 * element in extension manifests. Typically, this will be a subclass
335 * of ExtensionVisitor.
336 */
337 protected abstract ExtensionVisitor createExtensionVisitor(
338 ElementVisitorFactory hookVisitorFactory );
339
340 /**
341 * Add the specified extension source to the classpath, if required.
342 *
343 * @param source the source of an extension. E.g if this is a JAR, it
344 * needs to be added to the classpath.
345 */
346 protected abstract void addToClassPath( ExtensionSource source );
347
348 /**
349 * Create the visitor factory for hooks. This implementation returns a new
350 * instance of javax.ide.extension.spi.DefaultHookVisitorFactory.
351 *
352 * @return an ElementVisitorFactory for hooks.
353 */
354 protected ElementVisitorFactory createHookVisitorFactory()
355 {
356 return new DefaultHookVisitorFactory();
357 }
358
359 /**
360 * Initializes the extension registry.
361 *
362 * This implementation
363 */
364 protected void initialize()
365 {
366 Collection extensions = loadExtensions();
367
368 _extensions = new LinkedHashMap();
369 for ( Iterator i = extensions.iterator(); i.hasNext(); )
370 {
371 Extension ext = (Extension) i.next();
372 _extensions.put( ext.getID(), ext );
373 }
374 }
375
376 /**
377 * Get the extension registry implementation for this IDE.
378 *
379 * @return the extension registry implementation for this ide.
380 */
381 public static ExtensionRegistry getExtensionRegistry()
382 {
383 try
384 {
385 return (ExtensionRegistry) getService( ExtensionRegistry.class );
386 }
387 catch ( ProviderNotFoundException nse )
388 {
389 nse.printStackTrace();
390 throw new IllegalStateException( "No extension registry" );
391 }
392 }
393 }