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