%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
nl.toolforge.karma.core.cmd.util.DependencyHelper |
|
|
1 | /* |
|
2 | Karma core - Core of the Karma application |
|
3 | Copyright (C) 2004 Toolforge <www.toolforge.nl> |
|
4 | ||
5 | This library is free software; you can redistribute it and/or |
|
6 | modify it under the terms of the GNU Lesser General Public |
|
7 | License as published by the Free Software Foundation; either |
|
8 | version 2.1 of the License, or (at your option) any later version. |
|
9 | ||
10 | This library is distributed in the hope that it will be useful, |
|
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
13 | Lesser General Public License for more details. |
|
14 | ||
15 | You should have received a copy of the GNU Lesser General Public |
|
16 | License along with this library; if not, write to the Free Software |
|
17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|
18 | */ |
|
19 | package nl.toolforge.karma.core.cmd.util; |
|
20 | ||
21 | import java.io.File; |
|
22 | import java.io.FileWriter; |
|
23 | import java.io.IOException; |
|
24 | import java.util.HashSet; |
|
25 | import java.util.Iterator; |
|
26 | import java.util.LinkedHashSet; |
|
27 | import java.util.Set; |
|
28 | ||
29 | import net.sf.sillyexceptions.OutOfTheBlueException; |
|
30 | ||
31 | import nl.toolforge.karma.core.Version; |
|
32 | import nl.toolforge.karma.core.boot.WorkingContext; |
|
33 | import nl.toolforge.karma.core.manifest.Manifest; |
|
34 | import nl.toolforge.karma.core.manifest.ManifestException; |
|
35 | import nl.toolforge.karma.core.module.Module; |
|
36 | import nl.toolforge.karma.core.module.ModuleTypeException; |
|
37 | import nl.toolforge.karma.core.scm.ModuleDependency; |
|
38 | import nl.toolforge.karma.core.vc.VersionControlException; |
|
39 | import nl.toolforge.karma.core.vc.cvsimpl.Utils; |
|
40 | ||
41 | /** |
|
42 | * Dependency management is heavily used by Karma. This helper class provides methods to resolve dependencies, check |
|
43 | * them, etc. |
|
44 | * |
|
45 | * @author D.A. Smedes |
|
46 | * @version $Id: DependencyHelper.java,v 1.28 2004/11/16 22:28:02 hippe Exp $ |
|
47 | */ |
|
48 | public final class DependencyHelper { |
|
49 | ||
50 | public final static String MODULE_DEPENDENCIES_PROPERTIES = "module-dependencies.properties"; |
|
51 | ||
52 | 0 | private Manifest manifest = null; |
53 | ||
54 | 0 | public DependencyHelper(Manifest currentManifest) { |
55 | ||
56 | 0 | if (currentManclass="keyword">ifest == null) { |
57 | 0 | throw new IllegalArgumentException("Manifest cannot be null."); |
58 | } |
|
59 | ||
60 | 0 | this.manifest = currentManifest; |
61 | 0 | } |
62 | ||
63 | /** |
|
64 | * Returns the classpath for <code>module</code>, or an empty <code>String</code> if no dependencies exist. |
|
65 | * |
|
66 | * @param module The module for which a classpath should be determined. |
|
67 | * @return See method description. |
|
68 | */ |
|
69 | public String getClassPath(Module module) throws ModuleTypeException, DependencyException { |
|
70 | ||
71 | 0 | Set deps = getAllDependencies(module, false, false); |
72 | 0 | return DependencyPath.concat(deps, false, ';'); |
73 | } |
|
74 | ||
75 | /** |
|
76 | * Returns the classpath for <code>module</code>, or an empty <code>String</code> if no dependencies exist. |
|
77 | * <p> |
|
78 | * The classpath consists of: |
|
79 | * <ul> |
|
80 | * <li>The classes of the module's dependencies. |
|
81 | * <li>The resources of the module's dependencies. |
|
82 | * <li>The test classes of the module's dependencies. |
|
83 | 0 | * <li>The test resources of the module's dependencies. |
84 | * </ul> |
|
85 | * @param module The module for which the test classpath should be determined. |
|
86 | 0 | * @return See method description. |
87 | 0 | */ |
88 | 0 | public String getTestClassPath(Module module) throws ModuleTypeException, DependencyException { |
89 | 0 | |
90 | 0 | Set deps = getAllDependencies(module, true, false); |
91 | 0 | return DependencyPath.concat(deps, false, ';'); |
92 | } |
|
93 | ||
94 | public Set getAllDependencies(Module module, boolean doTest, class="keyword">boolean doPackage) throws ModuleTypeException, DependencyException { |
|
95 | 0 | Set all = new LinkedHashSet(); |
96 | 0 | all.addAll(getModuleDependencies(module, doTest, doPackage)); |
97 | 0 | all.addAll(getJarDependencies(module, doPackage)); |
98 | 0 | return all; |
99 | } |
|
100 | ||
101 | /** |
|
102 | * Gets a <code>Set</code> of {@link DependencyPath}s, each one identifying the path to a module dependency (a |
|
103 | * dependency of <code>module</code> to another <code>Module</code>). |
|
104 | 0 | * |
105 | 0 | * @param module The module for which a dependency-path should be determined. |
106 | 0 | * @param doTest Whether to include test resources for all deps. |
107 | * @param doPackage Whether to include only the deps that are to be packaged or all deps. |
|
108 | 0 | * @param moduleType Only return modules of the specified type. Return all types when null. |
109 | 0 | * |
110 | 0 | * @return See method description. |
111 | 0 | * @throws DependencyException When a dependency for a module is not available. |
112 | 0 | */ |
113 | 0 | public Set getModuleDependencies(Module module, boolean doTest, class="keyword">boolean doPackage, Module.Type moduleType) throws ModuleTypeException, DependencyException { |
114 | 0 | if (module == null) { |
115 | 0 | throw new IllegalArgumentException("Module cannot be null."); |
116 | 0 | } |
117 | ||
118 | 0 | Set s = new LinkedHashSet(); |
119 | ||
120 | 0 | for (Iterator iterator = module.getDependencies().iterator(); iterator.hasNext();) { |
121 | 0 | ModuleDependency dep = (ModuleDependency) iterator.next(); |
122 | 0 | if (dep.isModuleDependency()) { |
123 | ||
124 | 0 | try { |
125 | 0 | Module depModule = manifest.getModule(dep.getModule()); |
126 | 0 | DependencyPath path; |
127 | 0 | |
128 | 0 | //when packaging we want to have the archive |
129 | 0 | //when we are not packaging, i.e. building or testing, then we want the classes. |
130 | //todo: refactor the code below to minimize duplicate code. |
|
131 | 0 | if (doPackage) { |
132 | 0 | path = new DependencyPath(manifest.getBuildBaseDirectory(), class="keyword">new File(dep.getModule(), resolveArchiveName(depModule))); |
133 | 0 | if (!path.exists()) { |
134 | 0 | throw new DependencyException(DependencyException.DEPENDENCY_NOT_FOUND, class="keyword">new Object[]{dep.getModule()}); |
135 | 0 | } |
136 | 0 | if ((!doPackage || dep.doPackage()) && |
137 | 0 | (moduleType == null || moduleType.equals(depModule.getType())) ) { |
138 | 0 | s.add(path); |
139 | 0 | } |
140 | } else { |
|
141 | 0 | Set subSet = getModuleDependencies(depModule, doTest, doPackage, moduleType); |
142 | 0 | path = new DependencyPath(manifest.getBuildBaseDirectory(), class="keyword">new File(dep.getModule(), "build")); |
143 | 0 | if (!path.exists()) { |
144 | 0 | throw new DependencyException(DependencyException.DEPENDENCY_NOT_FOUND, class="keyword">new Object[]{dep.getModule()}); |
145 | } |
|
146 | 0 | if ((!doPackage || dep.doPackage()) && |
147 | (moduleType == null || moduleType.equals(depModule.getType())) ) { |
|
148 | 0 | subSet.add(path); |
149 | } |
|
150 | 0 | if (doTest) { |
151 | //todo: in case of tests we need the resources as well. |
|
152 | //In case of a test dependency the test classes are needed, as well as |
|
153 | //the resources for running the tests. |
|
154 | //for the time being only do this for the java source modules. |
|
155 | 0 | path = new DependencyPath(manifest.getBuildBaseDirectory(), class="keyword">new File(dep.getModule(), "test/classes")); |
156 | 0 | if (moduleType == null || moduleType.equals(depModule.getType()) ) { |
157 | 0 | subSet.add(path); |
158 | 0 | } |
159 | 0 | if (moduleType != null && moduleType.equals(depModule.getType()) && |
160 | moduleType.equals(Module.JAVA_SOURCE_MODULE)) { |
|
161 | 0 | path = new DependencyPath(manifest.getBaseDirectory(), class="keyword">new File(module.getBaseDir().getPath(), "src/resources")); |
162 | 0 | if (path.exists()) { |
163 | 0 | subSet.add(path); |
164 | } |
|
165 | 0 | path = new DependencyPath(manifest.getBaseDirectory(), class="keyword">new File(module.getBaseDir().getPath(), "test/resources")); |
166 | 0 | if (path.exists()) { |
167 | 0 | subSet.add(path); |
168 | 0 | } |
169 | } |
|
170 | 0 | } |
171 | 0 | s.addAll(subSet); |
172 | 0 | } |
173 | 0 | } catch (ManifestException me) { |
174 | 0 | if (me.getErrorCode().equals(Manclass="keyword">ifestException.MODULE_NOT_FOUND)) { |
175 | 0 | throw new DependencyException(DependencyException.MODULE_NOT_IN_MANIFEST, me.getMessageArguments()); |
176 | 0 | } else { |
177 | 0 | throw new DependencyException(me.getErrorCode(), me.getMessageArguments()); |
178 | 0 | } |
179 | 0 | } |
180 | } |
|
181 | 0 | } |
182 | 0 | return s; |
183 | 0 | } |
184 | 0 | |
185 | /** |
|
186 | 0 | * Gets a <code>Set</code> of {@link DependencyPath}s, each one identifying the path to a module dependency (a |
187 | * dependency of <code>module</code> to another <code>Module</code>). |
|
188 | * |
|
189 | 0 | * @param module The module for which a dependency-path should be determined. |
190 | 0 | * @param doTest Whether to include test resources for all deps. |
191 | 0 | * @param doPackage Whether to include only the deps that are to be packaged or all deps. |
192 | 0 | * |
193 | * @return See method description. |
|
194 | 0 | * @throws DependencyException When a dependency for a module is not available. |
195 | 0 | */ |
196 | 0 | public Set getModuleDependencies(Module module, boolean doTest, class="keyword">boolean doPackage) throws ModuleTypeException, DependencyException { |
197 | 0 | return getModuleDependencies(module, doTest, doPackage, null); |
198 | 0 | } |
199 | ||
200 | 0 | /** |
201 | * Create a properties file that contains mappings from module name to |
|
202 | * module name plus version. E.g. karma-core -> karma-core_0-1. |
|
203 | * <p> |
|
204 | * The properties file is called 'module-dependencies.properties' and is |
|
205 | * stored in the build directory of the given module. |
|
206 | * </p> |
|
207 | */ |
|
208 | public void createModuleDependenciesFilter(Module module) throws DependencyException { |
|
209 | 0 | BuildEnvironment env = new BuildEnvironment(manifest, module); |
210 | ||
211 | 0 | FileWriter write1 = null; |
212 | try { |
|
213 | 0 | Set moduleDeps = module.getDependencies(); |
214 | 0 | Iterator it = moduleDeps.iterator(); |
215 | 0 | |
216 | 0 | File moduleBuildDir = env.getModuleBuildDirectory(); |
217 | 0 | moduleBuildDir.mkdirs(); |
218 | 0 | File archivesProperties = new File(moduleBuildDir, MODULE_DEPENDENCIES_PROPERTIES); |
219 | 0 | archivesProperties.createNewFile(); |
220 | 0 | write1 = new FileWriter(archivesProperties); |
221 | ||
222 | 0 | while (it.hasNext()) { |
223 | 0 | ModuleDependency dep = (ModuleDependency) it.next(); |
224 | 0 | if (dep.isModuleDependency()) { |
225 | 0 | Module mod = manifest.getModule(dep.getModule()); |
226 | ||
227 | 0 | write1.write(mod.getName()+"="+resolveArtifactName(mod)+"\n"); |
228 | } |
|
229 | } |
|
230 | 0 | } catch (ManifestException me) { |
231 | 0 | me.printStackTrace(); |
232 | 0 | } catch (IOException ioe) { |
233 | 0 | ioe.printStackTrace(); |
234 | } finally { |
|
235 | 0 | try { |
236 | 0 | write1.close(); |
237 | 0 | } catch (Exception e) { |
238 | 0 | throw new OutOfTheBlueException("Unexpected exception when closing file writer.", e); |
239 | 0 | } |
240 | } |
|
241 | 0 | } |
242 | ||
243 | /** |
|
244 | * Check whether a certain module has an other module as a dependency. |
|
245 | * |
|
246 | 0 | * @param module The module for which is checked whether it has <code>dependency</code> as a dependency. |
247 | 0 | * @param dependency The module for which to check whether it is a dependency of the current module. |
248 | * @param doPackage Whether to include only the deps that are to be packaged or all deps. |
|
249 | * @return Whether the given module had the other given module as a dependency. |
|
250 | 0 | */ |
251 | public boolean hasModuleDependency(Module module, Module dependency, class="keyword">boolean doPackage) { |
|
252 | 0 | Iterator it = module.getDependencies().iterator(); |
253 | ModuleDependency dep; |
|
254 | 0 | boolean found = false; |
255 | ||
256 | 0 | while (it.hasNext() && !found) { |
257 | 0 | dep = (ModuleDependency) it.next(); |
258 | 0 | if (dep.isModuleDependency()) { |
259 | 0 | if (dep.getModule().equals(dependency.getName())) { |
260 | 0 | found = (!doPackage || dep.doPackage()); |
261 | } |
|
262 | } |
|
263 | 0 | } |
264 | ||
265 | 0 | return found; |
266 | } |
|
267 | 0 | |
268 | ||
269 | /** |
|
270 | 0 | * <p>Gets a <code>Set</code> of {@link DependencyPath}s, each one identifying a <code>jar</code>-file. Jar files are looked |
271 | 0 | * up Maven-style (see {@link ModuleDependency}. |
272 | * |
|
273 | * @param module The module for which jar dependencies should be determined. |
|
274 | * @param doPackage Indicate if the dependencies that are to be packaged (<code><package="true"></code>) |
|
275 | 0 | * should be included (<code>true</code>) or all dependencies should be included |
276 | * (<code>false</code>). |
|
277 | * |
|
278 | * @return A <code>Set</code> containing {@link DependencyPath}s |
|
279 | * |
|
280 | * @throws DependencyException |
|
281 | * When a jar dependency is not phsyically available on disk. A check is performed on the |
|
282 | * existence of the jar file in either the local jar repository ({@link WorkingContext#getLocalRepository()}) or in |
|
283 | * the lib module that is specified as being part of the manifest. |
|
284 | */ |
|
285 | public Set getJarDependencies(Module module, boolean doPackage) throws DependencyException { |
|
286 | ||
287 | 0 | if (module == null) { |
288 | 0 | throw new IllegalArgumentException("Module cannot be null."); |
289 | } |
|
290 | ||
291 | 0 | Set s = new LinkedHashSet(); |
292 | ||
293 | 0 | for (Iterator iterator = module.getDependencies().iterator(); iterator.hasNext();) { |
294 | ||
295 | 0 | ModuleDependency dep = (ModuleDependency) iterator.next(); |
296 | 0 | |
297 | 0 | if (dep.isLibModuleDependency() || !dep.isModuleDependency()) { |
298 | 0 | DependencyPath path; |
299 | 0 | if (dep.isLibModuleDependency()) { |
300 | 0 | //dep on jar in lib module. This one is relative to the base dir of the manifest. |
301 | 0 | path = new DependencyPath(manifest.getModuleBaseDirectory(), class="keyword">new File(dep.getJarDependency())); |
302 | 0 | } else { |
303 | 0 | //dep on jar in Maven-style repo. |
304 | 0 | path = new DependencyPath(WorkingContext.getLocalRepository(), class="keyword">new File(dep.getJarDependency())); |
305 | 0 | } |
306 | 0 | if (!path.exists()) { |
307 | 0 | // todo this bit could have to download the dependency, like maven does. |
308 | 0 | throw new DependencyException(DependencyException.DEPENDENCY_NOT_FOUND, class="keyword">new Object[]{dep.getJarDependency()}); |
309 | 0 | } |
310 | 0 | |
311 | 0 | if (!doPackage || dep.doPackage()) { |
312 | 0 | s.add(path); |
313 | 0 | } |
314 | } |
|
315 | } |
|
316 | 0 | return s; |
317 | } |
|
318 | ||
319 | /** |
|
320 | * Determines the correct artifact name for <code>module</code>. |
|
321 | * The artifact-name is determined as follows: |
|
322 | * |
|
323 | * <ul> |
|
324 | * <li/>If the state of the module is <code>WORKING</code>, the artifact-name is |
|
325 | * <code><module-name>-WORKING</code>. |
|
326 | * <li/>If the state of the module is <code>DYNAMIC</code>, the artifact-name is |
|
327 | * <code><module-name>-<latest-versions></code>. |
|
328 | * <li/>If the state of the module is <code>STATIC</code>, the artifact-name is |
|
329 | * <code><module-name>-<version></code>. |
|
330 | * </ul> |
|
331 | * |
|
332 | * @param module The module for which to determine the artifact name. |
|
333 | * @return The artifact name |
|
334 | */ |
|
335 | public String resolveArtifactName(Module module) throws DependencyException { |
|
336 | ||
337 | 0 | String artifact = module.getName() + "-"; |
338 | ||
339 | 0 | String version = ""; |
340 | try { |
|
341 | 0 | if (manclass="keyword">ifest.getState(module).equals(Module.WORKING)) { |
342 | 0 | version = Module.WORKING.toString(); |
343 | 0 | } else if (manclass="keyword">ifest.getState(module).equals(Module.DYNAMIC)) { |
344 | 0 | version = Utils.getLocalVersion(module).toString(); |
345 | 0 | } else { // STATIC module |
346 | 0 | version = ((Module) module).getVersionAsString(); |
347 | 0 | } |
348 | 0 | version = version.replaceAll(Version.VERSION_SEPARATOR_CHAR, "."); |
349 | 0 | } catch (VersionControlException v) { |
350 | 0 | throw new DependencyException(v.getErrorCode(), v.getMessageArguments()); |
351 | 0 | } |
352 | 0 | artifact += version; |
353 | ||
354 | 0 | return artifact; |
355 | } |
|
356 | ||
357 | /** |
|
358 | * <p>Determines the correct archive name for <code>module</code>. The archive |
|
359 | * name is determined as follows: |
|
360 | * |
|
361 | * <ul> |
|
362 | * <li/>If the state of the module is <code>WORKING</code>, the archive-name is |
|
363 | * <code><module-name>_WORKING.jar</code>. |
|
364 | * <li/>If the state of the module is <code>DYNAMIC</code>, the archive-name is |
|
365 | * <code><module-name>_<latest-versions>.jar</code>. |
|
366 | 0 | * <li/>If the state of the module is <code>STATIC</code>, the archive-name is |
367 | * <code><module-name>_<version>.jar</code>. |
|
368 | * </ul> |
|
369 | * |
|
370 | * <p>The extension is <code>.war</code> if the module is a |
|
371 | * <code>webapp</code>-module and <code>.ear</code> if the module is an |
|
372 | * <code>eapp</code>-module |
|
373 | 0 | * |
374 | 0 | * @param module A <code>SourceModule</code> instance. |
375 | * @return The archive-name as determined the way as described above. |
|
376 | */ |
|
377 | 0 | public String resolveArchiveName(Module module) throws ModuleTypeException, DependencyException { |
378 | ||
379 | 0 | // todo introduce a method to determine if a module is webapp-module; maybe its own class. |
380 | 0 | // |
381 | String extension; |
|
382 | 0 | if (module.getType().equals(Module.JAVA_WEB_APPLICATION)) { |
383 | 0 | extension = ".war"; |
384 | 0 | } else if (module.getType().equals(Module.JAVA_ENTERPRISE_APPLICATION)) { |
385 | 0 | extension = ".ear"; |
386 | 0 | } else if (module.getType().equals(Module.JAVA_SOURCE_MODULE)) { |
387 | 0 | extension = ".jar"; |
388 | 0 | } else if (module.getType().equals(Module.OTHER_MODULE)) { |
389 | 0 | extension = ".zip"; |
390 | 0 | } else { |
391 | 0 | extension = ""; |
392 | } |
|
393 | 0 | return resolveArtifactName(module) + extension; |
394 | 0 | } |
395 | ||
396 | ||
397 | /** |
|
398 | * Traverses the modules' dependencies, and traverses all module dependencies as well (recursively), calculating |
|
399 | * the set of dependencies that are unique. A dependency is not unique if the artifact-name already exists in the |
|
400 | * set but with another version. This will result in a DependencyException. |
|
401 | * |
|
402 | * @param module |
|
403 | * |
|
404 | * @return A <code>Set</code> containing all dependencies, all the way down to the lowest |
|
405 | */ |
|
406 | public Set getAllLevels(Module module) throws ManifestException, DependencyException { |
|
407 | 0 | return getLevels(module, null); |
408 | } |
|
409 | ||
410 | // Method that is called recursively to dive into a modules' dependency tree. |
|
411 | // |
|
412 | private Set getLevels(Module module, Set currentSet) throws ManifestException, DependencyException{ |
|
413 | ||
414 | 0 | if (currentSet == null) { |
415 | 0 | currentSet = new HashSet(); |
416 | } |
|
417 | ||
418 | 0 | Set moduleDeps = module.getDependencies(); |
419 | ||
420 | 0 | Iterator i = moduleDeps.iterator(); |
421 | 0 | while (i.hasNext()) { |
422 | ||
423 | 0 | ModuleDependency dep = (ModuleDependency) i.next(); |
424 | ||
425 | 0 | if (!currentSet.add(dep)) { |
426 | // todo ??????????????????????????????????????????????? |
|
427 | // throw new DependencyException(DependencyException.DUPLICATE_ARTIFACT_VERSION); |
|
428 | } else { |
|
429 | 0 | if (dep.isModuleDependency()) { |
430 | 0 | Module moduleDep = manifest.getModule(dep.getModule()); |
431 | 0 | currentSet.addAll(getLevels(moduleDep, currentSet)); |
432 | } |
|
433 | } |
|
434 | } |
|
435 | 0 | return currentSet; |
436 | } |
|
437 | ||
438 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |