%line | %branch | |||||||||
---|---|---|---|---|---|---|---|---|---|---|
nl.toolforge.karma.core.manifest.AbstractManifest$2 |
|
|
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 (!manclass="keyword">ifestBaseDirectory.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 (!manclass="keyword">ifestTempDirectory.exists()) { |
|
218 | manifestTempDirectory.mkdir(); |
|
219 | } |
|
220 | return manifestTempDirectory; |
|
221 | } |
|
222 | ||
223 | /** |
|
224 | * Gets a manifests' name (the <name>-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 <version>-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><version></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, class="keyword">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 Manclass="keyword">ifest) { |
|
362 | if (getName().equals(((Manclass="keyword">ifest) 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 ReleaseManclass="keyword">ifest) { |
|
545 | return Module.STATIC; |
|
546 | } else { |
|
547 | return Module.DYNAMIC; |
|
548 | } |
|
549 | } |
|
550 | ||
551 | FilenameFilter filter = new FilenameFilter() { |
|
552 | 0 | public boolean accept(File dir, String name) { |
553 | 0 | if ((name != null) && name.matches(".WORKING|.STATIC|.DYNAMIC")) { |
554 | 0 | return true; |
555 | } else { |
|
556 | 0 | 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 ReleaseManclass="keyword">ifest) { |
|
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 | } |
This report is generated by jcoverage, Maven and Maven JCoverage Plugin. |