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