1 package org.apache.maven.plugin.changes;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.ResourceBundle;
28
29 import org.apache.commons.lang.StringUtils;
30
31 import org.apache.maven.doxia.sink.Sink;
32 import org.apache.maven.doxia.sink.SinkEventAttributeSet;
33 import org.apache.maven.doxia.sink.SinkEventAttributes;
34 import org.apache.maven.doxia.util.HtmlTools;
35 import org.apache.maven.plugin.issues.AbstractIssuesReportGenerator;
36 import org.apache.maven.plugins.changes.model.Action;
37 import org.apache.maven.plugins.changes.model.Component;
38 import org.apache.maven.plugins.changes.model.DueTo;
39 import org.apache.maven.plugins.changes.model.FixedIssue;
40 import org.apache.maven.plugins.changes.model.Release;
41
42
43
44
45
46
47 public class ChangesReportGenerator
48 extends AbstractIssuesReportGenerator
49 {
50
51
52
53
54 private static final String URL_TOKEN = "%URL%";
55
56
57
58
59 private static final String ISSUE_TOKEN = "%ISSUE%";
60
61 static final String DEFAULT_ISSUE_SYSTEM_KEY = "default";
62
63 private static final String NO_TEAMLIST = "none";
64
65
66
67
68
69
70 private String system;
71
72 private String teamlist;
73
74 private String url;
75
76 private Map<String, String> issueLinksPerSystem;
77
78 private boolean addActionDate;
79
80 private boolean linkToFeed;
81
82
83
84
85 private boolean escapeHTML;
86
87
88
89
90 private List<Release> releaseList;
91
92 public ChangesReportGenerator()
93 {
94 issueLinksPerSystem = new HashMap<String, String>();
95 }
96
97 public ChangesReportGenerator( List<Release> releaseList )
98 {
99 this();
100 this.releaseList = releaseList;
101 }
102
103
104
105
106 public boolean isEscapeHTML()
107 {
108 return escapeHTML;
109 }
110
111
112
113
114 public void setEscapeHTML( boolean escapeHTML )
115 {
116 this.escapeHTML = escapeHTML;
117 }
118
119
120
121
122 public String getSystem()
123 {
124 return system;
125 }
126
127
128
129
130 public void setSystem( String system )
131 {
132 this.system = system;
133 }
134
135 public void setTeamlist( final String teamlist )
136 {
137 this.teamlist = teamlist;
138 }
139
140 public String getTeamlist()
141 {
142 return teamlist;
143 }
144
145 public void setUrl( String url )
146 {
147 this.url = url;
148 }
149
150 public String getUrl()
151 {
152 return url;
153 }
154
155 public Map<String, String> getIssueLinksPerSystem()
156 {
157 return issueLinksPerSystem;
158 }
159
160 public void setIssueLinksPerSystem( Map<String, String> issueLinksPerSystem )
161 {
162 if ( this.issueLinksPerSystem != null && issueLinksPerSystem == null )
163 {
164 return;
165 }
166 this.issueLinksPerSystem = issueLinksPerSystem;
167 }
168
169 public boolean isAddActionDate()
170 {
171 return addActionDate;
172 }
173
174 public void setAddActionDate( boolean addActionDate )
175 {
176 this.addActionDate = addActionDate;
177 }
178
179 public boolean isLinkToFeed()
180 {
181 return linkToFeed;
182 }
183
184 public void setLinkToFeed( boolean generateLinkTofeed )
185 {
186 this.linkToFeed = generateLinkTofeed;
187 }
188
189
190
191
192
193
194
195 public boolean canGenerateIssueLinks( String system )
196 {
197 if ( !this.issueLinksPerSystem.containsKey( system ) )
198 {
199 return false;
200 }
201 String issueLink = this.issueLinksPerSystem.get( system );
202
203
204 if ( StringUtils.isBlank( issueLink ) )
205 {
206 return false;
207 }
208
209
210 if ( issueLink.contains( URL_TOKEN ) && StringUtils.isBlank( getUrl() ) )
211 {
212 return false;
213 }
214 return true;
215 }
216
217 public void doGenerateEmptyReport( ResourceBundle bundle, Sink sink, String message )
218 {
219 sinkBeginReport( sink, bundle );
220
221 sink.text( message );
222
223 sinkEndReport( sink );
224 }
225
226 public void doGenerateReport( ResourceBundle bundle, Sink sink )
227 {
228 sinkBeginReport( sink, bundle );
229
230 constructReleaseHistory( sink, bundle, releaseList );
231
232 constructReleases( sink, bundle, releaseList );
233
234 sinkEndReport( sink );
235 }
236
237
238
239
240
241
242
243
244 private void constructAction( Sink sink, ResourceBundle bundle, Action action )
245 {
246 sink.tableRow();
247
248 sinkShowTypeIcon( sink, action.getType() );
249
250 sink.tableCell();
251
252 String actionDescription = action.getAction();
253
254 if ( escapeHTML )
255 {
256 sink.text( actionDescription );
257 }
258 else
259 {
260 sink.rawText( actionDescription );
261 }
262
263
264 if ( StringUtils.isNotEmpty( action.getIssue() ) || ( !action.getFixedIssues().isEmpty() ) )
265 {
266 if ( StringUtils.isNotBlank( actionDescription ) && !actionDescription.endsWith( "." ) )
267 {
268 sink.text( "." );
269 }
270 sink.text( " " + bundle.getString( "report.changes.text.fixes" ) + " " );
271
272
273 String system = action.getSystem();
274
275 if ( StringUtils.isEmpty( system ) )
276 {
277 system = this.system;
278 }
279
280 if ( StringUtils.isEmpty( system ) )
281 {
282 system = DEFAULT_ISSUE_SYSTEM_KEY;
283 }
284 if ( !canGenerateIssueLinks( system ) )
285 {
286 constructIssueText( action.getIssue(), sink, action.getFixedIssues() );
287 }
288 else
289 {
290 constructIssueLink( action.getIssue(), system, sink, action.getFixedIssues() );
291 }
292 sink.text( "." );
293 }
294
295 if ( StringUtils.isNotEmpty( action.getDueTo() ) || ( !action.getDueTos().isEmpty() ) )
296 {
297 constructDueTo( sink, action, bundle, action.getDueTos() );
298 }
299
300 sink.tableCell_();
301
302 if ( NO_TEAMLIST.equals( teamlist ) )
303 {
304 sinkCell( sink, action.getDev() );
305 }
306 else
307 {
308 sinkCellLink( sink, action.getDev(), teamlist + "#" + action.getDev() );
309 }
310
311 if ( this.isAddActionDate() )
312 {
313 sinkCell( sink, action.getDate() );
314 }
315
316 sink.tableRow_();
317 }
318
319
320
321
322
323
324
325
326
327 private void constructDueTo( Sink sink, Action action, ResourceBundle bundle, List<DueTo> dueTos )
328 {
329
330
331 Map<String, String> namesEmailMap = new LinkedHashMap<String, String>();
332
333
334 if ( StringUtils.isNotEmpty( action.getDueTo() ) || StringUtils.isNotEmpty( action.getDueToEmail() ) )
335 {
336 namesEmailMap.put( action.getDueTo(), action.getDueToEmail() );
337 }
338
339 for ( DueTo dueTo : dueTos )
340 {
341 namesEmailMap.put( dueTo.getName(), dueTo.getEmail() );
342 }
343
344 if ( namesEmailMap.isEmpty() )
345 {
346 return;
347 }
348
349 sink.text( " " + bundle.getString( "report.changes.text.thanx" ) + " " );
350 int i = 0;
351 for ( String currentDueTo : namesEmailMap.keySet() )
352 {
353 String currentDueToEmail = namesEmailMap.get( currentDueTo );
354 i++;
355
356 if ( StringUtils.isNotEmpty( currentDueToEmail ) )
357 {
358 sinkLink( sink, currentDueTo, "mailto:" + currentDueToEmail );
359 }
360 else if ( StringUtils.isNotEmpty( currentDueTo ) )
361 {
362 sink.text( currentDueTo );
363 }
364
365 if ( i < namesEmailMap.size() )
366 {
367 sink.text( ", " );
368 }
369 }
370
371 sink.text( "." );
372 }
373
374
375
376
377
378
379
380
381
382 private void constructIssueLink( String issue, String system, Sink sink, List<FixedIssue> fixes )
383 {
384 if ( StringUtils.isNotEmpty( issue ) )
385 {
386 sink.link( parseIssueLink( issue, system ) );
387
388 sink.text( issue );
389
390 sink.link_();
391
392 if ( !fixes.isEmpty() )
393 {
394 sink.text( ", " );
395 }
396 }
397
398 for ( Iterator<FixedIssue> iterator = fixes.iterator(); iterator.hasNext(); )
399 {
400 FixedIssue fixedIssue = iterator.next();
401 String currentIssueId = fixedIssue.getIssue();
402 if ( StringUtils.isNotEmpty( currentIssueId ) )
403 {
404 sink.link( parseIssueLink( currentIssueId, system ) );
405
406 sink.text( currentIssueId );
407
408 sink.link_();
409 }
410
411 if ( iterator.hasNext() )
412 {
413 sink.text( ", " );
414 }
415 }
416 }
417
418
419
420
421
422
423
424
425 private void constructIssueText( String issue, Sink sink, List<FixedIssue> fixes )
426 {
427 if ( StringUtils.isNotEmpty( issue ) )
428 {
429 sink.text( issue );
430
431 if ( !fixes.isEmpty() )
432 {
433 sink.text( ", " );
434 }
435 }
436
437 for ( Iterator<FixedIssue> iterator = fixes.iterator(); iterator.hasNext(); )
438 {
439 FixedIssue fixedIssue = iterator.next();
440
441 String currentIssueId = fixedIssue.getIssue();
442 if ( StringUtils.isNotEmpty( currentIssueId ) )
443 {
444 sink.text( currentIssueId );
445 }
446
447 if ( iterator.hasNext() )
448 {
449 sink.text( ", " );
450 }
451 }
452 }
453
454 private void constructReleaseHistory( Sink sink, ResourceBundle bundle, List<Release> releaseList )
455 {
456 sink.section2();
457
458 sink.sectionTitle2();
459 sink.text( bundle.getString( "report.changes.label.releasehistory" ) );
460 sink.sectionTitle2_();
461
462 sink.table();
463
464 sink.tableRow();
465
466 sinkHeader( sink, bundle.getString( "report.issues.label.fixVersion" ) );
467
468 sinkHeader( sink, bundle.getString( "report.changes.label.releaseDate" ) );
469
470 sinkHeader( sink, bundle.getString( "report.changes.label.releaseDescription" ) );
471
472 sink.tableRow_();
473
474 for ( Release release : releaseList )
475 {
476 sink.tableRow();
477
478 sinkCellLink( sink, release.getVersion(), "#" + HtmlTools.encodeId( release.getVersion() ) );
479
480 sinkCell( sink, release.getDateRelease() );
481
482 sinkCell( sink, release.getDescription() );
483
484 sink.tableRow_();
485 }
486
487 sink.table_();
488
489
490 if ( linkToFeed )
491 {
492 sink.paragraph();
493 sink.text( bundle.getString( "report.changes.text.rssfeed" ) );
494 sink.nonBreakingSpace();
495 sink.link( "changes.rss" );
496 sinkFigure( sink, "images/rss.png", "rss feed" );
497 sink.link_();
498 sink.paragraph_();
499 }
500
501 sink.section2_();
502 }
503
504
505
506
507
508
509
510
511 private void constructReleases( Sink sink, ResourceBundle bundle, List<Release> releaseList )
512 {
513 for ( Release release : releaseList )
514 {
515 constructRelease( sink, bundle, release );
516 }
517 }
518
519
520
521
522
523
524
525
526 private void constructRelease( Sink sink, ResourceBundle bundle, Release release )
527 {
528 sink.section2();
529
530 final String date = ( release.getDateRelease() == null ) ? "" : " \u2013 " + release.getDateRelease();
531
532 SinkEventAttributes attrs = new SinkEventAttributeSet();
533 attrs.addAttribute( SinkEventAttributes.ID, HtmlTools.encodeId( release.getVersion() ) );
534 sink.sectionTitle( Sink.SECTION_LEVEL_2, attrs );
535 sink.text( bundle.getString( "report.changes.label.release" ) + " " + release.getVersion() + date );
536 sink.sectionTitle_( Sink.SECTION_LEVEL_2 );
537
538 if ( isReleaseEmpty( release ) )
539 {
540 sink.paragraph();
541 sink.text( bundle.getString( "report.changes.text.no.changes" ) );
542 sink.paragraph_();
543 }
544 else
545 {
546 sink.table();
547
548 sink.tableRow();
549 sinkHeader( sink, bundle.getString( "report.issues.label.type" ) );
550 sinkHeader( sink, bundle.getString( "report.issues.label.summary" ) );
551 sinkHeader( sink, bundle.getString( "report.issues.label.assignee" ) );
552 if ( this.isAddActionDate() )
553 {
554 sinkHeader( sink, bundle.getString( "report.issues.label.updated" ) );
555 }
556 sink.tableRow_();
557
558 for ( Action action : release.getActions() )
559 {
560 constructAction( sink, bundle, action );
561 }
562
563 for ( Object o : release.getComponents() )
564 {
565 Component component = (Component) o;
566 constructComponent( sink, bundle, component );
567 }
568
569 sink.table_();
570 }
571
572 sink.section2_();
573 }
574
575
576
577
578
579
580
581
582
583 private void constructComponent( Sink sink, ResourceBundle bundle, Component component )
584 {
585 if ( !component.getActions().isEmpty() )
586 {
587 sink.tableRow();
588
589 sink.tableHeaderCell();
590 sink.tableHeaderCell_();
591
592 sink.tableHeaderCell();
593 sink.text( component.getName() );
594 sink.tableHeaderCell_();
595
596 sink.tableHeaderCell();
597 sink.tableHeaderCell_();
598
599 if ( isAddActionDate() )
600 {
601 sink.tableHeaderCell();
602 sink.tableHeaderCell_();
603 }
604
605 sink.tableRow_();
606
607 for ( Action action : component.getActions() )
608 {
609 constructAction( sink, bundle, action );
610 }
611 }
612 }
613
614
615
616
617
618
619
620 private boolean isReleaseEmpty( Release release )
621 {
622 if ( !release.getActions().isEmpty() )
623 {
624 return false;
625 }
626
627 for ( Object o : release.getComponents() )
628 {
629 Component component = (Component) o;
630 if ( !component.getActions().isEmpty() )
631 {
632 return false;
633 }
634 }
635
636 return true;
637 }
638
639
640
641
642
643
644
645
646 private String parseIssueLink( String issue, String system )
647 {
648 String parseLink;
649 String issueLink = (String) this.issueLinksPerSystem.get( system );
650 parseLink = issueLink.replaceFirst( ISSUE_TOKEN, issue );
651 if ( parseLink.contains( URL_TOKEN ) )
652 {
653 String url = this.url.substring( 0, this.url.lastIndexOf( "/" ) );
654 parseLink = parseLink.replaceFirst( URL_TOKEN, url );
655 }
656
657 return parseLink;
658 }
659
660 }