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 816601 2012-05-08 12:50:18Z 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<String,String> namesEmailMap = new LinkedHashMap<String,String>();
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 ( String currentDueTo : namesEmailMap.keySet() )
358         {
359             String currentDueToEmail = namesEmailMap.get( currentDueTo );
360             i++;
361 
362             if ( StringUtils.isNotEmpty( currentDueToEmail ) )
363             {
364                 sinkLink( sink, currentDueTo, "mailto:" + currentDueToEmail );
365             }
366             else if ( StringUtils.isNotEmpty( currentDueTo ) )
367             {
368                 sink.text( currentDueTo );
369             }
370 
371             if ( i < namesEmailMap.size() )
372             {
373                 sink.text( ", " );
374             }
375         }
376 
377         sink.text( "." );
378     }
379 
380     /**
381      * Construct links to the issues that were solved by an action.
382      *
383      * @param issue The issue specified by attributes
384      * @param system The issue management system
385      * @param sink The sink
386      * @param fixes The List of issues specified as fixes elements
387      */
388     private void constructIssueLink( String issue, String system, Sink sink, List fixes )
389     {
390         if ( StringUtils.isNotEmpty( issue ) )
391         {
392             sink.link( parseIssueLink( issue, system ) );
393 
394             sink.text( issue );
395 
396             sink.link_();
397 
398             if ( !fixes.isEmpty() )
399             {
400                 sink.text( ", " );
401             }
402         }
403 
404         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
405         {
406             FixedIssue fixedIssue = (FixedIssue) iterator.next();
407             String currentIssueId = fixedIssue.getIssue();
408             if ( StringUtils.isNotEmpty( currentIssueId ) )
409             {
410                 sink.link( parseIssueLink( currentIssueId, system ) );
411 
412                 sink.text( currentIssueId );
413 
414                 sink.link_();
415             }
416 
417             if ( iterator.hasNext() )
418             {
419                 sink.text( ", " );
420             }
421         }
422     }
423 
424     /**
425      * Construct a text that references (but does not link to) the issues that
426      * were solved by an action.
427      *
428      * @param issue The issue specified by attributes
429      * @param sink The sink
430      * @param fixes The List of issues specified as fixes elements
431      */
432     private void constructIssueText( String issue, Sink sink, List fixes )
433     {
434         if ( StringUtils.isNotEmpty( issue ) )
435         {
436             sink.text( issue );
437 
438             if ( !fixes.isEmpty() )
439             {
440                 sink.text( ", " );
441             }
442         }
443 
444         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
445         {
446             FixedIssue fixedIssue = (FixedIssue) iterator.next();
447 
448             String currentIssueId = fixedIssue.getIssue();
449             if ( StringUtils.isNotEmpty( currentIssueId ) )
450             {
451                 sink.text( currentIssueId );
452             }
453 
454             if ( iterator.hasNext() )
455             {
456                 sink.text( ", " );
457             }
458         }
459     }
460 
461     private void constructReleaseHistory( Sink sink, ResourceBundle bundle, List releaseList )
462     {
463         sink.section2();
464 
465         sinkSectionTitle2Anchor( sink, bundle.getString( "report.changes.label.releasehistory" ),
466                                  bundle.getString( "report.changes.label.releasehistory" ) );
467 
468         sink.table();
469 
470         sink.tableRow();
471 
472         sinkHeader( sink, bundle.getString( "report.issues.label.fixVersion" ) );
473 
474         sinkHeader( sink, bundle.getString( "report.changes.label.releaseDate" ) );
475 
476         sinkHeader( sink, bundle.getString( "report.changes.label.releaseDescription" ) );
477 
478         sink.tableRow_();
479 
480         for ( int idx = 0; idx < releaseList.size(); idx++ )
481         {
482             Release release = (Release) releaseList.get( idx );
483 
484             sink.tableRow();
485 
486             sinkCellLink( sink, release.getVersion(), "#" + HtmlTools.encodeId( release.getVersion() ) );
487 
488             sinkCell( sink, release.getDateRelease() );
489 
490             sinkCell( sink, release.getDescription() );
491 
492             sink.tableRow_();
493         }
494 
495         sink.table_();
496 
497         // @todo Temporarily commented out until MCHANGES-46 is completely solved
498         // sink.rawText( bundle.getString( "report.changes.text.rssfeed" ) );
499         // sink.text( " " );
500         // sink.link( "changes.rss" );
501         // sinkFigure( "images/rss.png", sink );
502         // sink.link_();
503         //
504         // sink.lineBreak();
505 
506         sink.section2_();
507     }
508 
509     private void constructReleases( Sink sink, ResourceBundle bundle, List releaseList )
510     {
511 
512         for ( int idx = 0; idx < releaseList.size(); idx++ )
513         {
514             Release release = (Release) releaseList.get( idx );
515 
516             sink.section2();
517 
518             final String date = ( release.getDateRelease() == null ) ? "" : " - " + release.getDateRelease();
519 
520             sinkSectionTitle2Anchor( sink, bundle.getString( "report.changes.label.release" ) + " "
521                 + release.getVersion() + date, release.getVersion() );
522 
523             constructActions( sink, release.getActions(), bundle );
524 
525             sink.section2_();
526         }
527     }
528 
529     /**
530      * Replace tokens in the issue link template with the real values.
531      *
532      * @param issue The issue identifier
533      * @param system The issue management system
534      * @return An interpolated issue link
535      */
536     private String parseIssueLink( String issue, String system )
537     {
538         String parseLink;
539         String issueLink = (String) this.issueLinksPerSystem.get( system );
540         parseLink = issueLink.replaceFirst( ISSUE_TOKEN, issue );
541         if ( parseLink.indexOf( URL_TOKEN ) >= 0 )
542         {
543             String url = this.url.substring( 0, this.url.lastIndexOf( "/" ) );
544             parseLink = parseLink.replaceFirst( URL_TOKEN, url );
545         }
546 
547         return parseLink;
548     }
549 
550 }