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