View Javadoc

1   package org.apache.maven.plugin.idea;
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 org.apache.maven.artifact.Artifact;
23  import org.apache.maven.artifact.factory.ArtifactFactory;
24  import org.apache.maven.artifact.manager.WagonManager;
25  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
26  import org.apache.maven.artifact.repository.ArtifactRepository;
27  import org.apache.maven.artifact.resolver.ArtifactResolver;
28  import org.apache.maven.model.Resource;
29  import org.apache.maven.plugin.MojoExecutionException;
30  import org.apache.maven.plugin.logging.Log;
31  import org.apache.maven.project.MavenProject;
32  import org.apache.maven.wagon.ResourceDoesNotExistException;
33  import org.apache.maven.wagon.TransferFailedException;
34  import org.codehaus.plexus.util.StringUtils;
35  import org.dom4j.Document;
36  import org.dom4j.DocumentException;
37  import org.dom4j.Element;
38  
39  import java.io.File;
40  import java.io.IOException;
41  import java.util.ArrayList;
42  import java.util.Collections;
43  import java.util.HashMap;
44  import java.util.HashSet;
45  import java.util.Iterator;
46  import java.util.List;
47  import java.util.Map;
48  import java.util.Set;
49  import java.util.regex.Matcher;
50  import java.util.regex.Pattern;
51  
52  /**
53   * Creates the module files (*.iml) for IntelliJ IDEA.
54   *
55   * @author Edwin Punzalan
56   * @goal module
57   * @execute phase="generate-sources"
58   */
59  public class IdeaModuleMojo
60      extends AbstractIdeaMojo
61  {
62      /**
63       * The reactor projects in a multi-module build.
64       *
65       * @parameter expression="${reactorProjects}"
66       * @required
67       * @readonly
68       */
69      private List reactorProjects;
70  
71      /**
72       * @component
73       */
74      private WagonManager wagonManager;
75  
76      /**
77       * Whether to link the reactor projects as dependency modules or as libraries.
78       *
79       * @parameter expression="${linkModules}" default-value="true"
80       */
81      private boolean linkModules;
82  
83      /**
84       * Specify the location of the deployment descriptor file, if one is provided.
85       *
86       * @parameter expression="${deploymentDescriptorFile}"
87       */
88      private String deploymentDescriptorFile;
89  
90      /**
91       * Whether to use full artifact names when referencing libraries.
92       *
93       * @parameter expression="${useFullNames}" default-value="false"
94       */
95      private boolean useFullNames;
96  
97      /**
98       * Enables/disables the downloading of source attachments.
99       *
100      * @parameter expression="${downloadSources}" default-value="false"
101      */
102     private boolean downloadSources;
103 
104     /**
105      * Enables/disables the downloading of javadoc attachments.
106      *
107      * @parameter expression="${downloadJavadocs}" default-value="false"
108      */
109     private boolean downloadJavadocs;
110 
111     /**
112      * Sets the classifier string attached to an artifact source archive name.
113      *
114      * @parameter expression="${sourceClassifier}" default-value="sources"
115      */
116     private String sourceClassifier;
117 
118     /**
119      * Sets the classifier string attached to an artifact javadoc archive name.
120      *
121      * @parameter expression="${javadocClassifier}" default-value="javadoc"
122      */
123     private String javadocClassifier;
124 
125     /**
126      * An optional set of Library objects that allow you to specify a comma separated list of source dirs, class dirs,
127      * or to indicate that the library should be excluded from the module. For example:
128      * <p/>
129      * <pre>
130      * &lt;libraries&gt;
131      *  &lt;library&gt;
132      *      &lt;name&gt;webwork&lt;/name&gt;
133      *      &lt;sources&gt;file://$webwork$/src/java&lt;/sources&gt;
134      *      &lt;!--
135      *      &lt;classes&gt;...&lt;/classes&gt;
136      *      &lt;exclude&gt;true&lt;/exclude&gt;
137      *      --&gt;
138      *  &lt;/library&gt;
139      * &lt;/libraries&gt;
140      * </pre>
141      *
142      * @parameter
143      */
144     private Library[] libraries;
145 
146     /**
147      * A comma-separated list of directories that should be excluded. These directories are in addition to those
148      * already excluded, such as target.
149      *
150      * @parameter
151      */
152     private String exclude;
153 
154     /**
155      * Causes the module libraries to use a short name for all dependencies. This is very convenient but has been
156      * reported to cause problems with IDEA.
157      *
158      * @parameter default-value="false"
159      */
160     private boolean dependenciesAsLibraries;
161 
162     /**
163      * A temporary cache of artifacts that's already been downloaded or
164      * attempted to be downloaded. This is to refrain from trying to download a
165      * dependency that we have already tried to download.
166      *
167      * @todo this is nasty! the only reason this is static is to use the same cache between reactor calls
168      */
169     private static Map attemptedDownloads = new HashMap();
170 
171     /**
172      * Tell IntelliJ IDEA that this module is an IntelliJ IDEA Plugin.
173      *
174      * @parameter default-value="false"
175      */
176     private boolean ideaPlugin;
177 
178     /**
179      * Specify the version of IDEA to target.  This is needed to identify the default formatting of
180      * project-jdk-name used by IDEA.  Currently supports 4.x and 5.x.
181      * <p/>
182      * This will only be used when parameter jdkName is not set.
183      *
184      * @parameter expression="${ideaVersion}" default-value="5.x"
185      */
186     private String ideaVersion;
187 
188     private Set macros;
189 
190     public void initParam( MavenProject project, ArtifactFactory artifactFactory, ArtifactRepository localRepo,
191                            ArtifactResolver artifactResolver, ArtifactMetadataSource artifactMetadataSource, Log log,
192                            boolean overwrite, MavenProject executedProject, List reactorProjects,
193                            WagonManager wagonManager, boolean linkModules, boolean useFullNames,
194                            boolean downloadSources, String sourceClassifier, boolean downloadJavadocs,
195                            String javadocClassifier, Library[] libraries, Set macros, String exclude,
196                            boolean useShortDependencyNames, String deploymentDescriptorFile, boolean ideaPlugin,
197                            String ideaVersion )
198     {
199         super.initParam( project, artifactFactory, localRepo, artifactResolver, artifactMetadataSource, log,
200                          overwrite );
201 
202         this.reactorProjects = reactorProjects;
203 
204         this.wagonManager = wagonManager;
205 
206         this.linkModules = linkModules;
207 
208         this.useFullNames = useFullNames;
209 
210         this.downloadSources = downloadSources;
211 
212         this.sourceClassifier = sourceClassifier;
213 
214         this.downloadJavadocs = downloadJavadocs;
215 
216         this.javadocClassifier = javadocClassifier;
217 
218         this.libraries = libraries;
219 
220         this.macros = macros;
221 
222         this.exclude = exclude;
223 
224         this.dependenciesAsLibraries = useShortDependencyNames;
225 
226         this.deploymentDescriptorFile = deploymentDescriptorFile;
227 
228         this.ideaPlugin = ideaPlugin;
229 
230         this.ideaVersion = ideaVersion;
231     }
232 
233     /**
234      * Create IDEA (.iml) project files.
235      *
236      * @throws org.apache.maven.plugin.MojoExecutionException
237      *
238      */
239     public void execute()
240         throws MojoExecutionException
241     {
242         try
243         {
244             doDependencyResolution( executedProject, localRepo );
245         }
246         catch ( Exception e )
247         {
248             throw new MojoExecutionException( "Unable to build project dependencies.", e );
249         }
250 
251         rewriteModule();
252     }
253 
254     public void rewriteModule()
255         throws MojoExecutionException
256     {
257         File moduleFile = new File( executedProject.getBasedir(), executedProject.getArtifactId() + ".iml" );
258         try
259         {
260             Document document = readXmlDocument( moduleFile, "module.xml" );
261 
262             Element module = document.getRootElement();
263 
264             // TODO: how can we let the WAR/EJBs plugin hook in and provide this?
265             // TODO: merge in ejb-module, etc.
266             if ( "war".equals( executedProject.getPackaging() ) )
267             {
268                 addWebModule( module );
269             }
270             else if ( "ejb".equals( executedProject.getPackaging() ) )
271             {
272                 addEjbModule( module );
273             }
274             else if ( "ear".equals( executedProject.getPackaging() ) )
275             {
276                 addEarModule( module );
277             }
278             else if ( ideaPlugin )
279             {
280                 addPluginModule( module );
281             }
282 
283             Element component = findComponent( module, "NewModuleRootManager" );
284             Element output = findElement( component, "output" );
285             output.addAttribute( "url", getModuleFileUrl( executedProject.getBuild().getOutputDirectory() ) );
286 
287             Element outputTest = findElement( component, "output-test" );
288             outputTest.addAttribute( "url", getModuleFileUrl( executedProject.getBuild().getTestOutputDirectory() ) );
289 
290             Element content = findElement( component, "content" );
291 
292             removeOldElements( content, "sourceFolder" );
293 
294             for ( Iterator i = executedProject.getCompileSourceRoots().iterator(); i.hasNext(); )
295             {
296                 String directory = (String) i.next();
297                 addSourceFolder( content, directory, false );
298             }
299             for ( Iterator i = executedProject.getTestCompileSourceRoots().iterator(); i.hasNext(); )
300             {
301                 String directory = (String) i.next();
302                 addSourceFolder( content, directory, true );
303             }
304 
305             for ( Iterator i = executedProject.getBuild().getResources().iterator(); i.hasNext(); )
306             {
307                 Resource resource = (Resource) i.next();
308                 String directory = resource.getDirectory();
309                 if ( resource.getTargetPath() == null && !resource.isFiltering() )
310                 {
311                     addSourceFolder( content, directory, false );
312                 }
313                 else
314                 {
315                     getLog().info(
316                         "Not adding resource directory as it has an incompatible target path or filtering: "
317                             + directory );
318                 }
319             }
320 
321             for ( Iterator i = executedProject.getBuild().getTestResources().iterator(); i.hasNext(); )
322             {
323                 Resource resource = (Resource) i.next();
324                 String directory = resource.getDirectory();
325                 if ( resource.getTargetPath() == null && !resource.isFiltering() )
326                 {
327                     addSourceFolder( content, directory, true );
328                 }
329                 else
330                 {
331                     getLog().info(
332                         "Not adding test resource directory as it has an incompatible target path or filtering: "
333                             + directory );
334                 }
335             }
336 
337             removeOldElements( content, "excludeFolder" );
338 
339             //For excludeFolder
340             File target = new File( executedProject.getBuild().getDirectory() );
341             File classes = new File( executedProject.getBuild().getOutputDirectory() );
342             File testClasses = new File( executedProject.getBuild().getTestOutputDirectory() );
343 
344             List sourceFolders = content.elements( "sourceFolder" );
345 
346             List filteredExcludes = new ArrayList();
347             filteredExcludes.addAll( getExcludedDirectories( target, filteredExcludes, sourceFolders ) );
348             filteredExcludes.addAll( getExcludedDirectories( classes, filteredExcludes, sourceFolders ) );
349             filteredExcludes.addAll( getExcludedDirectories( testClasses, filteredExcludes, sourceFolders ) );
350 
351             if ( exclude != null )
352             {
353                 String[] dirs = exclude.split( "[,\\s]+" );
354                 for ( int i = 0; i < dirs.length; i++ )
355                 {
356                     File excludedDir = new File( executedProject.getBasedir(), dirs[i] );
357                     filteredExcludes.addAll( getExcludedDirectories( excludedDir, filteredExcludes, sourceFolders ) );
358                 }
359             }
360 
361             // even though we just ran all the directories in the filteredExcludes List through the intelligent
362             // getExcludedDirectories method, we never actually were guaranteed the order that they were added was
363             // in the order required to make the most optimized exclude list. In addition, the smart logic from
364             // that method is entirely skipped if the directory doesn't currently exist. A simple string matching
365             // will do pretty much the same thing and make the list more concise.
366             ArrayList actuallyExcluded = new ArrayList();
367             Collections.sort( filteredExcludes );
368             for ( Iterator i = filteredExcludes.iterator(); i.hasNext(); )
369             {
370                 String dirToExclude = i.next().toString();
371                 String dirToExcludeTemp = dirToExclude.replace( '\\', '/' );
372                 boolean addExclude = true;
373                 for ( Iterator iterator = actuallyExcluded.iterator(); iterator.hasNext(); )
374                 {
375                     String dir = iterator.next().toString();
376                     String dirTemp = dir.replace( '\\', '/' );
377                     if ( dirToExcludeTemp.startsWith( dirTemp + "/" ) )
378                     {
379                         addExclude = false;
380                         break;
381                     }
382                     else if ( dir.startsWith( dirToExcludeTemp + "/" ) )
383                     {
384                         actuallyExcluded.remove( dir );
385                     }
386                 }
387 
388                 if ( addExclude )
389                 {
390                     actuallyExcluded.add( dirToExclude );
391                     addExcludeFolder( content, dirToExclude );
392                 }
393             }
394 
395             //Remove default exclusion for output dirs if there are sources in it
396             String outputModuleUrl = getModuleFileUrl( executedProject.getBuild().getOutputDirectory() );
397             String testOutputModuleUrl = getModuleFileUrl( executedProject.getBuild().getTestOutputDirectory() );
398             for ( Iterator i = content.elements( "sourceFolder" ).iterator(); i.hasNext(); )
399             {
400                 Element sourceFolder = (Element) i.next();
401                 String sourceUrl = sourceFolder.attributeValue( "url" ).replace( '\\', '/' );
402                 if ( sourceUrl.startsWith( outputModuleUrl + "/" ) || sourceUrl.startsWith( testOutputModuleUrl ) )
403                 {
404                     component.remove( component.element( "exclude-output" ) );
405                     break;
406                 }
407             }
408 
409             rewriteDependencies( component );
410 
411             writeXmlDocument( moduleFile, document );
412         }
413         catch ( DocumentException e )
414         {
415             throw new MojoExecutionException( "Error parsing existing IML file " + moduleFile.getAbsolutePath(), e );
416         }
417         catch ( IOException e )
418         {
419             throw new MojoExecutionException( "Error parsing existing IML file " + moduleFile.getAbsolutePath(), e );
420         }
421     }
422 
423     private void rewriteDependencies( Element component )
424     {
425         Map modulesByName = new HashMap();
426         Map modulesByUrl = new HashMap();
427         Set unusedModules = new HashSet();
428         for ( Iterator children = component.elementIterator( "orderEntry" ); children.hasNext(); )
429         {
430             Element orderEntry = (Element) children.next();
431 
432             String type = orderEntry.attributeValue( "type" );
433             if ( "module".equals( type ) )
434             {
435                 modulesByName.put( orderEntry.attributeValue( "module-name" ), orderEntry );
436             }
437             else if ( "module-library".equals( type ) )
438             {
439                 // keep track for later so we know what is left
440                 unusedModules.add( orderEntry );
441 
442                 Element lib = orderEntry.element( "library" );
443                 String name = lib.attributeValue( "name" );
444                 if ( name != null )
445                 {
446                     modulesByName.put( name, orderEntry );
447                 }
448                 else
449                 {
450                     Element classesChild = lib.element( "CLASSES" );
451                     if ( classesChild != null )
452                     {
453                         Element rootChild = classesChild.element( "root" );
454                         if ( rootChild != null )
455                         {
456                             String url = rootChild.attributeValue( "url" );
457                             if ( url != null )
458                             {
459                                 // Need to ignore case because of Windows drive letters
460                                 modulesByUrl.put( url.toLowerCase(), orderEntry );
461                             }
462                         }
463                     }
464                 }
465             }
466         }
467 
468         List testClasspathElements = executedProject.getTestArtifacts();
469         for ( Iterator i = testClasspathElements.iterator(); i.hasNext(); )
470         {
471             Artifact a = (Artifact) i.next();
472 
473             Library library = findLibrary( a );
474             if ( library != null && library.isExclude() )
475             {
476                 continue;
477             }
478 
479             String moduleName;
480             if ( useFullNames )
481             {
482                 moduleName = a.getGroupId() + ':' + a.getArtifactId() + ':' + a.getType() + ':' + a.getVersion();
483             }
484             else
485             {
486                 moduleName = a.getArtifactId();
487             }
488 
489             Element dep = (Element) modulesByName.get( moduleName );
490 
491             if ( dep == null )
492             {
493                 // Need to ignore case because of Windows drive letters
494                 dep = (Element) modulesByUrl.get( getLibraryUrl( a ).toLowerCase() );
495             }
496 
497             if ( dep != null )
498             {
499                 unusedModules.remove( dep );
500             }
501             else
502             {
503                 dep = createElement( component, "orderEntry" );
504             }
505 
506             boolean isIdeaModule = false;
507             if ( linkModules )
508             {
509                 isIdeaModule = isReactorProject( a.getGroupId(), a.getArtifactId() );
510 
511                 if ( isIdeaModule )
512                 {
513                     dep.addAttribute( "type", "module" );
514                     dep.addAttribute( "module-name", moduleName );
515                 }
516             }
517 
518             if ( a.getFile() != null && !isIdeaModule )
519             {
520                 dep.addAttribute( "type", "module-library" );
521 
522                 Element lib = dep.element( "library" );
523 
524                 if ( lib == null )
525                 {
526                     lib = createElement( dep, "library" );
527                 }
528 
529                 if ( dependenciesAsLibraries )
530                 {
531                     lib.addAttribute( "name", moduleName );
532                 }
533 
534                 // replace classes
535                 removeOldElements( lib, "CLASSES" );
536                 Element classes = createElement( lib, "CLASSES" );
537                 if ( library != null && library.getSplitClasses().length > 0 )
538                 {
539                     lib.addAttribute( "name", moduleName );
540                     String[] libraryClasses = library.getSplitClasses();
541                     for ( int k = 0; k < libraryClasses.length; k++ )
542                     {
543                         String classpath = libraryClasses[k];
544                         extractMacro( classpath );
545                         Element classEl = createElement( classes, "root" );
546                         classEl.addAttribute( "url", classpath );
547                     }
548                 }
549                 else
550                 {
551                     createElement( classes, "root" ).addAttribute( "url", getLibraryUrl( a ) );
552                 }
553 
554                 if ( library != null && library.getSplitSources().length > 0 )
555                 {
556                     removeOldElements( lib, "SOURCES" );
557                     Element sourcesElement = createElement( lib, "SOURCES" );
558                     String[] sources = library.getSplitSources();
559                     for ( int k = 0; k < sources.length; k++ )
560                     {
561                         String source = sources[k];
562                         extractMacro( source );
563                         Element sourceEl = createElement( sourcesElement, "root" );
564                         sourceEl.addAttribute( "url", source );
565                     }
566                 }
567                 else if ( downloadSources )
568                 {
569                     resolveClassifier( createOrGetElement( lib, "SOURCES" ), a, sourceClassifier );
570                 }
571 
572                 if ( library != null && library.getSplitJavadocs().length > 0 )
573                 {
574                     removeOldElements( lib, "JAVADOC" );
575                     Element javadocsElement = createElement( lib, "JAVADOC" );
576                     String[] javadocs = library.getSplitJavadocs();
577                     for ( int k = 0; k < javadocs.length; k++ )
578                     {
579                         String javadoc = javadocs[k];
580                         extractMacro( javadoc );
581                         Element sourceEl = createElement( javadocsElement, "root" );
582                         sourceEl.addAttribute( "url", javadoc );
583                     }
584                 }
585                 else if ( downloadJavadocs )
586                 {
587                     resolveClassifier( createOrGetElement( lib, "JAVADOC" ), a, javadocClassifier );
588                 }
589             }
590         }
591 
592         for ( Iterator i = unusedModules.iterator(); i.hasNext(); )
593         {
594             Element orderEntry = (Element) i.next();
595 
596             component.remove( orderEntry );
597         }
598     }
599 
600     private Element createOrGetElement( Element lib, String name )
601     {
602         Element el = lib.element( name );
603 
604         if ( el == null )
605         {
606             el = createElement( lib, name );
607         }
608         return el;
609     }
610 
611     private void addEarModule( Element module )
612     {
613         module.addAttribute( "type", "J2EE_APPLICATION_MODULE" );
614         Element component = findComponent( module, "ApplicationModuleProperties" );
615         addDeploymentDescriptor( component, "application.xml", "1.3",
616                                  executedProject.getBuild().getDirectory() + "/application.xml" );
617     }
618 
619     private void addEjbModule( Element module )
620     {
621         String ejbVersion = getPluginSetting( "maven-ejb-plugin", "ejbVersion", "2.x" );
622 
623         module.addAttribute( "type", "J2EE_EJB_MODULE" );
624 
625         String explodedDir = executedProject.getBuild().getDirectory() + "/" + executedProject.getArtifactId();
626 
627         Element component = findComponent( module, "EjbModuleBuildComponent" );
628 
629         Element setting = findSetting( component, "EXPLODED_URL" );
630         setting.addAttribute( "value", getModuleFileUrl( explodedDir ) );
631 
632         component = findComponent( module, "EjbModuleProperties" );
633         Element deployDescElement =
634             addDeploymentDescriptor( component, "ejb-jar.xml", ejbVersion, "src/main/resources/META-INF/ejb-jar.xml" );
635         deployDescElement.addAttribute( "optional", ejbVersion.startsWith( "3" ) + "" );
636 
637         removeOldElements( component, "containerElement" );
638         List artifacts = executedProject.getTestArtifacts();
639         for ( Iterator i = artifacts.iterator(); i.hasNext(); )
640         {
641             Artifact artifact = (Artifact) i.next();
642 
643             Element containerElement = createElement( component, "containerElement" );
644 
645             if ( linkModules && isReactorProject( artifact.getGroupId(), artifact.getArtifactId() ) )
646             {
647                 containerElement.addAttribute( "type", "module" );
648                 containerElement.addAttribute( "name", artifact.getArtifactId() );
649                 Element methodAttribute = createElement( containerElement, "attribute" );
650                 methodAttribute.addAttribute( "name", "method" );
651                 methodAttribute.addAttribute( "value", "6" );
652                 Element uriAttribute = createElement( containerElement, "attribute" );
653                 uriAttribute.addAttribute( "name", "URI" );
654                 uriAttribute.addAttribute( "value", "/lib/" + artifact.getArtifactId() + ".jar" );
655             }
656             else if ( artifact.getFile() != null )
657             {
658                 containerElement.addAttribute( "type", "library" );
659                 containerElement.addAttribute( "level", "module" );
660 
661                 //no longer needed in IntelliJ 6
662                 if ( StringUtils.isEmpty( ideaVersion ) || !ideaVersion.startsWith( "6" ) )
663                 {
664                     containerElement.addAttribute( "name", artifact.getArtifactId() );
665                 }
666 
667                 Element methodAttribute = createElement( containerElement, "attribute" );
668                 methodAttribute.addAttribute( "name", "method" );
669                 methodAttribute.addAttribute( "value", "2" );
670                 Element uriAttribute = createElement( containerElement, "attribute" );
671                 uriAttribute.addAttribute( "name", "URI" );
672                 uriAttribute.addAttribute( "value", "/lib/" + artifact.getFile().getName() );
673                 Element urlElement = createElement( containerElement, "url" );
674                 urlElement.setText( getLibraryUrl( artifact ) );
675             }
676         }
677     }
678 
679     private void extractMacro( String path )
680     {
681         if ( macros != null )
682         {
683             Pattern p = Pattern.compile( ".*\\$([^\\$]+)\\$.*" );
684             Matcher matcher = p.matcher( path );
685             while ( matcher.find() )
686             {
687                 String macro = matcher.group( 1 );
688                 macros.add( macro );
689             }
690         }
691     }
692 
693     private Library findLibrary( Artifact a )
694     {
695         if ( libraries != null )
696         {
697             for ( int j = 0; j < libraries.length; j++ )
698             {
699                 Library library = libraries[j];
700                 if ( a.getArtifactId().equals( library.getName() ) )
701                 {
702                     return library;
703                 }
704             }
705         }
706 
707         return null;
708     }
709 
710     private List getExcludedDirectories( File target, List excludeList, List sourceFolders )
711     {
712         List foundFolders = new ArrayList();
713 
714         int totalDirs = 0, excludedDirs = 0;
715 
716         if ( target.exists() && !excludeList.contains( target.getAbsolutePath() ) )
717         {
718             File[] files = target.listFiles();
719 
720             for ( int i = 0; i < files.length; i++ )
721             {
722                 File file = files[i];
723                 if ( file.isDirectory() && !excludeList.contains( file.getAbsolutePath() ) )
724                 {
725                     totalDirs++;
726 
727                     String absolutePath = file.getAbsolutePath();
728                     String url = getModuleFileUrl( absolutePath );
729 
730                     boolean addToExclude = true;
731                     for ( Iterator sources = sourceFolders.iterator(); sources.hasNext(); )
732                     {
733                         String source = ( (Element) sources.next() ).attributeValue( "url" );
734                         if ( source.equals( url ) )
735                         {
736                             addToExclude = false;
737                             break;
738                         }
739                         else if ( source.indexOf( url ) == 0 )
740                         {
741                             foundFolders.addAll(
742                                 getExcludedDirectories( new File( absolutePath ), excludeList, sourceFolders ) );
743                             addToExclude = false;
744                             break;
745                         }
746                     }
747                     if ( addToExclude )
748                     {
749                         excludedDirs++;
750                         foundFolders.add( absolutePath );
751                     }
752                 }
753             }
754 
755             //if all directories are excluded, then just exclude the parent directory
756             if ( totalDirs > 0 && totalDirs == excludedDirs )
757             {
758                 foundFolders.clear();
759 
760                 foundFolders.add( target.getAbsolutePath() );
761             }
762         }
763         else if ( !target.exists() )
764         {
765             //might as well exclude a non-existent dir so that it won't show when it suddenly appears
766             foundFolders.add( target.getAbsolutePath() );
767         }
768 
769         return foundFolders;
770     }
771 
772     /**
773      * Adds the Web module to the (.iml) project file.
774      *
775      * @param module Xpp3Dom element
776      */
777     private void addWebModule( Element module )
778     {
779         // TODO: this is bad - reproducing war plugin defaults, etc!
780         //   --> this is where the OGNL out of a plugin would be helpful as we could run package first and
781         //       grab stuff from the mojo
782 
783         String warWebapp = executedProject.getBuild().getDirectory() + "/" + executedProject.getArtifactId();
784         String warSrc = getPluginSetting( "maven-war-plugin", "warSourceDirectory", "src/main/webapp" );
785         String webXml = warSrc + "/WEB-INF/web.xml";
786 
787         module.addAttribute( "type", "J2EE_WEB_MODULE" );
788 
789         Element component = findComponent( module, "WebModuleBuildComponent" );
790         Element setting = findSetting( component, "EXPLODED_URL" );
791         setting.addAttribute( "value", getModuleFileUrl( warWebapp ) );
792 
793         component = findComponent( module, "WebModuleProperties" );
794 
795         removeOldElements( component, "containerElement" );
796         List artifacts = executedProject.getTestArtifacts();
797         for ( Iterator i = artifacts.iterator(); i.hasNext(); )
798         {
799             Artifact artifact = (Artifact) i.next();
800 
801             Element containerElement = createElement( component, "containerElement" );
802 
803             if ( linkModules && isReactorProject( artifact.getGroupId(), artifact.getArtifactId() ) )
804             {
805                 containerElement.addAttribute( "type", "module" );
806                 containerElement.addAttribute( "name", artifact.getArtifactId() );
807                 Element methodAttribute = createElement( containerElement, "attribute" );
808                 methodAttribute.addAttribute( "name", "method" );
809                 methodAttribute.addAttribute( "value", "5" );
810                 Element uriAttribute = createElement( containerElement, "attribute" );
811                 uriAttribute.addAttribute( "name", "URI" );
812                 uriAttribute.addAttribute( "value", "/WEB-INF/lib/" + artifact.getArtifactId() + "-"
813                     + artifact.getVersion() + ".jar" );
814             }
815             else if ( artifact.getFile() != null )
816             {
817                 containerElement.addAttribute( "type", "library" );
818                 containerElement.addAttribute( "level", "module" );
819                 Element methodAttribute = createElement( containerElement, "attribute" );
820                 methodAttribute.addAttribute( "name", "method" );
821                 if ( Artifact.SCOPE_PROVIDED.equalsIgnoreCase( artifact.getScope() )
822                     || Artifact.SCOPE_SYSTEM.equalsIgnoreCase( artifact.getScope() )
823                     || Artifact.SCOPE_TEST.equalsIgnoreCase( artifact.getScope() ) )
824                 {
825                     // If scope is provided, system or test - do not package.
826                     methodAttribute.addAttribute( "value", "0" );
827                 }
828                 else
829                 {
830                     methodAttribute.addAttribute( "value", "1" ); // IntelliJ 5.0.2 is bugged and doesn't read it
831                 }
832                 Element uriAttribute = createElement( containerElement, "attribute" );
833                 uriAttribute.addAttribute( "name", "URI" );
834                 uriAttribute.addAttribute( "value", "/WEB-INF/lib/" + artifact.getFile().getName() );
835                 Element url = createElement( containerElement, "url" );
836                 url.setText( getLibraryUrl( artifact ) );
837             }
838         }
839 
840         addDeploymentDescriptor( component, "web.xml", "2.3", webXml );
841 
842         Element element = findElement( component, "webroots" );
843         removeOldElements( element, "root" );
844 
845         element = createElement( element, "root" );
846         element.addAttribute( "relative", "/" );
847         element.addAttribute( "url", getModuleFileUrl( warSrc ) );
848     }
849 
850     private void addPluginModule( Element module )
851     {
852         module.addAttribute( "type", "PLUGIN_MODULE" );
853 
854         // this is where the META-INF/plugin.xml file is located
855         Element pluginDevElement = createElement( module, "component" );
856         pluginDevElement.addAttribute( "name", "DevKit.ModuleBuildProperties" );
857         pluginDevElement.addAttribute( "url", getModuleFileUrl( "src/main/resources/META-INF/plugin.xml" ) );
858     }
859 
860     /**
861      * Translate the relative path of the file into module path
862      *
863      * @param basedir File to use as basedir
864      * @param path    Absolute path string to translate to ModuleFileUrl
865      * @return moduleFileUrl Translated Module File URL
866      */
867     private String getModuleFileUrl( File basedir, String path )
868     {
869         return "file://$MODULE_DIR$/" + toRelative( basedir.getAbsolutePath(), path );
870     }
871 
872     private String getModuleFileUrl( String file )
873     {
874         return getModuleFileUrl( executedProject.getBasedir(), file );
875     }
876 
877     /**
878      * Adds a sourceFolder element to IDEA (.iml) project file
879      *
880      * @param content   Xpp3Dom element
881      * @param directory Directory to set as url.
882      * @param isTest    True if directory isTestSource.
883      */
884     private void addSourceFolder( Element content, String directory, boolean isTest )
885     {
886         if ( !StringUtils.isEmpty( directory ) && new File( directory ).isDirectory() )
887         {
888             Element sourceFolder = createElement( content, "sourceFolder" );
889             sourceFolder.addAttribute( "url", getModuleFileUrl( directory ) );
890             sourceFolder.addAttribute( "isTestSource", Boolean.toString( isTest ) );
891         }
892     }
893 
894     private void addExcludeFolder( Element content, String directory )
895     {
896         Element excludeFolder = createElement( content, "excludeFolder" );
897         excludeFolder.addAttribute( "url", getModuleFileUrl( directory ) );
898     }
899 
900     private boolean isReactorProject( String groupId, String artifactId )
901     {
902         if ( reactorProjects != null )
903         {
904             for ( Iterator j = reactorProjects.iterator(); j.hasNext(); )
905             {
906                 MavenProject p = (MavenProject) j.next();
907                 if ( p.getGroupId().equals( groupId ) && p.getArtifactId().equals( artifactId ) )
908                 {
909                     return true;
910                 }
911             }
912         }
913         return false;
914     }
915 
916     private void resolveClassifier( Element element, Artifact a, String classifier )
917     {
918         String id = a.getId() + '-' + classifier;
919 
920         String path;
921         if ( attemptedDownloads.containsKey( id ) )
922         {
923             getLog().debug( id + " was already downloaded." );
924             path = (String) attemptedDownloads.get( id );
925         }
926         else
927         {
928             getLog().debug( id + " was not attempted to be downloaded yet: trying..." );
929             path = resolveClassifiedArtifact( a, classifier );
930             attemptedDownloads.put( id, path );
931         }
932 
933         if ( path != null )
934         {
935             String jarPath = "jar://" + path + "!/";
936             getLog().debug( "Setting " + classifier + " for " + id + " to " + jarPath );
937             removeOldElements( element, "root" );
938             createElement( element, "root" ).addAttribute( "url", jarPath );
939         }
940     }
941 
942     private String resolveClassifiedArtifact( Artifact artifact, String classifier )
943     {
944         String basePath = artifact.getFile().getAbsolutePath().replace( '\\', '/' );
945         int delIndex = basePath.indexOf( ".jar" );
946         if ( delIndex < 0 )
947         {
948             return null;
949         }
950 
951         List remoteRepos = executedProject.getRemoteArtifactRepositories();
952         try
953         {
954             Artifact classifiedArtifact = artifactFactory.createArtifactWithClassifier( artifact.getGroupId(),
955                                                                                         artifact.getArtifactId(),
956                                                                                         artifact.getVersion(),
957                                                                                         artifact.getType(),
958                                                                                         classifier );
959             String dstFilename = basePath.substring( 0, delIndex ) + '-' + classifier + ".jar";
960             File dstFile = new File( dstFilename );
961             classifiedArtifact.setFile( dstFile );
962             //this check is here because wagonManager does not seem to check if the remote file is newer
963             //    or such feature is not working
964             if ( !dstFile.exists() )
965             {
966                 wagonManager.getArtifact( classifiedArtifact, remoteRepos );
967             }
968             return dstFile.getAbsolutePath().replace( '\\', '/' );
969         }
970         catch ( TransferFailedException e )
971         {
972             getLog().debug( e );
973             return null;
974         }
975         catch ( ResourceDoesNotExistException e )
976         {
977             getLog().debug( e );
978             return null;
979         }
980     }
981 
982     /**
983      * Returns an Xpp3Dom element (setting).
984      *
985      * @param component Xpp3Dom element
986      * @param name      Setting attribute to find
987      * @return setting Xpp3Dom element
988      */
989     private Element findSetting( Element component, String name )
990     {
991         return findElement( component, "setting", name );
992     }
993 
994     private String getLibraryUrl( Artifact artifact )
995     {
996         return "jar://" + convertDriveLetter( artifact.getFile().getAbsolutePath() ).replace( '\\', '/' ) + "!/";
997     }
998 
999     private Element addDeploymentDescriptor( Element component, String name, String version, String file )
1000     {
1001         Element deploymentDescriptor = findElement( component, "deploymentDescriptor" );
1002 
1003         if ( deploymentDescriptor.attributeValue( "version" ) == null )
1004         {
1005             deploymentDescriptor.addAttribute( "version", version );
1006         }
1007 
1008         if ( deploymentDescriptor.attributeValue( "name" ) == null )
1009         {
1010             deploymentDescriptor.addAttribute( "name", name );
1011         }
1012 
1013         deploymentDescriptor.addAttribute( "optional", "false" );
1014 
1015         if ( deploymentDescriptorFile == null )
1016         {
1017             deploymentDescriptorFile = file;
1018         }
1019 
1020         deploymentDescriptor.addAttribute( "url", getModuleFileUrl( deploymentDescriptorFile ) );
1021 
1022         return deploymentDescriptor;
1023     }
1024 }