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