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 788423 2009-06-25 16:48:33Z 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 mavenProject.getPackaging().toLowerCase().equals( "pom" );
568     }
569 
570     /**
571      * @param mavenProject not null
572      * @return true if project packaging equals <code>jar</code> or <code>maven-plugin</code>
573      */
574     public static boolean isJarPackaging( MavenProject mavenProject )
575     {
576         return mavenProject.getPackaging().toLowerCase().equals( "jar" )
577             || mavenProject.getPackaging().toLowerCase().equals( "ejb" )
578             || mavenProject.getPackaging().toLowerCase().equals( "maven-plugin" );
579     }
580 
581     /**
582      * @param mavenProject
583      * @return true if project packaging equals <code>ear</code>
584      */
585     public static boolean isEarPackaging( MavenProject mavenProject )
586     {
587         return mavenProject.getPackaging().toLowerCase().equals( "ear" );
588     }
589 
590     /**
591      * @param mavenProject not null
592      * @return true if project packaging equals <code>war</code>
593      */
594     public static boolean isWarPackaging( MavenProject mavenProject )
595     {
596         return mavenProject.getPackaging().toLowerCase().equals( "war" );
597     }
598 
599     /**
600      * Return the <code>optionName</code> value defined in a project for the "maven-compiler-plugin" plugin.
601      *
602      * @param project not null
603      * @param optionName the option name wanted
604      * @param defaultValue a default value
605      * @return the value for the option name or the default value. Could be null if not found.
606      * @throws IOException if any
607      */
608     public static String getMavenCompilerPluginBasicOption( MavenProject project, String optionName, String defaultValue )
609         throws IOException
610     {
611         return getMavenPluginBasicOption( project, "maven-compiler-plugin", optionName, defaultValue );
612     }
613 
614     /**
615      * Return the map of <code>optionName</code> value defined in a project for the "maven-compiler-plugin" plugin.
616      *
617      * @param project not null
618      * @param optionName the option name wanted
619      * @param defaultValue a default value
620      * @return the map for the option name or the default value. Could be null if not found.
621      * @throws IOException if any
622      */
623     public static Map getMavenCompilerPluginOption( MavenProject project, String optionName, String defaultValue )
624         throws IOException
625     {
626         return getMavenPluginOption( project, "maven-compiler-plugin", optionName, defaultValue );
627     }
628 
629     /**
630      * Return an array of map of <code>optionName</code> value defined in a project for the "maven-compiler-plugin" plugin.
631      *
632      * @param project not null
633      * @param optionName the option name wanted
634      * @param defaultValue a default value
635      * @return the array of option name or the default value. Could be null if not found.
636      * @throws IOException if any
637      */
638     public static Map[] getMavenCompilerPluginOptions( MavenProject project, String optionName, String defaultValue )
639         throws IOException
640     {
641         return getMavenPluginOptions( project, "maven-compiler-plugin", optionName, defaultValue );
642     }
643 
644     /**
645      * Return the <code>optionName</code> value defined in a project for the "maven-surefire-plugin" plugin.
646      *
647      * @param project not null
648      * @param optionName the option name wanted
649      * @param defaultValue a default value
650      * @return the value for the option name or the default value. Could be null if not found.
651      * @throws IOException if any
652      */
653     public static String getMavenSurefirePluginBasicOption( MavenProject project, String optionName, String defaultValue )
654         throws IOException
655     {
656         return getMavenPluginBasicOption( project, "maven-surefire-plugin", optionName, defaultValue );
657     }
658 
659     /**
660      * Return the map of <code>optionName</code> value defined in a project for the "maven-surefire-plugin" plugin.
661      *
662      * @param project not null
663      * @param optionName the option name wanted
664      * @param defaultValue a default value
665      * @return the map for the option name or the default value. Could be null if not found.
666      * @throws IOException if any
667      */
668     public static Map getMavenSurefirePluginOption( MavenProject project, String optionName, String defaultValue )
669         throws IOException
670     {
671         return getMavenPluginOption( project, "maven-surefire-plugin", optionName, defaultValue );
672     }
673 
674     /**
675      * Return an array of map of <code>optionName</code> value defined in a project for the "maven-surefire-plugin" plugin.
676      *
677      * @param project not null
678      * @param optionName the option name wanted
679      * @param defaultValue a default value
680      * @return the array of option name or the default value. Could be null if not found.
681      * @throws IOException if any
682      */
683     public static Map[] getMavenSurefirePluginOptions( MavenProject project, String optionName, String defaultValue )
684         throws IOException
685     {
686         return getMavenPluginOptions( project, "maven-surefire-plugin", optionName, defaultValue );
687     }
688 
689     /**
690      * Return the <code>optionName</code> value defined in a project for the "maven-javadoc-plugin" plugin.
691      *
692      * @param project not null
693      * @param optionName the option name wanted
694      * @param defaultValue a default value
695      * @return the value for the option name or the default value. Could be null if not found.
696      * @throws IOException if any
697      */
698     public static String getMavenJavadocPluginBasicOption( MavenProject project, String optionName, String defaultValue )
699         throws IOException
700     {
701         return getMavenPluginBasicOption( project, "maven-javadoc-plugin", optionName, defaultValue );
702     }
703 
704     /**
705      * Return a map of <code>optionName</code> value defined in a project for the "maven-javadoc-plugin" plugin.
706      *
707      * @param project not null
708      * @param optionName the option name wanted
709      * @param defaultValue a default value
710      * @return the map for the option name or the default value. Could be null if not found.
711      * @throws IOException if any
712      */
713     public static Map getMavenJavadocPluginOption( MavenProject project, String optionName, String defaultValue )
714         throws IOException
715     {
716         return getMavenPluginOption( project, "maven-javadoc-plugin", optionName, defaultValue );
717     }
718 
719     /**
720      * Return an array of map of <code>optionName</code> value defined in a project for the "maven-javadoc-plugin" plugin.
721      *
722      * @param project not null
723      * @param optionName the option name wanted
724      * @param defaultValue a default value
725      * @return an array of option name. Could be null if not found.
726      * @throws IOException if any
727      */
728     public static Map[] getMavenJavadocPluginOptions( MavenProject project, String optionName, String defaultValue )
729         throws IOException
730     {
731         return getMavenPluginOptions( project, "maven-javadoc-plugin", optionName, defaultValue );
732     }
733 
734     /**
735      * Return the <code>optionName</code> value defined in a project for the "maven-jar-plugin" plugin.
736      *
737      * @param project not null
738      * @param optionName the option name wanted
739      * @param defaultValue a default value
740      * @return the value for the option name or the default value. Could be null if not found.
741      * @throws IOException if any
742      */
743     public static String getMavenJarPluginBasicOption( MavenProject project, String optionName, String defaultValue )
744         throws IOException
745     {
746         return getMavenPluginBasicOption( project, "maven-jar-plugin", optionName, defaultValue );
747     }
748 
749     /**
750      * Return the <code>optionName</code> value defined in a project for the "maven-ear-plugin" plugin.
751      *
752      * @param project not null
753      * @param optionName the option name wanted
754      * @param defaultValue a default value
755      * @return the value for the option name or the default value. Could be null if not found.
756      * @throws IOException if any
757      */
758     public static String getMavenEarPluginBasicOption( MavenProject project, String optionName, String defaultValue )
759         throws IOException
760     {
761         return getMavenPluginBasicOption( project, "maven-ear-plugin", optionName, defaultValue );
762     }
763 
764     /**
765      * Return the <code>optionName</code> value defined in a project for the "maven-war-plugin" plugin.
766      *
767      * @param project not null
768      * @param optionName the option name wanted
769      * @param defaultValue a default value
770      * @return the value for the option name or the default value. Could be null if not found.
771      * @throws IOException if any
772      */
773     public static String getMavenWarPluginBasicOption( MavenProject project, String optionName, String defaultValue )
774         throws IOException
775     {
776         return getMavenPluginBasicOption( project, "maven-war-plugin", optionName, defaultValue );
777     }
778 
779     // ----------------------------------------------------------------------
780     // Convenience methods
781     // ----------------------------------------------------------------------
782 
783     /**
784      * Return the value for the option <code>optionName</code> defined in a project with the given
785      * <code>artifactId</code> plugin.
786      * <br/>
787      * Example:
788      * <table>
789      *   <tr>
790      *     <td>Configuration</td>
791      *     <td>Result</td>
792      *   </tr>
793      *   <tr>
794      *     <td><pre>&lt;option&gt;value&lt;/option&gt;</pre></td>
795      *     <td><pre>value</pre></td>
796      *   </tr>
797      * </table>
798      *
799      * @param project not null
800      * @param pluginArtifactId not null
801      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
802      * @param defaultValue could be null
803      * @return the value for the option name or null if not found
804      * @throws IOException if any
805      */
806     private static String getMavenPluginBasicOption( MavenProject project, String pluginArtifactId, String optionName,
807                                                     String defaultValue )
808         throws IOException
809     {
810         return (String) getMavenPluginConfigurationsImpl( project, pluginArtifactId, optionName, defaultValue )
811             .get( optionName );
812     }
813 
814     /**
815      * Return a Map 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>
826      * &lt;option&gt;
827      *  &lt;param1&gt;value1&lt;/param1&gt;
828      *  &lt;param2&gt;value2&lt;/param2&gt;
829      * &lt;/option&gt;
830      * </pre></td>
831      *     <td><pre>{param1=value1, param2=value2}<pre></td>
832      *   </tr>
833      * </table>
834      *
835      * @param project not null
836      * @param pluginArtifactId not null
837      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
838      * @param defaultValue could be null
839      * @return the value for the option name or null if not found
840      * @throws IOException if any
841      */
842     private static Map getMavenPluginOption( MavenProject project, String pluginArtifactId, String optionName,
843                                             String defaultValue )
844         throws IOException
845     {
846         return (Map) getMavenPluginConfigurationsImpl( project, pluginArtifactId, optionName, defaultValue )
847             .get( optionName );
848     }
849 
850     /**
851      * Return an array of Map for the option <code>optionName</code> defined in a project with the given
852      * <code>artifactId</code> plugin.
853      * <br/>
854      * Example:
855      * <table>
856      *   <tr>
857      *     <td>Configuration</td>
858      *     <td>Result</td>
859      *   </tr>
860      *   <tr>
861      *     <td><pre>
862      * &lt;options&gt;
863      *   &lt;option&gt;
864      *    &lt;param1&gt;value1&lt;/param1&gt;
865      *    &lt;param2&gt;value2&lt;/param2&gt;
866      *   &lt;/option&gt;
867      *   &lt;option&gt;
868      *    &lt;param1&gt;value1&lt;/param1&gt;
869      *    &lt;param2&gt;value2&lt;/param2&gt;
870      *   &lt;/option&gt;
871      * &lt;/options&gt;
872      * </pre></td>
873      *     <td><pre>[{option=[{param1=value1, param2=value2}]}, {option=[{param1=value1, param2=value2}]<pre></td>
874      *   </tr>
875      * </table>
876      *
877      * @param project not null
878      * @param pluginArtifactId not null
879      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
880      * @param defaultValue could be null
881      * @return the value for the option name  or null if not found
882      * @throws IOException if any
883      */
884     private static Map[] getMavenPluginOptions( MavenProject project, String pluginArtifactId, String optionName,
885                                                String defaultValue )
886         throws IOException
887     {
888         return (Map[]) getMavenPluginConfigurationsImpl( project, pluginArtifactId, optionName, defaultValue )
889             .get( optionName );
890     }
891 
892     /**
893      * Return a Map for the option <code>optionName</code> defined in a project with the given
894      * <code>artifactId</code> plugin.
895      * <br/>
896      * Example:
897      * <table>
898      *   <tr>
899      *     <td>Configuration</td>
900      *     <td>Result</td>
901      *   </tr>
902      *   <tr>
903      *     <td><pre>&lt;option&gt;value&lt;/option&gt;</pre></td>
904      *     <td><pre>{option=value}</pre></td>
905      *   </tr>
906      *   <tr>
907      *     <td><pre>
908      * &lt;option&gt;
909      *  &lt;param1&gt;value1&lt;/param1&gt;
910      *  &lt;param2&gt;value2&lt;/param2&gt;
911      * &lt;/option&gt;
912      * </pre></td>
913      *     <td><pre>{option={param1=value1, param2=value2}}<pre></td>
914      *   </tr>
915      *   <tr>
916      *     <td><pre>
917      * &lt;options&gt;
918      *   &lt;option&gt;
919      *    &lt;param1&gt;value1&lt;/param1&gt;
920      *    &lt;param2&gt;value2&lt;/param2&gt;
921      *   &lt;/option&gt;
922      *   &lt;option&gt;
923      *    &lt;param1&gt;value1&lt;/param1&gt;
924      *    &lt;param2&gt;value2&lt;/param2&gt;
925      *   &lt;/option&gt;
926      * &lt;/options&gt;
927      * </pre></td>
928      *     <td><pre>{options=[{option=[{param1=value1, param2=value2}]}, {option=[{param1=value1, param2=value2}]}]<pre></td>
929      *   </tr>
930      * </table>
931      *
932      * @param project not null
933      * @param pluginArtifactId not null
934      * @param optionName an <code>Xpath</code> expression from the plugin <code>&lt;configuration/&gt;</code>
935      * @param defaultValue could be null
936      * @return a map with the options found
937      * @throws IOException if any
938      */
939     private static Map getMavenPluginConfigurationsImpl( MavenProject project, String pluginArtifactId,
940                                                         String optionName, String defaultValue )
941         throws IOException
942     {
943         List plugins = new ArrayList();
944         for ( Iterator it = project.getModel().getReporting().getPlugins().iterator(); it.hasNext(); )
945         {
946             plugins.add( it.next() );
947         }
948         for ( Iterator it = project.getModel().getBuild().getPlugins().iterator(); it.hasNext(); )
949         {
950             plugins.add( it.next() );
951         }
952         if ( project.getBuild().getPluginManagement() != null )
953         {
954             for ( Iterator it = project.getBuild().getPluginManagement().getPlugins().iterator(); it.hasNext(); )
955             {
956                 plugins.add( it.next() );
957             }
958         }
959 
960         for ( Iterator it = plugins.iterator(); it.hasNext(); )
961         {
962             Object next = it.next();
963 
964             Object pluginConf = null;
965 
966             if ( next instanceof Plugin )
967             {
968                 Plugin plugin = (Plugin) next;
969 
970                 // using out-of-box Maven plugins
971                 if ( !( ( plugin.getGroupId().equals( "org.apache.maven.plugins" ) ) && ( plugin.getArtifactId()
972                     .equals( pluginArtifactId ) ) ) )
973                 {
974                     continue;
975                 }
976 
977                 pluginConf = plugin.getConfiguration();
978             }
979 
980             if ( next instanceof ReportPlugin )
981             {
982                 ReportPlugin reportPlugin = (ReportPlugin) next;
983 
984                 // using out-of-box Maven plugins
985                 if ( !( ( reportPlugin.getGroupId().equals( "org.apache.maven.plugins" ) ) && ( reportPlugin
986                     .getArtifactId().equals( pluginArtifactId ) ) ) )
987                 {
988                     continue;
989                 }
990 
991                 pluginConf = reportPlugin.getConfiguration();
992             }
993 
994             if ( pluginConf == null )
995             {
996                 continue;
997             }
998 
999             try
1000             {
1001                 Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
1002                     .parse( new ByteArrayInputStream( pluginConf.toString().getBytes( "UTF-8" ) ) );
1003 
1004                 NodeList nodeList = XPathAPI.eval( doc, "//configuration/" + optionName ).nodelist();
1005                 if ( nodeList.getLength() > 0 )
1006                 {
1007                     Node optionNode = nodeList.item( 0 );
1008 
1009                     if ( isList( optionNode ) )
1010                     {
1011                         /*
1012                          * <optionNames>
1013                          *   <optionName>
1014                          *    <param1>value1</param1>
1015                          *    <param2>value2</param2>
1016                          *   </optionName>
1017                          * </optionNames>
1018                          */
1019                         Map options = new HashMap();
1020 
1021                         List optionNames = new ArrayList();
1022                         NodeList childs = optionNode.getChildNodes();
1023                         for ( int i = 0; i < childs.getLength(); i++ )
1024                         {
1025                             Node child = childs.item( i );
1026                             if ( child.getNodeType() == Node.ELEMENT_NODE )
1027                             {
1028                                 Map option = new HashMap();
1029 
1030                                 if ( isElementContent( child ) )
1031                                 {
1032                                     Map properties = new HashMap();
1033                                     NodeList childs2 = child.getChildNodes();
1034                                     if ( childs2.getLength() > 0 )
1035                                     {
1036                                         for ( int j = 0; j < childs2.getLength(); j++ )
1037                                         {
1038                                             Node child2 = childs2.item( j );
1039                                             if ( child2.getNodeType() == Node.ELEMENT_NODE )
1040                                             {
1041                                                 properties.put( child2.getNodeName(), getTextContent( child2 ) );
1042                                             }
1043                                         }
1044                                         option.put( child.getNodeName(), properties );
1045                                     }
1046                                 }
1047                                 else
1048                                 {
1049                                     option.put( child.getNodeName(), getTextContent( child ) );
1050                                 }
1051 
1052                                 optionNames.add( option );
1053                             }
1054                         }
1055 
1056                         options.put( optionName, optionNames.toArray( new Map[0] ) );
1057 
1058                         return options;
1059                     }
1060 
1061                     if ( isElementContent( optionNode ) )
1062                     {
1063                         /*
1064                          * <optionName>
1065                          *  <param1>value1</param1>
1066                          *  <param2>value2</param2>
1067                          * </optionName>
1068                          */
1069                         Map option = new HashMap();
1070 
1071                         NodeList childs = optionNode.getChildNodes();
1072                         if ( childs.getLength() > 1 )
1073                         {
1074                             Map parameters = new HashMap();
1075 
1076                             for ( int i = 0; i < childs.getLength(); i++ )
1077                             {
1078                                 Node child = childs.item( i );
1079                                 if ( child.getNodeType() == Node.ELEMENT_NODE )
1080                                 {
1081                                     parameters.put( child.getNodeName(), getTextContent( child ) );
1082                                 }
1083                             }
1084 
1085                             option.put( optionName, parameters );
1086                         }
1087 
1088                         return option;
1089                     }
1090                     else
1091                     {
1092                         /*
1093                          * <optionName>value1</optionName>
1094                          */
1095                         Map option = new HashMap();
1096 
1097                         option.put( optionName, getTextContent( optionNode ) );
1098 
1099                         return option;
1100                     }
1101                 }
1102             }
1103             catch ( Exception e )
1104             {
1105                 throw new IOException( "Exception occured: " + e.getMessage() );
1106             }
1107         }
1108 
1109         Map properties = new HashMap();
1110         properties.put( optionName, defaultValue );
1111 
1112         return properties;
1113     }
1114 
1115     /**
1116      * Write copy tasks in an outputDir for EAR and WAR targets for project depencies without
1117      * <code>provided</code> or <code>test</code> as scope
1118      *
1119      * @param writer not null
1120      * @param project not null
1121      * @param artifactResolverWrapper not null
1122      * @param outputDir not null
1123      */
1124     private static void writeCopyLib( XMLWriter writer, MavenProject project,
1125                                       ArtifactResolverWrapper artifactResolverWrapper, String outputDir )
1126     {
1127         writer.startElement( "mkdir" );
1128         writer.addAttribute( "dir", outputDir );
1129         writer.endElement(); // mkdir
1130 
1131         if ( !project.getDependencyArtifacts().isEmpty() )
1132         {
1133             for ( Iterator i = project.getDependencyArtifacts().iterator(); i.hasNext(); )
1134             {
1135                 Artifact artifact = (Artifact) i.next();
1136 
1137                 if ( !artifact.getScope().equals( Artifact.SCOPE_PROVIDED )
1138                     && !artifact.getScope().equals( Artifact.SCOPE_TEST ) )
1139                 {
1140                     String path = artifactResolverWrapper.getLocalArtifactPath( artifact );
1141                     if ( !new File( path ).isAbsolute() )
1142                     {
1143                         path = "${maven.repo.local}/" + path;
1144                     }
1145 
1146                     writer.startElement( "copy" );
1147                     writer.addAttribute( "file", path );
1148                     addWrapAttribute( writer, "copy", "todir", outputDir, 3 );
1149                     writer.endElement(); // copy
1150                 }
1151             }
1152         }
1153     }
1154 
1155     /**
1156      * Check if a given <code>node</code> is a list of nodes or not.
1157      * <br/>
1158      * For instance, the node <code>options</code> is a list of <code>option</code> in the following case:
1159      *<pre>
1160      * &lt;options&gt;
1161      *   &lt;option&gt;
1162      *    &lt;param1&gt;value1&lt;/param1&gt;
1163      *    &lt;param2&gt;value2&lt;/param2&gt;
1164      *   &lt;/option&gt;
1165      *   &lt;option&gt;
1166      *    &lt;param1&gt;value1&lt;/param1&gt;
1167      *    &lt;param2&gt;value2&lt;/param2&gt;
1168      *   &lt;/option&gt;
1169      * &lt;/options&gt;
1170      * </pre>
1171      *
1172      * @param node a given node, may be <code>null</code>.
1173      * @return true if the node is a list, false otherwise.
1174      */
1175     private static boolean isList( Node node )
1176     {
1177         if ( node == null )
1178         {
1179             return false;
1180         }
1181 
1182         NodeList children = node.getChildNodes();
1183 
1184         boolean isList = false;
1185         String lastNodeName = null;
1186         for ( int i = 0; i < children.getLength(); i++ )
1187         {
1188             Node child = children.item( i );
1189             if ( child.getNodeType() == Node.ELEMENT_NODE )
1190             {
1191                 isList = isList || ( child.getNodeName().equals( lastNodeName ) );
1192                 lastNodeName = child.getNodeName();
1193             }
1194         }
1195         if ( StringUtils.isNotEmpty( lastNodeName ) )
1196         {
1197             isList = isList || lastNodeName.equals( getSingularForm( node.getNodeName() ) );
1198         }
1199 
1200         return isList;
1201     }
1202 
1203     /**
1204      * Checks whether the specified node has element content or consists only of character data.
1205      *
1206      * @param node The node to test, may be <code>null</code>.
1207      * @return <code>true</code> if any child node is an element, <code>false</code> otherwise.
1208      */
1209     private static boolean isElementContent( Node node )
1210     {
1211         if ( node == null )
1212         {
1213             return false;
1214         }
1215         NodeList children = node.getChildNodes();
1216         for ( int i = 0; i < children.getLength(); i++ )
1217         {
1218             Node child = children.item( i );
1219             if ( child.getNodeType() == Node.ELEMENT_NODE )
1220             {
1221                 return true;
1222             }
1223         }
1224         return false;
1225     }
1226 
1227     /**
1228      * Gets the text content of the specified node.
1229      *
1230      * @param node The node whose text contents should be retrieved, may be <code>null</code>.
1231      * @return The text content of the node, can be empty but never <code>null</code>.
1232      */
1233     private static String getTextContent( Node node )
1234     {
1235         StringBuffer buffer = new StringBuffer();
1236         if ( node != null )
1237         {
1238             NodeList children = node.getChildNodes();
1239             for ( int i = 0; i < children.getLength(); i++ )
1240             {
1241                 Node child = children.item( i );
1242                 if ( child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE )
1243                 {
1244                     buffer.append( child.getNodeValue() );
1245                 }
1246             }
1247         }
1248         return buffer.toString();
1249     }
1250 
1251     /**
1252      * Gets the singular form of the specified (English) plural form. For example:
1253      *
1254      * <pre>
1255      * properties -&gt; property
1256      * branches   -&gt; branch
1257      * reports    -&gt; report
1258      * </pre>
1259      *
1260      * @param pluralForm The plural form for which to derive the singular form, may be <code>null</code>.
1261      * @return The corresponding singular form or an empty string if the input string was not recognized as a plural
1262      *         form.
1263      */
1264     static String getSingularForm( String pluralForm )
1265     {
1266         String singularForm = "";
1267         if ( StringUtils.isNotEmpty( pluralForm ) )
1268         {
1269             if ( pluralForm.endsWith( "ies" ) )
1270             {
1271                 singularForm = pluralForm.substring( 0, pluralForm.length() - 3 ) + 'y';
1272             }
1273             else if ( pluralForm.endsWith( "ches" ) )
1274             {
1275                 singularForm = pluralForm.substring( 0, pluralForm.length() - 2 );
1276             }
1277             else if ( pluralForm.endsWith( "s" ) && pluralForm.length() > 1 )
1278             {
1279                 singularForm = pluralForm.substring( 0, pluralForm.length() - 1 );
1280             }
1281         }
1282         return singularForm;
1283     }
1284 
1285     /**
1286      * Relativizes the specified path against the given base directory (if possible). If the specified path is a
1287      * subdirectory of the base directory, the base directory prefix will be chopped off. If the specified path is equal
1288      * to the base directory, the path "." is returned. Otherwise, the path is returned as is. Examples:
1289      * <table border="1">
1290      * <tr>
1291      * <td>basedir</td>
1292      * <td>path</td>
1293      * <td>result</td>
1294      * </tr>
1295      * <tr>
1296      * <td>/home</td>
1297      * <td>/home/dir</td>
1298      * <td>dir</td>
1299      * </tr>
1300      * <tr>
1301      * <td>/home</td>
1302      * <td>/home/dir/</td>
1303      * <td>dir/</td>
1304      * </tr>
1305      * <tr>
1306      * <td>/home</td>
1307      * <td>/home</td>
1308      * <td>.</td>
1309      * </tr>
1310      * <tr>
1311      * <td>/home</td>
1312      * <td>/home/</td>
1313      * <td>./</td>
1314      * </tr>
1315      * <tr>
1316      * <td>/home</td>
1317      * <td>dir</td>
1318      * <td>dir</td>
1319      * </tr>
1320      * </table>
1321      * The returned path will always use the forward slash ('/') as the file separator regardless of the current
1322      * platform. Also, the result path will have a trailing slash if the input path has a trailing file separator.
1323      * 
1324      * @param basedir The base directory to relativize the path against, must not be <code>null</code>.
1325      * @param path The path to relativize, must not be <code>null</code>.
1326      * @return The relativized path, never <code>null</code>.
1327      */
1328     static String toRelative( File basedir, String path )
1329     {
1330         String result = null;
1331         if ( new File( path ).isAbsolute() )
1332         {
1333             String pathNormalized = path.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
1334             result = PathTool.getRelativeFilePath( basedir.getAbsolutePath(), pathNormalized );
1335         }
1336         if ( result == null )
1337         {
1338             result = path;
1339         }
1340         result = result.replace( '\\', '/' );
1341         if ( result.length() <= 0 || "/".equals( result ) )
1342         {
1343             result = '.' + result;
1344         }
1345         return result;
1346     }
1347 
1348 }