1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.changes;
20
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.ResourceBundle;
26
27 import org.apache.commons.lang3.StringUtils;
28 import org.apache.maven.doxia.sink.Sink;
29 import org.apache.maven.doxia.util.DoxiaUtils;
30 import org.apache.maven.plugins.changes.issues.AbstractIssuesReportRenderer;
31 import org.apache.maven.plugins.changes.model.Action;
32 import org.apache.maven.plugins.changes.model.Component;
33 import org.apache.maven.plugins.changes.model.DueTo;
34 import org.apache.maven.plugins.changes.model.Release;
35
36
37
38
39
40
41 public class ChangesReportRenderer extends AbstractIssuesReportRenderer {
42
43
44
45
46 private static final String URL_TOKEN = "%URL%";
47
48
49
50
51 private static final String ISSUE_TOKEN = "%ISSUE%";
52
53 static final String DEFAULT_ISSUE_SYSTEM_KEY = "default";
54
55 private static final String NO_TEAM = "none";
56
57 private final ChangesXML changesXML;
58
59
60
61
62
63
64 private String system;
65
66 private String team;
67
68 private String url;
69
70 private Map<String, String> issueLinksPerSystem;
71
72 private boolean addActionDate;
73
74 private boolean linkToFeed;
75
76 private boolean escapeText;
77
78 public ChangesReportRenderer(Sink sink, ResourceBundle bundleName, ChangesXML changesXML) {
79 super(sink, bundleName);
80 this.issueLinksPerSystem = new HashMap<>();
81 this.changesXML = changesXML;
82 }
83
84 public void setSystem(String system) {
85 this.system = system;
86 }
87
88 public void setTeam(final String team) {
89 this.team = team;
90 }
91
92 public void setUrl(String url) {
93 this.url = url;
94 }
95
96 public void setIssueLinksPerSystem(Map<String, String> issueLinksPerSystem) {
97 if (this.issueLinksPerSystem != null && issueLinksPerSystem == null) {
98 return;
99 }
100 this.issueLinksPerSystem = issueLinksPerSystem;
101 }
102
103 public void setAddActionDate(boolean addActionDate) {
104 this.addActionDate = addActionDate;
105 }
106
107 public void setLinkToFeed(boolean generateLinkTofeed) {
108 this.linkToFeed = generateLinkTofeed;
109 }
110
111 public void setEscapeText(boolean escapeText) {
112 this.escapeText = escapeText;
113 }
114
115
116
117
118
119
120
121 private boolean canGenerateIssueLinks(String system) {
122 if (!this.issueLinksPerSystem.containsKey(system)) {
123 return false;
124 }
125 String issueLink = this.issueLinksPerSystem.get(system);
126
127
128 if (StringUtils.isBlank(issueLink)) {
129 return false;
130 }
131
132
133 if (issueLink.contains(URL_TOKEN) && StringUtils.isBlank(url)) {
134 return false;
135 }
136 return true;
137 }
138
139 @Override
140 protected void renderBody() {
141 constructReleaseHistory();
142 constructReleases();
143 }
144
145 @Override
146 public String getTitle() {
147 String title = changesXML.getTitle();
148 if (title == null) {
149 title = bundle.getString("report.issues.header");
150 }
151 return title;
152 }
153
154
155
156
157
158
159 private void constructAction(Action action) {
160 sink.tableRow();
161
162 sinkShowTypeIcon(action.getType());
163
164 sink.tableCell();
165
166 String actionDescription = action.getAction();
167
168 if (escapeText || StringUtils.isEmpty(actionDescription)) {
169 text(actionDescription);
170 } else {
171 sink.rawText(actionDescription);
172 }
173
174
175 if (StringUtils.isNotEmpty(action.getIssue())
176 || (!action.getFixedIssues().isEmpty())) {
177 if (StringUtils.isNotBlank(actionDescription) && !actionDescription.endsWith(".")) {
178 text(".");
179 }
180 text(" " + bundle.getString("report.changes.text.fixes") + " ");
181
182
183 String system = action.getSystem();
184
185 if (StringUtils.isEmpty(system)) {
186 system = this.system;
187 }
188
189 if (StringUtils.isEmpty(system)) {
190 system = DEFAULT_ISSUE_SYSTEM_KEY;
191 }
192 if (!canGenerateIssueLinks(system)) {
193 constructIssueText(action.getIssue(), action.getFixedIssues());
194 } else {
195 constructIssueLink(action.getIssue(), system, action.getFixedIssues());
196 }
197 text(".");
198 }
199
200 if (!action.getDueTos().isEmpty()) {
201 constructDueTo(action);
202 }
203
204 sink.tableCell_();
205
206 if (NO_TEAM.equals(team) || action.getDev() == null || action.getDev().isEmpty()) {
207 sinkCell(action.getDev());
208 } else {
209 sinkCellLink(action.getDev(), team + "#" + action.getDev());
210 }
211
212 if (addActionDate) {
213 sinkCell(action.getDate());
214 }
215
216 sink.tableRow_();
217 }
218
219
220
221
222
223
224 private void constructDueTo(Action action) {
225
226 text(" " + bundle.getString("report.changes.text.thanx") + " ");
227 int i = 0;
228 for (DueTo dueTo : action.getDueTos()) {
229 i++;
230
231 if (StringUtils.isNotEmpty(dueTo.getEmail())) {
232 String text = dueTo.getName();
233 link("mailto:" + dueTo.getEmail(), text);
234 } else {
235 text(dueTo.getName());
236 }
237
238 if (i < action.getDueTos().size()) {
239 text(", ");
240 }
241 }
242
243 text(".");
244 }
245
246
247
248
249
250
251
252
253 private void constructIssueLink(String issue, String system, List<String> fixes) {
254 if (StringUtils.isNotEmpty(issue)) {
255 link(parseIssueLink(issue, system), issue);
256 if (!fixes.isEmpty()) {
257 text(", ");
258 }
259 }
260
261 for (Iterator<String> iterator = fixes.iterator(); iterator.hasNext(); ) {
262 String currentIssueId = iterator.next();
263 if (StringUtils.isNotEmpty(currentIssueId)) {
264 link(parseIssueLink(currentIssueId, system), currentIssueId);
265 }
266
267 if (iterator.hasNext()) {
268 text(", ");
269 }
270 }
271 }
272
273
274
275
276
277
278
279 private void constructIssueText(String issue, List<String> fixes) {
280 if (StringUtils.isNotEmpty(issue)) {
281 text(issue);
282
283 if (!fixes.isEmpty()) {
284 text(", ");
285 }
286 }
287
288 for (Iterator<String> iterator = fixes.iterator(); iterator.hasNext(); ) {
289 String currentIssueId = iterator.next();
290 if (StringUtils.isNotEmpty(currentIssueId)) {
291 text(currentIssueId);
292 }
293
294 if (iterator.hasNext()) {
295 text(", ");
296 }
297 }
298 }
299
300 private void constructReleaseHistory() {
301 startSection(bundle.getString("report.changes.label.releasehistory"));
302
303 startTable();
304
305 tableHeader(new String[] {
306 bundle.getString("report.issues.label.fixVersion"),
307 bundle.getString("report.changes.label.releaseDate"),
308 bundle.getString("report.changes.label.releaseDescription")
309 });
310
311 for (Release release : changesXML.getReleaseList()) {
312 sink.tableRow();
313 sinkCellLink(release.getVersion(), "#" + DoxiaUtils.encodeId(release.getVersion()));
314 sinkCell(release.getDateRelease());
315 sinkCell(release.getDescription());
316 sink.tableRow_();
317 }
318
319 endTable();
320
321
322 if (linkToFeed) {
323 sink.paragraph();
324 text(bundle.getString("report.changes.text.rssfeed"));
325 sink.nonBreakingSpace();
326 sink.link("changes.rss");
327 sinkFigure("images/rss.png", "rss feed");
328 sink.link_();
329 sink.paragraph_();
330 }
331
332 endSection();
333 }
334
335
336
337
338 private void constructReleases() {
339 for (Release release : changesXML.getReleaseList()) {
340 constructRelease(release);
341 }
342 }
343
344
345
346
347
348
349 private void constructRelease(Release release) {
350
351 final String date = (release.getDateRelease() == null) ? "" : " \u2013 " + release.getDateRelease();
352
353 startSection(
354 bundle.getString("report.changes.label.release") + " " + release.getVersion() + date,
355 DoxiaUtils.encodeId(release.getVersion()));
356
357 if (isReleaseEmpty(release)) {
358 sink.paragraph();
359 text(bundle.getString("report.changes.text.no.changes"));
360 sink.paragraph_();
361 } else {
362 startTable();
363
364 sink.tableRow();
365 tableHeaderCell(bundle.getString("report.issues.label.type"));
366 tableHeaderCell(bundle.getString("report.issues.label.summary"));
367 tableHeaderCell(bundle.getString("report.issues.label.assignee"));
368 if (addActionDate) {
369 tableHeaderCell(bundle.getString("report.issues.label.updated"));
370 }
371 sink.tableRow_();
372
373 for (Action action : release.getActions()) {
374 constructAction(action);
375 }
376
377 for (Component component : release.getComponents()) {
378 constructComponent(component);
379 }
380
381 endTable();
382 }
383
384 endSection();
385 }
386
387
388
389
390
391
392
393 private void constructComponent(Component component) {
394 if (!component.getActions().isEmpty()) {
395 sink.tableRow();
396
397 sink.tableHeaderCell();
398 sink.tableHeaderCell_();
399
400 sink.tableHeaderCell();
401 text(component.getName());
402 sink.tableHeaderCell_();
403
404 sink.tableHeaderCell();
405 sink.tableHeaderCell_();
406
407 if (addActionDate) {
408 sink.tableHeaderCell();
409 sink.tableHeaderCell_();
410 }
411
412 sink.tableRow_();
413
414 for (Action action : component.getActions()) {
415 constructAction(action);
416 }
417 }
418 }
419
420
421
422
423
424
425
426 private boolean isReleaseEmpty(Release release) {
427 if (!release.getActions().isEmpty()) {
428 return false;
429 }
430
431 for (Component component : release.getComponents()) {
432 if (!component.getActions().isEmpty()) {
433 return false;
434 }
435 }
436
437 return true;
438 }
439
440
441
442
443
444
445
446
447 private String parseIssueLink(String issue, String system) {
448 String parseLink;
449 String issueLink = this.issueLinksPerSystem.get(system);
450 parseLink = issueLink.replaceFirst(ISSUE_TOKEN, issue);
451 if (parseLink.contains(URL_TOKEN)) {
452 String url = this.url.substring(0, this.url.lastIndexOf("/"));
453 parseLink = parseLink.replaceFirst(URL_TOKEN, url);
454 }
455
456 return parseLink;
457 }
458 }