Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: javax/ide/extension/spi/DependencyTree.java


1   package javax.ide.extension.spi;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   
6   import java.util.ArrayList;
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.HashMap;
10  import java.util.HashSet;
11  import java.util.Iterator;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.Set;
15  import java.util.logging.Level;
16  
17  import java.util.logging.Logger;
18  import javax.ide.extension.ElementEndContext;
19  import javax.ide.extension.ElementStartContext;
20  import javax.ide.extension.ElementVisitor;
21  import javax.ide.extension.Extension;
22  import javax.ide.extension.ExtensionDependency;
23  import javax.ide.extension.ExtensionHook;
24  
25  import javax.ide.util.Version;
26  import javax.xml.parsers.ParserConfigurationException;
27  
28  import org.xml.sax.InputSource;
29  import org.xml.sax.SAXException;
30  
31  /**
32   * DependencyTree is responsible for determining dependencies between extensions
33   * and providing a load order that satisfies those dependencies.
34   */
35  public final class DependencyTree 
36  {
37    private static final String SOURCE = "source";
38  
39    private final Map _sourcesByExtension;
40    private final Map _extensionsById = new HashMap();
41    
42    private List _topologicalExtensionList;
43    private final Map _unsatisfiedDependencies = new HashMap();
44    private final List _cycles = new ArrayList();
45  
46    private DependencyTree( List failedSources, Map sourcesByExtension )
47    {
48      _sourcesByExtension = sourcesByExtension;
49      removeDuplicates();
50      
51      for ( Iterator i = _sourcesByExtension.keySet().iterator(); i.hasNext(); )
52      {
53        Extension extension = (Extension) i.next();
54        _extensionsById.put( extension.getID(), extension );
55      }
56      
57      // Now create a topological sort of the extensions. We must try the
58      // sort from all vertices because we may have multiple disconnected
59      // graph fragments. However, this is not as inefficient as it seems, since
60      // we remember which vertices we've already visited.
61      
62      TopoSortState state = new TopoSortState();
63      for ( Iterator i = _sourcesByExtension.keySet().iterator(); i.hasNext(); )
64      {
65        Extension thisExt = (Extension) i.next();
66        if ( state.isUnvisited( thisExt ) )
67        {
68          topologicalSort( state, thisExt );
69        }
70      }
71      
72      _topologicalExtensionList = state.getTopoList();
73  
74    }
75  
76    /**
77     * Build a dependency tree for the specified collection of extension 
78     * sources.
79     * 
80     * @param extensionSources a collection of 
81     *    javax.ide.extension.spi.ExtensionSource.
82     * @return an instance of DependencyTree.
83     */
84    public static DependencyTree buildTree( Collection extensionSources, 
85      EnabledExtensionLookup lookup, Logger logger, 
86      DefaultElementContext initialContext )
87    {
88      SAXManifestParser minimalParser = new SAXManifestParser( initialContext );
89      ((DefaultElementContext)minimalParser.getContext()).setMessageReporter( logger );
90      MinimalExtensionVisitor visitor = new MinimalExtensionVisitor();
91      
92      minimalParser.getContext().registerChildVisitor(
93        ExtensionVisitor.ELEMENT, visitor
94      );
95      
96      List failedSources = new ArrayList();
97      
98      for ( Iterator i = extensionSources.iterator(); i.hasNext(); )
99      {
100       ExtensionSource source = (ExtensionSource) i.next();
101       minimalParser.getContext().getScopeData().put( SOURCE, source );
102       InputStream inputStream = null;
103       try
104       {
105         // TODO handle encoding... Always a pain in the butt with xml. We need
106         // to partially read the xml stream to figure out which encoding to use.
107         
108         inputStream = source.getInputStream();
109       
110         InputSource inputSource = new InputSource( inputStream );
111         inputSource.setSystemId( source.getManifestURI().toString() );
112         minimalParser.parse( inputSource );
113       }
114       catch ( ParserConfigurationException pce )
115       {
116         throw new IllegalStateException( "JAXP is misconfigured" ); // , pce );
117       }
118       catch ( SAXException saxe )
119       {
120         minimalParser.getContext().getLogger().log(
121           Level.SEVERE,
122           "Failed to process extension source "+ source.getName()+": "+saxe.getLocalizedMessage(),
123           new LocatorImpl( source.getManifestURI().toString() )
124         );
125         failedSources.add( source );
126       }
127       catch ( IOException ioe )
128       {
129         minimalParser.getContext().getLogger().log(
130           Level.SEVERE,
131           "Failed to process extension source "+ source.getName()+": "+ioe.getLocalizedMessage(),
132           new LocatorImpl( source.getManifestURI().toString() )
133         );
134         failedSources.add( source );
135       }
136       finally
137       {
138         try
139         {
140           if ( inputStream != null )
141             inputStream.close();
142         }
143         catch ( IOException ioe )
144         {
145           minimalParser.getContext().getLogger().log( 
146             Level.SEVERE, 
147             "Exception closing stream",
148             ioe
149           );
150         }
151       }
152     }
153     
154     Map sbe = visitor.getSourcesByExtension();
155     for ( Iterator i = sbe.keySet().iterator(); i.hasNext(); )
156     {
157       Extension e = (Extension) i.next();
158       if ( !lookup.isExtensionEnabled( e ) )
159       {
160         i.remove();
161       }
162     }
163     
164     return new DependencyTree( failedSources, sbe );
165   }
166   
167   /**
168    * Resolve duplicate extension ids. If two or more extensions are present 
169    * with the same id but different versions, we discard the older versions.
170    * If two or more extensions have the same version, we "arbitrarily" pick
171    * only one.
172    */
173   private void removeDuplicates()
174   {
175     Map latestExtensionById = new HashMap();
176     List extensionsToRemove = new ArrayList();
177     
178     for ( Iterator i = _sourcesByExtension.keySet().iterator(); i.hasNext(); )
179     {
180       Extension extension = (Extension) i.next();
181       Extension latest = 
182         (Extension) latestExtensionById.get( extension.getID() );
183       if ( latest == null ) 
184       {
185         latestExtensionById.put( extension.getID(), extension );
186       }
187       // Do we have a later version?
188       else if ( extension.getVersion().compareTo( latest.getVersion() ) > 0 )
189       {
190         // Remember the older extension. We will need to remove it from the map
191         // later.
192         extensionsToRemove.add( latest );
193         latestExtensionById.put( extension.getID(), extension );
194       }
195       else
196       {
197         // Else, the extension version is <= the latest one, so we skip it.
198         extensionsToRemove.add( extension );
199       }
200     }
201     
202     // Now remove all extensions in the list of duplicates we've built.
203     for ( Iterator i = extensionsToRemove.iterator(); i.hasNext(); )
204     {
205       Extension extension = (Extension) i.next();
206       _sourcesByExtension.remove( extension );
207     }
208   }
209   
210   /**
211    * The state of a topological sort. Primarily avoids passing lots of 
212    * parameters around...
213    */
214   private class TopoSortState
215   {
216     private Map _stateByExtension = new HashMap();
217     private Set _unsatisfied = new HashSet();
218     private List _currentlyVisiting = new ArrayList();
219     private List _topoList = new ArrayList();
220     
221     public List getTopoList()
222     {
223       return _topoList;
224     }
225     
226     public boolean isVisiting( Extension extension )
227     {
228       return _stateByExtension.get( extension ) == STATE_VISITING;
229     }
230     
231     public boolean isVisited( Extension extension )
232     {
233       return _stateByExtension.get( extension ) == STATE_VISITED;
234     }
235     
236     public boolean isUnvisited( Extension extension )
237     {
238       Object o = _stateByExtension.get( extension );
239       return o == null || o == STATE_NOT_VISITED;
240     }
241     
242     
243     public void addUnsatisfied( Extension extension )
244     {
245       _unsatisfied.add( extension );
246     }
247     
248     public boolean isUnsatisfied( Extension extension )
249     {
250       return _unsatisfied.contains( extension );
251     }
252     
253     public void startVisiting( Extension extension )
254     {
255       _currentlyVisiting.add( extension );
256       _stateByExtension.put( extension, STATE_VISITING );
257     }
258     
259     public void endVisiting( Extension extension )
260     {
261       _currentlyVisiting.remove( extension );
262       _stateByExtension.put( extension, STATE_VISITED );
263     }
264     
265     public void markUnsatisfiedChain()
266     {
267       for ( Iterator i = _currentlyVisiting.iterator(); i.hasNext(); )
268       {
269         addUnsatisfied( (Extension) i.next() );
270       }
271     }
272     
273     public void markCycleChain( Extension current )
274     {
275       ArrayList al = new ArrayList();
276       al.addAll( _currentlyVisiting );
277       al.add( current );
278       _cycles.add( al );
279     }
280     
281     public void addToTopo( Extension extension )
282     {
283       _topoList.add( extension );
284     }
285   }
286   
287   /**
288    * Topologically sort the set of extensions in _sourcesByExtension based on
289    * their dependencies. This algorithm detects cycles, but does not abort
290    * on the detection of them. Instead, it discards cyclic dependencies for
291    * the purposes of topo sorts (we should probably signal some kind of error
292    * condition so that it can be reported)
293    * 
294    * @param stateByExtension a map of STATE_ constants by extension. Indicate
295    *    the current "color" of this vertex in the algorithm.
296    * @param topo the current topological value. 
297    * @param topoByExtension assigned topological values for visited vertices.
298    * @param vertex the current vertex (extension).
299    */
300   private void topologicalSort( TopoSortState state, Extension ext )
301   {
302 
303     try
304     {
305       state.startVisiting( ext );
306       for ( Iterator i = ext.getDependencies().iterator(); i.hasNext(); )
307       {
308         ExtensionDependency dep = (ExtensionDependency) i.next();
309         Extension depExt = (Extension) _extensionsById.get( dep.getID() );
310         
311         if ( depExt == null || 
312              state.isUnsatisfied( depExt ) ||
313              !isVersionSatisfied( dep, depExt )  )
314         {
315           // We hit an unsatisfied dependency.
316           state.markUnsatisfiedChain();
317           List unsat = (List) _unsatisfiedDependencies.get( ext );
318           if ( unsat == null )
319           {
320             unsat = new ArrayList();
321             _unsatisfiedDependencies.put( ext, unsat );
322           }
323           unsat.add( dep );
324         }
325         // Follow to extensions which this one depends on even if they are not
326         // satisfied (by version). This means if we have
327         //   A 1.0 requires B 1.1 requires C 1.2
328         // but the installed versions are:
329         //   A 1.0
330         //   B 1.5
331         //   C 1.2
332         // we still process (and load) C, but not B.
333         if ( depExt != null )
334         {
335           if ( state.isVisiting( depExt ) )
336           {
337             // We hit a cycle. 
338             state.markCycleChain( depExt );
339           }           
340           if ( state.isUnvisited( depExt ) )
341           {
342             topologicalSort( state, depExt );
343           }
344         }
345       }
346     }
347     finally
348     {
349       if ( !state.isUnsatisfied( ext ) )
350       {
351         state.addToTopo( ext );
352       }    
353       state.endVisiting( ext );
354     }
355   }
356   
357   /**
358    * Get an extension which satisfies the specified dependency. 
359    * 
360    * @param dep the dependency to satisfy.
361    * @return the extension satisfying this dependency, or null if no 
362    *    extension was found satisfying the dependency.
363    */
364   private boolean isVersionSatisfied( ExtensionDependency dep, Extension ext )
365   {
366     if ( ext != null )
367     {
368       // Check minimum version.
369       if ( dep.getMinimumVersion() != null && 
370         dep.getMinimumVersion().compareTo( ext.getVersion() ) > 0 )
371       {
372         return false;
373       }
374       
375       // Check maximum version.
376       if ( dep.getMaximumVersion() != null && 
377         dep.getMaximumVersion().compareTo( ext.getVersion() ) < 0 )
378       {
379         return false;
380       }
381     }
382     
383     return true;
384   }
385   
386   /**
387    * Get the list of extension ids, sorted in order of the topological sort of
388    * their dependency graph.<p>
389    * 
390    * This list will not contain the ids of extensions which have unsatisfied
391    * dependencies. To get a list of unsatisfied dependencies, call 
392    * {@link #getUnsatisfiedExtensions()}.<p>
393    * 
394    * This list may contain extensions whose dependencies are not entirely 
395    * satisfied because the dependency graph contained cycles. To get a list
396    * of all identified cycles in the dependency graph, call 
397    * getCycles().<p>
398    * 
399    * IDE implementations should generally process the manifests of extensions
400    * in the order the extensions are returned by this method.
401    * 
402    * @return the set of all extension ids retrieved from processed sources.
403    */
404   public List getSortedExtensionIDs()
405   {
406     ArrayList idList = new ArrayList( _topologicalExtensionList.size() );
407     for ( Iterator i = _topologicalExtensionList.iterator(); i.hasNext(); )
408     {
409       Extension ext = (Extension) i.next();
410       idList.add( ext.getID() );
411     }
412     
413     return Collections.unmodifiableList( idList );
414   }
415   
416   /**
417    * Get the resolved version of the specified extension id.
418    * 
419    * @param id the id of an extension.
420    * @return the resolved version of the extension.
421    */
422   public Version getResolvedVersion( String id )
423   {
424     Extension ext = (Extension) _extensionsById.get( id );
425     return ext.getVersion();
426   }
427   
428   /**
429    * Get cycles in the dependency graph. This method returns a collection of 
430    * Lists, one for each identified cycle in the graph. Each list entry consists
431    * of Extension instances for each extension in the cycle, starting at some
432    * arbitrary extension and ending with the second encountered instance of 
433    * some extension.
434    * 
435    * @return a collection of lists.
436    */
437   public Collection getCycles()
438   {
439     return _cycles;
440   }
441   
442   /**
443    * Get a collection of extensions with unsatisfied dependencies. 
444    * 
445    * @return a collection of Extensions which have one or more unsatisfied
446    *    dependencies.
447    */
448   public Collection getUnsatisfiedExtensions()
449   {
450     return Collections.unmodifiableCollection( _unsatisfiedDependencies.keySet() );
451   }
452   
453   /**
454    * Get the set of unsatisfied dependencies for the specified extension.
455    * 
456    * @param unsatisfied an unsatisfied Extension. Must be in the set of 
457    *    extensions returned by getUnsatisfiedExtensions(). Must not be null.
458    * @return a collection of ExtensionDependency instances, one for each
459    *    dependency of this extension that was missing.
460    */
461   public Collection getUnsatisfiedDependencies( Extension unsatisfied )
462   {
463     if ( unsatisfied == null )
464     {
465       throw new NullPointerException( "unsatisfied is null" );
466     }
467     Collection result = (Collection) _unsatisfiedDependencies.get( unsatisfied );
468     if ( result == null )
469     {
470       throw new IllegalArgumentException( 
471         "Not in the list of unsatisfied extensions: " + unsatisfied );
472     }
473     
474     return Collections.unmodifiableCollection( result );
475   }
476   
477   
478   
479   /**
480    * Get the source of an extension. If there is more than one extension with
481    * the same id, this returns the most recent version of the extension.
482    * 
483    * @param id the id of an extension. Must not be null, must be a known
484    *    extension.
485    * @return the source of the most recent version of the specified extension.
486    */
487   public ExtensionSource getSource( String id )
488   {
489     if ( id == null )
490     {
491       throw new NullPointerException( "id is null" );
492     }
493     
494     Extension extension = (Extension) _extensionsById.get( id );
495     if ( extension == null )
496     {
497       throw new IllegalArgumentException( "Unknown extension id " + id );
498     }
499     
500     return (ExtensionSource) _sourcesByExtension.get( extension );
501   }
502   
503 
504   
505   /**
506    * A minimal visitor which just processes the id, version, esdk-version and
507    * dependencies of an extension.
508    */
509   private static class MinimalExtensionVisitor extends BaseExtensionVisitor
510   {
511     private final Map _sourcesByExtension = new HashMap();
512     private ElementVisitor _dependenciesVisitor = new DependenciesVisitor();
513     
514     public Map getSourcesByExtension()
515     {
516       return _sourcesByExtension;
517     }
518     
519     public void start( ElementStartContext start )
520     {
521       DefaultExtension ext = processExtension( start );
522       if ( ext != null )
523       {
524         start.registerChildVisitor( DependenciesVisitor.ELEMENT, 
525           _dependenciesVisitor );
526       }
527     }
528     
529     public void end( ElementEndContext end )
530     {
531       ExtensionSource source = 
532         (ExtensionSource) end.getScopeData().get( SOURCE );
533       Extension extension = 
534         (Extension) end.getScopeData().get( ExtensionHook.KEY_EXTENSION );
535       if ( extension != null )
536       {
537         _sourcesByExtension.put( extension, source );
538       }
539     }
540   }
541   
542   // Vertex states for topologicalSort(). Should really be an enum.
543   private Object STATE_NOT_VISITED = "notvisited";
544   private Object STATE_VISITING = "visiting";
545   private Object STATE_VISITED = "visited";
546   
547   
548   /**
549    * Mechanism used by this class to look up whether an extension is 
550    * enabled.
551    */
552   public static interface EnabledExtensionLookup
553   {
554     public boolean isExtensionEnabled( Extension e );
555   }
556 }