View Javadoc

1   package org.apache.maven.plugin.ant;
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.ByteArrayInputStream;
23  import java.io.File;
24  import java.io.IOException;
25  import java.text.DateFormat;
26  import java.util.ArrayList;
27  import java.util.Date;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Locale;
32  import java.util.Map;
33  
34  import javax.xml.parsers.DocumentBuilderFactory;
35  
36  import org.apache.maven.artifact.Artifact;
37  import org.apache.maven.model.Plugin;
38  import org.apache.maven.model.ReportPlugin;
39  import org.apache.maven.project.MavenProject;
40  import org.apache.xpath.XPathAPI;
41  import org.codehaus.plexus.util.PathTool;
42  import org.codehaus.plexus.util.StringUtils;
43  import org.codehaus.plexus.util.xml.XMLWriter;
44  import org.codehaus.plexus.util.xml.XmlWriterUtil;
45  import org.w3c.dom.Document;
46  import org.w3c.dom.Node;
47  import org.w3c.dom.NodeList;
48  
49  /**
50   * Utility class for the <code>AntBuildWriter</code> class.
51   *
52   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
53   * @version $Id: AntBuildWriterUtil.java 833678 2009-11-07 14:02:13Z bentmann $
54   */
55  public class AntBuildWriterUtil
56  {
57      /**
58       * @param compileSourceRoots
59       * @return not null list
60       */
61      public static List removeEmptyCompileSourceRoots( List compileSourceRoots )
62      {
63          List newCompileSourceRootsList = new ArrayList();
64          if ( compileSourceRoots != null )
65          {
66              // copy as I may be modifying it
67              for ( Iterator i = compileSourceRoots.iterator(); i.hasNext(); )
68              {
69                  String srcDir = (String) i.next();
70                  if ( new File( srcDir ).exists() )
71                  {
72                      newCompileSourceRootsList.add( srcDir );
73                  }
74              }
75          }
76  
77          return newCompileSourceRootsList;
78      }
79  
80      /**
81       * Convenience method to write <code>&lt;include/&gt;</code> and <code>&lt;exclude/&gt;</code>
82       *
83       * @param writer not null
84       * @param includes
85       * @param excludes
86       */
87      public static void writeIncludesExcludes( XMLWriter writer, List includes, List excludes )
88      {
89          if ( includes != null )
90          {
91              for ( Iterator i = includes.iterator(); i.hasNext(); )
92              {
93                  String include = (String) i.next();
94                  writer.startElement( "include" );
95                  writer.addAttribute( "name", include );
96                  writer.endElement(); // include
97              }
98          }
99          if ( excludes != null )
100         {
101             for ( Iterator i = excludes.iterator(); i.hasNext(); )
102             {
103                 String exclude = (String) i.next();
104                 writer.startElement( "exclude" );
105                 writer.addAttribute( "name", exclude );
106                 writer.endElement(); // exclude
107             }
108         }
109     }
110 
111     /**
112      * Write comments in the Ant build file header
113      *
114      * @param writer
115      */
116     public static void writeHeader( XMLWriter writer )
117     {
118         writeAntVersionHeader( writer );
119 
120         XmlWriterUtil.writeCommentLineBreak( writer );
121         XmlWriterUtil.writeComment( writer, StringUtils.repeat( "=", 21 ) + " - DO NOT EDIT THIS FILE! - "
122             + StringUtils.repeat( "=", 21 ) );
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 Ant Plugin on "
129             + dateFormat.format( new Date( System.currentTimeMillis() ) ) );
130         XmlWriterUtil.writeComment( writer, "See: http://maven.apache.org/plugins/maven-ant-plugin/" );
131         XmlWriterUtil.writeComment( writer, " " );
132         XmlWriterUtil.writeCommentLineBreak( writer );
133 
134         XmlWriterUtil.writeLineBreak( writer );
135     }
136 
137     /**
138      * Write comment for the Ant supported version
139      *
140      * @param writer
141      */
142     public static void writeAntVersionHeader( XMLWriter writer )
143     {
144         XmlWriterUtil.writeCommentText( writer, "Ant build file (http://ant.apache.org/) for Ant 1.6.2 or above.", 0 );
145     }
146 
147     /**
148      * Convenience method to write XML ant task
149      *
150      * @param writer not null
151      * @param project not null
152      * @param moduleSubPath not null
153      * @param tasks not null
154      */
155     public static void writeAntTask( XMLWriter writer, MavenProject project, String moduleSubPath, String tasks )
156     {
157         writer.startElement( "ant" );
158         writer.addAttribute( "antfile", "build.xml" );
159         writer.addAttribute( "dir", toRelative( project.getBasedir(), moduleSubPath ) );
160         writer.addAttribute( "target", tasks );
161         writer.endElement(); // ant
162     }
163 
164     /**
165      * Convenience method to write XML Ant javadoc task
166      *
167      * @param writer not null
168      * @param project not null
169      * @param wrapper not null
170      * @throws IOException if any
171      */
172     public static void writeJavadocTask( XMLWriter writer, MavenProject project, ArtifactResolverWrapper wrapper )
173         throws IOException
174     {
175         List sources = new ArrayList();
176         for ( Iterator it = project.getCompileSourceRoots().iterator(); it.hasNext(); )
177         {
178             String source = (String) it.next();
179 
180             if ( new File( source ).exists() )
181             {
182                 sources.add( source );
183             }
184         }
185 
186         // No sources
187         if ( sources.size() == 0 )
188         {
189             return;
190         }
191 
192         writer.startElement( "javadoc" );
193         String sourcepath = getMavenJavadocPluginBasicOption( project, "sourcepath", null );
194         if ( sourcepath == null )
195         {
196             StringBuffer sb = new StringBuffer();
197             String[] compileSourceRoots = (String[]) sources.toArray( new String[0] );
198             for ( int i = 0; i < compileSourceRoots.length; i++ )
199             {
200                 sb.append( "${maven.build.srcDir." ).append( i ).append( "}" );
201 
202                 if ( i < ( compileSourceRoots.length - 1 ) )
203                 {
204                     sb.append( File.pathSeparatorChar );
205                 }
206             }
207             writer.addAttribute( "sourcepath", sb.toString() );
208             addWrapAttribute( writer, "javadoc", "packagenames", "*", 3 );
209         }
210         else
211         {
212             writer.addAttribute( "sourcepath", sourcepath );
213         }
214         addWrapAttribute( writer, "javadoc", "destdir",
215                           getMavenJavadocPluginBasicOption( project, "destdir",
216                                                             "${maven.reporting.outputDirectory}/apidocs" ), 3 );
217         addWrapAttribute( writer, "javadoc", "extdirs", getMavenJavadocPluginBasicOption( project, "extdirs", null ), 3 );
218 
219         addWrapAttribute( writer, "javadoc", "overview", getMavenJavadocPluginBasicOption( project, "overview", null ),
220                           3 );
221         addWrapAttribute( writer, "javadoc", "access",
222                           getMavenJavadocPluginBasicOption( project, "show", "protected" ), 3 );
223         addWrapAttribute( writer, "javadoc", "old", getMavenJavadocPluginBasicOption( project, "old", "false" ), 3 );
224         addWrapAttribute( writer, "javadoc", "verbose",
225                           getMavenJavadocPluginBasicOption( project, "verbose", "false" ), 3 );
226         addWrapAttribute( writer, "javadoc", "locale", getMavenJavadocPluginBasicOption( project, "locale", null ), 3 );
227         addWrapAttribute( writer, "javadoc", "encoding", getMavenJavadocPluginBasicOption( project, "encoding", null ),
228                           3 );
229         addWrapAttribute( writer, "javadoc", "version", getMavenJavadocPluginBasicOption( project, "version", "true" ),
230                           3 );
231         addWrapAttribute( writer, "javadoc", "use", getMavenJavadocPluginBasicOption( project, "use", "true" ), 3 );
232         addWrapAttribute( writer, "javadoc", "author", getMavenJavadocPluginBasicOption( project, "author", "true" ), 3 );
233         addWrapAttribute( writer, "javadoc", "splitindex", getMavenJavadocPluginBasicOption( project, "splitindex",
234                                                                                              "false" ), 3 );
235         addWrapAttribute( writer, "javadoc", "windowtitle", getMavenJavadocPluginBasicOption( project, "windowtitle",
236                                                                                               null ), 3 );
237         addWrapAttribute( writer, "javadoc", "nodeprecated", getMavenJavadocPluginBasicOption( project, "nodeprecated",
238                                                                                                "false" ), 3 );
239         addWrapAttribute( writer, "javadoc", "nodeprecatedlist", getMavenJavadocPluginBasicOption( project,
240                                                                                                    "nodeprecatedlist",
241                                                                                                    "false" ), 3 );
242         addWrapAttribute( writer, "javadoc", "notree", getMavenJavadocPluginBasicOption( project, "notree", "false" ),
243                           3 );
244         addWrapAttribute( writer, "javadoc", "noindex",
245                           getMavenJavadocPluginBasicOption( project, "noindex", "false" ), 3 );
246         addWrapAttribute( writer, "javadoc", "nohelp", getMavenJavadocPluginBasicOption( project, "nohelp", "false" ),
247                           3 );
248         addWrapAttribute( writer, "javadoc", "nonavbar",
249                           getMavenJavadocPluginBasicOption( project, "nonavbar", "false" ), 3 );
250         addWrapAttribute( writer, "javadoc", "serialwarn", getMavenJavadocPluginBasicOption( project, "serialwarn",
251                                                                                              "false" ), 3 );
252         addWrapAttribute( writer, "javadoc", "helpfile", getMavenJavadocPluginBasicOption( project, "helpfile", null ),
253                           3 );
254         addWrapAttribute( writer, "javadoc", "stylesheetfile",
255                           getMavenJavadocPluginBasicOption( project, "stylesheetfile", null ), 3 );
256         addWrapAttribute( writer, "javadoc", "charset", getMavenJavadocPluginBasicOption( project, "charset",
257                                                                                           "ISO-8859-1" ), 3 );
258         addWrapAttribute( writer, "javadoc", "docencoding", getMavenJavadocPluginBasicOption( project, "docencoding",
259                                                                                               null ), 3 );
260         addWrapAttribute( writer, "javadoc", "excludepackagenames",
261                           getMavenJavadocPluginBasicOption( project, "excludepackagenames", null ), 3 );
262         addWrapAttribute( writer, "javadoc", "source", getMavenJavadocPluginBasicOption( project, "source", null ), 3 );
263         addWrapAttribute( writer, "javadoc", "linksource", getMavenJavadocPluginBasicOption( project, "linksource",
264                                                                                              "false" ), 3 );
265         addWrapAttribute( writer, "javadoc", "breakiterator", getMavenJavadocPluginBasicOption( project,
266                                                                                                 "breakiterator",
267                                                                                                 "false" ), 3 );
268         addWrapAttribute( writer, "javadoc", "noqualifier", getMavenJavadocPluginBasicOption( project, "noqualifier",
269                                                                                               null ), 3 );
270         // miscellaneous
271         addWrapAttribute( writer, "javadoc", "maxmemory",
272                           getMavenJavadocPluginBasicOption( project, "maxmemory", null ), 3 );
273         addWrapAttribute( writer, "javadoc", "additionalparam", getMavenJavadocPluginBasicOption( project,
274                                                                                                   "additionalparam",
275                                                                                                   null ), 3 );
276 
277         // Nested arg
278         String doctitle = getMavenJavadocPluginBasicOption( project, "doctitle", null );
279         if ( doctitle != null )
280         {
281             writer.startElement( "doctitle" );
282             writer.writeText( "<![CDATA[" + doctitle + "]]>" );
283             writer.endElement(); // doctitle
284         }
285         String header = getMavenJavadocPluginBasicOption( project, "header", null );
286         if ( header != null )
287         {
288             writer.startElement( "header" );
289             writer.writeText( "<![CDATA[" + header + "]]>" );
290             writer.endElement(); // header
291         }
292         String footer = getMavenJavadocPluginBasicOption( project, "footer", null );
293         if ( footer != null )
294         {
295             writer.startElement( "footer" );
296             writer.writeText( "<![CDATA[" + footer + "]]>" );
297             writer.endElement(); // footer
298         }
299         String bottom = getMavenJavadocPluginBasicOption( project, "bottom", null );
300         if ( bottom != null )
301         {
302             writer.startElement( "bottom" );
303             writer.writeText( "<![CDATA[" + bottom + "]]>" );
304             writer.endElement(); // bottom
305         }
306 
307         Map[] links = getMavenJavadocPluginOptions( project, "links", null );
308         if ( links != null )
309         {
310             for ( int i = 0; i < links.length; i++ )
311             {
312                 writer.startElement( "link" );
313                 writer.addAttribute( "href", (String) links[i].get( "link" ) );
314                 writer.endElement(); // link
315             }
316         }
317 
318         Map[] offlineLinks = getMavenJavadocPluginOptions( project, "offlineLinks", null );
319         if ( offlineLinks != null )
320         {
321             for ( int i = 0; i < offlineLinks.length; i++ )
322             {
323                 writer.startElement( "link" );
324                 writer.addAttribute( "href", (String) offlineLinks[i].get( "url" ) );
325                 addWrapAttribute( writer, "javadoc", "offline", "true", 4 );
326                 writer.endElement(); // link
327             }
328         }
329 
330         Map[] groups = getMavenJavadocPluginOptions( project, "groups", null );
331         if ( groups != null )
332         {
333             for ( int i = 0; i < groups.length; i++ )
334             {
335                 Map group = (Map) groups[i].get( "group" );
336                 writer.startElement( "group" );
337                 writer.addAttribute( "title", (String) group.get( "title" ) );
338                 addWrapAttribute( writer, "javadoc", "package", (String) group.get( "package" ), 4 );
339                 writer.endElement(); // group
340             }
341         }
342 
343         // TODO Handle docletArtifacts
344         String doclet = getMavenJavadocPluginBasicOption( project, "doclet", null );
345         if ( doclet != null )
346         {
347             String docletpath = getMavenJavadocPluginBasicOption( project, "docletpath", null );
348             if ( StringUtils.isNotEmpty( docletpath ) )
349             {
350                 writer.startElement( "doclet" );
351                 writer.addAttribute( "name", doclet );
352                 addWrapAttribute( writer, "javadoc", "path", docletpath, 4 );
353                 writer.endElement(); // doclet
354             }
355             else
356             {
357                 Map docletArtifact = getMavenJavadocPluginOption( project, "docletArtifact", null );
358                 String path = wrapper.getArtifactAbsolutePath( (String) docletArtifact.get( "groupId" ),
359                                                                (String) docletArtifact.get( "artifactId" ),
360                                                                (String) docletArtifact.get( "version" ) );
361                 path = StringUtils.replace( path, wrapper.getLocalRepository().getBasedir(), "${maven.repo.local}" );
362 
363                 writer.startElement( "doclet" );
364                 writer.addAttribute( "name", doclet );
365                 addWrapAttribute( writer, "javadoc", "path", path, 4 );
366                 writer.endElement(); // doclet
367             }
368         }
369 
370         // TODO Handle taglets
371         String taglet = getMavenJavadocPluginBasicOption( project, "taglet", null );
372         if ( taglet != null )
373         {
374             String tagletpath = getMavenJavadocPluginBasicOption( project, "tagletpath", null );
375             if ( StringUtils.isNotEmpty( tagletpath ) )
376             {
377                 writer.startElement( "taglet" );
378                 writer.addAttribute( "name", taglet );
379                 addWrapAttribute( writer, "javadoc", "path", tagletpath, 4 );
380                 writer.endElement(); // taglet
381             }
382             else
383             {
384                 Map tagletArtifact = getMavenJavadocPluginOption( project, "tagletArtifact", null );
385                 String path = wrapper.getArtifactAbsolutePath( (String) tagletArtifact.get( "groupId" ),
386                                                                (String) tagletArtifact.get( "artifactId" ),
387                                                                (String) tagletArtifact.get( "version" ) );
388                 path = StringUtils.replace( path, wrapper.getLocalRepository().getBasedir(), "${maven.repo.local}" );
389 
390                 writer.startElement( "taglet" );
391                 writer.addAttribute( "name", taglet );
392                 addWrapAttribute( writer, "javadoc", "path", path, 4 );
393                 writer.endElement(); // taglet
394             }
395         }
396 
397         Map[] tags = getMavenJavadocPluginOptions( project, "tags", null );
398         if ( tags != null )
399         {
400             for ( int i = 0; i < tags.length; i++ )
401             {
402                 Map props = (Map) tags[i].get( "tag" );
403                 writer.startElement( "tag" );
404                 writer.addAttribute( "name", (String) props.get( "name" ) );
405                 addWrapAttribute( writer, "javadoc", "scope", (String) props.get( "placement" ), 4 );
406                 addWrapAttribute( writer, "javadoc", "description", (String) props.get( "head" ), 4 );
407                 writer.endElement(); // tag
408             }
409         }
410 
411         writer.endElement(); // javadoc
412     }
413 
414     /**
415      * Convenience method to write XML Ant jar task
416      *
417      * @param writer not null
418      * @param project not null
419      * @throws IOException if any
420      */
421     public static void writeJarTask( XMLWriter writer, MavenProject project )
422         throws IOException
423     {
424         writer.startElement( "jar" );
425         writer.addAttribute( "jarfile", "${maven.build.dir}/${maven.build.finalName}.jar" );
426         addWrapAttribute( writer, "jar", "compress",
427                           getMavenJarPluginBasicOption( project, "archive//compress", "true" ), 3 );
428         addWrapAttribute( writer, "jar", "index", getMavenJarPluginBasicOption( project, "archive//index", "false" ), 3 );
429         if ( getMavenJarPluginBasicOption( project, "archive//manifestFile", null ) != null )
430         {
431             addWrapAttribute( writer, "jar", "manifest", getMavenJarPluginBasicOption( project,
432                                                                                        "archive//manifestFile", null ),
433                               3 );
434         }
435         addWrapAttribute( writer, "jar", "basedir", "${maven.build.outputDir}", 3 );
436         addWrapAttribute( writer, "jar", "excludes", "**/package.html", 3 );
437         if ( getMavenPluginOption( project, "maven-jar-plugin", "archive//manifest", null ) != null )
438         {
439             writer.startElement( "manifest" );
440             writer.startElement( "attribute" );
441             writer.addAttribute( "name", "Main-Class" );
442             addWrapAttribute( writer, "attribute", "value",
443                               getMavenJarPluginBasicOption( project, "archive//manifest//mainClass", null ), 5 );
444             writer.endElement(); // attribute
445             writer.endElement(); // manifest
446         }
447         writer.endElement(); // jar
448     }
449 
450     /**
451      * Convenience method to write XML Ant ear task
452      *
453      * @param writer not null
454      * @param project not null
455      * @param artifactResolverWrapper not null
456      * @throws IOException if any
457      */
458     public static void writeEarTask( XMLWriter writer, MavenProject project,
459                                      ArtifactResolverWrapper artifactResolverWrapper )
460         throws IOException
461     {
462         writeCopyLib( writer, project, artifactResolverWrapper, "${maven.build.dir}/${maven.build.finalName}" );
463 
464         writer.startElement( "ear" );
465         writer.addAttribute( "destfile", "${maven.build.dir}/${maven.build.finalName}.ear" );
466         addWrapAttribute( writer, "ear", "basedir", "${maven.build.dir}/${maven.build.finalName}", 3 );
467         addWrapAttribute( writer, "ear", "compress",
468                           getMavenEarPluginBasicOption( project, "archive//compress", "true" ), 3 );
469         addWrapAttribute( writer, "ear", "includes ", getMavenEarPluginBasicOption( project, "includes", null ), 3 );
470         addWrapAttribute( writer, "ear", "excludes", getMavenEarPluginBasicOption( project, "excludes", null ), 3 );
471         if ( getMavenEarPluginBasicOption( project, "applicationXml", null ) != null )
472         {
473             addWrapAttribute( writer, "ear", "appxml", getMavenEarPluginBasicOption( project, "applicationXml", null ),
474                               3 );
475         }
476         else
477         {
478             // Generated appxml
479             addWrapAttribute( writer, "ear", "appxml", "${maven.build.dir}/application.xml", 3 );
480         }
481         if ( getMavenEarPluginBasicOption( project, "manifestFile", null ) != null )
482         {
483             addWrapAttribute( writer, "ear", "manifest", getMavenEarPluginBasicOption( project, "manifestFile", null ),
484                               3 );
485         }
486         writer.endElement(); // ear
487     }
488 
489     /**
490      * Convenience method to write XML Ant war task
491      *
492      * @param writer not null
493      * @param project not null
494      * @param artifactResolverWrapper not null
495      * @throws IOException if any
496      */
497     public static void writeWarTask( XMLWriter writer, MavenProject project,
498                                      ArtifactResolverWrapper artifactResolverWrapper )
499         throws IOException
500     {
501         String webXml =
502             getMavenWarPluginBasicOption( project, "webXml", "${basedir}/src/main/webapp/WEB-INF/web.xml" );
503         if ( webXml.startsWith( "${basedir}/" ) )
504         {
505             webXml = webXml.substring( "${basedir}/".length() );
506         }
507         
508         writeCopyLib( writer, project, artifactResolverWrapper, "${maven.build.dir}/${maven.build.finalName}/WEB-INF/lib" );
509 
510         writer.startElement( "war" );
511         writer.addAttribute( "destfile", "${maven.build.dir}/${maven.build.finalName}.war" );
512         addWrapAttribute( writer, "war", "compress",
513                           getMavenWarPluginBasicOption( project, "archive//compress", "true" ), 3 );
514         addWrapAttribute( writer, "war", "webxml", webXml, 3 );
515         if ( getMavenWarPluginBasicOption( project, "manifestFile", null ) != null )
516         {
517             addWrapAttribute( writer, "war", "manifest", getMavenWarPluginBasicOption( project, "manifestFile", null ),
518                               3 );
519         }
520         writer.startElement( "lib" );
521         writer.addAttribute( "dir", "${maven.build.dir}/${maven.build.finalName}/WEB-INF/lib" );
522         writer.endElement(); // lib
523         writer.startElement( "classes" );
524         writer.addAttribute( "dir", "${maven.build.outputDir}" );
525         writer.endElement(); // classes
526         writer.startElement( "fileset" );
527         writer.addAttribute( "dir", "src/main/webapp" );
528         addWrapAttribute( writer, "fileset", "excludes", "WEB-INF/web.xml", 4 );
529         writer.endElement(); // fileset
530         writer.endElement(); // war
531     }
532 
533     /**
534      * Convenience method to wrap long element tags for a given attribute.
535      *
536      * @param writer not null
537      * @param tag not null
538      * @param name not null
539      * @param value not null
540      * @param indent positive value
541      */
542     public static void addWrapAttribute( XMLWriter writer, String tag, String name, String value, int indent )
543     {
544         if ( StringUtils.isEmpty( value ) )
545         {
546             return;
547         }
548 
549         if ( indent < 0 )
550         {
551             writer.addAttribute( name, value );
552         }
553         else
554         {
555             writer.addAttribute( "\n"
556                 + StringUtils.repeat( " ", ( StringUtils.isEmpty( tag ) ? 0 : tag.length() ) + indent
557                     * XmlWriterUtil.DEFAULT_INDENTATION_SIZE ) + name, value );
558         }
559     }
560 
561     /**
562      * @param mavenProject not null
563      * @return true if project packaging equals <code>pom</code>
564      */
565     public static boolean isPomPackaging( MavenProject mavenProject )
566     {
567         return "pom".equals( mavenProject.getPackaging() );
568     }
569 
570     /**
571      * @param mavenProject
572      * @return true if project packaging equals one of several packaging types 
573      *         including  <code>jar</code>, <code>maven-plugin</code>, <code>ejb</code>, or
574      *         <code>bundle</code>
575      */
576     public static boolean isJarPackaging( MavenProject mavenProject )
577     {
578         return "jar".equals( mavenProject.getPackaging() )
579             || isEjbPackaging( mavenProject )
580             || isMavenPluginPackaging( mavenProject )
581             || isBundlePackaging( mavenProject )
582             ;
583     }
584 
585     /**
586      * @param mavenProject
587      * @return true if project packaging equals <code>bundle</code>
588      */
589     public static boolean isBundlePackaging( MavenProject mavenProject )
590     {
591         return "bundle".equals( mavenProject.getPackaging() );
592     }
593 
594     /**
595      * @param mavenProject
596      * @return true if project packaging equals <code>ejb</code>
597      */
598     public static boolean isEjbPackaging( MavenProject mavenProject )
599     {
600         return "ejb".equals( mavenProject.getPackaging() );
601     }
602 
603     /**
604      * @param mavenProject
605      * @return true if project packaging equals <code>maven-plugin</code>
606      */
607     public static boolean isMavenPluginPackaging( MavenProject mavenProject )
608     {
609         return "maven-plugin".equals( mavenProject.getPackaging() );
610     }
611 
612     /**
613      * @param mavenProject
614      * @return true if project packaging equals <code>ear</code>
615      */
616     public static boolean isEarPackaging( MavenProject mavenProject )
617     {
618         return "ear".equals( mavenProject.getPackaging() );
619     }
620 
621     /**
622      * @param mavenProject not null
623      * @return true if project packaging equals <code>war</code>
624      */
625     public static boolean isWarPackaging( MavenProject mavenProject )
626     {
627         return "war".equals( mavenProject.getPackaging() );
628     }
629 
630     /**
631      * Return the <code>optionName</code> value defined in a project for the "maven-compiler-plugin" plugin.
632      *
633      * @param project not null
634      * @param optionName the option name wanted
635      * @param defaultValue a default value
636      * @return the value for the option name or the default value. Could be null if not found.
637      * @throws IOException if any
638      */
639     public static String getMavenCompilerPluginBasicOption( MavenProject project, String optionName, String defaultValue )
640         throws IOException
641     {
642         return getMavenPluginBasicOption( project, "maven-compiler-plugin", optionName, defaultValue );
643     }
644 
645     /**
646      * Return the map of <code>optionName</code> value defined in a project for the "maven-compiler-plugin" plugin.
647      *
648      * @param project not null
649      * @param optionName the option name wanted
650      * @param defaultValue a default value
651      * @return the map for the option name or the default value. Could be null if not found.
652      * @throws IOException if any
653      */
654     public static Map getMavenCompilerPluginOption( MavenProject project, String optionName, String defaultValue )
655         throws IOException
656     {
657         return getMavenPluginOption( project, "maven-compiler-plugin", optionName, defaultValue );
658     }
659 
660     /**
661      * Return an array of map of <code>optionName</code> value defined in a project for the "maven-compiler-plugin" plugin.
662      *
663      * @param project not null
664      * @param optionName the option name wanted
665      * @param defaultValue a default value
666      * @return the array of option name or the default value. Could be null if not found.
667      * @throws IOException if any
668      */
669     public static Map[] getMavenCompilerPluginOptions( MavenProject project, String optionName, String defaultValue )
670         throws IOException
671     {
672         return getMavenPluginOptions( project, "maven-compiler-plugin", optionName, defaultValue );
673     }
674 
675     /**
676      * Return the <code>optionName</code> value defined in a project for the "maven-surefire-plugin" plugin.
677      *
678      * @param project not null
679      * @param optionName the option name wanted
680      * @param defaultValue a default value
681      * @return the value for the option name or the default value. Could be null if not found.
682      * @throws IOException if any
683      */
684     public static String getMavenSurefirePluginBasicOption( MavenProject project, String optionName, String defaultValue )
685         throws IOException
686     {
687         return getMavenPluginBasicOption( project, "maven-surefire-plugin", optionName, defaultValue );
688     }
689 
690     /**
691      * Return the map of <code>optionName</code> value defined in a project for the "maven-surefire-plugin" plugin.
692      *
693      * @param project not null
694      * @param optionName the option name wanted
695      * @param defaultValue a default value
696      * @return the map for the option name or the default value. Could be null if not found.
697      * @throws IOException if any
698      */
699     public static Map getMavenSurefirePluginOption( MavenProject project, String optionName, String defaultValue )
700         throws IOException
701     {
702         return getMavenPluginOption( project, "maven-surefire-plugin", optionName, defaultValue );
703     }
704 
705     /**
706      * Return an array of map of <code>optionName</code> value defined in a project for the "maven-surefire-plugin" plugin.
707      *
708      * @param project not null
709      * @param optionName the option name wanted
710      * @param defaultValue a default value
711      * @return the array of option name or the default value. Could be null if not found.
712      * @throws IOException if any
713      */
714     public static Map[] getMavenSurefirePluginOptions( MavenProject project, String optionName, String defaultValue )
715         throws IOException
716     {
717         return getMavenPluginOptions( project, "maven-surefire-plugin", optionName, defaultValue );
718     }
719 
720     /**
721      * Return the <code>optionName</code> value defined in a project for the "maven-javadoc-plugin" plugin.
722      *
723      * @param project not null
724      * @param optionName the option name wanted
725      * @param defaultValue a default value
726      * @return the value for the option name or the default value. Could be null if not found.
727      * @throws IOException if any
728      */
729     public static String getMavenJavadocPluginBasicOption( MavenProject project, String optionName, String defaultValue )
730         throws IOException
731     {
732         return getMavenPluginBasicOption( project, "maven-javadoc-plugin", optionName, defaultValue );
733     }
734 
735     /**
736      * Return a map of <code>optionName</code> value defined in a project for the "maven-javadoc-plugin" plugin.
737      *
738      * @param project not null
739      * @param optionName the option name wanted
740      * @param defaultValue a default value
741      * @return the map for the option name or the default value. Could be null if not found.
742      * @throws IOException if any
743      */
744     public static Map getMavenJavadocPluginOption( MavenProject project, String optionName, String defaultValue )
745         throws IOException
746     {
747         return getMavenPluginOption( project, "maven-javadoc-plugin", optionName, defaultValue );
748     }
749 
750     /**
751      * Return an array of map of <code>optionName</code> value defined in a project for the "maven-javadoc-plugin" plugin.
752      *
753      * @param project not null
754      * @param optionName the option name wanted
755      * @param defaultValue a default value
756      * @return an array of option name. Could be null if not found.
757      * @throws IOException if any
758      */
759     public static Map[] getMavenJavadocPluginOptions( MavenProject project, String optionName, String defaultValue )
760         throws IOException
761     {
762         return getMavenPluginOptions( project, "maven-javadoc-plugin", optionName, defaultValue );
763     }
764 
765     /**
766      * Return the <code>optionName</code> value defined in a project for the "maven-jar-plugin" plugin.
767      *
768      * @param project not null
769      * @param optionName the option name wanted
770      * @param defaultValue a default value
771      * @return the value for the option name or the default value. Could be null if not found.
772      * @throws IOException if any
773      */
774     public static String getMavenJarPluginBasicOption( MavenProject project, String optionName, String defaultValue )
775         throws IOException
776     {
777         return getMavenPluginBasicOption( project, "maven-jar-plugin", optionName, defaultValue );
778     }
779 
780     /**
781      * Return the <code>optionName</code> value defined in a project for the "maven-ear-plugin" plugin.
782      *
783      * @param project not null
784      * @param optionName the option name wanted
785      * @param defaultValue a default value
786      * @return the value for the option name or the default value. Could be null if not found.
787      * @throws IOException if any
788      */
789     public static String getMavenEarPluginBasicOption( MavenProject project, String optionName, String defaultValue )
790         throws IOException
791     {
792         return getMavenPluginBasicOption( project, "maven-ear-plugin", optionName, defaultValue );
793     }
794 
795     /**
796      * Return the <code>optionName</code> value defined in a project for the "maven-war-plugin" plugin.
797      *
798      * @param project not null
799      * @param optionName the option name wanted
800      * @param defaultValue a default value
801      * @return the value for the option name or the default value. Could be null if not found.
802      * @throws IOException if any
803      */
804     public static String getMavenWarPluginBasicOption( MavenProject project, String optionName, String defaultValue )
805         throws IOException
806     {
807         return getMavenPluginBasicOption( project, "maven-war-plugin", optionName, defaultValue );
808     }
809 
810     // ----------------------------------------------------------------------
811     // Convenience methods
812     // ----------------------------------------------------------------------
813 
814     /**
815      * Return the value for the option <code>optionName</code> defined in a project with the given
816      * <code>artifactId</code> plugin.
817      * <br/>
818      * Example:
819      * <table>
820      *   <tr>
821      *     <td>Configuration</td>
822      *     <td>Result</td>
823      *   </tr>
824      *   <tr>
825      *     <td><pre>&lt;option&gt;value&lt;/option&gt;</pre></td>
826      *     <td><pre>value</pre></td>
827      *   </tr>
828      * </table>
829      *
830      * @param project not null
831      * @param pluginArtifactId not null
832      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
833      * @param defaultValue could be null
834      * @return the value for the option name or null if not found
835      * @throws IOException if any
836      */
837     private static String getMavenPluginBasicOption( MavenProject project, String pluginArtifactId, String optionName,
838                                                     String defaultValue )
839         throws IOException
840     {
841         return (String) getMavenPluginConfigurationsImpl( project, pluginArtifactId, optionName, defaultValue )
842             .get( optionName );
843     }
844 
845     /**
846      * Return a Map for the option <code>optionName</code> defined in a project with the given
847      * <code>artifactId</code> plugin.
848      * <br/>
849      * Example:
850      * <table>
851      *   <tr>
852      *     <td>Configuration</td>
853      *     <td>Result</td>
854      *   </tr>
855      *   <tr>
856      *     <td><pre>
857      * &lt;option&gt;
858      *  &lt;param1&gt;value1&lt;/param1&gt;
859      *  &lt;param2&gt;value2&lt;/param2&gt;
860      * &lt;/option&gt;
861      * </pre></td>
862      *     <td><pre>{param1=value1, param2=value2}<pre></td>
863      *   </tr>
864      * </table>
865      *
866      * @param project not null
867      * @param pluginArtifactId not null
868      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
869      * @param defaultValue could be null
870      * @return the value for the option name or null if not found
871      * @throws IOException if any
872      */
873     private static Map getMavenPluginOption( MavenProject project, String pluginArtifactId, String optionName,
874                                             String defaultValue )
875         throws IOException
876     {
877         return (Map) getMavenPluginConfigurationsImpl( project, pluginArtifactId, optionName, defaultValue )
878             .get( optionName );
879     }
880 
881     /**
882      * Return an array of Map for the option <code>optionName</code> defined in a project with the given
883      * <code>artifactId</code> plugin.
884      * <br/>
885      * Example:
886      * <table>
887      *   <tr>
888      *     <td>Configuration</td>
889      *     <td>Result</td>
890      *   </tr>
891      *   <tr>
892      *     <td><pre>
893      * &lt;options&gt;
894      *   &lt;option&gt;
895      *    &lt;param1&gt;value1&lt;/param1&gt;
896      *    &lt;param2&gt;value2&lt;/param2&gt;
897      *   &lt;/option&gt;
898      *   &lt;option&gt;
899      *    &lt;param1&gt;value1&lt;/param1&gt;
900      *    &lt;param2&gt;value2&lt;/param2&gt;
901      *   &lt;/option&gt;
902      * &lt;/options&gt;
903      * </pre></td>
904      *     <td><pre>[{option=[{param1=value1, param2=value2}]}, {option=[{param1=value1, param2=value2}]<pre></td>
905      *   </tr>
906      * </table>
907      *
908      * @param project not null
909      * @param pluginArtifactId not null
910      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
911      * @param defaultValue could be null
912      * @return the value for the option name  or null if not found
913      * @throws IOException if any
914      */
915     private static Map[] getMavenPluginOptions( MavenProject project, String pluginArtifactId, String optionName,
916                                                String defaultValue )
917         throws IOException
918     {
919         return (Map[]) getMavenPluginConfigurationsImpl( project, pluginArtifactId, optionName, defaultValue )
920             .get( optionName );
921     }
922 
923     /**
924      * Return a Map for the option <code>optionName</code> defined in a project with the given
925      * <code>artifactId</code> plugin.
926      * <br/>
927      * Example:
928      * <table>
929      *   <tr>
930      *     <td>Configuration</td>
931      *     <td>Result</td>
932      *   </tr>
933      *   <tr>
934      *     <td><pre>&lt;option&gt;value&lt;/option&gt;</pre></td>
935      *     <td><pre>{option=value}</pre></td>
936      *   </tr>
937      *   <tr>
938      *     <td><pre>
939      * &lt;option&gt;
940      *  &lt;param1&gt;value1&lt;/param1&gt;
941      *  &lt;param2&gt;value2&lt;/param2&gt;
942      * &lt;/option&gt;
943      * </pre></td>
944      *     <td><pre>{option={param1=value1, param2=value2}}<pre></td>
945      *   </tr>
946      *   <tr>
947      *     <td><pre>
948      * &lt;options&gt;
949      *   &lt;option&gt;
950      *    &lt;param1&gt;value1&lt;/param1&gt;
951      *    &lt;param2&gt;value2&lt;/param2&gt;
952      *   &lt;/option&gt;
953      *   &lt;option&gt;
954      *    &lt;param1&gt;value1&lt;/param1&gt;
955      *    &lt;param2&gt;value2&lt;/param2&gt;
956      *   &lt;/option&gt;
957      * &lt;/options&gt;
958      * </pre></td>
959      *     <td><pre>{options=[{option=[{param1=value1, param2=value2}]}, {option=[{param1=value1, param2=value2}]}]<pre></td>
960      *   </tr>
961      * </table>
962      *
963      * @param project not null
964      * @param pluginArtifactId not null
965      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
966      * @param defaultValue could be null
967      * @return a map with the options found
968      * @throws IOException if any
969      */
970     private static Map getMavenPluginConfigurationsImpl( MavenProject project, String pluginArtifactId,
971                                                         String optionName, String defaultValue )
972         throws IOException
973     {
974         List plugins = new ArrayList();
975         for ( Iterator it = project.getModel().getReporting().getPlugins().iterator(); it.hasNext(); )
976         {
977             plugins.add( it.next() );
978         }
979         for ( Iterator it = project.getModel().getBuild().getPlugins().iterator(); it.hasNext(); )
980         {
981             plugins.add( it.next() );
982         }
983         if ( project.getBuild().getPluginManagement() != null )
984         {
985             for ( Iterator it = project.getBuild().getPluginManagement().getPlugins().iterator(); it.hasNext(); )
986             {
987                 plugins.add( it.next() );
988             }
989         }
990 
991         for ( Iterator it = plugins.iterator(); it.hasNext(); )
992         {
993             Object next = it.next();
994 
995             Object pluginConf = null;
996 
997             if ( next instanceof Plugin )
998             {
999                 Plugin plugin = (Plugin) next;
1000 
1001                 // using out-of-box Maven plugins
1002                 if ( !( ( plugin.getGroupId().equals( "org.apache.maven.plugins" ) ) && ( plugin.getArtifactId()
1003                     .equals( pluginArtifactId ) ) ) )
1004                 {
1005                     continue;
1006                 }
1007 
1008                 pluginConf = plugin.getConfiguration();
1009             }
1010 
1011             if ( next instanceof ReportPlugin )
1012             {
1013                 ReportPlugin reportPlugin = (ReportPlugin) next;
1014 
1015                 // using out-of-box Maven plugins
1016                 if ( !( ( reportPlugin.getGroupId().equals( "org.apache.maven.plugins" ) ) && ( reportPlugin
1017                     .getArtifactId().equals( pluginArtifactId ) ) ) )
1018                 {
1019                     continue;
1020                 }
1021 
1022                 pluginConf = reportPlugin.getConfiguration();
1023             }
1024 
1025             if ( pluginConf == null )
1026             {
1027                 continue;
1028             }
1029 
1030             try
1031             {
1032                 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
1033                     .parse( new ByteArrayInputStream( pluginConf.toString().getBytes( "UTF-8" ) ) );
1034 
1035                 NodeList nodeList = XPathAPI.eval( doc, "//configuration/" + optionName ).nodelist();
1036                 if ( nodeList.getLength() > 0 )
1037                 {
1038                     Node optionNode = nodeList.item( 0 );
1039 
1040                     if ( isList( optionNode ) )
1041                     {
1042                         /*
1043                          * <optionNames>
1044                          *   <optionName>
1045                          *    <param1>value1</param1>
1046                          *    <param2>value2</param2>
1047                          *   </optionName>
1048                          * </optionNames>
1049                          */
1050                         Map options = new HashMap();
1051 
1052                         List optionNames = new ArrayList();
1053                         NodeList childs = optionNode.getChildNodes();
1054                         for ( int i = 0; i < childs.getLength(); i++ )
1055                         {
1056                             Node child = childs.item( i );
1057                             if ( child.getNodeType() == Node.ELEMENT_NODE )
1058                             {
1059                                 Map option = new HashMap();
1060 
1061                                 if ( isElementContent( child ) )
1062                                 {
1063                                     Map properties = new HashMap();
1064                                     NodeList childs2 = child.getChildNodes();
1065                                     if ( childs2.getLength() > 0 )
1066                                     {
1067                                         for ( int j = 0; j < childs2.getLength(); j++ )
1068                                         {
1069                                             Node child2 = childs2.item( j );
1070                                             if ( child2.getNodeType() == Node.ELEMENT_NODE )
1071                                             {
1072                                                 properties.put( child2.getNodeName(), getTextContent( child2 ) );
1073                                             }
1074                                         }
1075                                         option.put( child.getNodeName(), properties );
1076                                     }
1077                                 }
1078                                 else
1079                                 {
1080                                     option.put( child.getNodeName(), getTextContent( child ) );
1081                                 }
1082 
1083                                 optionNames.add( option );
1084                             }
1085                         }
1086 
1087                         options.put( optionName, optionNames.toArray( new Map[0] ) );
1088 
1089                         return options;
1090                     }
1091 
1092                     if ( isElementContent( optionNode ) )
1093                     {
1094                         /*
1095                          * <optionName>
1096                          *  <param1>value1</param1>
1097                          *  <param2>value2</param2>
1098                          * </optionName>
1099                          */
1100                         Map option = new HashMap();
1101 
1102                         NodeList childs = optionNode.getChildNodes();
1103                         if ( childs.getLength() > 1 )
1104                         {
1105                             Map parameters = new HashMap();
1106 
1107                             for ( int i = 0; i < childs.getLength(); i++ )
1108                             {
1109                                 Node child = childs.item( i );
1110                                 if ( child.getNodeType() == Node.ELEMENT_NODE )
1111                                 {
1112                                     parameters.put( child.getNodeName(), getTextContent( child ) );
1113                                 }
1114                             }
1115 
1116                             option.put( optionName, parameters );
1117                         }
1118 
1119                         return option;
1120                     }
1121                     else
1122                     {
1123                         /*
1124                          * <optionName>value1</optionName>
1125                          */
1126                         Map option = new HashMap();
1127 
1128                         option.put( optionName, getTextContent( optionNode ) );
1129 
1130                         return option;
1131                     }
1132                 }
1133             }
1134             catch ( Exception e )
1135             {
1136                 throw new IOException( "Exception occured: " + e.getMessage() );
1137             }
1138         }
1139 
1140         Map properties = new HashMap();
1141         properties.put( optionName, defaultValue );
1142 
1143         return properties;
1144     }
1145 
1146     /**
1147      * Write copy tasks in an outputDir for EAR and WAR targets for project depencies without
1148      * <code>provided</code> or <code>test</code> as scope
1149      *
1150      * @param writer not null
1151      * @param project not null
1152      * @param artifactResolverWrapper not null
1153      * @param outputDir not null
1154      */
1155     private static void writeCopyLib( XMLWriter writer, MavenProject project,
1156                                       ArtifactResolverWrapper artifactResolverWrapper, String outputDir )
1157     {
1158         writer.startElement( "mkdir" );
1159         writer.addAttribute( "dir", outputDir );
1160         writer.endElement(); // mkdir
1161 
1162         if ( project.getArtifacts() != null )
1163         {
1164             for ( Iterator i = project.getArtifacts().iterator(); i.hasNext(); )
1165             {
1166                 Artifact artifact = (Artifact) i.next();
1167 
1168                 if ( Artifact.SCOPE_COMPILE.equals( artifact.getScope() )
1169                     || Artifact.SCOPE_RUNTIME.equals( artifact.getScope() ) )
1170                 {
1171                     String path = artifactResolverWrapper.getLocalArtifactPath( artifact );
1172                     if ( !new File( path ).isAbsolute() )
1173                     {
1174                         path = "${maven.repo.local}/" + path;
1175                     }
1176 
1177                     writer.startElement( "copy" );
1178                     writer.addAttribute( "file", path );
1179                     addWrapAttribute( writer, "copy", "todir", outputDir, 3 );
1180                     writer.endElement(); // copy
1181                 }
1182             }
1183         }
1184     }
1185 
1186     /**
1187      * Check if a given <code>node</code> is a list of nodes or not.
1188      * <br/>
1189      * For instance, the node <code>options</code> is a list of <code>option</code> in the following case:
1190      *<pre>
1191      * &lt;options&gt;
1192      *   &lt;option&gt;
1193      *    &lt;param1&gt;value1&lt;/param1&gt;
1194      *    &lt;param2&gt;value2&lt;/param2&gt;
1195      *   &lt;/option&gt;
1196      *   &lt;option&gt;
1197      *    &lt;param1&gt;value1&lt;/param1&gt;
1198      *    &lt;param2&gt;value2&lt;/param2&gt;
1199      *   &lt;/option&gt;
1200      * &lt;/options&gt;
1201      * </pre>
1202      *
1203      * @param node a given node, may be <code>null</code>.
1204      * @return true if the node is a list, false otherwise.
1205      */
1206     private static boolean isList( Node node )
1207     {
1208         if ( node == null )
1209         {
1210             return false;
1211         }
1212 
1213         NodeList children = node.getChildNodes();
1214 
1215         boolean isList = false;
1216         String lastNodeName = null;
1217         for ( int i = 0; i < children.getLength(); i++ )
1218         {
1219             Node child = children.item( i );
1220             if ( child.getNodeType() == Node.ELEMENT_NODE )
1221             {
1222                 isList = isList || ( child.getNodeName().equals( lastNodeName ) );
1223                 lastNodeName = child.getNodeName();
1224             }
1225         }
1226         if ( StringUtils.isNotEmpty( lastNodeName ) )
1227         {
1228             isList = isList || lastNodeName.equals( getSingularForm( node.getNodeName() ) );
1229         }
1230 
1231         return isList;
1232     }
1233 
1234     /**
1235      * Checks whether the specified node has element content or consists only of character data.
1236      *
1237      * @param node The node to test, may be <code>null</code>.
1238      * @return <code>true</code> if any child node is an element, <code>false</code> otherwise.
1239      */
1240     private static boolean isElementContent( Node node )
1241     {
1242         if ( node == null )
1243         {
1244             return false;
1245         }
1246         NodeList children = node.getChildNodes();
1247         for ( int i = 0; i < children.getLength(); i++ )
1248         {
1249             Node child = children.item( i );
1250             if ( child.getNodeType() == Node.ELEMENT_NODE )
1251             {
1252                 return true;
1253             }
1254         }
1255         return false;
1256     }
1257 
1258     /**
1259      * Gets the text content of the specified node.
1260      *
1261      * @param node The node whose text contents should be retrieved, may be <code>null</code>.
1262      * @return The text content of the node, can be empty but never <code>null</code>.
1263      */
1264     private static String getTextContent( Node node )
1265     {
1266         StringBuffer buffer = new StringBuffer();
1267         if ( node != null )
1268         {
1269             NodeList children = node.getChildNodes();
1270             for ( int i = 0; i < children.getLength(); i++ )
1271             {
1272                 Node child = children.item( i );
1273                 if ( child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE )
1274                 {
1275                     buffer.append( child.getNodeValue() );
1276                 }
1277             }
1278         }
1279         return buffer.toString();
1280     }
1281 
1282     /**
1283      * Gets the singular form of the specified (English) plural form. For example:
1284      *
1285      * <pre>
1286      * properties -&gt; property
1287      * branches   -&gt; branch
1288      * reports    -&gt; report
1289      * </pre>
1290      *
1291      * @param pluralForm The plural form for which to derive the singular form, may be <code>null</code>.
1292      * @return The corresponding singular form or an empty string if the input string was not recognized as a plural
1293      *         form.
1294      */
1295     static String getSingularForm( String pluralForm )
1296     {
1297         String singularForm = "";
1298         if ( StringUtils.isNotEmpty( pluralForm ) )
1299         {
1300             if ( pluralForm.endsWith( "ies" ) )
1301             {
1302                 singularForm = pluralForm.substring( 0, pluralForm.length() - 3 ) + 'y';
1303             }
1304             else if ( pluralForm.endsWith( "ches" ) )
1305             {
1306                 singularForm = pluralForm.substring( 0, pluralForm.length() - 2 );
1307             }
1308             else if ( pluralForm.endsWith( "s" ) && pluralForm.length() > 1 )
1309             {
1310                 singularForm = pluralForm.substring( 0, pluralForm.length() - 1 );
1311             }
1312         }
1313         return singularForm;
1314     }
1315 
1316     /**
1317      * Relativizes the specified path against the given base directory (if possible). If the specified path is a
1318      * subdirectory of the base directory, the base directory prefix will be chopped off. If the specified path is equal
1319      * to the base directory, the path "." is returned. Otherwise, the path is returned as is. Examples:
1320      * <table border="1">
1321      * <tr>
1322      * <td>basedir</td>
1323      * <td>path</td>
1324      * <td>result</td>
1325      * </tr>
1326      * <tr>
1327      * <td>/home</td>
1328      * <td>/home/dir</td>
1329      * <td>dir</td>
1330      * </tr>
1331      * <tr>
1332      * <td>/home</td>
1333      * <td>/home/dir/</td>
1334      * <td>dir/</td>
1335      * </tr>
1336      * <tr>
1337      * <td>/home</td>
1338      * <td>/home</td>
1339      * <td>.</td>
1340      * </tr>
1341      * <tr>
1342      * <td>/home</td>
1343      * <td>/home/</td>
1344      * <td>./</td>
1345      * </tr>
1346      * <tr>
1347      * <td>/home</td>
1348      * <td>dir</td>
1349      * <td>dir</td>
1350      * </tr>
1351      * </table>
1352      * The returned path will always use the forward slash ('/') as the file separator regardless of the current
1353      * platform. Also, the result path will have a trailing slash if the input path has a trailing file separator.
1354      * 
1355      * @param basedir The base directory to relativize the path against, must not be <code>null</code>.
1356      * @param path The path to relativize, must not be <code>null</code>.
1357      * @return The relativized path, never <code>null</code>.
1358      */
1359     static String toRelative( File basedir, String path )
1360     {
1361         String result = null;
1362         if ( new File( path ).isAbsolute() )
1363         {
1364             String pathNormalized = path.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
1365             result = PathTool.getRelativeFilePath( basedir.getAbsolutePath(), pathNormalized );
1366         }
1367         if ( result == null )
1368         {
1369             result = path;
1370         }
1371         result = result.replace( '\\', '/' );
1372         if ( result.length() <= 0 || "/".equals( result ) )
1373         {
1374             result = '.' + result;
1375         }
1376         return result;
1377     }
1378 
1379 }