View Javadoc

1   package org.apache.maven.plugin.doap;
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.io.FileNotFoundException;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.lang.reflect.Method;
27  import java.net.MalformedURLException;
28  import java.net.SocketTimeoutException;
29  import java.net.URL;
30  import java.text.DateFormat;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.Date;
34  import java.util.HashMap;
35  import java.util.LinkedList;
36  import java.util.List;
37  import java.util.Locale;
38  import java.util.Map;
39  import java.util.StringTokenizer;
40  import java.util.WeakHashMap;
41  import java.util.Map.Entry;
42  import java.util.regex.Matcher;
43  import java.util.regex.Pattern;
44  import java.util.Set;
45  import java.util.Properties;
46  
47  import org.apache.commons.httpclient.Credentials;
48  import org.apache.commons.httpclient.HttpClient;
49  import org.apache.commons.httpclient.HttpStatus;
50  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
51  import org.apache.commons.httpclient.UsernamePasswordCredentials;
52  import org.apache.commons.httpclient.auth.AuthScope;
53  import org.apache.commons.httpclient.methods.GetMethod;
54  import org.apache.commons.httpclient.params.HttpClientParams;
55  import org.apache.commons.httpclient.params.HttpMethodParams;
56  import org.apache.maven.model.Contributor;
57  import org.apache.maven.project.MavenProject;
58  import org.apache.maven.settings.Proxy;
59  import org.apache.maven.settings.Settings;
60  import org.apache.maven.wagon.proxy.ProxyInfo;
61  import org.apache.maven.wagon.proxy.ProxyUtils;
62  import org.codehaus.plexus.i18n.I18N;
63  import org.codehaus.plexus.interpolation.EnvarBasedValueSource;
64  import org.codehaus.plexus.interpolation.InterpolationException;
65  import org.codehaus.plexus.interpolation.ObjectBasedValueSource;
66  import org.codehaus.plexus.interpolation.PrefixedObjectValueSource;
67  import org.codehaus.plexus.interpolation.PropertiesBasedValueSource;
68  import org.codehaus.plexus.interpolation.RegexBasedInterpolator;
69  import org.codehaus.plexus.util.IOUtil;
70  import org.codehaus.plexus.util.StringUtils;
71  import org.codehaus.plexus.util.introspection.ClassMap;
72  import org.codehaus.plexus.util.xml.XMLWriter;
73  import org.codehaus.plexus.util.xml.XmlWriterUtil;
74  
75  import com.hp.hpl.jena.rdf.model.Model;
76  import com.hp.hpl.jena.rdf.model.ModelFactory;
77  import com.hp.hpl.jena.rdf.model.RDFReader;
78  import com.hp.hpl.jena.rdf.model.impl.RDFDefaultErrorHandler;
79  
80  /**
81   * Utility class for {@link DoapMojo} class.
82   *
83   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
84   * @version $Id: DoapUtil.html 815337 2012-05-01 21:52:10Z hboutemy $
85   * @since 1.0
86   */
87  public class DoapUtil
88  {
89      /** Email regex */
90      private static final String EMAIL_REGEX =
91          "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
92  
93      /** Email pattern */
94      private static final Pattern EMAIL_PATTERN = Pattern.compile( EMAIL_REGEX );
95  
96      /** Magic number to repeat '=' */
97      private static final int REPEAT_EQUALS = 21;
98  
99      /** The default timeout used when fetching url, i.e. 2000. */
100     public static final int DEFAULT_TIMEOUT = 2000;
101 
102     /** RDF resource attribute */
103     protected static final String RDF_RESOURCE = "rdf:resource";
104 
105     /** RDF nodeID attribute */
106     protected static final String RDF_NODE_ID = "rdf:nodeID";
107 
108     /** DoaP Organizations stored by name */
109     private static Map<String, DoapUtil.Organization> organizations = new HashMap<String, DoapUtil.Organization>();
110 
111     /**
112      * Write comments in the DOAP file header
113      *
114      * @param writer not null
115      */
116     public static void writeHeader( XMLWriter writer )
117     {
118         XmlWriterUtil.writeLineBreak( writer );
119 
120         XmlWriterUtil.writeCommentLineBreak( writer );
121         XmlWriterUtil.writeComment( writer, StringUtils.repeat( "=", REPEAT_EQUALS ) + " - DO NOT EDIT THIS FILE! - "
122             + StringUtils.repeat( "=", REPEAT_EQUALS ) );
123         XmlWriterUtil.writeCommentLineBreak( writer );
124         XmlWriterUtil.writeComment( writer, " " );
125         XmlWriterUtil.writeComment( writer, "Any modifications will be overwritten." );
126         XmlWriterUtil.writeComment( writer, " " );
127         DateFormat dateFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, Locale.US );
128         XmlWriterUtil.writeComment( writer, "Generated by Maven Doap Plugin " + getPluginVersion() + " on "
129             + dateFormat.format( new Date( System.currentTimeMillis() ) ) );
130         XmlWriterUtil.writeComment( writer, "See: http://maven.apache.org/plugins/maven-doap-plugin/" );
131         XmlWriterUtil.writeComment( writer, " " );
132         XmlWriterUtil.writeCommentLineBreak( writer );
133 
134         XmlWriterUtil.writeLineBreak( writer );
135     }
136 
137     /**
138      * Write comment.
139      *
140      * @param writer not null
141      * @param comment not null
142      * @throws IllegalArgumentException if comment is null or empty
143      * @since 1.1
144      */
145     public static void writeComment( XMLWriter writer, String comment )
146         throws IllegalArgumentException
147     {
148         if ( StringUtils.isEmpty( comment ) )
149         {
150             throw new IllegalArgumentException( "comment should be defined" );
151         }
152 
153         XmlWriterUtil.writeLineBreak( writer );
154         XmlWriterUtil.writeCommentText( writer, comment, 2 );
155     }
156 
157     /**
158      * @param writer not null
159      * @param xmlnsPrefix could be null
160      * @param name not null
161      * @param value could be null. In this case, the element is not written.
162      * @throws IllegalArgumentException if name is null or empty
163      */
164     public static void writeElement( XMLWriter writer, String xmlnsPrefix, String name, String value )
165         throws IllegalArgumentException
166     {
167         if ( StringUtils.isEmpty( name ) )
168         {
169             throw new IllegalArgumentException( "name should be defined" );
170         }
171 
172         if ( value != null )
173         {
174             writeStartElement( writer, xmlnsPrefix, name );
175             writer.writeText( value );
176             writer.endElement();
177         }
178     }
179 
180     /**
181      * @param writer not null
182      * @param xmlnsPrefix could be null
183      * @param name not null
184      * @param lang not null
185      * @param value could be null. In this case, the element is not written.
186      * @throws IllegalArgumentException if name is null or empty
187      */
188     public static void writeElement( XMLWriter writer, String xmlnsPrefix, String name, String value, String lang )
189         throws IllegalArgumentException
190     {
191         if ( StringUtils.isEmpty( lang ) )
192         {
193             writeElement( writer, xmlnsPrefix, name, value );
194             return;
195         }
196 
197         if ( StringUtils.isEmpty( name ) )
198         {
199             throw new IllegalArgumentException( "name should be defined" );
200         }
201 
202         if ( value != null )
203         {
204             writeStartElement( writer, xmlnsPrefix, name );
205             writer.addAttribute( "xml:lang", lang );
206             writer.writeText( value );
207             writer.endElement();
208         }
209     }
210 
211     /**
212      * @param writer not null
213      * @param xmlnsPrefix could be null
214      * @param name not null
215      * @throws IllegalArgumentException if name is null or empty
216      * @since 1.1
217      */
218     public static void writeStartElement( XMLWriter writer, String xmlnsPrefix, String name )
219         throws IllegalArgumentException
220     {
221         if ( StringUtils.isEmpty( name ) )
222         {
223             throw new IllegalArgumentException( "name should be defined" );
224         }
225 
226         if ( StringUtils.isNotEmpty( xmlnsPrefix ) )
227         {
228             writer.startElement( xmlnsPrefix + ":" + name );
229         }
230         else
231         {
232             writer.startElement( name );
233         }
234     }
235 
236     /**
237      * @param writer not null
238      * @param xmlnsPrefix could be null
239      * @param name not null
240      * @param value could be null. In this case, the element is not written.
241      * @throws IllegalArgumentException if name is null or empty
242      */
243     public static void writeRdfResourceElement( XMLWriter writer, String xmlnsPrefix, String name, String value )
244         throws IllegalArgumentException
245     {
246         if ( StringUtils.isEmpty( name ) )
247         {
248             throw new IllegalArgumentException( "name should be defined" );
249         }
250 
251         if ( value != null )
252         {
253             writeStartElement( writer, xmlnsPrefix, name );
254             writer.addAttribute( RDF_RESOURCE, value );
255             writer.endElement();
256         }
257     }
258 
259     /**
260      * @param writer not null
261      * @param name not null
262      * @param value could be null. In this case, the element is not written.
263      * @throws IllegalArgumentException if name is null or empty
264      */
265     public static void writeRdfNodeIdElement( XMLWriter writer, String xmlnsPrefix, String name, String value )
266         throws IllegalArgumentException
267     {
268         if ( StringUtils.isEmpty( name ) )
269         {
270             throw new IllegalArgumentException( "name should be defined" );
271         }
272 
273         if ( value != null )
274         {
275             writeStartElement( writer, xmlnsPrefix, name );
276             writer.addAttribute( RDF_NODE_ID, value );
277             writer.endElement();
278         }
279     }
280 
281     /**
282      * @param i18n the internationalization component
283      * @param developersOrContributors list of <code>{@link Contributor}</code>
284      * @return a none null list of <code>{@link Contributor}</code> which have a <code>developer</code> DOAP role.
285      */
286     public static List<Contributor> getContributorsWithDeveloperRole( I18N i18n,
287                                                                       List<Contributor> developersOrContributors )
288     {
289         return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "developers" );
290     }
291 
292     /**
293      * @param i18n the internationalization component
294      * @param developersOrContributors list of <code>{@link Contributor}</code>
295      * @return a none null list of <code>{@link Contributor}</code> which have a <code>documenter</code> DOAP role.
296      */
297     public static List<Contributor> getContributorsWithDocumenterRole( I18N i18n,
298                                                                        List<Contributor> developersOrContributors )
299     {
300         return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "documenters" );
301     }
302 
303     /**
304      * @param i18n the internationalization component
305      * @param developersOrContributors list of <code>{@link Contributor}</code>
306      * @return a none null list of <code>{@link Contributor}</code> which have an <code>helper</code> DOAP role.
307      */
308     public static List<Contributor> getContributorsWithHelperRole( I18N i18n, List<Contributor> developersOrContributors )
309     {
310         return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "helpers" );
311     }
312 
313     /**
314      * @param i18n the internationalization component
315      * @param developersOrContributors list of <code>{@link Contributor}</code>
316      * @return a none null list of <code>{@link Contributor}</code> which have a <code>maintainer</code> DOAP role.
317      */
318     public static List<Contributor> getContributorsWithMaintainerRole( I18N i18n,
319                                                                        List<Contributor> developersOrContributors )
320     {
321         return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "maintainers" );
322     }
323 
324     /**
325      * @param i18n the internationalization component
326      * @param developersOrContributors list of <code>{@link Contributor}</code>
327      * @return a none null list of <code>{@link Contributor}</code> which have a <code>tester</code> DOAP role.
328      */
329     public static List<Contributor> getContributorsWithTesterRole( I18N i18n, List<Contributor> developersOrContributors )
330     {
331         return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "testers" );
332     }
333 
334     /**
335      * @param i18n the internationalization component
336      * @param developersOrContributors list of <code>{@link Contributor}</code>
337      * @return a none null list of <code>{@link Contributor}</code> which have a <code>translator</code> DOAP role.
338      */
339     public static List<Contributor> getContributorsWithTranslatorRole( I18N i18n,
340                                                                        List<Contributor> developersOrContributors )
341     {
342         return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "translators" );
343     }
344 
345     /**
346      * @param i18n the internationalization component
347      * @param developersOrContributors list of <code>{@link Contributor}</code>
348      * @return a none null list of <code>{@link Contributor}</code> which have an <code>unknown</code> DOAP role.
349      */
350     public static List<Contributor> getContributorsWithUnknownRole( I18N i18n,
351                                                                     List<Contributor> developersOrContributors )
352     {
353         return filterContributorsByDoapRoles( i18n, developersOrContributors ).get( "unknowns" );
354     }
355 
356     /**
357      * Utility class for keeping track of DOAP organizations in the DoaP mojo.
358      *
359      * @author <a href="mailto:t.fliss@gmail.com">Tim Fliss</a>
360      * @version $Id: DoapUtil.html 815337 2012-05-01 21:52:10Z hboutemy $
361      * @since 1.1
362      */
363     public static class Organization
364     {
365         private String name;
366 
367         private String url;
368 
369         private List<String> members = new LinkedList<String>();
370 
371         public Organization( String name, String url )
372         {
373             this.name = name;
374             this.url = url;
375         }
376 
377         public void setName( String name )
378         {
379             this.name = name;
380         }
381 
382         public String getName()
383         {
384             return name;
385         }
386 
387         public void setUrl( String url )
388         {
389             this.url = url;
390         }
391 
392         public String getUrl()
393         {
394             return url;
395         }
396 
397         public void addMember( String nodeId )
398         {
399             members.add( nodeId );
400         }
401 
402         public List<String> getMembers()
403         {
404             return members;
405         }
406     }
407 
408     /**
409      * put an organization from the pom file in the organization list.
410      *
411      * @param name from the pom file (e.g. Yoyodyne)
412      * @param url from the pom file (e.g. http://yoyodyne.example.org/about)
413      * @return the existing organization if a duplicate, or a new one.
414      */
415     public static DoapUtil.Organization addOrganization( String name, String url )
416     {
417         Organization organization = organizations.get( name );
418 
419         if ( organization == null )
420         {
421             organization = new DoapUtil.Organization( name, url );
422         }
423 
424         organizations.put( name, organization );
425 
426         return organization;
427     }
428 
429     // unique RDF blank node index scoped internal to the DOAP file
430     private static int nodeNumber = 1;
431 
432     /**
433      * get a unique (within the DoaP file) RDF blank node ID
434      *
435      * @return the nodeID
436      * @see <a href="http://www.w3.org/TR/rdf-syntax-grammar/#section-Syntax-blank-nodes">
437      *      http://www.w3.org/TR/rdf-syntax-grammar/#section-Syntax-blank-nodes</a>
438      */
439     public static String getNodeId()
440     {
441         return "b" + nodeNumber++;
442     }
443 
444     /**
445      * get the set of Organizations that people are members of
446      *
447      * @return Map.EntrySet of DoapUtil.Organization
448      */
449     public static Set<Entry<String, DoapUtil.Organization>> getOrganizations()
450     {
451         return organizations.entrySet();
452     }
453 
454     /**
455      * Validate the given DOAP file.
456      *
457      * @param doapFile not null and should exists.
458      * @return an empty list if the DOAP file is valid, otherwise a list of errors.
459      * @since 1.1
460      */
461     public static List<String> validate( File doapFile )
462     {
463         if ( doapFile == null || !doapFile.isFile() )
464         {
465             throw new IllegalArgumentException( "The DOAP file should exist" );
466         }
467 
468         Model model = ModelFactory.createDefaultModel();
469         RDFReader r = model.getReader( "RDF/XML" );
470         r.setProperty( "error-mode", "strict-error" );
471         final List<String> errors = new ArrayList<String>();
472         r.setErrorHandler( new RDFDefaultErrorHandler()
473         {
474             @Override
475             public void error( Exception e )
476             {
477                 errors.add( e.getMessage() );
478             }
479         } );
480 
481         try
482         {
483             r.read( model, doapFile.toURI().toURL().toString() );
484         }
485         catch ( MalformedURLException e )
486         {
487             // ignored
488         }
489 
490         return errors;
491     }
492 
493     /**
494      * @param str not null
495      * @return <code>true</code> if the str parameter is a valid email, <code>false</code> otherwise.
496      * @since 1.1
497      */
498     public static boolean isValidEmail( String str )
499     {
500         if ( StringUtils.isEmpty( str ) )
501         {
502             return false;
503         }
504 
505         Matcher matcher = EMAIL_PATTERN.matcher( str );
506         return matcher.matches();
507     }
508 
509     /**
510      * Fetch an URL
511      *
512      * @param settings the user settings used to fetch the url with an active proxy, if defined.
513      * @param url the url to fetch
514      * @throws IOException if any
515      * @see #DEFAULT_TIMEOUT
516      * @since 1.1
517      */
518     public static void fetchURL( Settings settings, URL url )
519         throws IOException
520     {
521         if ( url == null )
522         {
523             throw new IllegalArgumentException( "The url is null" );
524         }
525 
526         if ( "file".equals( url.getProtocol() ) )
527         {
528             InputStream in = null;
529             try
530             {
531                 in = url.openStream();
532             }
533             finally
534             {
535                 IOUtil.close( in );
536             }
537 
538             return;
539         }
540 
541         // http, https...
542         HttpClient httpClient = new HttpClient( new MultiThreadedHttpConnectionManager() );
543         httpClient.getHttpConnectionManager().getParams().setConnectionTimeout( DEFAULT_TIMEOUT );
544         httpClient.getHttpConnectionManager().getParams().setSoTimeout( DEFAULT_TIMEOUT );
545         httpClient.getParams().setBooleanParameter( HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true );
546 
547         // Some web servers don't allow the default user-agent sent by httpClient
548         httpClient.getParams().setParameter( HttpMethodParams.USER_AGENT,
549                                              "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" );
550 
551         if ( settings != null && settings.getActiveProxy() != null )
552         {
553             Proxy activeProxy = settings.getActiveProxy();
554 
555             ProxyInfo proxyInfo = new ProxyInfo();
556             proxyInfo.setNonProxyHosts( activeProxy.getNonProxyHosts() );
557 
558             if ( StringUtils.isNotEmpty( activeProxy.getHost() )
559                 && !ProxyUtils.validateNonProxyHosts( proxyInfo, url.getHost() ) )
560             {
561                 httpClient.getHostConfiguration().setProxy( activeProxy.getHost(), activeProxy.getPort() );
562 
563                 if ( StringUtils.isNotEmpty( activeProxy.getUsername() ) && activeProxy.getPassword() != null )
564                 {
565                     Credentials credentials =
566                         new UsernamePasswordCredentials( activeProxy.getUsername(), activeProxy.getPassword() );
567 
568                     httpClient.getState().setProxyCredentials( AuthScope.ANY, credentials );
569                 }
570             }
571         }
572 
573         GetMethod getMethod = new GetMethod( url.toString() );
574         try
575         {
576             int status;
577             try
578             {
579                 status = httpClient.executeMethod( getMethod );
580             }
581             catch ( SocketTimeoutException e )
582             {
583                 // could be a sporadic failure, one more retry before we give up
584                 status = httpClient.executeMethod( getMethod );
585             }
586 
587             if ( status != HttpStatus.SC_OK )
588             {
589                 throw new FileNotFoundException( url.toString() );
590             }
591         }
592         finally
593         {
594             getMethod.releaseConnection();
595         }
596     }
597 
598     /**
599      * Interpolate a string with project and settings.
600      *
601      * @param value could be null
602      * @param project not null
603      * @param settings could be null
604      * @return the value trimmed and interpolated or null if the interpolation doesn't work.
605      * @since 1.1
606      */
607     public static String interpolate( String value, final MavenProject project, Settings settings )
608     {
609         if ( project == null )
610         {
611             throw new IllegalArgumentException( "project is required" );
612         }
613 
614         if ( value == null )
615         {
616             return value;
617         }
618 
619         if ( !value.contains( "${" ) )
620         {
621             return value.trim();
622         }
623 
624         RegexBasedInterpolator interpolator = new RegexBasedInterpolator();
625         try
626         {
627             interpolator.addValueSource( new EnvarBasedValueSource() );
628         }
629         catch ( IOException e )
630         {
631         }
632         interpolator.addValueSource( new PropertiesBasedValueSource( System.getProperties() ) );
633         interpolator.addValueSource( new PropertiesBasedValueSource( project.getProperties() ) );
634         interpolator.addValueSource( new PrefixedObjectValueSource( "project", project ) );
635         interpolator.addValueSource( new PrefixedObjectValueSource( "pom", project ) );
636         interpolator.addValueSource( new ObjectBasedValueSource( project )
637         {
638             @Override
639             public Object getValue( String expression )
640             {
641                 try
642                 {
643                     return ReflectionValueExtractor.evaluate( expression, project, true );
644                 }
645                 catch ( Exception e )
646                 {
647                     addFeedback( "Failed to extract \'" + expression + "\' from: " + project, e );
648                 }
649 
650                 return null;
651             }
652         } );
653 
654         if ( settings != null )
655         {
656             interpolator.addValueSource( new PrefixedObjectValueSource( "settings", settings ) );
657         }
658 
659         String interpolatedValue = value;
660         try
661         {
662             interpolatedValue = interpolator.interpolate( value ).trim();
663         }
664         catch ( InterpolationException e )
665         {
666         }
667 
668         if ( interpolatedValue.startsWith( "${" ) )
669         {
670             return null;
671         }
672 
673         return interpolatedValue;
674     }
675 
676     // ----------------------------------------------------------------------
677     // Private methods
678     // ----------------------------------------------------------------------
679 
680     /**
681      * Filter the developers/contributors roles by the keys from {@link I18N#getBundle()}. <br/>
682      * I18N roles supported in DOAP, i.e. <code>maintainer</code>, <code>developer</code>, <code>documenter</code>,
683      * <code>translator</code>, <code>tester</code>, <code>helper</code>. <br/>
684      * <b>Note:</b> Actually, only English keys are used.
685      *
686      * @param i18n i18n component
687      * @param developersOrContributors list of <code>{@link Contributor}</code>
688      * @return a none null map with <code>maintainers</code>, <code>developers</code>, <code>documenters</code>,
689      *         <code>translators</code>, <code>testers</code>, <code>helpers</code>, <code>unknowns</code> as keys and
690      *         list of <code>{@link Contributor}</code> as value.
691      */
692     private static Map<String, List<Contributor>> filterContributorsByDoapRoles( I18N i18n,
693                                                                                  List<Contributor> developersOrContributors )
694     {
695         Map<String, List<Contributor>> returnMap = new HashMap<String, List<Contributor>>( 7 );
696         returnMap.put( "maintainers", new ArrayList<Contributor>() );
697         returnMap.put( "developers", new ArrayList<Contributor>() );
698         returnMap.put( "documenters", new ArrayList<Contributor>() );
699         returnMap.put( "translators", new ArrayList<Contributor>() );
700         returnMap.put( "testers", new ArrayList<Contributor>() );
701         returnMap.put( "helpers", new ArrayList<Contributor>() );
702         returnMap.put( "unknowns", new ArrayList<Contributor>() );
703 
704         if ( developersOrContributors == null || developersOrContributors.isEmpty() )
705         {
706             return returnMap;
707         }
708 
709         for ( Contributor contributor : developersOrContributors )
710         {
711             List<String> roles = contributor.getRoles();
712 
713             if ( roles != null && roles.size() != 0 )
714             {
715                 for ( String role : roles )
716                 {
717                     role = role.toLowerCase( Locale.ENGLISH );
718                     if ( role.contains( getLowerCaseString( i18n, "doap.maintainer" ) ) )
719                     {
720                         if ( !returnMap.get( "maintainers" ).contains( contributor ) )
721                         {
722                             returnMap.get( "maintainers" ).add( contributor );
723                         }
724                     }
725                     else if ( role.contains( getLowerCaseString( i18n, "doap.developer" ) ) )
726                     {
727                         if ( !returnMap.get( "developers" ).contains( contributor ) )
728                         {
729                             returnMap.get( "developers" ).add( contributor );
730                         }
731                     }
732                     else if ( role.contains( getLowerCaseString( i18n, "doap.documenter" ) ) )
733                     {
734                         if ( !returnMap.get( "documenters" ).contains( contributor ) )
735                         {
736                             returnMap.get( "documenters" ).add( contributor );
737                         }
738                     }
739                     else if ( role.contains( getLowerCaseString( i18n, "doap.translator" ) ) )
740                     {
741                         if ( !returnMap.get( "translators" ).contains( contributor ) )
742                         {
743                             returnMap.get( "translators" ).add( contributor );
744                         }
745                     }
746                     else if ( role.contains( getLowerCaseString( i18n, "doap.tester" ) ) )
747                     {
748                         if ( !returnMap.get( "testers" ).contains( contributor ) )
749                         {
750                             returnMap.get( "testers" ).add( contributor );
751                         }
752                     }
753                     else if ( role.contains( getLowerCaseString( i18n, "doap.helper" ) ) )
754                     {
755                         if ( !returnMap.get( "helpers" ).contains( contributor ) )
756                         {
757                             returnMap.get( "helpers" ).add( contributor );
758                         }
759                     }
760                     else if ( role.contains( getLowerCaseString( i18n, "doap.emeritus" ) ) )
761                     {
762                         // Don't add as developer nor as contributor as the person is no longer involved
763                     }
764                     else
765                     {
766                         if ( !returnMap.get( "unknowns" ).contains( contributor ) )
767                         {
768                             returnMap.get( "unknowns" ).add( contributor );
769                         }
770                     }
771                 }
772             }
773             else
774             {
775                 if ( !returnMap.get( "unknowns" ).contains( contributor ) )
776                 {
777                     returnMap.get( "unknowns" ).add( contributor );
778                 }
779             }
780         }
781 
782         return returnMap;
783     }
784 
785     /**
786      * @param i18n not null
787      * @param key not null
788      * @return lower case value for the key in the i18n bundle.
789      */
790     private static String getLowerCaseString( I18N i18n, String key )
791     {
792         return i18n.getString( "doap-person", Locale.ENGLISH, key ).toLowerCase( Locale.ENGLISH );
793     }
794 
795     /**
796      * @return the Maven artefact version.
797      */
798     private static String getPluginVersion()
799     {
800         Properties pomProperties = new Properties();
801         InputStream is = null;
802         try
803         {
804             is =
805                 DoapUtil.class.getResourceAsStream( "/META-INF/maven/org.apache.maven.plugins/maven-doap-plugin/pom.properties" );
806             if ( is == null )
807             {
808                 return "<unknown>";
809             }
810 
811             pomProperties.load( is );
812 
813             return pomProperties.getProperty( "version", "<unknown>" );
814         }
815         catch ( IOException e )
816         {
817             return "<unknown>";
818         }
819         finally
820         {
821             IOUtil.close( is );
822         }
823     }
824 
825     /**
826      * Fork of {@link org.codehaus.plexus.interpolation.reflection.ReflectionValueExtractor} to care of list or arrays.
827      */
828     static class ReflectionValueExtractor
829     {
830         @SuppressWarnings( "rawtypes" )
831         private static final Class[] CLASS_ARGS = new Class[0];
832 
833         private static final Object[] OBJECT_ARGS = new Object[0];
834 
835         /**
836          * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected. This approach prevents permgen
837          * space overflows due to retention of discarded classloaders.
838          */
839         @SuppressWarnings( "rawtypes" )
840         private static final Map<Class,ClassMap> classMaps = new WeakHashMap<Class,ClassMap>();
841 
842         private ReflectionValueExtractor()
843         {
844         }
845 
846         public static Object evaluate( String expression, Object root )
847             throws Exception
848         {
849             return evaluate( expression, root, true );
850         }
851 
852         // TODO: don't throw Exception
853         public static Object evaluate( String expression, Object root, boolean trimRootToken )
854             throws Exception
855         {
856             // if the root token refers to the supplied root object parameter, remove it.
857             if ( trimRootToken )
858             {
859                 expression = expression.substring( expression.indexOf( '.' ) + 1 );
860             }
861 
862             Object value = root;
863 
864             // ----------------------------------------------------------------------
865             // Walk the dots and retrieve the ultimate value desired from the
866             // MavenProject instance.
867             // ----------------------------------------------------------------------
868 
869             StringTokenizer parser = new StringTokenizer( expression, "." );
870 
871             while ( parser.hasMoreTokens() )
872             {
873                 String token = parser.nextToken();
874                 if ( value == null )
875                 {
876                     return null;
877                 }
878 
879                 StringTokenizer parser2 = new StringTokenizer( token, "[]" );
880                 int index = -1;
881                 if ( parser2.countTokens() > 1 )
882                 {
883                     token = parser2.nextToken();
884                     try
885                     {
886                         index = Integer.valueOf( parser2.nextToken() ).intValue();
887                     }
888                     catch ( NumberFormatException e )
889                     {
890                     }
891                 }
892 
893                 final ClassMap classMap = getClassMap( value.getClass() );
894 
895                 final String methodBase = StringUtils.capitalizeFirstLetter( token );
896 
897                 String methodName = "get" + methodBase;
898 
899                 Method method = classMap.findMethod( methodName, CLASS_ARGS );
900 
901                 if ( method == null )
902                 {
903                     // perhaps this is a boolean property??
904                     methodName = "is" + methodBase;
905 
906                     method = classMap.findMethod( methodName, CLASS_ARGS );
907                 }
908 
909                 if ( method == null )
910                 {
911                     return null;
912                 }
913 
914                 value = method.invoke( value, OBJECT_ARGS );
915                 if ( value == null )
916                 {
917                     return null;
918                 }
919                 if ( Collection.class.isAssignableFrom( value.getClass() ) )
920                 {
921                     ClassMap classMap2 = getClassMap( value.getClass() );
922 
923                     Method method2 = classMap2.findMethod( "toArray", CLASS_ARGS );
924 
925                     value = method2.invoke( value, OBJECT_ARGS );
926                 }
927                 if ( value.getClass().isArray() )
928                 {
929                     value = ( (Object[]) value )[index];
930                 }
931             }
932 
933             return value;
934         }
935 
936         private static ClassMap getClassMap( Class<? extends Object> clazz )
937         {
938             ClassMap classMap = classMaps.get( clazz );
939 
940             if ( classMap == null )
941             {
942                 classMap = new ClassMap( clazz );
943 
944                 classMaps.put( clazz, classMap );
945             }
946 
947             return classMap;
948         }
949     }
950 }