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