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 1042855 2010-12-06 23:27:08Z 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                         int tz = ProjectInfoReportUtils.toInt( 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 '" + tz + "' for the " + type + " '" + member.getName()
328                                 + "' is not a recognised time zone, use a number in the range -12 to +14 instead of." );
329                         }
330                         else
331                         {
332                             text( member.getTimezone() );
333                             javascript.append( "    offsetDate('" ).append( type ).append( "-" ).append( rowId ).append( "', '" );
334                             javascript.append( member.getTimezone() ).append( "');" ).append( SystemUtils.LINE_SEPARATOR );
335                         }
336                     }
337                     sink.rawText( "</span>" );
338                     sink.tableCell_();
339                 }
340             }
341 
342             if ( headersMap.get( PROPERTIES ) == Boolean.TRUE )
343             {
344                 Properties props = member.getProperties();
345                 if ( props != null )
346                 {
347                     tableCell( propertiesToString( props ) );
348                 }
349                 else
350                 {
351                     tableCell( null );
352                 }
353             }
354 
355             sink.tableRow_();
356         }
357 
358         /**
359          * @param requiredHeaders
360          * @return
361          */
362         private String[] getRequiredContrHeaderArray( Map<String, Boolean> requiredHeaders )
363         {
364             List<String> requiredArray = new ArrayList<String>();
365             String name = getI18nString( "contributors.name" );
366             String email = getI18nString( "contributors.email" );
367             String url = getI18nString( "contributors.url" );
368             String organization = getI18nString( "contributors.organization" );
369             String organizationUrl = getI18nString( "contributors.organizationurl" );
370             String roles = getI18nString( "contributors.roles" );
371             String timeZone = getI18nString( "contributors.timezone" );
372             String actualTime = getI18nString( "contributors.actualtime" );
373             String properties = getI18nString( "contributors.properties" );
374 
375             setRequiredArray( requiredHeaders, requiredArray, name, email, url, organization, organizationUrl, roles,
376                               timeZone, actualTime, properties );
377 
378             return requiredArray.toArray( new String[requiredArray.size()] );
379         }
380 
381         /**
382          * @param requiredHeaders
383          * @return
384          */
385         private String[] getRequiredDevHeaderArray( Map<String, Boolean> requiredHeaders )
386         {
387             List<String> requiredArray = new ArrayList<String>();
388 
389             String id = getI18nString( "developers.id" );
390             String name = getI18nString( "developers.name" );
391             String email = getI18nString( "developers.email" );
392             String url = getI18nString( "developers.url" );
393             String organization = getI18nString( "developers.organization" );
394             String organizationUrl = getI18nString( "developers.organizationurl" );
395             String roles = getI18nString( "developers.roles" );
396             String timeZone = getI18nString( "developers.timezone" );
397             String actualTime = getI18nString( "developers.actualtime" );
398             String properties = getI18nString( "developers.properties" );
399 
400             if ( requiredHeaders.get( ID ) == Boolean.TRUE )
401             {
402                 requiredArray.add( id );
403             }
404 
405             setRequiredArray( requiredHeaders, requiredArray, name, email, url, organization, organizationUrl, roles,
406                               timeZone, actualTime, properties );
407 
408             return requiredArray.toArray( new String[requiredArray.size()] );
409         }
410 
411         /**
412          * @param requiredHeaders
413          * @param requiredArray
414          * @param name
415          * @param email
416          * @param url
417          * @param organization
418          * @param organizationUrl
419          * @param roles
420          * @param timeZone
421          * @param actualTime
422          * @param properties
423          */
424         private void setRequiredArray( Map<String, Boolean> requiredHeaders, List<String> requiredArray, String name,
425                                        String email, String url, String organization, String organizationUrl,
426                                        String roles, String timeZone, String actualTime, String properties )
427         {
428             if ( requiredHeaders.get( NAME ) == Boolean.TRUE )
429             {
430                 requiredArray.add( name );
431             }
432             if ( requiredHeaders.get( EMAIL ) == Boolean.TRUE )
433             {
434                 requiredArray.add( email );
435             }
436             if ( requiredHeaders.get( URL ) == Boolean.TRUE )
437             {
438                 requiredArray.add( url );
439             }
440             if ( requiredHeaders.get( ORGANIZATION ) == Boolean.TRUE )
441             {
442                 requiredArray.add( organization );
443             }
444             if ( requiredHeaders.get( ORGANIZATION_URL ) == Boolean.TRUE )
445             {
446                 requiredArray.add( organizationUrl );
447             }
448             if ( requiredHeaders.get( ROLES ) == Boolean.TRUE )
449             {
450                 requiredArray.add( roles );
451             }
452             if ( requiredHeaders.get( TIME_ZONE ) == Boolean.TRUE )
453             {
454                 requiredArray.add( timeZone );
455                 requiredArray.add( actualTime );
456             }
457 
458             if ( requiredHeaders.get( PROPERTIES ) == Boolean.TRUE )
459             {
460                 requiredArray.add( properties );
461             }
462         }
463 
464         /**
465          * @param units contributors and developers to check
466          * @return required headers
467          */
468         private Map<String, Boolean> checkRequiredHeaders( List<? extends Contributor> units )
469         {
470             Map<String, Boolean> requiredHeaders = new HashMap<String, Boolean>();
471 
472             requiredHeaders.put( ID, Boolean.FALSE );
473             requiredHeaders.put( NAME, Boolean.FALSE );
474             requiredHeaders.put( EMAIL, Boolean.FALSE );
475             requiredHeaders.put( URL, Boolean.FALSE );
476             requiredHeaders.put( ORGANIZATION, Boolean.FALSE );
477             requiredHeaders.put( ORGANIZATION_URL, Boolean.FALSE );
478             requiredHeaders.put( ROLES, Boolean.FALSE );
479             requiredHeaders.put( TIME_ZONE, Boolean.FALSE );
480             requiredHeaders.put( PROPERTIES, Boolean.FALSE );
481 
482             for ( Contributor unit : units )
483             {
484                 if ( unit instanceof Developer )
485                 {
486                     Developer developer = (Developer) unit;
487                     if ( StringUtils.isNotEmpty( developer.getId() ) )
488                     {
489                         requiredHeaders.put( ID, Boolean.TRUE );
490                     }
491                 }
492                 if ( StringUtils.isNotEmpty( unit.getName() ) )
493                 {
494                     requiredHeaders.put( NAME, Boolean.TRUE );
495                 }
496                 if ( StringUtils.isNotEmpty( unit.getEmail() ) )
497                 {
498                     requiredHeaders.put( EMAIL, Boolean.TRUE );
499                 }
500                 if ( StringUtils.isNotEmpty( unit.getUrl() ) )
501                 {
502                     requiredHeaders.put( URL, Boolean.TRUE );
503                 }
504                 if ( StringUtils.isNotEmpty( unit.getOrganization() ) )
505                 {
506                     requiredHeaders.put( ORGANIZATION, Boolean.TRUE );
507                 }
508                 if ( StringUtils.isNotEmpty( unit.getOrganizationUrl() ) )
509                 {
510                     requiredHeaders.put( ORGANIZATION_URL, Boolean.TRUE );
511                 }
512                 if ( !isEmpty( unit.getRoles() ) )
513                 {
514                     requiredHeaders.put( ROLES, Boolean.TRUE );
515                 }
516                 if ( StringUtils.isNotEmpty( unit.getTimezone() ) )
517                 {
518                     requiredHeaders.put( TIME_ZONE, Boolean.TRUE );
519                 }
520                 Properties properties = unit.getProperties();
521                 if ( null != properties && !properties.isEmpty() )
522                 {
523                     requiredHeaders.put( PROPERTIES, Boolean.TRUE );
524                 }
525             }
526             return requiredHeaders;
527         }
528 
529         /**
530          * Create a table cell with a link to the given url. The url is not validated.
531          *
532          * @param url
533          */
534         private void tableCellForUrl( String url )
535         {
536             sink.tableCell();
537 
538             if ( StringUtils.isEmpty( url ) )
539             {
540                 text( url );
541             }
542             else
543             {
544                 link( url, url );
545             }
546 
547             sink.tableCell_();
548         }
549 
550         private boolean isEmpty( List<?> list )
551         {
552             return ( list == null ) || list.isEmpty();
553         }
554     }
555 }