1 package de.matthias_burbach.deputy.core;
2
3 import java.util.Date;
4 import java.util.Iterator;
5 import java.util.List;
6
7 import org.jdom.Comment;
8 import org.jdom.Document;
9 import org.jdom.Element;
10 import org.jdom.xpath.XPath;
11
12 import de.matthias_burbach.deputy.core.project.Project;
13 import de.matthias_burbach.deputy.core.rule.DeprecationRule;
14 import de.matthias_burbach.deputy.core.rule.EnforcementRule;
15 import de.matthias_burbach.deputy.core.rule.RemovalRule;
16 import de.matthias_burbach.deputy.core.rule.ReplacementRule;
17 import de.matthias_burbach.deputy.core.rule.RetentionRule;
18 import de.matthias_burbach.deputy.core.util.FileUtils;
19 import de.matthias_burbach.deputy.core.util.XmlUtils;
20
21 /***
22 * Generates a new XML document for a Maven project from its current XML file
23 * and the changes made in the application.
24 *
25 * @author Matthias Burbach
26 */
27 public class ProjectGenerator {
28 /***
29 * The version of the Deputy application.
30 * To be printed in the generator comment.
31 */
32 private String deputyVersion = "";
33
34 /***
35 * @param deputyVersion The version of the Deputy application.
36 * To be printed in the generator comment.
37 */
38 public ProjectGenerator(final String deputyVersion) {
39 this.deputyVersion = deputyVersion;
40 }
41
42 /***
43 * Generates a new XML document for a Maven project from its current XML
44 * file and the changes made in the application.
45 *
46 * @param project The project to generate the document for.
47 * @param projectFileName The full path of the current project file.
48 * @return The generated document.
49 * @throws Exception if anything goes unexpectedly wrong
50 */
51 public Document createUpdatedDocument(
52 final Project project,
53 final String projectFileName)
54 throws Exception {
55 Document newDocument = XmlUtils.loadXmlDocument(projectFileName);
56 generateDependencies(project, newDocument);
57 generateRules(project, newDocument);
58 return newDocument;
59 }
60
61 /***
62 * Generates a new XML document for a Maven project from its current XML
63 * file and the changes made in the application.
64 * <p/>
65 * Saves a backup of the previous version of the XML file under the path and
66 * name of the original file plus the suffix '.bak' if the original file
67 * is overridden.
68 *
69 * @param project The project to generate the document for.
70 * @param oldProjectFileName The full path of the current project file.
71 * @param newProjectFileName The full path to save the generated document
72 * under.
73 * @throws Exception if anything goes unexpectedly wrong
74 */
75 public void createAndSaveUpdatedDocument(
76 final Project project,
77 final String oldProjectFileName,
78 final String newProjectFileName)
79 throws Exception {
80 if (newProjectFileName.equals(oldProjectFileName)) {
81 backupFile(oldProjectFileName);
82 }
83 Document updatedDocument =
84 createUpdatedDocument(project, oldProjectFileName);
85 XmlUtils.saveXmlDocument(newProjectFileName, updatedDocument);
86 }
87
88 /***
89 * @param project The project to generate dependencies for.
90 * @param document The document to generate into.
91 * @throws Exception if anything goes unexpectedly wrong
92 */
93 private void generateDependencies(
94 final Project project,
95 final Document document)
96 throws Exception {
97
98 Element dependenciesElement = getDependenciesElement(document);
99
100
101
102 dependenciesElement.getContent().clear();
103
104
105 dependenciesElement.addContent(getGeneratorComment());
106
107
108 Iterator iter = project.getDependencies();
109 while (iter.hasNext()) {
110 Project dependency = (Project) iter.next();
111 generateDependency(
112 dependenciesElement,
113 dependency,
114 true,
115 project.isAssembly(),
116 project.getDependencyPropertyList(dependency.getArtifactId()));
117 }
118
119 if (project.isAssembly()) {
120
121
122 dependenciesElement.addContent(getIndirectDependenciesComment());
123 iter = project.getIndirectDependencies();
124 while (iter.hasNext()) {
125 Project dependency = (Project) iter.next();
126 generateDependency(
127 dependenciesElement,
128 dependency,
129 false,
130 project.isAssembly(),
131 project.getDependencyPropertyList(
132 dependency.getArtifactId()));
133 }
134 }
135 }
136
137 /***
138 * @param dependenciesElement The parent Element to add the generated
139 * dependency element to.
140 * @param dependency The dependency to generate an XML element for.
141 * @param isTop Whether this is a direct (or: top level) dependency.
142 * @param isAssembly Whether we generate an assembly or a component project.
143 * @param preservableProperties The dependency properties to be nested into
144 * the dependency.
145 */
146 private void generateDependency(
147 final Element dependenciesElement,
148 final Project dependency,
149 final boolean isTop,
150 final boolean isAssembly,
151 final List preservableProperties) {
152 Element dependencyElement = new Element("dependency");
153 addChildElement(
154 dependencyElement, "groupId", dependency.getGroupId());
155 addChildElement(
156 dependencyElement, "artifactId", dependency.getArtifactId());
157 addChildElement(
158 dependencyElement, "version", dependency.getVersion());
159 addChildElement(
160 dependencyElement, "jar", dependency.getJar());
161
162 String type = dependency.getType();
163 if ("war".equals(type) && !isAssembly) {
164
165
166
167 type = null;
168 }
169 addChildElement(
170 dependencyElement, "type", type);
171
172 addChildElement(
173 dependencyElement, "url", dependency.getUrl());
174
175 Element propertiesElement = new Element("properties");
176 Element topElement = new Element("top");
177 topElement.setText((Boolean.valueOf(isTop)).toString());
178 propertiesElement.addContent(topElement);
179
180
181
182 if (preservableProperties != null) {
183 Iterator iter = preservableProperties.iterator();
184 while (iter.hasNext()) {
185 Element element = (Element) iter.next();
186 propertiesElement.addContent((Element) element.clone());
187 }
188 }
189 dependencyElement.addContent(propertiesElement);
190
191
192 dependenciesElement.addContent(dependencyElement);
193 }
194
195 /***
196 * @param project The project to generate the rules for.
197 * @param document The document to generate into.
198 * @throws Exception if anything goes unexpectedly wrong
199 */
200 private void generateRules(
201 final Project project, final Document document) throws Exception {
202
203 Element rulesElement = getRulesElement(project, document);
204
205
206
207 rulesElement.getContent().clear();
208
209
210 Element defaultElement = new Element("default");
211 String value = project.getRuleSet().getDefaultRule();
212 defaultElement.setAttribute("value", value);
213 rulesElement.addContent(defaultElement);
214
215
216 Element enforcementsElement = new Element("enforcements");
217 Iterator iter = project.getRuleSet().getEnforcementRules().iterator();
218 while (iter.hasNext()) {
219 EnforcementRule rule = (EnforcementRule) iter.next();
220 generateRule(enforcementsElement, rule);
221 }
222 rulesElement.addContent(enforcementsElement);
223
224
225 Element deprecationsElement = new Element("deprecations");
226 iter = project.getRuleSet().getDeprecationRules().iterator();
227 while (iter.hasNext()) {
228 DeprecationRule rule = (DeprecationRule) iter.next();
229 generateRule(deprecationsElement, rule);
230 }
231 rulesElement.addContent(deprecationsElement);
232
233
234 Element replacementsElement = new Element("replacements");
235 iter = project.getRuleSet().getReplacementRules().iterator();
236 while (iter.hasNext()) {
237 ReplacementRule rule = (ReplacementRule) iter.next();
238 generateRule(replacementsElement, rule);
239 }
240 rulesElement.addContent(replacementsElement);
241
242
243 Element removalsElement = new Element("removals");
244 iter = project.getRuleSet().getRemovalRules().iterator();
245 while (iter.hasNext()) {
246 RemovalRule rule = (RemovalRule) iter.next();
247 generateRule(removalsElement, rule);
248 }
249 rulesElement.addContent(removalsElement);
250
251
252 Element retentionsElement = new Element("retentions");
253 iter = project.getRuleSet().getRetentionRules().iterator();
254 while (iter.hasNext()) {
255 RetentionRule rule = (RetentionRule) iter.next();
256 generateRule(retentionsElement, rule);
257 }
258 rulesElement.addContent(retentionsElement);
259 }
260
261 /***
262 * @param enforcementsElement The parent to add the generated element to.
263 * @param rule The rule to generate an XML element for.
264 */
265 private void generateRule(
266 final Element enforcementsElement,
267 final EnforcementRule rule) {
268 Element element = new Element("enforcement");
269 addChildElement(element, "groupId", rule.getGroupId());
270 addChildElement(element, "artifactId", rule.getArtifactId());
271 addChildElement(element, "version", rule.getVersion());
272 if (rule.isDerived()) {
273 addChildElement(
274 element, "derived", Boolean.toString(rule.isDerived()));
275 }
276
277
278 enforcementsElement.addContent(element);
279 }
280
281 /***
282 * @param deprecationsElement The parent to add the generated element to.
283 * @param rule The rule to generate an XML element for.
284 */
285 private void generateRule(
286 final Element deprecationsElement,
287 final DeprecationRule rule) {
288 Element element = new Element("deprecation");
289 addChildElement(element, "groupId", rule.getGroupId());
290 addChildElement(element, "artifactId", rule.getArtifactId());
291 addChildElement(element, "version", rule.getVersion());
292
293
294 deprecationsElement.addContent(element);
295 }
296
297 /***
298 * @param replacementsElement The parent to add the generated element to.
299 * @param rule The rule to generate an XML element for.
300 */
301 private void generateRule(
302 final Element replacementsElement,
303 final ReplacementRule rule) {
304 Element element = new Element("replacement");
305
306 Element displacementElement = new Element("displacement");
307 addChildElement(
308 displacementElement,
309 "groupId",
310 rule.getDisplacedGroupId());
311 addChildElement(
312 displacementElement,
313 "artifactId",
314 rule.getDisplacedArtifactId());
315 if (rule.getDisplacedVersion() != null) {
316 addChildElement(
317 displacementElement,
318 "version",
319 rule.getDisplacedVersion());
320 }
321
322 Element placementElement = new Element("placement");
323 addChildElement(
324 placementElement,
325 "groupId",
326 rule.getGroupId());
327 addChildElement(
328 placementElement,
329 "artifactId",
330 rule.getArtifactId());
331 if (rule.getVersion() != null) {
332 addChildElement(
333 placementElement,
334 "version",
335 rule.getVersion());
336 }
337
338 element.addContent(displacementElement);
339 element.addContent(placementElement);
340
341 replacementsElement.addContent(element);
342 }
343
344 /***
345 * @param removalsElement The parent to add the generated element to.
346 * @param rule The rule to generate an XML element for.
347 */
348 private void generateRule(
349 final Element removalsElement,
350 final RemovalRule rule) {
351 Element element = new Element("removal");
352 addChildElement(element, "groupId", rule.getGroupId());
353 addChildElement(element, "artifactId", rule.getArtifactId());
354 if (rule.getVersion() != null) {
355 addChildElement(element, "version", rule.getVersion());
356 }
357
358 removalsElement.addContent(element);
359 }
360
361 /***
362 * @param retentionsElement The parent to add the generated element to.
363 * @param rule The rule to generate an XML element for.
364 */
365 private void generateRule(
366 final Element retentionsElement,
367 final RetentionRule rule) {
368 Element element = new Element("retention");
369 addChildElement(element, "groupId", rule.getGroupId());
370 addChildElement(element, "artifactId", rule.getArtifactId());
371 if (rule.getVersion() != null) {
372 addChildElement(element, "version", rule.getVersion());
373 }
374
375 retentionsElement.addContent(element);
376 }
377
378 /***
379 * Adds an element to the parent if the text is not <code>null</code>.
380 *
381 * @param parentElement The parent to add to.
382 * @param childName The name of the child element to add.
383 * @param childText The contents of the child element to add. Can be
384 * <code>null</code> which will make this operation a noop.
385 */
386 private void addChildElement(
387 final Element parentElement,
388 final String childName,
389 final String childText) {
390 if (childText != null) {
391 Element childElement = new Element(childName);
392 childElement.setText(childText);
393 parentElement.addContent(childElement);
394 }
395 }
396
397 /***
398 * Finds or creates the dependencies element in the document.
399 *
400 * @param document The document to parse for a potentially already existing
401 * dependencies element.
402 * @return The dependencies element.
403 * @throws Exception if anything goes unexpectedly wrong
404 */
405 private Element getDependenciesElement(
406 final Document document) throws Exception {
407 Element dependenciesElement = null;
408 if (document != null) {
409 dependenciesElement =
410 (Element) XPath.newInstance("//project/dependencies")
411 .selectSingleNode(document);
412 }
413 if (dependenciesElement == null) {
414 dependenciesElement = new Element("dependencies");
415 Element projectElement =
416 (Element) XPath.newInstance("//project")
417 .selectSingleNode(document);
418 if (projectElement != null) {
419 projectElement.addContent(dependenciesElement);
420 }
421 }
422 return dependenciesElement;
423 }
424
425 /***
426 * Finds or creates the rules element in the document.
427 *
428 * @param project The current project.
429 * @param document The document to parse for a potentially already existing
430 * rules element.
431 * @return The rules element.
432 * @throws Exception if anything goes unexpectedly wrong
433 */
434 private Element getRulesElement(
435 final Project project,
436 final Document document) throws Exception {
437 Element resultElement = null;
438 Element deputyElement = getDeputyElement(project, document);
439 if (deputyElement != null) {
440 resultElement =
441 (Element) XPath.newInstance("rules").selectSingleNode(
442 deputyElement);
443 if (resultElement == null) {
444 resultElement = new Element("rules");
445 deputyElement.addContent(resultElement);
446 }
447 }
448 return resultElement;
449 }
450
451 /***
452 * Finds or creates the deputy element in the document.
453 *
454 * @param project The current project.
455 * @param document The document to parse for a potentially already existing
456 * deputy element.
457 *
458 * @return The deputy element from the project's properties.
459 * @throws Exception if anything goes unexpectedly wrong
460 */
461 private Element getDeputyElement(
462 final Project project,
463 final Document document) throws Exception {
464 Element resultElement = null;
465 Element propertiesElement = getPropertiesElement(document);
466 if (propertiesElement != null) {
467 resultElement =
468 (Element) XPath.newInstance("deputy").selectSingleNode(
469 propertiesElement);
470 if (resultElement == null) {
471 resultElement = new Element("deputy");
472 propertiesElement.addContent(resultElement);
473 }
474 }
475
476 resultElement.setAttribute(
477 "isAssembly",
478 Boolean.valueOf(project.isAssembly()).toString());
479 return resultElement;
480 }
481
482 /***
483 * Finds or creates the project's properties element.
484 *
485 * @param document The document to parse for the potentially already
486 * existing properties element.
487 * @return The properties element.
488 * @throws Exception if anything goes unexpectedly wrong
489 */
490 private Element getPropertiesElement(
491 final Document document) throws Exception {
492 Element resultElement = null;
493 Element projectElement = getProjectElement(document);
494 if (projectElement != null) {
495 resultElement =
496 (Element) XPath.newInstance("properties").selectSingleNode(
497 projectElement);
498 if (resultElement == null) {
499 resultElement = new Element("properties");
500 projectElement.addContent(resultElement);
501 }
502 }
503 return resultElement;
504 }
505
506 /***
507 * Finds or creates the project element.
508 *
509 * @param document The document to parse for the potentially already
510 * existing project element.
511 * @return The project element.
512 * @throws Exception if anything goes unexpectedly wrong
513 */
514 private Element getProjectElement(
515 final Document document) throws Exception {
516 Element resultElement = null;
517 if (document != null) {
518 resultElement =
519 (Element) XPath.newInstance("//project").selectSingleNode(
520 document);
521 }
522 return resultElement;
523 }
524
525 /***
526 * @return The comment stating the name of the generator tool and the time
527 * of the generation.
528 */
529 private Comment getGeneratorComment() {
530 return new Comment(
531 "Generated by Deputy " + deputyVersion + " at " + new Date());
532 }
533
534 /***
535 * @return The comment heading the indirect dependencies in assemblies.
536 */
537 private Comment getIndirectDependenciesComment() {
538 return new Comment("Indirect Dependencies");
539 }
540
541 /***
542 * Backs up the file passed in.
543 * <p/>
544 * The backup is copied to the same directory the original is in. It is
545 * named '<original-file-name>.bak'.
546 *
547 * @param pathAndFile The file to be backed up.
548 * @throws Exception if anything goes unexpectedly wrong
549 */
550 private void backupFile(final String pathAndFile) throws Exception {
551 FileUtils.copy(pathAndFile, pathAndFile + ".bak");
552 }
553 }