View Javadoc

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    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           //when packaging we want to have the archive
129           //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           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               //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               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>&lt;package="true"&gt;</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           //dep on jar in lib module. This one is relative to the base dir of the manifest.
301           path = new DependencyPath(manifest.getModuleBaseDirectory(), new File(dep.getJarDependency()));
302         } else {
303           //dep on jar in Maven-style repo.
304           path = new DependencyPath(WorkingContext.getLocalRepository(), new File(dep.getJarDependency()));
305         }
306         if (!path.exists()) {
307           // todo this bit could have to download the dependency, like maven does.
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>&lt;module-name&gt;-WORKING</code>.
326    *   <li/>If the state of the module is <code>DYNAMIC</code>, the artifact-name is
327    *        <code>&lt;module-name&gt;-&lt;latest-versions&gt;</code>.
328    *   <li/>If the state of the module is <code>STATIC</code>, the artifact-name is
329    *        <code>&lt;module-name&gt;-&lt;version&gt;</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 { // STATIC module
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>&lt;module-name&gt;_WORKING.jar</code>.
364    *   <li/>If the state of the module is <code>DYNAMIC</code>, the archive-name is
365    *        <code>&lt;module-name&gt;_&lt;latest-versions&gt;.jar</code>.
366    *   <li/>If the state of the module is <code>STATIC</code>, the archive-name is
367    *        <code>&lt;module-name&gt;_&lt;version&gt;.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     // todo introduce a method to determine if a module is webapp-module; maybe its own class.
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   // 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     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 //        todo ???????????????????????????????????????????????
427 //        throw new DependencyException(DependencyException.DUPLICATE_ARTIFACT_VERSION);
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 }