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.manifest;
20  
21  import nl.toolforge.karma.core.KarmaRuntimeException;
22  import nl.toolforge.karma.core.boot.WorkingContext;
23  import nl.toolforge.karma.core.location.LocationException;
24  import nl.toolforge.karma.core.module.BaseModule;
25  import nl.toolforge.karma.core.module.Module;
26  import nl.toolforge.karma.core.module.ModuleDigester;
27  import nl.toolforge.karma.core.module.ModuleFactory;
28  import nl.toolforge.karma.core.scm.ModuleDependency;
29  import nl.toolforge.karma.core.vc.cvsimpl.AdminHandler;
30  import org.apache.commons.io.FileUtils;
31  
32  import java.io.File;
33  import java.io.FilenameFilter;
34  import java.io.IOException;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.HashMap;
38  import java.util.HashSet;
39  import java.util.Hashtable;
40  import java.util.Iterator;
41  import java.util.Map;
42  import java.util.Set;
43  
44  /***
45   * <p>General stuff for a manifest.</p>
46   *
47   * <p>Check the <a href="package-summary.html">package documentation</a> for more information on the concepts behind
48   * Karma.</p>
49   *
50   * @author D.A. Smedes
51   * @version $Id: AbstractManifest.java,v 1.38 2004/11/10 23:53:09 asmedes Exp $
52   */
53  public abstract class AbstractManifest implements Manifest {
54  
55    private Collection childManifests = new ArrayList();
56  
57    private String name = null;
58    private String version = null;
59    private String description = null;
60  
61    private Map modules = null;
62  
63    private WorkingContext workingContext = null;
64    private ManifestStructure manifestStructure = null;
65  
66    private File manifestBaseDirectory = null;
67    private File manifestTempDirectory = null;
68    private Map moduleCache = null;
69  
70    /***
71     * Constructs a manifest instance; <code>name</code> is mandatory.
72     */
73    public AbstractManifest(WorkingContext workingContext, String name) throws ManifestException, LocationException {
74  
75      if ("".equals(name) || name == null) {
76        throw new IllegalArgumentException("Manifest name cannot be empty or null.");
77      }
78      this.name = name;
79  
80      ManifestLoader loader = new ManifestLoader(workingContext);
81      this.manifestStructure = loader.load(name);
82  
83      init();
84    }
85  
86    /***
87     * A manifest is created based on its <code>ManifestStructure</code>, which can be loaded by the
88     * <code>ManifestLoader</code>. The <code>ManifestStructure</code> is the basis for the Manifest; a number of checks
89     * are applied to it, including a linking of the manifest to the {@link WorkingContext}.
90     *
91     * @param workingContext The current working context.
92     * @param structure The ManifestStructure, which is the basis for the manifest.
93     */
94    public AbstractManifest(WorkingContext workingContext, ManifestStructure structure) throws LocationException {
95  
96      this.workingContext = workingContext;
97      this.manifestStructure = structure;
98  
99      this.name = structure.getName();
100 
101     init();
102   }
103 
104   private void init() throws LocationException {
105 
106     // Recursively load all modules from the root manifest and all includes to have them available quickly as one
107     // list.
108     //
109     copyStructure();
110 
111     // Apply the current working context to this manifest.
112     //
113     applyWorkingContext();
114   }
115 
116   //
117   //
118   //
119   private void copyStructure() throws LocationException {
120 
121     // Step 1
122     //
123     modules = new Hashtable();
124 
125     ModuleFactory moduleFactory = new ModuleFactory(workingContext);
126 
127     for (Iterator i = manifestStructure.getModules().iterator(); i.hasNext();) {
128       Module module = moduleFactory.create((ModuleDigester) i.next(), Module.UNKNOWN);
129       modules.put(module.getName(), module);
130     }
131 
132     // Step 2
133     //
134     moduleCache = new HashMap();
135 
136     // If there is nothing in the cache, there is a chance that we have not yet
137 
138     ManifestFactory factory = new ManifestFactory();
139 
140     for (Iterator i = manifestStructure.getChilds().values().iterator(); i.hasNext();) {
141 
142       ManifestStructure childStructure = (ManifestStructure) i.next();
143       Manifest manifest = factory.create(workingContext, childStructure);
144 
145       childManifests.add(manifest);
146       moduleCache.putAll(manifest.getAllModules());
147     }
148     moduleCache.putAll(getModulesForManifest());
149   }
150 
151   //
152   //
153   //
154   private void applyWorkingContext() {
155 
156     manifestBaseDirectory = new File(workingContext.getProjectBaseDirectory(), getName());
157     manifestTempDirectory = new File(getBaseDirectory(), "tmp");
158 
159     for (Iterator i = moduleCache.values().iterator(); i.hasNext();) {
160 
161       Module module = (Module) i.next();
162       setModuleBaseDir(module);
163       applyWorkingContext(workingContext, module);
164       removeLocal(module);
165     }
166   }
167 
168   /***
169    * A specific <code>Manifest</code> implementation may have to apply specific actions to modules per working context.
170    * Each implementation should therefor implement this method and do what it has to do.
171    *
172    * @param context The current {@link WorkingContext}.
173    * @param module The module to which <code>context</code> should be applied.
174    */
175   protected abstract void applyWorkingContext(WorkingContext context, Module module);
176 
177   public final File getBaseDirectory() {
178 
179     if (!manifestBaseDirectory.exists()) {
180       manifestBaseDirectory.mkdir();
181     }
182     return manifestBaseDirectory;
183   }
184 
185   public File getBuildBaseDirectory() {
186 
187     File f = new File(getBaseDirectory(), "build");
188 
189     if (!f.exists()) {
190       f.mkdir();
191     }
192     return f;
193   }
194 
195   public File getReportsBaseDirectory() {
196 
197     File f = new File(getBaseDirectory(), "reports");
198 
199     if (!f.exists()) {
200       f.mkdir();
201     }
202     return f;
203   }
204 
205   public File getModuleBaseDirectory() {
206 
207     File f = new File(getBaseDirectory(), "modules");
208 
209     if (!f.exists()) {
210       f.mkdir();
211     }
212     return f;
213   }
214 
215   public final File getTempDirectory() {
216 
217     if (!manifestTempDirectory.exists()) {
218       manifestTempDirectory.mkdir();
219     }
220     return manifestTempDirectory;
221   }
222 
223   /***
224    * Gets a manifests' name (the &lt;name&gt;-attribute) from the manifest XML file.
225    *
226    * @return The manifests' name.
227    */
228   public final String getName() {
229     return name;
230   }
231 
232   public abstract String getType();
233 
234   /***
235    * Gets a manifests' version (the &lt;version&gt;-attribute) from the manifest XML file.
236    *
237    * @return The manifests' version.
238    */
239   public final String getVersion() {
240     return version;
241   }
242 
243   /***
244    * Sets the manifests' version. This method is called by
245    * <a href="http://jakarta.apache.org/commons/digester">Digester</a> while parsing the manifest XML file.
246    *
247    * @param version The manifests' version (<code>&lt;version&gt;</code>-attribute); may be <code>null</code>.
248    */
249   public final void setVersion(String version) {
250     this.version = version;
251   }
252 
253   public final String getDescription() {
254     return description;
255   }
256 
257   public final void setDescription(String description) {
258     this.description = description;
259   }
260 
261   /***
262    * Gets all modules defined in this manifest (excluding includedManifests).
263    *
264    * @see #getAllModules()
265    *
266    * @return A <code>Map</code> with {@link Module} instances.
267    */
268   public final Map getModulesForManifest() {
269     return modules;
270   }
271 
272   /***
273    * Gets all modules defined in this manifest including all modules for all child manifests.
274    *
275    * @see #getModulesForManifest()
276    *
277    * @return A <code>Map</code> with {@link Module} instances.
278    */
279   public final Map getAllModules() {
280     return moduleCache;
281   }
282 
283   /***
284    * Counts all modules for a manifest, also counting all modules of all included manifests. This method thus counts
285    * all child manifests as well.
286    *
287    * @return The total number of modules in this manifest (inlcuding all included manifests).
288    */
289   public final int size() {
290 
291     int total = getModulesForManifest().size();
292 
293     for (Iterator i = childManifests.iterator(); i.hasNext();) {
294       total += ((AbstractManifest) i.next()).size();
295     }
296 
297     return total;
298   }
299 
300 
301   /***
302    *
303    * @param moduleName
304    * @return
305    * @throws ManifestException
306    */
307   public final Module getModule(String moduleName) throws ManifestException {
308 
309     Map allModules = getAllModules();
310 
311     if (allModules.containsKey(moduleName)) {
312       return (Module) allModules.get(moduleName);
313     } else {
314       throw new ManifestException(ManifestException.MODULE_NOT_FOUND, new Object[]{moduleName});
315     }
316   }
317 
318   public final boolean isLocal() {
319 
320     for (Iterator i = getAllModules().values().iterator(); i.hasNext();) {
321 
322       Module m = (Module) i.next();
323 
324       // If we stumble upon a non local module, return false
325       if (!isLocal(m)) {
326         return false;
327       }
328     }
329 
330     return true;
331   }
332 
333   public final boolean isLocal(Module module) {
334     return module.getBaseDir().exists();
335   }
336 
337   /***
338    * Retrieves all included manifests.
339    *
340    * @return A <code>Collection</code> of <code>AbstractManifest</code> instances, or an empty collection if no included
341    *   manifests are available.
342    */
343   public final Collection getIncludes() {
344     return childManifests;
345   }
346 
347   /***
348    * Saves the manifest to disk, including all its included manifests.
349    */
350   public void save() throws ManifestException {
351     // todo this requires a manifest to maintain a map of which module belongs to which manifest ...
352   }
353 
354   /***
355    * A manifest is equal to another manifest if their names are equal.
356    *
357    * @param o A <code>AbstractManifest</code> instance.
358    */
359   public final boolean equals(Object o) {
360 
361     if (o instanceof Manifest) {
362       if (getName().equals(((Manifest) o).getName())) {
363         return true;
364       } else {
365         return false;
366       }
367     } else {
368       return false;
369     }
370   }
371 
372   public int hashCode() {
373     return name.hashCode();
374   }
375 
376   /***
377    *
378    *
379    * @param module
380    * @return Interdependencies for <code>module</code> or an empty <code>Collection</code>.
381    */
382   public final Collection getModuleInterdependencies(Module module) throws ManifestException {
383 
384     Collection deps = (Collection) getInterdependencies().get(module.getName());
385 
386     return (deps == null ? new HashSet() : deps);
387   }
388 
389   /***
390    * <p>Calculates interdepencies between modules in the manifest; interdependencies are inverse relationships
391    * between a module and other modules (being <code>SourceModule</code> instances).
392    *
393    * <p>If a module <code>B</code> has a dependency on module <code>A</code>, then this method will return a map, with
394    * a key <code>A</code> and its value a <code>Collection</code> of interdependencies (in this case, <code>B</code>).
395    *
396    * @return
397    */
398   public final Map getInterdependencies() throws ManifestException {
399 
400     Map interDependencies = new Hashtable();
401 
402     // Interdependencies can only be determined if the module has been checked out locally ...
403     //
404 
405     Map allModules = getAllModules();
406 
407     for (Iterator i = allModules.keySet().iterator(); i.hasNext();) {
408 
409       Module module = (Module) allModules.get((String) i.next());
410 
411       if (isLocal(module)) {
412 
413         // Does the module have 'module'-deps ?
414         //
415 
416         Set moduleDependencies = null;
417         moduleDependencies = ((BaseModule) module).getDependencies();
418 
419         // Iterate over all dependencies. If it is a module dep, check if we already have an
420         // entry in the interdep-collection; create one when necessary.
421         //
422         for (Iterator j = moduleDependencies.iterator(); j.hasNext();) {
423           ModuleDependency moduleDependency = (ModuleDependency) j.next();
424           if (moduleDependency.isModuleDependency()) {
425 
426             // Check if a key for the module dep already exists.
427             //
428             if (interDependencies.containsKey(moduleDependency.getModule())) {
429 
430               // If so, get the corresponding collection and add the module to the collection.
431               //
432               Collection col = (Collection) interDependencies.get(moduleDependency.getModule());
433               col.add(module);
434             } else {
435               // For the dependency, no entry exists, so we create one.
436               //
437               Collection col = new HashSet();
438               col.add(module);
439               // todo TEST (!) if the mechanism works for 'duplicate' keys.
440               interDependencies.put(moduleDependency.getModule(), col);
441             }
442           }
443         }
444       } else {
445         // todo else what ???
446       }
447     }
448 
449     return interDependencies;
450   }
451 
452   /***
453    * <p>Checks is <code>module</code> should be removed locally. This can - e.g. - happen if the module was checked out
454    * from a location elsewhere and the module with the same name but with a different location has been defined in the
455    * manifest, before the module was cleaned locally.
456    *
457    * <p>This method only supports CVS.
458    *
459    * @param module
460    * @return <code>true</code> if a local version has been removed or <code>false</code> if nothing has been removed.
461    */
462   private boolean removeLocal(Module module) {
463 
464     // todo this method is not abstract ! handles CVS only.
465 
466     AdminHandler handler = new AdminHandler(module);
467     if (!handler.isEqualLocation()) {
468       try {
469         FileUtils.deleteDirectory(module.getBaseDir());
470       } catch (IOException e) {
471         return false;
472       }
473     }
474 
475     return true;
476   }
477 
478   private void setModuleBaseDir(Module module) {
479 
480     try {
481       module.setBaseDir(new File(getModuleBaseDirectory(), module.getName()));
482 
483 //      if (((VersionControlSystem)module.getLocation()).getModuleOffset() == null) {
484 //        module.setBaseDir(new File(getModuleBaseDirectory(), module.getName()));
485 //      } else {
486 //        module.setBaseDir(new File(new File(getModuleBaseDirectory(), ((VersionControlSystem)module.getLocation()).getModuleOffset()), module.getName()));
487 //      }
488 //      module.setCheckoutDir(getModuleBaseDirectory());
489     } catch(Exception e) {
490       // Basically, if we can't do this, we have nothing ... really a RuntimeException
491       //
492       throw new KarmaRuntimeException("Could not set base directory for module " + module.getName());
493     }
494   }
495 
496   /***
497    * Sets a modules' state when the module is locally available.
498    *
499    * @param module
500    * @param state
501    */
502   public final void setState(Module module, Module.State state) {
503     if (state == null) {
504       throw new IllegalArgumentException("Parameter state cannot be null.");
505     }
506 
507     if (!isLocal(module)) {
508       return;
509     }
510 
511     try {
512 
513       // Remove old state files ...
514       //
515       FilenameFilter filter = new FilenameFilter() {
516         public boolean accept(File dir, String name) {
517           if ((name != null) && ((".WORKING".equals(name)) || (".STATIC".equals(name)) || (".DYNAMIC".equals(name)))) {
518             return true;
519           } else {
520             return false;
521           }
522         }
523       };
524 
525       String[] stateFiles = module.getBaseDir().list(filter);
526 
527       if (stateFiles != null) {
528         for (int i = 0; i < stateFiles.length; i++) {
529           new File(module.getBaseDir(), stateFiles[i]).delete();
530         }
531       }
532 
533       File stateFile = new File(module.getBaseDir(), state.getHiddenFileName());
534       stateFile.createNewFile();
535 
536     } catch (Exception e) {
537       throw new KarmaRuntimeException(e);
538     }
539   }
540 
541   public final Module.State getState(Module module) {
542 
543     if (!isLocal(module)) {
544       if (module.hasVersion() || this instanceof ReleaseManifest) {
545         return Module.STATIC;
546       } else {
547         return Module.DYNAMIC;
548       }
549     }
550 
551     FilenameFilter filter = new FilenameFilter() {
552       public boolean accept(File dir, String name) {
553         if ((name != null) && name.matches(".WORKING|.STATIC|.DYNAMIC")) {
554           return true;
555         } else {
556           return false;
557         }
558       }
559     };
560 
561     String[] stateFiles = module.getBaseDir().list(filter);
562 
563     if ((stateFiles == null || stateFiles.length == 0)) {
564       if (module.hasVersion()) {
565         return Module.STATIC;
566       } else {
567         return Module.DYNAMIC;
568       }
569     } else {
570 
571       // We have state files, meaning that the module was local.
572       //
573       if (".WORKING".equals(stateFiles[0])) {
574         return Module.WORKING;
575       } else {
576 
577         // The module is not working.
578         //
579         if (this instanceof ReleaseManifest) {
580           return Module.STATIC;
581         } else {
582           if (module.hasVersion()) {
583             return Module.STATIC;
584           }
585         }
586         return Module.DYNAMIC;
587       }
588     }
589   }
590 
591   public final String toString() {
592     return getName();
593   }
594 }