1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 private Manifest manifest = null;
53
54 public DependencyHelper(Manifest currentManifest) {
55
56 if (currentManifest == null) {
57 throw new IllegalArgumentException("Manifest cannot be null.");
58 }
59
60 this.manifest = currentManifest;
61 }
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 Set deps = getAllDependencies(module, false, false);
72 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 * <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 * @return See method description.
87 */
88 public String getTestClassPath(Module module) throws ModuleTypeException, DependencyException {
89
90 Set deps = getAllDependencies(module, true, false);
91 return DependencyPath.concat(deps, false, ';');
92 }
93
94 public Set getAllDependencies(Module module, boolean doTest, boolean doPackage) throws ModuleTypeException, DependencyException {
95 Set all = new LinkedHashSet();
96 all.addAll(getModuleDependencies(module, doTest, doPackage));
97 all.addAll(getJarDependencies(module, doPackage));
98 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 *
105 * @param module The module for which a dependency-path should be determined.
106 * @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 * @param moduleType Only return modules of the specified type. Return all types when null.
109 *
110 * @return See method description.
111 * @throws DependencyException When a dependency for a module is not available.
112 */
113 public Set getModuleDependencies(Module module, boolean doTest, boolean doPackage, Module.Type moduleType) throws ModuleTypeException, DependencyException {
114 if (module == null) {
115 throw new IllegalArgumentException("Module cannot be null.");
116 }
117
118 Set s = new LinkedHashSet();
119
120 for (Iterator iterator = module.getDependencies().iterator(); iterator.hasNext();) {
121 ModuleDependency dep = (ModuleDependency) iterator.next();
122 if (dep.isModuleDependency()) {
123
124 try {
125 Module depModule = manifest.getModule(dep.getModule());
126 DependencyPath path;
127
128
129
130
131 if (doPackage) {
132 path = new DependencyPath(manifest.getBuildBaseDirectory(), new File(dep.getModule(), resolveArchiveName(depModule)));
133 if (!path.exists()) {
134 throw new DependencyException(DependencyException.DEPENDENCY_NOT_FOUND, new Object[]{dep.getModule()});
135 }
136 if ((!doPackage || dep.doPackage()) &&
137 (moduleType == null || moduleType.equals(depModule.getType())) ) {
138 s.add(path);
139 }
140 } else {
141 Set subSet = getModuleDependencies(depModule, doTest, doPackage, moduleType);
142 path = new DependencyPath(manifest.getBuildBaseDirectory(), new File(dep.getModule(), "build"));
143 if (!path.exists()) {
144 throw new DependencyException(DependencyException.DEPENDENCY_NOT_FOUND, new Object[]{dep.getModule()});
145 }
146 if ((!doPackage || dep.doPackage()) &&
147 (moduleType == null || moduleType.equals(depModule.getType())) ) {
148 subSet.add(path);
149 }
150 if (doTest) {
151
152
153
154
155 path = new DependencyPath(manifest.getBuildBaseDirectory(), new File(dep.getModule(), "test/classes"));
156 if (moduleType == null || moduleType.equals(depModule.getType()) ) {
157 subSet.add(path);
158 }
159 if (moduleType != null && moduleType.equals(depModule.getType()) &&
160 moduleType.equals(Module.JAVA_SOURCE_MODULE)) {
161 path = new DependencyPath(manifest.getBaseDirectory(), new File(module.getBaseDir().getPath(), "src/resources"));
162 if (path.exists()) {
163 subSet.add(path);
164 }
165 path = new DependencyPath(manifest.getBaseDirectory(), new File(module.getBaseDir().getPath(), "test/resources"));
166 if (path.exists()) {
167 subSet.add(path);
168 }
169 }
170 }
171 s.addAll(subSet);
172 }
173 } catch (ManifestException me) {
174 if (me.getErrorCode().equals(ManifestException.MODULE_NOT_FOUND)) {
175 throw new DependencyException(DependencyException.MODULE_NOT_IN_MANIFEST, me.getMessageArguments());
176 } else {
177 throw new DependencyException(me.getErrorCode(), me.getMessageArguments());
178 }
179 }
180 }
181 }
182 return s;
183 }
184
185 /***
186 * 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 * @param module The module for which a dependency-path should be determined.
190 * @param doTest Whether to include test resources for all deps.
191 * @param doPackage Whether to include only the deps that are to be packaged or all deps.
192 *
193 * @return See method description.
194 * @throws DependencyException When a dependency for a module is not available.
195 */
196 public Set getModuleDependencies(Module module, boolean doTest, boolean doPackage) throws ModuleTypeException, DependencyException {
197 return getModuleDependencies(module, doTest, doPackage, null);
198 }
199
200 /***
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 BuildEnvironment env = new BuildEnvironment(manifest, module);
210
211 FileWriter write1 = null;
212 try {
213 Set moduleDeps = module.getDependencies();
214 Iterator it = moduleDeps.iterator();
215
216 File moduleBuildDir = env.getModuleBuildDirectory();
217 moduleBuildDir.mkdirs();
218 File archivesProperties = new File(moduleBuildDir, MODULE_DEPENDENCIES_PROPERTIES);
219 archivesProperties.createNewFile();
220 write1 = new FileWriter(archivesProperties);
221
222 while (it.hasNext()) {
223 ModuleDependency dep = (ModuleDependency) it.next();
224 if (dep.isModuleDependency()) {
225 Module mod = manifest.getModule(dep.getModule());
226
227 write1.write(mod.getName()+"="+resolveArtifactName(mod)+"\n");
228 }
229 }
230 } catch (ManifestException me) {
231 me.printStackTrace();
232 } catch (IOException ioe) {
233 ioe.printStackTrace();
234 } finally {
235 try {
236 write1.close();
237 } catch (Exception e) {
238 throw new OutOfTheBlueException("Unexpected exception when closing file writer.", e);
239 }
240 }
241 }
242
243 /***
244 * Check whether a certain module has an other module as a dependency.
245 *
246 * @param module The module for which is checked whether it has <code>dependency</code> as a dependency.
247 * @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 */
251 public boolean hasModuleDependency(Module module, Module dependency, boolean doPackage) {
252 Iterator it = module.getDependencies().iterator();
253 ModuleDependency dep;
254 boolean found = false;
255
256 while (it.hasNext() && !found) {
257 dep = (ModuleDependency) it.next();
258 if (dep.isModuleDependency()) {
259 if (dep.getModule().equals(dependency.getName())) {
260 found = (!doPackage || dep.doPackage());
261 }
262 }
263 }
264
265 return found;
266 }
267
268
269 /***
270 * <p>Gets a <code>Set</code> of {@link DependencyPath}s, each one identifying a <code>jar</code>-file. Jar files are looked
271 * 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 * 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 if (module == null) {
288 throw new IllegalArgumentException("Module cannot be null.");
289 }
290
291 Set s = new LinkedHashSet();
292
293 for (Iterator iterator = module.getDependencies().iterator(); iterator.hasNext();) {
294
295 ModuleDependency dep = (ModuleDependency) iterator.next();
296
297 if (dep.isLibModuleDependency() || !dep.isModuleDependency()) {
298 DependencyPath path;
299 if (dep.isLibModuleDependency()) {
300
301 path = new DependencyPath(manifest.getModuleBaseDirectory(), new File(dep.getJarDependency()));
302 } else {
303
304 path = new DependencyPath(WorkingContext.getLocalRepository(), new File(dep.getJarDependency()));
305 }
306 if (!path.exists()) {
307
308 throw new DependencyException(DependencyException.DEPENDENCY_NOT_FOUND, new Object[]{dep.getJarDependency()});
309 }
310
311 if (!doPackage || dep.doPackage()) {
312 s.add(path);
313 }
314 }
315 }
316 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 String artifact = module.getName() + "-";
338
339 String version = "";
340 try {
341 if (manifest.getState(module).equals(Module.WORKING)) {
342 version = Module.WORKING.toString();
343 } else if (manifest.getState(module).equals(Module.DYNAMIC)) {
344 version = Utils.getLocalVersion(module).toString();
345 } else {
346 version = ((Module) module).getVersionAsString();
347 }
348 version = version.replaceAll(Version.VERSION_SEPARATOR_CHAR, ".");
349 } catch (VersionControlException v) {
350 throw new DependencyException(v.getErrorCode(), v.getMessageArguments());
351 }
352 artifact += version;
353
354 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 * <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 *
374 * @param module A <code>SourceModule</code> instance.
375 * @return The archive-name as determined the way as described above.
376 */
377 public String resolveArchiveName(Module module) throws ModuleTypeException, DependencyException {
378
379
380
381 String extension;
382 if (module.getType().equals(Module.JAVA_WEB_APPLICATION)) {
383 extension = ".war";
384 } else if (module.getType().equals(Module.JAVA_ENTERPRISE_APPLICATION)) {
385 extension = ".ear";
386 } else if (module.getType().equals(Module.JAVA_SOURCE_MODULE)) {
387 extension = ".jar";
388 } else if (module.getType().equals(Module.OTHER_MODULE)) {
389 extension = ".zip";
390 } else {
391 extension = "";
392 }
393 return resolveArtifactName(module) + extension;
394 }
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 return getLevels(module, null);
408 }
409
410
411
412 private Set getLevels(Module module, Set currentSet) throws ManifestException, DependencyException{
413
414 if (currentSet == null) {
415 currentSet = new HashSet();
416 }
417
418 Set moduleDeps = module.getDependencies();
419
420 Iterator i = moduleDeps.iterator();
421 while (i.hasNext()) {
422
423 ModuleDependency dep = (ModuleDependency) i.next();
424
425 if (!currentSet.add(dep)) {
426
427
428 } else {
429 if (dep.isModuleDependency()) {
430 Module moduleDep = manifest.getModule(dep.getModule());
431 currentSet.addAll(getLevels(moduleDep, currentSet));
432 }
433 }
434 }
435 return currentSet;
436 }
437
438 }