001    /*
002     * Copyright 2001,2004 The Apache Software Foundation.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.apache.commons.jjar;
018    
019    import java.util.HashMap;
020    import java.util.List;
021    import java.util.Iterator;
022    import java.util.ArrayList;
023    import java.lang.Thread;
024    
025    /**
026     *  <p>
027     *  Simple class to figure out ordered dependency lists.  Basic
028     *  idea is that you load it with datum consisting of a set 
029     *  consisting of  a package name and list of packages that 
030     *  it's dependent upon.
031     *  </p>
032     *
033     *  <p>
034     *  Then, you should be able to ask for the dependencies for any
035     *  package placed in there.
036     *  </p>
037     *
038     *  <p> will detect loops at 'runtime', not loadtime.  Just punts
039     *  when that happens
040     *  </p>
041     *
042     *  <p>
043     *  This thing isn't close to threadsafe :)
044     *  </p>
045     *
046     *  @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>
047     *  @version $Id: DependencyEngine.java 155454 2005-02-26 13:23:34Z dirkv $ 
048     */
049    public class DependencyEngine
050    {    
051        private HashMap projects = new HashMap();
052        private ArrayList buildList = null;
053    
054        /**
055         *  this is a real sucky solution to something I don't want to 
056         *  think about right now...  we use this to ensure that
057         *  the information in our graph is fresh
058         */
059        private long currentTimestamp = -1;
060        
061        /**
062         *  CTOR 
063         */
064        public DependencyEngine()
065        {
066        }
067    
068        /**
069         * Reset the dependency engine, clear all entries
070         * and start from scratch.
071         */
072        public void reset()
073        {
074            projects = new HashMap();
075        }        
076    
077        /**
078         *  returns a list of dependencies for a given package
079         *  with the target being excluded from the list.
080         *
081         *  @param pkg package to get dependency list for
082         *  @return List list of dependencies, in order
083         */
084        public List getDependencies( String pkg )
085        {
086            return getDependencies(pkg, true);
087        }        
088    
089        /**
090         *  returns a list of dependencies for a given package
091         *  allowing the exclusion/inclusion of the target package.
092         *
093         *  @param pkg package to get dependency list for
094         *  @param excludeTarget boolean to control exclusion of target package
095         *  @return List list of dependencies, in order
096         */
097        public List getDependencies( String pkg, boolean excludeTarget )
098        {
099            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