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