View Javadoc

1   /*
2    * Copyright 2001,2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.apache.commons.jjar;
18  
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Iterator;
22  import java.util.ArrayList;
23  import java.lang.Thread;
24  
25  /**
26   *  <p>
27   *  Simple class to figure out ordered dependency lists.  Basic
28   *  idea is that you load it with datum consisting of a set 
29   *  consisting of  a package name and list of packages that 
30   *  it's dependent upon.
31   *  </p>
32   *
33   *  <p>
34   *  Then, you should be able to ask for the dependencies for any
35   *  package placed in there.
36   *  </p>
37   *
38   *  <p> will detect loops at 'runtime', not loadtime.  Just punts
39   *  when that happens
40   *  </p>
41   *
42   *  <p>
43   *  This thing isn't close to threadsafe :)
44   *  </p>
45   *
46   *  @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
47   *  @version $Id: DependencyEngine.java 155454 2005-02-26 13:23:34Z dirkv $ 
48   */
49  public class DependencyEngine
50  {    
51      private HashMap projects = new HashMap();
52      private ArrayList buildList = null;
53  
54      /**
55       *  this is a real sucky solution to something I don't want to 
56       *  think about right now...  we use this to ensure that
57       *  the information in our graph is fresh
58       */
59      private long currentTimestamp = -1;
60      
61      /**
62       *  CTOR 
63       */
64      public DependencyEngine()
65      {
66      }
67  
68      /**
69       * Reset the dependency engine, clear all entries
70       * and start from scratch.
71       */
72      public void reset()
73      {
74          projects = new HashMap();
75      }        
76  
77      /**
78       *  returns a list of dependencies for a given package
79       *  with the target being excluded from the list.
80       *
81       *  @param pkg package to get dependency list for
82       *  @return List list of dependencies, in order
83       */
84      public List getDependencies( String pkg )
85      {
86          return getDependencies(pkg, true);
87      }        
88  
89      /**
90       *  returns a list of dependencies for a given package
91       *  allowing the exclusion/inclusion of the target package.
92       *
93       *  @param pkg package to get dependency list for
94       *  @param excludeTarget boolean to control exclusion of target package
95       *  @return List list of dependencies, in order
96       */
97      public List getDependencies( String pkg, boolean excludeTarget )
98      {
99          buildList = new ArrayList();
100 
101         try
102         {
103             /*
104              *  if we are called in the same millisecond as our 
105              *  last trip through, sleep as the sucky 'fresh graph'
106              *  solution depends on this, and this would be quite
107              *  an interesting time-dependent thing to debug :)
108              */
109             if (System.currentTimeMillis() == currentTimestamp)
110             {
111                 Thread.sleep(1);
112             }
113 
114             /*
115              *  set the current time so we can see if our graph node
116              *  state is for this trip, or a previous trip.
117              */
118             currentTimestamp = System.currentTimeMillis();
119 
120             /*
121              *  now, just do it
122              */
123             doIt( pkg );
124         }
125         catch( Exception e )
126         {
127             System.out.println("DE.getDependencies() : " + pkg + " : "  + e);
128         }
129         
130         // The the multi project dep list this code is lopping
131         // off the package stated as the target. Need a flag to
132         // indicated whether you want the target included or
133         // or. For a multi-project build like maven you need
134         // the target because you actually want to build the
135         // target. For the JJAR task you don't want the target
136         // because you're just downloading JARs.
137         if( excludeTarget && buildList.size() > 0)
138         {
139             buildList.remove( buildList.size() - 1 );
140         }
141 
142         return buildList;
143     }
144 
145     /**
146      *  Generates a dependency list for a set of packages.
147      *
148      *  @param packages List of strings, each string is a package name
149      *  @return list of dependencies, in order
150      */
151     public List getDependencies(List packages)
152     {
153         return getDependencies(packages, true);
154     }
155 
156     /**
157      *  Generates a dependency list for a set of packages
158      *  where there is the option to exclude/include the
159      *  target packages.
160      *
161      *  @param packages List of strings, each string is a package name
162      *  @param excludeTarget boolean to exclude target
163      *  @return List list of dependencies, in order
164      */
165     public List getDependencies( List packages, boolean excludeTarget )
166     {
167         HashMap h = new HashMap();
168         ArrayList l = new ArrayList();
169 
170         /*
171          *  for each package, get the dependency list
172          *  and drop them into the list if it's not already 
173          *  in there
174          */
175         
176         for( Iterator i = packages.iterator(); i.hasNext(); )
177         {
178             String pkg = (String) i.next();
179 
180             List deps = getDependencies( pkg, excludeTarget );
181 
182             for (Iterator ii = deps.iterator(); ii.hasNext(); )
183             {
184                 String dep = (String) ii.next();
185 
186                 if ( h.get( dep ) == null)
187                 {
188                     h.put(dep, dep);
189                     l.add(dep);
190                 }
191             }
192         }
193 
194         return l;
195     }
196 
197 
198     /**
199      *  from previous use - generates a dependency list
200      *  spanning the entire tree.  Returns a list
201      *  of names.
202      */
203     public List generateNamelist()
204         throws Exception
205     {
206         /*
207          *  get the project list
208          */
209 
210         buildList = new ArrayList();
211 
212         Iterator i = projects.keySet().iterator();
213 
214         while(i.hasNext())
215         {
216             String s = (String) i.next();
217 
218             /*
219              *  make them by name
220              */
221 
222             doIt( s );
223         }
224 
225         return buildList;
226     }
227 
228     /**
229      *  from previous use - generates a dependency list
230      *  spanning the entire tree.  Returns a list
231      *  of cookies.
232      */
233     public List generateCookielist()
234         throws Exception
235     {
236         /*
237          *  get the project list
238          */
239 
240         List list = generateNamelist();
241         ArrayList cookies = new ArrayList();
242 
243         Iterator i = list.iterator();
244 
245         while(i.hasNext())
246         {
247             String s = (String) i.next();
248             Node n = (Node) projects.get( s );
249 
250             cookies.add( n.getCookie() );
251         }
252         
253         return cookies;
254     }
255 
256     /**
257      *  The recursive worker...
258      */
259     void doIt( String current )
260         throws Exception
261     {
262         Node project = (Node) projects.get(current);
263 
264         if (project == null)
265         {
266             /*
267              *  we may have a dependency that isn't a project.  
268              *  so what... (This shouldn't happen)
269              */
270 
271             buildList.add( current );
272             return;
273         }
274 
275         /*
276          *  get the timestamp and compare.  If not the same, reset
277          */
278 
279         if ( project.getTimestamp() != currentTimestamp)
280         {
281             project.setStatus( Node.ZILCH );
282         }
283 
284         project.setTimestamp( currentTimestamp );
285 
286         /*
287          *  check status of this one
288          */
289 
290         int status = project.getStatus();
291 
292         if ( status == Node.WORKING )
293         {
294            throw new Exception("Detected loop while trying to build " + current);
295         }
296         else if ( status == Node.ZILCH )
297         {
298             /*
299              *   not working - so mark as working and start on the dependencies
300              */
301             project.setStatus( Node.WORKING );
302 
303             /*
304              *  do we have any dependencies?
305              */
306             Iterator deps =  project.getDeps();
307 
308             /*
309              *  if so, work on each
310              */
311 
312             while( deps.hasNext() )
313             {
314                 String dep = (String) deps.next();
315                 Node depnode = (Node) projects.get( dep );
316 
317                 if (depnode == null)
318                 {
319                     /*
320                      *  we don't have this as a project, so 
321                      *  let the client try to build it...
322                      */
323                     
324                     // System.out.println("Adding non-project dep  build list : " + current );
325 
326                     buildList.add( dep );
327                     continue;
328                 }
329                 
330                 /*
331                  *  get the timestamp and compare.  If not the same, reset
332                  */
333                                 
334                 if ( depnode.getTimestamp() != currentTimestamp)
335                 {
336                     depnode.setStatus( Node.ZILCH );
337                 }
338                 
339                 depnode.setTimestamp( currentTimestamp );
340 
341                 /*
342                  * now, look at the status of this dependency
343                  */
344 
345                 int depstatus = depnode.getStatus();
346 
347                 if ( depstatus == Node.WORKING )
348                 {
349                     /*
350                      *  gaak. loop!
351                      */
352                     throw new Exception("LOOP : checking dep " + dep + " for current = " + current );
353                 }
354                 else if (  depstatus == Node.ZILCH )
355                 {
356                     //                    System.out.println(" trying to build " + current + " : need to build dep " + dep );
357                     
358                     /*
359                      *  recurse
360                      */
361 
362                     doIt( dep );
363                 }
364                 else if(  depstatus == Node.DONE ) 
365                 {
366                     // can skip
367                 }
368             }
369             
370             /*
371              *  if all clear, can build and mark as done.  We don't care
372              *  if the client couldn't do it for now.  That may change.
373              *  the client can tell
374              */
375             
376             //System.out.println("Adding to build list : " + current );
377 
378             buildList.add( current );
379             project.setStatus( Node.DONE );
380             
381             return;
382         }
383 
384         /*
385          *   node is done
386          */
387 
388         return; 
389     }
390 
391     public void addProject(String project, List dependencies)
392         throws Exception
393     {
394         addProject(project, dependencies, project);
395     }
396 
397     /**
398      *  Adds a project and it's associated dependencies.  The dependencies
399      *  currently do not have to be projects themselves.
400      *
401      *  @param project  Name of project to add
402      *  @param dependencies  java.util.List of project dependencies
403      *  @throws Exception in the even that it already has the project in the list
404      */
405     public void addProject( String project, List dependencies, Object cookie )
406         throws Exception
407     {
408         /*
409          *  first, see if we have it
410          */
411         Node n = (Node) projects.get( project );
412 
413         if (n != null)
414         {
415             //System.out.println(" addProject() : rejecting duplicate : " + project );
416             throw new Exception("already have it...");
417         }
418 
419         // System.out.println(" addProject() : adding project : " + project );
420 
421         /*
422          *  make a new one and add the dependencies
423          */
424         n = new Node( project, cookie );
425 
426         Iterator i = dependencies.iterator();
427 
428         while( i.hasNext() )
429         {
430             String dep = (String) i.next();
431 
432             if ( dep.equals( project ) )
433             {
434                 // System.out.println(" addProject() : rejecting self- dependency : " + project );
435             }
436             else
437             {
438                 // System.out.println(" addProject() :  adding dependency : " + dep + " for project : "  + project );
439                 n.addDep(  dep  );
440             }
441         }
442 
443         /*
444          * add to the pile
445          */
446 
447         projects.put( project, n );
448 
449         return;
450     }
451 }
452 
453 class Node
454 {
455     public static  int ZILCH = 0;
456     public static  int WORKING = 1;
457     public static  int DONE = 2;
458 
459     private int status = ZILCH;
460     private ArrayList deps = new ArrayList();
461     private String name = "";
462     private Object cookie = null;
463     private long timestamp = 0;
464 
465     public Node( String name, Object cookie)
466     {
467         this.name = name;
468         this.cookie = cookie;
469     }
470     
471     public Object getCookie()
472     {
473         return cookie;
474     }
475 
476     public void addDep( String dep )
477     {
478         deps.add( dep );
479     }
480     
481     public Iterator getDeps()
482     {
483         return deps.iterator();
484     }
485     
486     public void setStatus( int i )
487     {
488         status = i;
489     }
490     
491     public int getStatus()
492     {
493         return status;
494     }
495 
496     public long getTimestamp()
497     {
498         return timestamp;
499     }
500 
501     public void setTimestamp( long t)
502     {
503         timestamp = t;
504     }
505 }
506 
507