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