View Javadoc

1   package org.apache.maven.report.projectinfo;
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 org.apache.commons.lang.SystemUtils;
23  import org.apache.maven.doxia.sink.Sink;
24  import org.apache.maven.model.Contributor;
25  import org.apache.maven.model.Developer;
26  import org.apache.maven.model.Model;
27  import org.apache.maven.plugin.logging.Log;
28  import org.apache.maven.plugins.annotations.Mojo;
29  import org.codehaus.plexus.i18n.I18N;
30  import org.codehaus.plexus.util.StringUtils;
31  import org.joda.time.DateTimeZone;
32  
33  import java.util.ArrayList;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Locale;
37  import java.util.Map;
38  import java.util.Properties;
39  import java.util.TimeZone;
40  
41  /**
42   * Generates the Project Team report.
43   *
44   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton </a>
45   * @version $Id: TeamListReport.java 1367255 2012-07-30 20:01:21Z hboutemy $
46   * @since 2.0
47   */
48  @Mojo( name = "project-team" )
49  public class TeamListReport
50      extends AbstractProjectInfoReport
51  {
52      // ----------------------------------------------------------------------
53      // Public methods
54      // ----------------------------------------------------------------------
55  
56      @Override
57      public void executeReport( Locale locale )
58      {
59          TeamListRenderer r = new TeamListRenderer( getSink(), project.getModel(), getI18N( locale ), locale, getLog() );
60  
61          r.render();
62      }
63  
64      /** {@inheritDoc} */
65      public String getOutputName()
66      {
67          return "team-list";
68      }
69  
70      @Override
71      protected String getI18Nsection()
72      {
73          return "team-list";
74      }
75  
76      // ----------------------------------------------------------------------
77      // Private
78      // ----------------------------------------------------------------------
79  
80      /**
81       * Internal renderer class
82       */
83      private static class TeamListRenderer
84          extends AbstractProjectInfoRenderer
85      {
86          private static final String PROPERTIES = "properties";
87  
88          private static final String TIME_ZONE = "timeZone";
89  
90          private static final String ROLES = "roles";
91  
92          private static final String ORGANIZATION_URL = "organizationUrl";
93  
94          private static final String ORGANIZATION = "organization";
95  
96          private static final String URL = "url";
97  
98          private static final String EMAIL = "email";
99  
100         private static final String NAME = "name";
101 
102         private static final String ID = "id";
103 
104         private final Model model;
105 
106         private final Log log;
107 
108         private static final String[] EMPTY_STRING_ARRAY = new String[0];
109 
110         TeamListRenderer( Sink sink, Model model, I18N i18n, Locale locale, Log log )
111         {
112             super( sink, i18n, locale );
113 
114             this.model = model;
115             this.log = log;
116         }
117 
118         @Override
119         protected String getI18Nsection()
120         {
121             return "team-list";
122         }
123 
124         @Override
125         public void renderBody()
126         {
127             startSection( getI18nString( "intro.title" ) );
128 
129             // To handle JS
130             StringBuffer javascript =
131                 new StringBuffer( "function offsetDate(id, offset) {" ).append( SystemUtils.LINE_SEPARATOR );
132             javascript.append( "    var now = new Date();" ).append( SystemUtils.LINE_SEPARATOR );
133             javascript.append( "    var nowTime = now.getTime();" ).append( SystemUtils.LINE_SEPARATOR );
134             javascript.append( "    var localOffset = now.getTimezoneOffset();" ).append( SystemUtils.LINE_SEPARATOR );
135             javascript.append( "    var developerTime = nowTime + ( offset * 60 * 60 * 1000 )"
136                                    + "+ ( localOffset * 60 * 1000 );" ).append( SystemUtils.LINE_SEPARATOR );
137             javascript.append( "    var developerDate = new Date(developerTime);" ).append( SystemUtils.LINE_SEPARATOR );
138             javascript.append( SystemUtils.LINE_SEPARATOR );
139             javascript.append( "    document.getElementById(id).innerHTML = developerDate;" ).append( SystemUtils.LINE_SEPARATOR );
140             javascript.append( "}" ).append( SystemUtils.LINE_SEPARATOR );
141             javascript.append( SystemUtils.LINE_SEPARATOR );
142             javascript.append( "function init(){" ).append( SystemUtils.LINE_SEPARATOR );
143 
144             // Introduction
145             paragraph( getI18nString( "intro.description1" ) );
146             paragraph( getI18nString( "intro.description2" ) );
147 
148             // Developer section
149             List<Developer> developers = model.getDevelopers();
150 
151             startSection( getI18nString( "developers.title" ) );
152 
153             if ( isEmpty( developers ) )
154             {
155                 paragraph( getI18nString( "nodeveloper" ) );
156             }
157             else
158             {
159                 paragraph( getI18nString( "developers.intro" ) );
160 
161                 startTable();
162 
163                 // By default we think that all headers not required: set true for headers that are required
164                 Map<String, Boolean> headersMap = checkRequiredHeaders( developers );
165                 String[] requiredHeaders = getRequiredDevHeaderArray( headersMap );
166 
167                 tableHeader( requiredHeaders );
168 
169                 // To handle JS
170                 int developersRowId = 0;
171                 for ( Developer developer : developers )
172                 {
173                     renderTeamMember( developer, developersRowId, headersMap, javascript );
174 
175                     developersRowId++;
176                 }
177 
178                 endTable();
179             }
180 
181             endSection();
182 
183             // contributors section
184             List<Contributor> contributors = model.getContributors();
185 
186             startSection( getI18nString( "contributors.title" ) );
187 
188             if ( isEmpty( contributors ) )
189             {
190                 paragraph( getI18nString( "nocontributor" ) );
191             }
192             else
193             {
194                 paragraph( getI18nString( "contributors.intro" ) );
195 
196                 startTable();
197 
198                 Map<String, Boolean> headersMap = checkRequiredHeaders( contributors );
199                 String[] requiredHeaders = getRequiredContrHeaderArray( headersMap );
200 
201                 tableHeader( requiredHeaders );
202 
203                 // To handle JS
204                 int contributorsRowId = 0;
205                 for ( Contributor contributor : contributors )
206                 {
207                     renderTeamMember( contributor, contributorsRowId, headersMap, javascript );
208 
209                     contributorsRowId++;
210                 }
211 
212                 endTable();
213             }
214 
215             // To handle JS
216             javascript.append( "}" ).append( SystemUtils.LINE_SEPARATOR ).append( SystemUtils.LINE_SEPARATOR )
217                 .append( "window.onLoad = init();" ).append( SystemUtils.LINE_SEPARATOR );
218             javaScript( javascript.toString() );
219 
220             endSection();
221 
222             endSection();
223         }
224 
225         private void renderTeamMember( Contributor member, int rowId, Map<String, Boolean> headersMap,
226                                        StringBuffer javascript )
227         {
228             sink.tableRow();
229 
230             String type = "contributor";
231             if ( member instanceof Developer )
232             {
233                 type = "developer";
234                 if ( headersMap.get( ID ) == Boolean.TRUE )
235                 {
236                     String id = ( (Developer) member ).getId();
237                     if ( id == null )
238                     {
239                         tableCell( null );
240                     }
241                     else
242                     {
243                         tableCell( "<a name=\"" + id + "\"></a>" + id, true );
244                     }
245                 }
246             }
247             if ( headersMap.get( NAME ) == Boolean.TRUE )
248             {
249                 tableCell( member.getName() );
250             }
251             if ( headersMap.get( EMAIL ) == Boolean.TRUE )
252             {
253                 tableCell( createLinkPatternedText( member.getEmail(), member.getEmail() ) );
254             }
255             if ( headersMap.get( URL ) == Boolean.TRUE )
256             {
257                 tableCellForUrl( member.getUrl() );
258             }
259             if ( headersMap.get( ORGANIZATION ) == Boolean.TRUE )
260             {
261                 tableCell( member.getOrganization() );
262             }
263             if ( headersMap.get( ORGANIZATION_URL ) == Boolean.TRUE )
264             {
265                 tableCellForUrl( member.getOrganizationUrl() );
266             }
267             if ( headersMap.get( ROLES ) == Boolean.TRUE )
268             {
269                 if ( member.getRoles() != null )
270                 {
271                     // Comma separated roles
272                     tableCell( StringUtils.join( member.getRoles().toArray( EMPTY_STRING_ARRAY ), ", " ) );
273                 }
274                 else
275                 {
276                     tableCell( null );
277                 }
278             }
279             if ( headersMap.get( TIME_ZONE ) == Boolean.TRUE )
280             {
281                 tableCell( member.getTimezone() );
282 
283                 if ( StringUtils.isNotEmpty( member.getTimezone() )
284                     && ( !ProjectInfoReportUtils.isNumber( member.getTimezone().trim() ) ) )
285                 {
286                     String tz = member.getTimezone().trim();
287                     try
288                     {
289                         // check if it is a valid timeZone
290                         DateTimeZone.forID( tz );
291 
292                         sink.tableCell();
293                         sink.rawText( "<span id=\"" + type + "-" + rowId + "\">" );
294                         text( tz );
295                         String offSet = String.valueOf( TimeZone.getTimeZone( tz ).getRawOffset() / 3600000 );
296                         javascript.append( "    offsetDate('" ).append( type ).append( "-" ).append( rowId ).append( "', '" );
297                         javascript.append( offSet ).append( "');" ).append( SystemUtils.LINE_SEPARATOR );
298                         sink.rawText( "</span>" );
299                         sink.tableCell_();
300                     }
301                     catch ( IllegalArgumentException e )
302                     {
303                         log.warn( "The time zone '" + tz + "' for the " + type + " '" + member.getName()
304                             + "' is not a recognised time zone, use a number in the range -12 and +14 instead of." );
305 
306                         sink.tableCell();
307                         sink.rawText( "<span id=\"" + type + "-" + rowId + "\">" );
308                         text( null );
309                         sink.rawText( "</span>" );
310                         sink.tableCell_();
311                     }
312                 }
313                 else
314                 {
315                     // To handle JS
316                     sink.tableCell();
317                     sink.rawText( "<span id=\"" + type + "-" + rowId + "\">" );
318                     if ( StringUtils.isEmpty( member.getTimezone() ) )
319                     {
320                         text( null );
321                     }
322                     else
323                     {
324                         // check if number is between -12 and +14
325                         float tz = ProjectInfoReportUtils.toFloat( member.getTimezone().trim(), Integer.MIN_VALUE );
326                         if ( tz == Integer.MIN_VALUE || !( tz >= -12 && tz <= 14 ) )
327                         {
328                             text( null );
329                             log.warn( "The time zone '" + member.getTimezone().trim() + "' for the " + type + " '"
330                                 + member.getName()
331                                 + "' is not a recognised time zone, use a number in the range -12 to +14 instead of." );
332                         }
333                         else
334                         {
335                             text( member.getTimezone().trim() );
336                             javascript.append( "    offsetDate('" ).append( type ).append( "-" ).append( rowId ).append( "', '" );
337                             javascript.append( member.getTimezone() ).append( "');" ).append( SystemUtils.LINE_SEPARATOR );
338                         }
339                     }
340                     sink.rawText( "</span>" );
341                     sink.tableCell_();
342                 }
343             }
344 
345             if ( headersMap.get( PROPERTIES ) == Boolean.TRUE )
346             {
347                 Properties props = member.getProperties();
348                 if ( props != null )
349                 {
350                     tableCell( propertiesToString( props ) );
351                 }
352                 else
353                 {
354                     tableCell( null );
355                 }
356             }
357 
358             sink.tableRow_();
359         }
360 
361         /**
362          * @param requiredHeaders
363          * @return
364          */
365         private String[] getRequiredContrHeaderArray( Map<String, Boolean> requiredHeaders )
366         {
367             List<String> requiredArray = new ArrayList<String>();
368             String name = getI18nString( "contributors.name" );
369             String email = getI18nString( "contributors.email" );
370             String url = getI18nString( "contributors.url" );
371             String organization = getI18nString( "contributors.organization" );
372             String organizationUrl = getI18nString( "contributors.organizationurl" );
373             String roles = getI18nString( "contributors.roles" );
374             String timeZone = getI18nString( "contributors.timezone" );
375             String actualTime = getI18nString( "contributors.actualtime" );
376             String properties = getI18nString( "contributors.properties" );
377 
378             setRequiredArray( requiredHeaders, requiredArray, name, email, url, organization, organizationUrl, roles,
379                               timeZone, actualTime, properties );
380 
381             return requiredArray.toArray( new String[requiredArray.size()] );
382         }
383 
384         /**
385          * @param requiredHeaders
386          * @return
387          */
388         private String[] getRequiredDevHeaderArray( Map<String, Boolean> requiredHeaders )
389         {
390             List<String> requiredArray = new ArrayList<String>();
391 
392             String id = getI18nString( "developers.id" );
393             String name = getI18nString( "developers.name" );
394             String email = getI18nString( "developers.email" );
395             String url = getI18nString( "developers.url" );
396             String organization = getI18nString( "developers.organization" );
397             String organizationUrl = getI18nString( "developers.organizationurl" );
398             String roles = getI18nString( "developers.roles" );
399             String timeZone = getI18nString( "developers.timezone" );
400             String actualTime = getI18nString( "developers.actualtime" );
401             String properties = getI18nString( "developers.properties" );
402 
403             if ( requiredHeaders.get( ID ) == Boolean.TRUE )
404             {
405                 requiredArray.add( id );
406             }
407 
408             setRequiredArray( requiredHeaders, requiredArray, name, email, url, organization, organizationUrl, roles,
409                               timeZone, actualTime, properties );
410 
411             return requiredArray.toArray( new String[requiredArray.size()] );
412         }
413 
414         /**
415          * @param requiredHeaders
416          * @param requiredArray
417          * @param name
418          * @param email
419          * @param url
420          * @param organization
421          * @param organizationUrl
422          * @param roles
423          * @param timeZone
424          * @param actualTime
425          * @param properties
426          */
427         private void setRequiredArray( Map<String, Boolean> requiredHeaders, List<String> requiredArray, String name,
428                                        String email, String url, String organization, String organizationUrl,
429                                        String roles, String timeZone, String actualTime, String properties )
430         {
431             if ( requiredHeaders.get( NAME ) == Boolean.TRUE )
432             {
433                 requiredArray.add( name );
434             }
435             if ( requiredHeaders.get( EMAIL ) == Boolean.TRUE )
436             {
437                 requiredArray.add( email );
438             }
439             if ( requiredHeaders.get( URL ) == Boolean.TRUE )
440             {
441                 requiredArray.add( url );
442             }
443             if ( requiredHeaders.get( ORGANIZATION ) == Boolean.TRUE )
444             {
445                 requiredArray.add( organization );
446             }
447             if ( requiredHeaders.get( ORGANIZATION_URL ) == Boolean.TRUE )
448             {
449                 requiredArray.add( organizationUrl );
450             }
451             if ( requiredHeaders.get( ROLES ) == Boolean.TRUE )
452             {
453                 requiredArray.add( roles );
454             }
455             if ( requiredHeaders.get( TIME_ZONE ) == Boolean.TRUE )
456             {
457                 requiredArray.add( timeZone );
458                 requiredArray.add( actualTime );
459             }
460 
461             if ( requiredHeaders.get( PROPERTIES ) == Boolean.TRUE )
462             {
463                 requiredArray.add( properties );
464             }
465         }
466 
467         /**
468          * @param units contributors and developers to check
469          * @return required headers
470          */
471         private Map<String, Boolean> checkRequiredHeaders( List<? extends Contributor> units )
472         {
473             Map<String, Boolean> requiredHeaders = new HashMap<String, Boolean>();
474 
475             requiredHeaders.put( ID, Boolean.FALSE );
476             requiredHeaders.put( NAME, Boolean.FALSE );
477             requiredHeaders.put( EMAIL, Boolean.FALSE );
478             requiredHeaders.put( URL, Boolean.FALSE );
479             requiredHeaders.put( ORGANIZATION, Boolean.FALSE );
480             requiredHeaders.put( ORGANIZATION_URL, Boolean.FALSE );
481             requiredHeaders.put( ROLES, Boolean.FALSE );
482             requiredHeaders.put( TIME_ZONE, Boolean.FALSE );
483             requiredHeaders.put( PROPERTIES, Boolean.FALSE );
484 
485             for ( Contributor unit : units )
486             {
487                 if ( unit instanceof Developer )
488                 {
489                     Developer developer = (Developer) unit;
490                     if ( StringUtils.isNotEmpty( developer.getId() ) )
491                     {
492                         requiredHeaders.put( ID, Boolean.TRUE );
493                     }
494                 }
495                 if ( StringUtils.isNotEmpty( unit.getName() ) )
496                 {
497                     requiredHeaders.put( NAME, Boolean.TRUE );
498                 }
499                 if ( StringUtils.isNotEmpty( unit.getEmail() ) )
500                 {
501                     requiredHeaders.put( EMAIL, Boolean.TRUE );
502                 }
503                 if ( StringUtils.isNotEmpty( unit.getUrl() ) )
504                 {
505                     requiredHeaders.put( URL, Boolean.TRUE );
506                 }
507                 if ( StringUtils.isNotEmpty( unit.getOrganization() ) )
508                 {
509                     requiredHeaders.put( ORGANIZATION, Boolean.TRUE );
510                 }
511                 if ( StringUtils.isNotEmpty( unit.getOrganizationUrl() ) )
512                 {
513                     requiredHeaders.put( ORGANIZATION_URL, Boolean.TRUE );
514                 }
515                 if ( !isEmpty( unit.getRoles() ) )
516                 {
517                     requiredHeaders.put( ROLES, Boolean.TRUE );
518                 }
519                 if ( StringUtils.isNotEmpty( unit.getTimezone() ) )
520                 {
521                     requiredHeaders.put( TIME_ZONE, Boolean.TRUE );
522                 }
523                 Properties properties = unit.getProperties();
524                 if ( null != properties && !properties.isEmpty() )
525                 {
526                     requiredHeaders.put( PROPERTIES, Boolean.TRUE );
527                 }
528             }
529             return requiredHeaders;
530         }
531 
532         /**
533          * Create a table cell with a link to the given url. The url is not validated.
534          *
535          * @param url
536          */
537         private void tableCellForUrl( String url )
538         {
539             sink.tableCell();
540 
541             if ( StringUtils.isEmpty( url ) )
542             {
543                 text( url );
544             }
545             else
546             {
547                 link( url, url );
548             }
549 
550             sink.tableCell_();
551         }
552 
553         private boolean isEmpty( List<?> list )
554         {
555             return ( list == null ) || list.isEmpty();
556         }
557     }
558 }