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