View Javadoc

1   package org.apache.maven.plugin.changes;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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  import org.apache.maven.doxia.sink.Sink;
31  import org.apache.maven.doxia.util.HtmlTools;
32  import org.apache.maven.plugin.issues.AbstractIssuesReportGenerator;
33  import org.apache.maven.plugins.changes.model.Action;
34  import org.apache.maven.plugins.changes.model.DueTo;
35  import org.apache.maven.plugins.changes.model.FixedIssue;
36  import org.apache.maven.plugins.changes.model.Release;
37  
38  /**
39   * Generates a changes report.
40   *
41   * @version $Id: ChangesReportGenerator.html 816598 2012-05-08 12:46:49Z hboutemy $
42   */
43  public class ChangesReportGenerator extends AbstractIssuesReportGenerator
44  {
45  
46      /**
47       * The token in {@link #issueLinksPerSystem} denoting the base URL for the issue management.
48       */
49      private static final String URL_TOKEN = "%URL%";
50  
51      /**
52       * The token in {@link #issueLinksPerSystem} denoting the issue ID.
53       */
54      private static final String ISSUE_TOKEN = "%ISSUE%";
55  
56      static final String DEFAULT_ISSUE_SYSTEM_KEY = "default";
57  
58      private static final String NO_TEAMLIST = "none";
59  
60      /**
61       * The issue management system to use, for actions that do not specify a
62       * system.
63       *
64       * @since 2.4
65       */
66      private String system;
67  
68      private String teamlist;
69  
70      private String url;
71  
72      private Map issueLinksPerSystem;
73  
74      private boolean addActionDate;
75  
76      /**
77       * @since 2.4
78       */
79      private boolean escapeHTML;
80  
81      /**
82       * @since 2.4
83       */
84      private List releaseList;
85  
86      public ChangesReportGenerator()
87      {
88          issueLinksPerSystem = new HashMap();
89      }
90  
91      public ChangesReportGenerator( List releaseList )
92      {
93          this();
94          this.releaseList = releaseList;
95      }
96  
97      /**
98       * @since 2.4
99       */
100     public boolean isEscapeHTML()
101     {
102         return escapeHTML;
103     }
104 
105     /**
106      * @since 2.4
107      */
108     public void setEscapeHTML( boolean escapeHTML )
109     {
110         this.escapeHTML = escapeHTML;
111     }
112 
113     /**
114      * @since 2.4
115      */
116     public String getSystem()
117     {
118         return system;
119     }
120 
121     /**
122      * @since 2.4
123      */
124     public void setSystem( String system )
125     {
126         this.system = system;
127     }
128 
129     public void setTeamlist( final String teamlist )
130     {
131         this.teamlist = teamlist;
132     }
133 
134     public String getTeamlist()
135     {
136         return teamlist;
137     }
138 
139     public void setUrl( String url )
140     {
141         this.url = url;
142     }
143 
144     public String getUrl()
145     {
146         return url;
147     }
148 
149     public Map getIssueLinksPerSystem()
150     {
151         return issueLinksPerSystem;
152     }
153 
154     public void setIssueLinksPerSystem( Map issueLinksPerSystem )
155     {
156         if ( this.issueLinksPerSystem != null && issueLinksPerSystem == null )
157         {
158             return;
159         }
160         this.issueLinksPerSystem = issueLinksPerSystem;
161     }
162 
163     public boolean isAddActionDate()
164     {
165         return addActionDate;
166     }
167 
168     public void setAddActionDate( boolean addActionDate )
169     {
170         this.addActionDate = addActionDate;
171     }
172 
173     /**
174      * Checks whether links to the issues can be generated for the given system.
175      *
176      * @param system The issue management system
177      * @return <code>true</code> if issue links can be generated, <code>false</code> otherwise.
178      */
179     public boolean canGenerateIssueLinks( String system )
180     {
181         if ( !this.issueLinksPerSystem.containsKey( system ) )
182         {
183             return false;
184         }
185         String issueLink = (String) this.issueLinksPerSystem.get( system );
186 
187         // If the issue link entry is blank then no links are possible
188         if ( StringUtils.isBlank( issueLink ) )
189         {
190             return false;
191         }
192 
193         // If the %URL% token is used then the issue management system URL must be set.
194         if ( issueLink.indexOf( URL_TOKEN ) >= 0 && StringUtils.isBlank( getUrl() ) )
195         {
196             return false;
197         }
198         return true;
199     }
200 
201     public void doGenerateEmptyReport( ResourceBundle bundle, Sink sink, String message )
202     {
203         sinkBeginReport( sink, bundle );
204 
205         sink.text( message );
206 
207         sinkEndReport( sink );
208     }
209 
210     public void doGenerateReport( ResourceBundle bundle, Sink sink )
211     {
212         sinkBeginReport( sink, bundle );
213 
214         constructReleaseHistory( sink, bundle, releaseList );
215 
216         constructReleases( sink, bundle, releaseList );
217 
218         sinkEndReport( sink );
219     }
220 
221     private void constructActions( Sink sink, List actionList, ResourceBundle bundle )
222     {
223         if ( actionList.isEmpty() )
224         {
225             sink.paragraph();
226 
227             sink.text( bundle.getString( "report.changes.text.no.changes" ) );
228 
229             sink.paragraph_();
230         }
231         else
232         {
233             sink.table();
234 
235             sink.tableRow();
236 
237             sinkHeader( sink, bundle.getString( "report.issues.label.type" ) );
238 
239             sinkHeader( sink, bundle.getString( "report.issues.label.summary" ) );
240 
241             sinkHeader( sink, bundle.getString( "report.issues.label.assignee" ) );
242 
243             if ( this.isAddActionDate() )
244             {
245                 sinkHeader( sink, bundle.getString( "report.issues.label.updated" ) );
246             }
247             sink.tableRow_();
248 
249             for ( int idx = 0; idx < actionList.size(); idx++ )
250             {
251                 Action action = (Action) actionList.get( idx );
252 
253                 sink.tableRow();
254 
255                 sinkShowTypeIcon( sink, action.getType() );
256 
257                 sink.tableCell();
258 
259                 if ( escapeHTML )
260                 {
261                     sink.text( action.getAction() );
262                 }
263                 else
264                 {
265                     sink.rawText( action.getAction() );
266                 }
267 
268                 // no null check needed classes from modello return a new ArrayList
269                 if ( StringUtils.isNotEmpty( action.getIssue() ) || ( !action.getFixedIssues().isEmpty() ) )
270                 {
271                     sink.text( " " + bundle.getString( "report.changes.text.fixes" ) + " " );
272 
273                     // Try to get the issue management system specified in the changes.xml file
274                     String system = action.getSystem();
275                     // Try to get the issue management system configured in the POM
276                     if ( StringUtils.isEmpty( system ) )
277                     {
278                         system = this.system;
279                     }
280                     // Use the default issue management system
281                     if ( StringUtils.isEmpty( system ) )
282                     {
283                         system = DEFAULT_ISSUE_SYSTEM_KEY;
284                     }
285                     if ( !canGenerateIssueLinks( system ) )
286                     {
287                         constructIssueText( action.getIssue(), sink, action.getFixedIssues() );
288                     }
289                     else
290                     {
291                         constructIssueLink( action.getIssue(), system, sink, action.getFixedIssues() );
292                     }
293                     sink.text( "." );
294                 }
295 
296                 if ( StringUtils.isNotEmpty( action.getDueTo() ) || ( !action.getDueTos().isEmpty() ) )
297                 {
298                     constructDueTo( sink, action, bundle, action.getDueTos() );
299                 }
300 
301                 sink.tableCell_();
302 
303                 if ( NO_TEAMLIST.equals( teamlist ) )
304                 {
305                     sinkCell( sink, action.getDev() );
306                 }
307                 else
308                 {
309                     sinkCellLink( sink, action.getDev(), teamlist + "#" + action.getDev() );
310                 }
311 
312                 if ( this.isAddActionDate() )
313                 {
314                     sinkCell( sink, action.getDate() );
315                 }
316 
317                 sink.tableRow_();
318             }
319 
320             sink.table_();
321         }
322     }
323 
324     /**
325      * Construct a text or link that mention the people that helped with an action.
326      *
327      * @param sink The sink
328      * @param action The action that was done
329      * @param bundle A resource bundle for i18n
330      * @param dueTos Other people that helped with an action
331      */
332     private void constructDueTo( Sink sink, Action action, ResourceBundle bundle, List dueTos )
333     {
334 
335         // Create a Map with key : dueTo name, value : dueTo email
336         Map namesEmailMap = new LinkedHashMap();
337 
338         // Only add the dueTo specified as attributes, if it has either a dueTo or a dueToEmail
339         if ( StringUtils.isNotEmpty( action.getDueTo() ) || StringUtils.isNotEmpty( action.getDueToEmail() ) )
340         {
341             namesEmailMap.put( action.getDueTo(), action.getDueToEmail() );
342         }
343 
344         for ( Iterator iterator = dueTos.iterator(); iterator.hasNext(); )
345         {
346             DueTo dueTo = (DueTo) iterator.next();
347             namesEmailMap.put( dueTo.getName(), dueTo.getEmail() );
348         }
349 
350         if ( namesEmailMap.isEmpty() )
351         {
352             return;
353         }
354 
355         sink.text( " " + bundle.getString( "report.changes.text.thanx" ) + " " );
356         int i = 0;
357         for ( Iterator iterator = namesEmailMap.keySet().iterator(); iterator.hasNext(); )
358         {
359             String currentDueTo = (String) iterator.next();
360             String currentDueToEmail = (String) namesEmailMap.get( currentDueTo );
361             i++;
362 
363             if ( StringUtils.isNotEmpty( currentDueToEmail ) )
364             {
365                 sinkLink( sink, currentDueTo, "mailto:" + currentDueToEmail );
366             }
367             else if ( StringUtils.isNotEmpty( currentDueTo ) )
368             {
369                 sink.text( currentDueTo );
370             }
371 
372             if ( i < namesEmailMap.size() )
373             {
374                 sink.text( ", " );
375             }
376         }
377 
378         sink.text( "." );
379     }
380 
381     /**
382      * Construct links to the issues that were solved by an action.
383      *
384      * @param issue The issue specified by attributes
385      * @param system The issue management system
386      * @param sink The sink
387      * @param fixes The List of issues specified as fixes elements
388      */
389     private void constructIssueLink( String issue, String system, Sink sink, List fixes )
390     {
391         if ( StringUtils.isNotEmpty( issue ) )
392         {
393             sink.link( parseIssueLink( issue, system ) );
394 
395             sink.text( issue );
396 
397             sink.link_();
398 
399             if ( !fixes.isEmpty() )
400             {
401                 sink.text( ", " );
402             }
403         }
404 
405         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
406         {
407             FixedIssue fixedIssue = (FixedIssue) iterator.next();
408             String currentIssueId = fixedIssue.getIssue();
409             if ( StringUtils.isNotEmpty( currentIssueId ) )
410             {
411                 sink.link( parseIssueLink( currentIssueId, system ) );
412 
413                 sink.text( currentIssueId );
414 
415                 sink.link_();
416             }
417 
418             if ( iterator.hasNext() )
419             {
420                 sink.text( ", " );
421             }
422         }
423     }
424 
425     /**
426      * Construct a text that references (but does not link to) the issues that
427      * were solved by an action.
428      *
429      * @param issue The issue specified by attributes
430      * @param sink The sink
431      * @param fixes The List of issues specified as fixes elements
432      */
433     private void constructIssueText( String issue, Sink sink, List fixes )
434     {
435         if ( StringUtils.isNotEmpty( issue ) )
436         {
437             sink.text( issue );
438 
439             if ( !fixes.isEmpty() )
440             {
441                 sink.text( ", " );
442             }
443         }
444 
445         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
446         {
447             FixedIssue fixedIssue = (FixedIssue) iterator.next();
448 
449             String currentIssueId = fixedIssue.getIssue();
450             if ( StringUtils.isNotEmpty( currentIssueId ) )
451             {
452                 sink.text( currentIssueId );
453             }
454 
455             if ( iterator.hasNext() )
456             {
457                 sink.text( ", " );
458             }
459         }
460     }
461 
462     private void constructReleaseHistory( Sink sink, ResourceBundle bundle, List releaseList )
463     {
464         sink.section2();
465 
466         sinkSectionTitle2Anchor( sink, bundle.getString( "report.changes.label.releasehistory" ),
467                                  bundle.getString( "report.changes.label.releasehistory" ) );
468 
469         sink.table();
470 
471         sink.tableRow();
472 
473         sinkHeader( sink, bundle.getString( "report.issues.label.fixVersion" ) );
474 
475         sinkHeader( sink, bundle.getString( "report.changes.label.releaseDate" ) );
476 
477         sinkHeader( sink, bundle.getString( "report.changes.label.releaseDescription" ) );
478 
479         sink.tableRow_();
480 
481         for ( int idx = 0; idx < releaseList.size(); idx++ )
482         {
483             Release release = (Release) releaseList.get( idx );
484 
485             sink.tableRow();
486 
487             sinkCellLink( sink, release.getVersion(), "#" + HtmlTools.encodeId( release.getVersion() ) );
488 
489             sinkCell( sink, release.getDateRelease() );
490 
491             sinkCell( sink, release.getDescription() );
492 
493             sink.tableRow_();
494         }
495 
496         sink.table_();
497 
498         // @todo Temporarily commented out until MCHANGES-46 is completely solved
499         // sink.rawText( bundle.getString( "report.changes.text.rssfeed" ) );
500         // sink.text( " " );
501         // sink.link( "changes.rss" );
502         // sinkFigure( "images/rss.png", sink );
503         // sink.link_();
504         //
505         // sink.lineBreak();
506 
507         sink.section2_();
508     }
509 
510     private void constructReleases( Sink sink, ResourceBundle bundle, List releaseList )
511     {
512 
513         for ( int idx = 0; idx < releaseList.size(); idx++ )
514         {
515             Release release = (Release) releaseList.get( idx );
516 
517             sink.section2();
518 
519             final String date = ( release.getDateRelease() == null ) ? "" : " - " + release.getDateRelease();
520 
521             sinkSectionTitle2Anchor( sink, bundle.getString( "report.changes.label.release" ) + " "
522                 + release.getVersion() + date, release.getVersion() );
523 
524             constructActions( sink, release.getActions(), bundle );
525 
526             sink.section2_();
527         }
528     }
529 
530     /**
531      * Replace tokens in the issue link template with the real values.
532      *
533      * @param issue The issue identifier
534      * @param system The issue management system
535      * @return An interpolated issue link
536      */
537     private String parseIssueLink( String issue, String system )
538     {
539         String parseLink;
540         String issueLink = (String) this.issueLinksPerSystem.get( system );
541         parseLink = issueLink.replaceFirst( ISSUE_TOKEN, issue );
542         if ( parseLink.indexOf( URL_TOKEN ) >= 0 )
543         {
544             String url = this.url.substring( 0, this.url.lastIndexOf( "/" ) );
545             parseLink = parseLink.replaceFirst( URL_TOKEN, url );
546         }
547 
548         return parseLink;
549     }
550 
551 }