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 816595 2012-05-08 12:43:00Z 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.text( 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 = this.url.substring( 0, this.url.lastIndexOf( "/" ) );
347             parseLink = parseLink.replaceFirst( URL_TOKEN, url );
348         }
349 
350         return parseLink;
351     }
352 
353     private void sinkBeginReport( Sink sink, ResourceBundle bundle )
354     {
355         sink.head();
356         String title = null;
357         if ( report.getTitle() != null )
358         {
359             title = report.getTitle();
360         }
361         else
362         {
363             title = bundle.getString( "report.changes.header" );
364         }
365         sink.title();
366         sink.text( title );
367         sink.title_();
368 
369         if ( StringUtils.isNotEmpty( report.getAuthor() ) )
370         {
371             sink.author();
372             sink.text( report.getAuthor() );
373             sink.author_();
374         }
375 
376         sink.head_();
377 
378         sink.body();
379 
380         sink.section1();
381 
382         sinkSectionTitle1Anchor( sink, title, title );
383     }
384 
385     private void sinkCell( Sink sink, String text )
386     {
387         sink.tableCell();
388 
389         sink.text( text );
390 
391         sink.tableCell_();
392     }
393 
394     private void sinkCellLink( Sink sink, String text, String link )
395     {
396         sink.tableCell();
397 
398         sinkLink( sink, text, link );
399 
400         sink.tableCell_();
401     }
402 
403     private void sinkEndReport( Sink sink )
404     {
405         sink.section1_();
406 
407         sink.body_();
408 
409         sink.flush();
410 
411         sink.close();
412     }
413 
414     private void sinkFigure( String image, Sink sink, String altText )
415     {
416         sink.figure();
417 
418         sink.figureGraphics( image );
419 
420         sink.figureCaption();
421 
422         sink.text( altText );
423 
424         sink.figureCaption_();
425 
426         sink.figure_();
427     }
428 
429     private void sinkHeader( Sink sink, String header )
430     {
431         sink.tableHeaderCell();
432 
433         sink.text( header );
434 
435         sink.tableHeaderCell_();
436     }
437 
438     private void sinkLink( Sink sink, String text, String link )
439     {
440         sink.link( link );
441 
442         sink.text( text );
443 
444         sink.link_();
445     }
446 
447     private void sinkSectionTitle1Anchor( Sink sink, String text, String anchor )
448     {
449         sink.sectionTitle1();
450         sink.text( text );
451         sink.sectionTitle1_();
452 
453         sink.anchor( anchor );
454         sink.anchor_();
455     }
456 
457     private void sinkSectionTitle2Anchor( Sink sink, String text, String anchor )
458     {
459         sink.sectionTitle2();
460         sink.text( text );
461         sink.sectionTitle2_();
462 
463         sink.anchor( anchor );
464         sink.anchor_();
465     }
466 
467     private void sinkShowTypeIcon( Sink sink, String type )
468     {
469         String image = "";
470         String altText = "";
471 
472         if ( type == null )
473         {
474             image = "images/icon_help_sml.gif";
475             altText = "?";
476         }
477         else if ( type.equals( "fix" ) )
478         {
479             image = "images/fix.gif";
480             altText = "fix";
481         }
482         else if ( type.equals( "update" ) )
483         {
484             image = "images/update.gif";
485             altText = "update";
486         }
487         else if ( type.equals( "add" ) )
488         {
489             image = "images/add.gif";
490             altText = "add";
491         }
492         else if ( type.equals( "remove" ) )
493         {
494             image = "images/remove.gif";
495             altText = "remove";
496         }
497 
498         sink.tableCell();
499 
500         sinkFigure( image, sink, altText );
501 
502         sink.tableCell_();
503     }
504 
505     /**
506      * @param issue The issue specified by attributes
507      * @param fixes The List of issues specified as fixes elements
508      */
509     private void constructIssueLink( String issue, String system, Sink sink, List fixes )
510     {
511         if ( StringUtils.isNotEmpty( issue ) )
512         {
513             sink.link( parseIssueLink( issue, system ) );
514 
515             sink.text( issue );
516 
517             sink.link_();
518 
519             if ( !fixes.isEmpty() )
520             {
521                 sink.text( ", " );
522             }
523         }
524 
525         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
526         {
527             FixedIssue fixedIssue = (FixedIssue) iterator.next();
528             String currentIssueId = fixedIssue.getIssue();
529             if ( StringUtils.isNotEmpty( currentIssueId ) )
530             {
531                 sink.link( parseIssueLink( currentIssueId, system ) );
532 
533                 sink.text( currentIssueId );
534 
535                 sink.link_();
536             }
537 
538             if ( iterator.hasNext() )
539             {
540                 sink.text( ", " );
541             }
542         }
543     }
544 
545     /**
546      * @param issue The issue specified by attributes
547      * @param fixes The List of issues specified as fixes elements
548      */
549     private void constructIssueText( String issue, Sink sink, List fixes )
550     {
551         if ( StringUtils.isNotEmpty( issue ) )
552         {
553             sink.text( issue );
554 
555             if ( !fixes.isEmpty() )
556             {
557                 sink.text( ", " );
558             }
559         }
560 
561         for ( Iterator iterator = fixes.iterator(); iterator.hasNext(); )
562         {
563             FixedIssue fixedIssue = (FixedIssue) iterator.next();
564 
565             String currentIssueId = fixedIssue.getIssue();
566             if ( StringUtils.isNotEmpty( currentIssueId ) )
567             {
568                 sink.text( currentIssueId );
569             }
570 
571             if ( iterator.hasNext() )
572             {
573                 sink.text( ", " );
574             }
575         }
576     }
577 
578     /**
579      * @param sink
580      * @param action
581      * @param bundle
582      */
583     private void constructDueTo( Sink sink, Action action, ResourceBundle bundle, List dueTos )
584     {
585 
586         // Create a Map with key : dueTo name, value : dueTo email
587         Map namesEmailMap = new LinkedHashMap();
588 
589         // Only add the dueTo specified as attributes, if it has either a dueTo or a dueToEmail
590         if ( StringUtils.isNotEmpty( action.getDueTo() ) || StringUtils.isNotEmpty( action.getDueToEmail() ) )
591         {
592             namesEmailMap.put( action.getDueTo(), action.getDueToEmail() );
593         }
594 
595         for ( Iterator iterator = dueTos.iterator(); iterator.hasNext(); )
596         {
597             DueTo dueTo = (DueTo) iterator.next();
598             namesEmailMap.put( dueTo.getName(), dueTo.getEmail() );
599         }
600 
601         if ( namesEmailMap.isEmpty() )
602         {
603             return;
604         }
605 
606         sink.text( " " + bundle.getString( "report.changes.text.thanx" ) + " " );
607         int i = 0;
608         for ( Iterator iterator = namesEmailMap.keySet().iterator(); iterator.hasNext(); )
609         {
610             String currentDueTo = (String) iterator.next();
611             String currentDueToEmail = (String) namesEmailMap.get( currentDueTo );
612             i++;
613 
614             if ( StringUtils.isNotEmpty( currentDueToEmail ) )
615             {
616                 sinkLink( sink, currentDueTo, "mailto:" + currentDueToEmail );
617             }
618             else if ( StringUtils.isNotEmpty( currentDueTo ) )
619             {
620                 sink.text( currentDueTo );
621             }
622 
623             if ( i < namesEmailMap.size() )
624             {
625                 sink.text( ", " );
626             }
627         }
628 
629         sink.text( "." );
630     }
631 
632 }