/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.maven.plugin.eclipse;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.handler.ArtifactHandler;
import org.apache.maven.artifact.manager.WagonManager;
import org.apache.maven.model.Build;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.eclipse.reader.ReadWorkspaceLocations;
import org.apache.maven.plugin.eclipse.writers.EclipseAjdtWriter;
import org.apache.maven.plugin.eclipse.writers.EclipseClasspathWriter;
import org.apache.maven.plugin.eclipse.writers.EclipseManifestWriter;
import org.apache.maven.plugin.eclipse.writers.EclipseProjectWriter;
import org.apache.maven.plugin.eclipse.writers.EclipseWriterConfig;
import org.apache.maven.plugin.eclipse.writers.workspace.EclipseSettingsWriter;
import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpApplicationXMLWriter;
import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpComponent15Writer;
import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpComponentWriter;
import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpFacetsWriter;
import org.apache.maven.plugin.eclipse.writers.wtp.EclipseWtpmodulesWriter;
import org.apache.maven.plugin.ide.AbstractIdeSupportMojo;
import org.apache.maven.plugin.ide.IdeDependency;
import org.apache.maven.plugin.ide.IdeUtils;
import org.apache.maven.plugin.ide.JeeUtils;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.MavenSettingsBuilder;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.WagonException;
import org.apache.maven.wagon.observers.Debug;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.codehaus.plexus.resource.ResourceManager;
import org.codehaus.plexus.resource.loader.FileResourceLoader;
import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;

/**
 * Generates the following eclipse configuration files:
 * <ul>
 * <li><code>.project</code> and <code>.classpath</code> files</li>
 * <li><code>.setting/org.eclipse.jdt.core.prefs</code> with project specific compiler settings</li>
 * <li>various configuration files for WTP (Web Tools Project), if the parameter <code>wtpversion</code> is set to a
 * valid version (WTP configuration is not generated by default)</li>
 * </ul>
 * If this goal is run on a multiproject root, dependencies between modules will be configured as direct project
 * dependencies in Eclipse (unless <code>useProjectReferences</code> is set to <code>false</code>).
 *
 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
 * @author <a href="mailto:fgiust@apache.org">Fabrizio Giustina</a>
 * @version $Id: EclipsePlugin.java 1681500 2015-05-24 17:25:06Z agudian $
 */
@Mojo( name = "eclipse" )
@Execute( phase = LifecyclePhase.GENERATE_RESOURCES )
public class EclipsePlugin
    extends AbstractIdeSupportMojo
{
    private static final String WEAVE_DEPENDENCY = "weaveDependency";

    private static final String WEAVE_DEPENDENCIES = "weaveDependencies";

    private static final String ASPECT_LIBRARY = "aspectLibrary";

    private static final String ASPECT_LIBRARIES = "aspectLibraries";

    private static final String ASPECT_DIRECTORY = "aspectDirectory";

    private static final String TEST_ASPECT_DIRECTORY = "testAspectDirectory";

    private static final String ASPECTJ_MAVEN_PLUGIN = "aspectj-maven-plugin";

    private static final String ORG_CODEHAUS_MOJO = "org.codehaus.mojo";

    private static final String DEFAULT_TEST_ASPECT_DIRECTORY = "src/test/aspect";

    private static final String DEFAULT_ASPECT_DIRECTORY = "src/main/aspect";

    private static final String NATURE_WST_FACET_CORE_NATURE = "org.eclipse.wst.common.project.facet.core.nature"; //$NON-NLS-1$

    private static final String BUILDER_WST_COMPONENT_STRUCTURAL_DEPENDENCY_RESOLVER =
        "org.eclipse.wst.common.modulecore.ComponentStructuralBuilderDependencyResolver"; //$NON-NLS-1$

    protected static final String BUILDER_WST_VALIDATION = "org.eclipse.wst.validation.validationbuilder"; //$NON-NLS-1$

    private static final String BUILDER_JDT_CORE_JAVA = "org.eclipse.jdt.core.javabuilder"; //$NON-NLS-1$

    private static final String BUILDER_WST_COMPONENT_STRUCTURAL =
        "org.eclipse.wst.common.modulecore.ComponentStructuralBuilder"; //$NON-NLS-1$

    private static final String BUILDER_WST_FACET = "org.eclipse.wst.common.project.facet.core.builder"; //$NON-NLS-1$

    private static final String BUILDER_AJDT_CORE_JAVA = "org.eclipse.ajdt.core.ajbuilder"; //$NON-NLS-1$

    private static final String NATURE_WST_MODULE_CORE_NATURE = "org.eclipse.wst.common.modulecore.ModuleCoreNature"; //$NON-NLS-1$

    private static final String NATURE_JDT_CORE_JAVA = "org.eclipse.jdt.core.javanature"; //$NON-NLS-1$

    private static final String NATURE_JEM_WORKBENCH_JAVA_EMF = "org.eclipse.jem.workbench.JavaEMFNature"; //$NON-NLS-1$

    private static final String NATURE_AJDT_CORE_JAVA = "org.eclipse.ajdt.ui.ajnature"; //$NON-NLS-1$

    protected static final String COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER = "org.eclipse.jdt.launching.JRE_CONTAINER"; //$NON-NLS-1$

    protected static final String ASPECTJ_RT_CONTAINER = "org.eclipse.ajdt.core.ASPECTJRT_CONTAINER"; //$NON-NLS-1$

    // warning, order is important for binary search
    public static final String[] WTP_SUPPORTED_VERSIONS = new String[] { "1.0", "1.5", "2.0", "R7", "none" }; //$NON-NLS-1$ //$NON-NLS-2$  //$NON-NLS-3$

    public static final String ASPECTJ_FILE_PATTERN = "**/*.aj";

    public static final String JAVA_FILE_PATTERN = "**/*.java";

    /**
     * Constant for 'artifactId' element in POM.xml.
     */
    private static final String POM_ELT_ARTIFACT_ID = "artifactId"; //$NON-NLS-1$

    /**
     * Constant for 'groupId' element in POM.xml.
     */
    private static final String POM_ELT_GROUP_ID = "groupId"; //$NON-NLS-1$

    /**
     * List of eclipse project natures. By default the <code>org.eclipse.jdt.core.javanature</code> nature plus the
     * needed WTP natures are added. Natures added using this property <strong>replace</strong> the default list.
     *
     * <pre>
     * &lt;projectnatures&gt;
     *    &lt;projectnature&gt;org.eclipse.jdt.core.javanature&lt;/projectnature&gt;
     *    &lt;projectnature&gt;org.eclipse.wst.common.modulecore.ModuleCoreNature&lt;/projectnature&gt;
     * &lt;/projectnatures&gt;
     * </pre>
     */
    @Parameter
    private List projectnatures;

    /**
     * List of artifacts, represented as <code>groupId:artifactId</code>, to exclude from the eclipse classpath, being
     * provided by some eclipse classPathContainer.
     *
     * @see http://jira.codehaus.org/browse/MECLIPSE-79
     * @since 2.5
     */
    @Parameter
    private List excludes;

    /**
     * List of eclipse project natures to be added to the default ones.
     *
     * <pre>
     * &lt;additionalProjectnatures&gt;
     *    &lt;projectnature&gt;org.springframework.ide.eclipse.core.springnature&lt;/projectnature&gt;
     * &lt;/additionalProjectnatures&gt;
     * </pre>
     */
    @Parameter
    private List additionalProjectnatures;

    /**
     * List of eclipse project facets to be added to the default ones.
     *
     * <pre>
     * &lt;additionalProjectFacets&gt;
     *    &lt;jst.jsf&gt;1.1&lt;jst.jsf/&gt;
     * &lt;/additionalProjectFacets&gt;
     * </pre>
     */
    @Parameter
    private Map additionalProjectFacets;

    /**
     * List of eclipse build commands. By default the <code>org.eclipse.jdt.core.javabuilder</code> builder plus the
     * needed WTP builders are added. If you specify any configuration for this parameter, only those buildcommands
     * specified will be used; the defaults won't be added. Use the <code>additionalBuildCommands</code> parameter for
     * that. Configuration example: Old style:
     * 
     * <pre>
     * &lt;buildcommands&gt;
     *    &lt;buildcommand&gt;org.eclipse.wst.common.modulecore.ComponentStructuralBuilder&lt;/buildcommand&gt;
     *    &lt;buildcommand&gt;org.eclipse.jdt.core.javabuilder&lt;/buildcommand&gt;
     *    &lt;buildcommand&gt;org.eclipse.wst.common.modulecore.ComponentStructuralBuilderDependencyResolver&lt;/buildcommand&gt;
     * &lt;/buildcommands&gt;
     * </pre>
     * 
     * For new style, see <code>additionalBuildCommands</code>.
     */
    @Parameter
    private List buildcommands;

    /**
     * List of eclipse build commands to be added to the default ones. Old style:
     * 
     * <pre>
     * &lt;additionalBuildcommands&gt;
     *    &lt;buildcommand&gt;org.springframework.ide.eclipse.core.springbuilder&lt;/buildcommand&gt;
     * &lt;/additionalBuildcommands&gt;
     * </pre>
     * 
     * New style:
     * 
     * <pre>
     * &lt;additionalBuildcommands&gt;
     *    &lt;buildCommand&gt;
     *      &lt;name&gt;org.eclipse.ui.externaltools.ExternalToolBuilder&lt;/name&gt;
     *      &lt;triggers&gt;auto,full,incremental,&lt;/triggers&gt;
     *      &lt;arguments&gt;
     *        &lt;LaunchConfigHandle&gt;&amp;lt;project&amp;gt;./externalToolBuilders/MavenBuilder.launch&lt;/LaunchConfighandle&gt;
     *      &lt;/arguments&gt;
     *    &lt;/buildCommand&gt;
     * &lt;/additionalBuildcommands&gt;
     * </pre>
     * 
     * Note the difference between <code>build<strong>c</strong>ommand</code> and
     * <code>build<strong>C</strong>ommand</code>. You can mix and match old and new-style configuration entries.
     */
    @Parameter
    private List additionalBuildcommands;

    /**
     * List of container classpath entries. By default the <code>org.eclipse.jdt.launching.JRE_CONTAINER</code>
     * classpath container is added. Configuration example:
     * 
     * <pre>
     * &lt;classpathContainers&gt;
     *    &lt;classpathContainer&gt;org.eclipse.jdt.launching.JRE_CONTAINER&lt;/classpathContainer&gt;
     *    &lt;classpathContainer&gt;org.eclipse.jst.server.core.container/org.eclipse.jst.server.tomcat.runtimeTarget/Apache Tomcat v5.5&lt;/classpathContainer&gt;
     *    &lt;classpathContainer&gt;org.eclipse.jst.j2ee.internal.web.container/artifact&lt;/classpathContainer&gt;
     * &lt;/classpathContainers&gt;
     * </pre>
     */
    @Parameter
    private List classpathContainers;

    /**
     * Enables/disables the downloading of source attachments. Defaults to false. DEPRECATED - use downloadSources
     *
     * @deprecated use downloadSources
     */
    @Parameter( property = "eclipse.downloadSources" )
    private boolean eclipseDownloadSources;

    /**
     * Eclipse workspace directory.
     */
    @Parameter( property = "eclipse.projectDir", alias = "outputDir" )
    private File eclipseProjectDir;

    /**
     * When set to false, the plugin will not create sub-projects and instead reference those sub-projects using the
     * installed package in the local repository
     */
    @Parameter( property = "eclipse.useProjectReferences", defaultValue = "true", required = true )
    private boolean useProjectReferences;

    /**
     * The default output directory
     */
    @Parameter( property = "outputDirectory", alias = "outputDirectory", defaultValue = "${project.build.outputDirectory}", required = true )
    private File buildOutputDirectory;

    /**
     * The version of WTP for which configuration files will be generated. The default value is "none" (don't generate
     * WTP configuration), supported versions are "R7", "1.0", "1.5" and "2.0"
     */
    @Parameter( property = "wtpversion", defaultValue = "none" )
    private String wtpversion;

    /**
     * JEE context name of the WTP module. ( ex. WEB context name ). You can use "ROOT" if you want to map the webapp to
     * the root context.
     */
    @Parameter( property = "wtpContextName" )
    private String wtpContextName;

    /**
     * Is it an AJDT project? If yes, the plugin adds the necessary natures and build commands to the .project file.
     */
    private boolean ajdt;

    /**
     * The relative path of the manifest file
     */
    @Parameter( property = "eclipse.manifest", defaultValue = "${basedir}/META-INF/MANIFEST.MF" )
    private File manifest;

    /**
     * Allow to configure additional generic configuration files for eclipse that will be written out to disk when
     * running eclipse:eclipse. FOr each file you can specify the name and the text content.
     * 
     * <pre>
     * &lt;plugin&gt;
     *  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
     *  &lt;artifactId&gt;maven-eclipse-plugin&lt;/artifactId&gt;
     *  &lt;configuration&gt;
     *   &lt;additionalConfig&gt;
     *    &lt;file&gt;
     *      &lt;name&gt;.checkstyle&lt;/name&gt;
     *      &lt;content&gt;
     *        &lt;![CDATA[&lt;fileset-config file-format-version=&quot;1.2.0&quot; simple-config=&quot;true&quot;&gt;
     *          &lt;fileset name=&quot;all&quot; enabled=&quot;true&quot; check-config-name=&quot;acme corporate style&quot; local=&quot;false&quot;&gt;
     *              &lt;file-match-pattern match-pattern=&quot;.&quot; include-pattern=&quot;true&quot;/&gt;
     *          &lt;/fileset&gt;
     *          &lt;filter name=&quot;NonSrcDirs&quot; enabled=&quot;true&quot;/&gt;
     *        &lt;/fileset-config&gt;]]&gt;
     *      &lt;/content&gt;
     *    &lt;/file&gt;
     *   &lt;/additionalConfig&gt;
     *  &lt;/configuration&gt;
     * &lt;/plugin&gt;
     * </pre>
     * 
     * Instead of the content you can also define (from version 2.5) an url to download the file :
     * 
     * <pre>
     * &lt;plugin&gt;
     *  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
     *  &lt;artifactId&gt;maven-eclipse-plugin&lt;/artifactId&gt;
     *  &lt;configuration&gt;
     *   &lt;additionalConfig&gt;
     *    &lt;file&gt;
     *      &lt;name&gt;.checkstyle&lt;/name&gt;
     *      &lt;url&gt;http://some.place.org/path/to/file&lt;/url&gt;
     *    &lt;/file&gt;
     *   &lt;/additionalConfig&gt;
     *  &lt;/configuration&gt;
     * </pre>
     * 
     * or a location :
     * 
     * <pre>
     * &lt;plugin&gt;
     *  &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
     *  &lt;artifactId&gt;maven-eclipse-plugin&lt;/artifactId&gt;
     *  &lt;configuration&gt;
     *   &lt;additionalConfig&gt;
     *    &lt;file&gt;
     *     &lt;name&gt;.checkstyle&lt;/name&gt;
     *     &lt;location&gt;/checkstyle-config.xml&lt;/location&gt;
     *    &lt;/file&gt;
     *   &lt;/additionalConfig&gt;
     *  &lt;/configuration&gt;
     *  &lt;dependencies&gt;
     *   &lt;!-- The file defined in the location is stored in this dependency --&gt;
     *   &lt;dependency&gt;
     *    &lt;groupId&gt;eclipsetest&lt;/groupId&gt;
     *    &lt;artifactId&gt;checkstyle-config&lt;/artifactId&gt;
     *    &lt;version&gt;1.0&lt;/version&gt;
     *   &lt;/dependency&gt;
     *  &lt;/dependencies&gt;
     * &lt;/plugin&gt;
     * </pre>
     */
    @Parameter
    private EclipseConfigFile[] additionalConfig;

    /**
     * If set to <code>true</code>, the version number of the artifact is appended to the name of the generated Eclipse
     * project. See projectNameTemplate for other options.
     */
    @Parameter( property = "eclipse.addVersionToProjectName", defaultValue = "false" )
    private boolean addVersionToProjectName;

    /**
     * If set to <code>true</code>, the groupId of the artifact is appended to the name of the generated Eclipse
     * project. See projectNameTemplate for other options.
     */
    @Parameter( property = "eclipse.addGroupIdToProjectName", defaultValue = "false" )
    private boolean addGroupIdToProjectName;

    /**
     * Allows configuring the name of the eclipse projects. This property if set wins over addVersionToProjectName and
     * addGroupIdToProjectName You can use <code>[groupId]</code>, <code>[artifactId]</code> and <code>[version]</code>
     * variables. eg. <code>[groupId].[artifactId]-[version]</code>
     */
    @Parameter( property = "eclipse.projectNameTemplate" )
    private String projectNameTemplate;

    /**
     * Parsed wtp version.
     */
    private float wtpVersionFloat;

    /**
     * Not a plugin parameter. Is this a java project?
     */
    private boolean isJavaProject;

    /**
     * Must the manifest files be written for java projects so that that the jee classpath for wtp is correct.
     */
    @Parameter( property = "eclipse.wtpmanifest", defaultValue = "false" )
    private boolean wtpmanifest;

    /**
     * Must the application files be written for ear projects in a separate directory.
     */
    @Parameter( property = "eclipse.wtpapplicationxml", defaultValue = "false" )
    private boolean wtpapplicationxml;

    /**
     * What WTP defined server to use for deployment informations.
     */
    @Parameter( property = "eclipse.wtpdefaultserver" )
    private String wtpdefaultserver;

    /**
     * If set to <code>true</code>, the standard execution environment matching the compiler settings is set as JRE. If
     * set to <code>false</code>, the JRE matching the configured compiler-plugin executable or JAVA_HOME is selected by
     * name, if it is configured in the workspace.
     * 
     * @since 2.10
     */
    @Parameter( property = "eclipse.preferStandardClasspathContainer", defaultValue = "false" )
    private boolean preferStandardClasspathContainer;

    private WorkspaceConfiguration workspaceConfiguration;

    /**
     * ResourceManager for getting additonalConfig files from resources
     */
    @Component
    private ResourceManager locator;

    /**
     * WagonManager for accessing internet resources.
     */
    @Component
    private WagonManager wagonManager;

    /**
     * MavenSettingsBuilder for accessing settings.xml.
     */
    @Component
    private MavenSettingsBuilder mavenSettingsBuilder;

    /**
     * This eclipse workspace is read and all artifacts detected there will be connected as eclipse projects and will
     * not be linked to the jars in the local repository. Requirement is that it was created with the similar wtp
     * settings as the reactor projects, but the project name template my differ. The pom's in the workspace projects
     * may not contain variables in the artefactId, groupId and version tags. If workspace is not defined, then an
     * attempt to locate it by checking up the directory hierarchy will be made.
     *
     * @since 2.5
     */
    @Parameter( property = "eclipse.workspace" )
    protected File workspace;

    /**
     * Limit the use of project references to the current workspace. No project references will be created to projects
     * in the reactor when they are not available in the workspace.
     */
    @Parameter( property = "eclipse.limitProjectReferencesToWorkspace", defaultValue = "false" )
    protected boolean limitProjectReferencesToWorkspace;

    /**
     * The version of AJDT for which configuration files will be generated. The default value is "1.5", supported
     * versions are "none" (AJDT support disabled), "1.4", and "1.5".
     */
    @Parameter( property = "eclipse.ajdtVersion", defaultValue = "none" )
    private String ajdtVersion;

    /**
     * List of exclusions to add to the source directories on the classpath. Adds excluding="" to the classpathentry of
     * the eclipse .classpath file. [MECLIPSE-104]
     *
     * @since 2.6.1
     */
    @Parameter
    private List sourceExcludes;

    /**
     * List of inclusions to add to the source directories on the classpath. Adds including="" to the classpathentry of
     * the eclipse .classpath file.
     * <p>
     * Java projects will always include "**&#47;*.java"
     * <p>
     * Ajdt projects will always include "**&#47;*.aj"
     * <p>
     * [MECLIPSE-104]
     * 
     * @since 2.6.1
     */
    @Parameter
    private List sourceIncludes;

    /**
     * A list of links to local files in the system. A configuration like this one in the pom :
     * 
     * <pre>
     * &lt;plugin&gt;
     *   &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
     *   &lt;artifactId&gt;maven-eclipse-plugin&lt;/artifactId&gt;
     *   &lt;configuration&gt; 
     *     &lt;linkedResources&gt; 
     *       &lt;linkedResource&gt;
     *         &lt;name&gt;src/test/resources/oracle-ds.xml&lt;/name&gt;
     *         &lt;type&gt;1&lt;/type&gt;
     *         &lt;location&gt;C://jboss/server/default/deploy/oracle-ds.xml&lt;/location&gt; 
     *       &lt;/linkedResource&gt;
     *     &lt;/linkedResources&gt; 
     *   &lt;/configuration&gt; 
     * &lt;/plugin&gt;
     * </pre>
     * 
     * will produce in the .project :
     * 
     * <pre>
     * &lt;linkedResources&gt;
     *   &lt;link&gt;
     *     &lt;name&gt;src/test/resources/oracle-ds.xml&lt;/name&gt;
     *     &lt;type&gt;1&lt;/type&gt; 
     *     &lt;location&gt;C://jboss/server/default/deploy/oracle-ds.xml&lt;/location&gt;
     *   &lt;/link&gt;
     * &lt;/linkedResources&gt;
     * </pre>
     * 
     * @since 2.8
     */
    @Parameter
    private List linkedResources;

    /**
     * Put classpath container entries last in eclipse classpath configuration. Note that this behaviour, although
     * useful in situations were you want to override resources found in classpath containers, will made JRE classes
     * loaded after 3rd party jars, so enabling it is not suggested.
     * 
     * @since 2.9
     */
    @Parameter( property = "eclipse.classpathContainersLast", defaultValue = "false" )
    protected boolean classpathContainersLast;

    /**
     * Whether to place test resources after main resources. Note that the default behavior of Maven version 2.0.8 or
     * later is to have test dirs before main dirs in classpath so this is discouraged if you need to reproduce the
     * maven behavior during tests. The default behavior is also changed in eclipse plugin version 2.6 in order to
     * better match the maven one. Switching to "test source last" can anyway be useful if you need to run your
     * application in eclipse, since there is no concept in eclipse of "phases" with different set of source dirs and
     * dependencies like we have in maven.
     * 
     * @since 2.9
     */
    @Parameter( property = "eclipse.testSourcesLast", defaultValue = "false" )
    protected boolean testSourcesLast;

    /**
     * The plugin is often capable in predicting the required jee version based on the dependencies of the project. By
     * setting this parameter to one of the {@code jeeversion} options the version will be locked.
     * <table>
     * <thead>
     * <tr>
     * <th>jeeversion</th>
     * <th>EJB version</th>
     * <th>Servlet version</th>
     * <th>JSP version</th>
     * </tr>
     * </thead> <tbody>
     * <tr>
     * <td>6.0</td>
     * <td>3.1</td>
     * <td>3.0</td>
     * <td>2.2</td>
     * </tr>
     * <tr>
     * <td>5.0</td>
     * <td>3.0</td>
     * <td>2.5</td>
     * <td>2.1</td>
     * </tr>
     * <tr>
     * <td>1.4</td>
     * <td>2.1</td>
     * <td>2.4</td>
     * <td>2.0</td>
     * </tr>
     * <tr>
     * <td>1.3</td>
     * <td>2.0</td>
     * <td>2.3</td>
     * <td>1.2</td>
     * </tr>
     * <tr>
     * <td>1.2</td>
     * <td>1.1</td>
     * <td>2.2</td>
     * <td>1.1</td>
     * </tr>
     * </tbody>
     * </table>
     * 
     * @since 2.9
     */
    @Parameter( property = "eclipse.jeeversion" )
    protected String jeeversion;

    protected final boolean isJavaProject()
    {
        return isJavaProject;
    }

    /**
     * Getter for <code>buildcommands</code>.
     * 
     * @return Returns the buildcommands.
     */
    public final List getBuildcommands()
    {
        return buildcommands;
    }

    /**
     * Setter for <code>buildcommands</code>.
     * 
     * @param buildcommands The buildcommands to set.
     */
    public final void setBuildcommands( List buildcommands )
    {
        this.buildcommands = buildcommands;
    }

    /**
     * Getter for <code>buildOutputDirectory</code>.
     * 
     * @return Returns the buildOutputDirectory.
     */
    public final File getBuildOutputDirectory()
    {
        return buildOutputDirectory;
    }

    /**
     * Setter for <code>buildOutputDirectory</code>.
     * 
     * @param buildOutputDirectory The buildOutputDirectory to set.
     */
    public final void setBuildOutputDirectory( File buildOutputDirectory )
    {
        this.buildOutputDirectory = buildOutputDirectory;
    }

    /**
     * Getter for <code>classpathContainers</code>.
     * 
     * @return Returns the classpathContainers.
     */
    public final List getClasspathContainers()
    {
        return classpathContainers;
    }

    /**
     * Setter for <code>classpathContainers</code>.
     * 
     * @param classpathContainers The classpathContainers to set.
     */
    public final void setClasspathContainers( List classpathContainers )
    {
        this.classpathContainers = classpathContainers;
    }

    /**
     * Getter for <code>eclipseProjectDir</code>.
     * 
     * @return Returns the eclipseProjectDir.
     */
    public final File getEclipseProjectDir()
    {
        return eclipseProjectDir;
    }

    /**
     * Setter for <code>eclipseProjectDir</code>.
     * 
     * @param eclipseProjectDir The eclipseProjectDir to set.
     */
    public final void setEclipseProjectDir( File eclipseProjectDir )
    {
        this.eclipseProjectDir = eclipseProjectDir;
    }

    /**
     * Getter for <code>projectnatures</code>.
     * 
     * @return Returns the projectnatures.
     */
    public final List getProjectnatures()
    {
        return projectnatures;
    }

    /**
     * Setter for <code>projectnatures</code>.
     * 
     * @param projectnatures The projectnatures to set.
     */
    public final void setProjectnatures( List projectnatures )
    {
        this.projectnatures = projectnatures;
    }

    /**
     * Getter for <code>useProjectReferences</code>.
     * 
     * @return Returns the useProjectReferences.
     */
    public final boolean getUseProjectReferences()
    {
        return useProjectReferences;
    }

    /**
     * Setter for <code>useProjectReferences</code>.
     * 
     * @param useProjectReferences The useProjectReferences to set.
     */
    public final void setUseProjectReferences( boolean useProjectReferences )
    {
        this.useProjectReferences = useProjectReferences;
    }

    /**
     * Getter for <code>wtpversion</code>.
     * 
     * @return Returns the wtpversion.
     */
    public final String getWtpversion()
    {
        return wtpversion;
    }

    /**
     * Setter for <code>wtpversion</code>.
     * 
     * @param wtpversion The wtpversion to set.
     */
    public final void setWtpversion( String wtpversion )
    {
        this.wtpversion = wtpversion;
    }

    /**
     * Getter for <code>additionalBuildcommands</code>.
     * 
     * @return Returns the additionalBuildcommands.
     */
    public final List getAdditionalBuildcommands()
    {
        return additionalBuildcommands;
    }

    /**
     * Setter for <code>additionalBuildcommands</code>.
     * 
     * @param additionalBuildcommands The additionalBuildcommands to set.
     */
    public final void setAdditionalBuildcommands( List additionalBuildcommands )
    {
        this.additionalBuildcommands = additionalBuildcommands;
    }

    /**
     * Getter for <code>additionalProjectnatures</code>.
     * 
     * @return Returns the additionalProjectnatures.
     */
    public final List getAdditionalProjectnatures()
    {
        return additionalProjectnatures;
    }

    /**
     * Setter for <code>additionalProjectnatures</code>.
     * 
     * @param additionalProjectnatures The additionalProjectnatures to set.
     */
    public final void setAdditionalProjectnatures( List additionalProjectnatures )
    {
        this.additionalProjectnatures = additionalProjectnatures;
    }

    /**
     * Getter for <code>addVersionToProjectName</code>.
     */
    public final boolean isAddVersionToProjectName()
    {
        return addVersionToProjectName;
    }

    /**
     * Setter for <code>addVersionToProjectName</code>.
     */
    public final void setAddVersionToProjectName( boolean addVersionToProjectName )
    {
        this.addVersionToProjectName = addVersionToProjectName;
    }

    /**
     * Getter for <code>addGroupIdToProjectName</code>.
     */
    public final boolean isAddGroupIdToProjectName()
    {
        return addGroupIdToProjectName;
    }

    /**
     * Setter for <code>addGroupIdToProjectName</code>.
     */
    public final void setAddGroupIdToProjectName( boolean addGroupIdToProjectName )
    {
        this.addGroupIdToProjectName = addGroupIdToProjectName;
    }

    /**
     * Getter for <code>projectNameTemplate</code>
     * 
     * @return projectNameTemplate
     */
    public final String getProjectNameTemplate()
    {
        return projectNameTemplate;
    }

    /**
     * Setter for <code>projectNameTemplate</code>.
     * 
     * @param projectNameTemplate projectNameTemplate
     */
    public final void setProjectNameTemplate( String projectNameTemplate )
    {
        this.projectNameTemplate = projectNameTemplate;
    }

    /**
     * @return the linkedResources
     */
    public List getLinkedResources()
    {
        return linkedResources;
    }

    /**
     * @param linkedResources the linkedResources to set
     */
    public void setLinkedResources( List linkedResources )
    {
        this.linkedResources = linkedResources;
    }

    /**
     * @see org.apache.maven.plugin.Mojo#execute()
     */
    public final boolean setup()
        throws MojoExecutionException
    {
        boolean ready;

        checkDeprecations();
        setProjectNameTemplate( IdeUtils.calculateProjectNameTemplate( getProjectNameTemplate(),
                                                                       isAddVersionToProjectName(),
                                                                       isAddGroupIdToProjectName(), getLog() ) );
        ajdt = enableAjdt( executedProject ) && !ajdtVersion.equals( "none" );
        ready = validate();

        // TODO: Why are we using project in some places, and executedProject in others??
        ArtifactHandler artifactHandler = project.getArtifact().getArtifactHandler();

        // ear projects don't contain java sources
        // pde projects are always java projects
        isJavaProject =
            ( Constants.LANGUAGE_JAVA.equals( artifactHandler.getLanguage() ) && !Constants.PROJECT_PACKAGING_EAR.equals( packaging ) );

        if ( sourceIncludes == null )
        {
            sourceIncludes = new ArrayList();
        }
        if ( isJavaProject )
        {
            sourceIncludes.add( JAVA_FILE_PATTERN );
        }
        if ( ajdt )
        {
            sourceIncludes.add( ASPECTJ_FILE_PATTERN );
        }

        if ( sourceExcludes == null )
        {
            sourceExcludes = new ArrayList();
        }

        setupExtras();

        parseConfigurationOptions();

        // defaults
        if ( projectnatures == null )
        {
            fillDefaultNatures( packaging );
        }

        if ( additionalProjectnatures != null )
        {
            projectnatures.addAll( additionalProjectnatures );
        }

        if ( buildcommands == null )
        {
            fillDefaultBuilders( packaging );
        }
        else
        {
            convertBuildCommandList( buildcommands );
        }

        if ( additionalBuildcommands != null )
        {
            convertBuildCommandList( additionalBuildcommands );
            buildcommands.addAll( additionalBuildcommands );
        }

        if ( classpathContainers == null )
        {
            fillDefaultClasspathContainers( packaging );
        }
        else
        {
            verifyClasspathContainerListIsComplete();
        }

        if ( linkedResources == null )
        {
            linkedResources = new ArrayList();
        }

        locator.addSearchPath( FileResourceLoader.ID, project.getFile().getParentFile().getAbsolutePath() );
        locator.setOutputDirectory( new File( project.getBuild().getDirectory() ) );

        // ready to start
        return ready;
    }

    /**
     * Convert any Strings in the <code>commands</code> List to <code>BuildCommand</code>s. The conversion happens in
     * situ.
     * 
     * @param commands a list of commands to convert into <code>BuildCommand</code>
     */
    protected final void convertBuildCommandList( List commands )
    {
        if ( commands != null )
        {
            for ( ListIterator i = commands.listIterator(); i.hasNext(); )
            {
                Object command = i.next();

                if ( command instanceof String )
                {
                    command = new BuildCommand( (String) command );
                    i.set( command );
                }
            }
        }
    }

    private void parseConfigurationOptions()
    {
        if ( "R7".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$
        {
            wtpVersionFloat = 0.7f;
        }
        else if ( "1.0".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$
        {
            wtpVersionFloat = 1.0f;
        }
        else if ( "1.5".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$
        {
            wtpVersionFloat = 1.5f;
        }
        else if ( "2.0".equalsIgnoreCase( wtpversion ) ) //$NON-NLS-1$
        {
            wtpVersionFloat = 2.0f;
        }
        if ( !"none".equalsIgnoreCase( wtpversion ) )
        {
            getLog().info( Messages.getString( "EclipsePlugin.wtpversion", wtpversion ) );
        }
    }

    /**
     * Extension point for subclasses.
     * <p>
     * Called during <code>setup</code>.
     * 
     * @throws MojoExecutionException mojo failures.
     */
    protected void setupExtras()
        throws MojoExecutionException
    {
        // extension point.
    }

    private void verifyClasspathContainerListIsComplete()
    {
        boolean containsJREContainer = false;
        // Check if classpathContainer contains a JRE (default, alternate or
        // Execution Environment)
        for ( Object classPathContainer : classpathContainers )
        {
            if ( classPathContainer != null
                && classPathContainer.toString().startsWith( COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER ) )
            {
                containsJREContainer = true;
                break;
            }
        }
        if ( !containsJREContainer )
        {
            getLog().warn( Messages.getString( "EclipsePlugin.missingjrecontainer" ) ); //$NON-NLS-1$
            classpathContainers.add( 0, COMMON_PATH_JDT_LAUNCHING_JRE_CONTAINER );
        }
    }

    private boolean validate()
        throws MojoExecutionException
    {
        // validate sanity of the current m2 project
        if ( Arrays.binarySearch( WTP_SUPPORTED_VERSIONS, wtpversion ) < 0 )
        {
            throw new MojoExecutionException(
                                              Messages.getString( "EclipsePlugin.unsupportedwtp", new Object[] { //$NON-NLS-1$
                                                                  wtpversion,
                                                                      StringUtils.join( WTP_SUPPORTED_VERSIONS, " " ) } ) ); //$NON-NLS-1$
        }

        assertNotEmpty( executedProject.getGroupId(), POM_ELT_GROUP_ID );
        assertNotEmpty( executedProject.getArtifactId(), POM_ELT_ARTIFACT_ID );

        if ( executedProject.getFile() == null || !executedProject.getFile().exists() )
        {
            throw new MojoExecutionException( Messages.getString( "EclipsePlugin.missingpom" ) ); //$NON-NLS-1$
        }

        if ( "pom".equals( packaging ) && eclipseProjectDir == null ) //$NON-NLS-1$
        {
            getLog().info( Messages.getString( "EclipsePlugin.pompackaging" ) ); //$NON-NLS-1$
            return false;
        }

        if ( "eclipse-plugin".equals( packaging ) )
        {
            getLog().info( Messages.getString( "EclipsePlugin.pdepackaging" ) ); //$NON-NLS-1$
            return false;
        }

        if ( eclipseProjectDir == null )
        {
            eclipseProjectDir = executedProject.getFile().getParentFile();
        }

        if ( !eclipseProjectDir.exists() && !eclipseProjectDir.mkdirs() )
        {
            throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantcreatedir", eclipseProjectDir ) ); //$NON-NLS-1$
        }

        if ( !eclipseProjectDir.equals( executedProject.getFile().getParentFile() ) )
        {
            if ( !eclipseProjectDir.isDirectory() )
            {
                throw new MojoExecutionException( Messages.getString( "EclipsePlugin.notadir", eclipseProjectDir ) ); //$NON-NLS-1$
            }
            eclipseProjectDir = new File( eclipseProjectDir, executedProject.getArtifactId() );
            if ( !eclipseProjectDir.isDirectory() && !eclipseProjectDir.mkdirs() )
            {
                throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantcreatedir", eclipseProjectDir ) ); //$NON-NLS-1$
            }
        }

        validateExtras();

        return true;
    }

    /**
     * Extension point for subclasses.
     * <p>
     * Called during <code>setup</code> and used to validate that the configuration is sane.
     * 
     * @throws MojoExecutionException mojo failures.
     */
    protected void validateExtras()
        throws MojoExecutionException
    {
        // provided for extension.
    }

    private void checkDeprecations()
    {
        if ( eclipseDownloadSources )
        {
            // deprecated warning
            getLog().warn( Messages.getString( "EclipsePlugin.deprecatedpar", new Object[] { //$NON-NLS-1$
                                               "eclipse.downloadSources", //$NON-NLS-1$
                                                   "downloadSources" } ) ); //$NON-NLS-1$
            downloadSources = true;
        }

        checkDeprecationsExtras();
    }

    /**
     * Extension point for subclasses.
     * <p>
     * Check for any extra deprecations and log warnings. Called during <code>setup</code>
     */
    protected void checkDeprecationsExtras()
    {
        // provided for extension.
    }

    public final void writeConfiguration( IdeDependency[] deps )
        throws MojoExecutionException
    {
        EclipseWriterConfig config = createEclipseWriterConfig( deps );

        if ( wtpmanifest && isJavaProject() )
        {
            // NOTE: This could change the config!
            EclipseManifestWriter.addManifestResource( getLog(), config );
        }
        // NOTE: This could change the config!
        writeConfigurationExtras( config );

        if ( wtpVersionFloat == 0.7f )
        {
            new EclipseWtpmodulesWriter().init( getLog(), config ).write();
        }

        if ( wtpVersionFloat >= 1.0f )
        {
            new EclipseWtpFacetsWriter().init( getLog(), config ).write();
        }
        if ( wtpVersionFloat == 1.0f )
        {
            new EclipseWtpComponentWriter().init( getLog(), config ).write();
        }
        if ( wtpVersionFloat >= 1.5 )
        {
            new EclipseWtpComponent15Writer().init( getLog(), config ).write();
        }

        new EclipseSettingsWriter().init( getLog(), config ).write();

        if ( isJavaProject )
        {
            new EclipseClasspathWriter().init( getLog(), config ).write();
            if ( ajdt && ajdtVersion.equals( "1.4" ) )
            {
                new EclipseAjdtWriter().init( getLog(), config ).write();
            }
        }

        if ( wtpapplicationxml )
        {
            new EclipseWtpApplicationXMLWriter().init( getLog(), config ).write();
        }

        // NOTE: This one MUST be after EclipseClasspathwriter, and possibly others,
        // since currently EclipseClasspathWriter does some magic to detect nested
        // output folders and modifies the configuration by adding new (Ant) builders.
        // So the .project file must be written AFTER those have run!
        new EclipseProjectWriter().init( getLog(), config ).write();

        writeAdditionalConfig();

        getLog().info( Messages.getString( "EclipsePlugin.wrote", new Object[] { //$NON-NLS-1$
                                           config.getEclipseProjectName(), eclipseProjectDir.getAbsolutePath() } ) );
    }

    private void writeAdditionalConfig()
        throws MojoExecutionException
    {
        if ( additionalConfig != null )
        {
            for ( EclipseConfigFile file : additionalConfig )
            {
                File projectRelativeFile = new File( eclipseProjectDir, file.getName() );
                if ( projectRelativeFile.isDirectory() )
                {
                    // just ignore?
                    getLog().warn( Messages.getString( "EclipsePlugin.foundadir", //$NON-NLS-1$
                                                       projectRelativeFile.getAbsolutePath() ) );
                }

                try
                {
                    projectRelativeFile.getParentFile().mkdirs();
                    if ( file.getContent() == null )
                    {
                        if ( file.getLocation() != null )
                        {
                            InputStream inStream = locator.getResourceAsInputStream( file.getLocation() );
                            OutputStream outStream = new FileOutputStream( projectRelativeFile );
                            try
                            {
                                IOUtil.copy( inStream, outStream );
                            }
                            finally
                            {
                                IOUtil.close( inStream );
                                IOUtil.close( outStream );
                            }
                        }
                        else
                        {
                            URL url = file.getURL();
                            String endPointUrl = url.getProtocol() + "://" + url.getAuthority();
                            // Repository Id should be ignored by Wagon ...
                            Repository repository = new Repository( "additonal-configs", endPointUrl );
                            Wagon wagon = wagonManager.getWagon( repository );
                            if ( logger.isDebugEnabled() )
                            {
                                Debug debug = new Debug();
                                wagon.addSessionListener( debug );
                                wagon.addTransferListener( debug );
                            }
                            wagon.setTimeout( 1000 );
                            Settings settings = mavenSettingsBuilder.buildSettings();
                            ProxyInfo proxyInfo = null;
                            if ( settings != null && settings.getActiveProxy() != null )
                            {
                                Proxy settingsProxy = settings.getActiveProxy();

                                proxyInfo = new ProxyInfo();
                                proxyInfo.setHost( settingsProxy.getHost() );
                                proxyInfo.setType( settingsProxy.getProtocol() );
                                proxyInfo.setPort( settingsProxy.getPort() );
                                proxyInfo.setNonProxyHosts( settingsProxy.getNonProxyHosts() );
                                proxyInfo.setUserName( settingsProxy.getUsername() );
                                proxyInfo.setPassword( settingsProxy.getPassword() );
                            }

                            if ( proxyInfo != null )
                            {
                                wagon.connect( repository, wagonManager.getAuthenticationInfo( repository.getId() ),
                                               proxyInfo );
                            }
                            else
                            {
                                wagon.connect( repository, wagonManager.getAuthenticationInfo( repository.getId() ) );
                            }

                            wagon.get( url.getPath(), projectRelativeFile );
                        }
                    }
                    else
                    {
                        FileUtils.fileWrite( projectRelativeFile.getAbsolutePath(), file.getContent() );
                    }
                }
                catch ( WagonException e )
                {
                    throw new MojoExecutionException(
                                                      Messages.getString( "EclipsePlugin.remoteexception", //$NON-NLS-1$
                                                                          new Object[] { file.getURL(), e.getMessage() } ) );
                }
                catch ( IOException e )
                {
                    throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantwritetofile", //$NON-NLS-1$
                                                                          projectRelativeFile.getAbsolutePath() ) );
                }
                catch ( ResourceNotFoundException e )
                {
                    throw new MojoExecutionException( Messages.getString( "EclipsePlugin.cantfindresource", //$NON-NLS-1$
                                                                          file.getLocation() ) );
                }
                catch ( XmlPullParserException e )
                {
                    throw new MojoExecutionException( Messages.getString( "EclipsePlugin.settingsxmlfailure", //$NON-NLS-1$
                                                                          e.getMessage() ) );
                }
            }
        }
    }

    /**
     * Create the <code>EclipseWriterConfig</code> for the specified dependencies.
     * 
     * @param deps the project dependencies
     * @return a configured <code>EclipseWriterConfig</code>
     * @throws MojoExecutionException mojo failures.
     */
    protected final EclipseWriterConfig createEclipseWriterConfig( IdeDependency[] deps )
        throws MojoExecutionException
    {
        File projectBaseDir = executedProject.getFile().getParentFile();

        // build a list of UNIQUE source dirs (both src and resources) to be
        // used in classpath and wtpmodules
        EclipseSourceDir[] sourceDirs = buildDirectoryList( executedProject, eclipseProjectDir, buildOutputDirectory );

        EclipseWriterConfig config = new EclipseWriterConfig();

        config.setWorkspaceConfiguration( getWorkspaceConfiguration() );

        config.setProjectNameTemplate( getProjectNameTemplate() );

        String projectName = IdeUtils.getProjectName( config.getProjectNameTemplate(), project );

        config.setEclipseProjectName( projectName );

        config.setWtpapplicationxml( wtpapplicationxml );

        config.setWtpVersion( wtpVersionFloat );

        float ajdtVersionFloat;
        try
        {
            ajdtVersionFloat = Float.parseFloat( ajdtVersion );
        }
        catch ( NumberFormatException e )
        {
            ajdtVersionFloat = 0.0f;
        }

        config.setAjdtVersion( ajdtVersionFloat );

        Set convertedBuildCommands = new LinkedHashSet();

        if ( buildcommands != null )
        {
            for ( Object cmd : buildcommands )
            {
                if ( cmd instanceof BuildCommand )
                {
                    convertedBuildCommands.add( cmd );
                }
                else
                {
                    convertedBuildCommands.add( new BuildCommand( (String) cmd ) );
                }
            }
        }

        if ( ajdt )
        {
            buildAjdtWeaveDeps( deps );
            buildAspectjDeps( deps );
        }

        config.setBuildCommands( new LinkedList( convertedBuildCommands ) );

        config.setBuildOutputDirectory( buildOutputDirectory );
        config.setClasspathContainers( classpathContainers );
        config.setDeps( deps );
        config.setEclipseProjectDirectory( eclipseProjectDir );
        config.setLocalRepository( localRepository );
        config.setOSGIManifestFile( manifest );
        config.setProject( project );
        config.setProjectBaseDir( projectBaseDir );
        config.setProjectnatures( projectnatures );
        config.setProjectFacets( additionalProjectFacets );
        config.setSourceDirs( sourceDirs );
        config.setPackaging( packaging );
        config.setLinkedResources( linkedResources );
        config.setClasspathContainersLast( classpathContainersLast );
        config.setJeeVersion( jeeversion );

        collectWarContextRootsFromReactorEarConfiguration( config );

        return config;
    }

    /**
     * If this is a war module peek into the reactor an search for an ear module that defines the context root of this
     * module.
     * 
     * @param config config to save the context root.
     */
    private void collectWarContextRootsFromReactorEarConfiguration( EclipseWriterConfig config )
    {
        if ( reactorProjects != null && wtpContextName == null
            && Constants.PROJECT_PACKAGING_WAR.equals( project.getPackaging() ) )
        {
            for ( Object reactorProject1 : reactorProjects )
            {
                MavenProject reactorProject = (MavenProject) reactorProject1;

                if ( Constants.PROJECT_PACKAGING_EAR.equals( reactorProject.getPackaging() ) )
                {
                    Xpp3Dom[] warDefinitions =
                        IdeUtils.getPluginConfigurationDom( reactorProject, JeeUtils.ARTIFACT_MAVEN_EAR_PLUGIN,
                                                            new String[] { "modules", "webModule" } );
                    for ( Xpp3Dom warDefinition : warDefinitions )
                    {
                        Xpp3Dom groupId = warDefinition.getChild( "groupId" );
                        Xpp3Dom artifactId = warDefinition.getChild( "artifactId" );
                        Xpp3Dom contextRoot = warDefinition.getChild( "contextRoot" );
                        if ( groupId != null && artifactId != null && contextRoot != null && groupId.getValue() != null
                            && artifactId.getValue() != null && contextRoot.getValue() != null )
                        {
                            getLog().info( "Found context root definition for " + groupId.getValue() + ":"
                                               + artifactId.getValue() + " " + contextRoot.getValue() );
                            if ( project.getArtifactId().equals( artifactId.getValue() )
                                && project.getGroupId().equals( groupId.getValue() ) )
                            {
                                config.setContextName( contextRoot.getValue() );
                            }
                        }
                        else
                        {
                            getLog().info( "Found incomplete ear configuration in " + reactorProject.getGroupId() + ":"
                                               + reactorProject.getGroupId() + " found " + warDefinition.toString() );
                        }
                    }
                }
            }
        }
        if ( config.getContextName() == null && Constants.PROJECT_PACKAGING_WAR.equals( project.getPackaging() ) )
        {
            if ( wtpContextName == null )
            {
                config.setContextName( project.getArtifactId() );
            }
            else if ( "ROOT".equals( wtpContextName ) )
            {
                config.setContextName( "" );
            }
            else
            {
                config.setContextName( wtpContextName );
            }
        }
    }

    /**
     * Write any extra configuration information for the Eclipse project. This is an extension point, called before the
     * main configurations are written. <br/>
     * <b> NOTE: This could change the config! </b>
     * 
     * @param config
     * @throws MojoExecutionException
     */
    protected void writeConfigurationExtras( EclipseWriterConfig config )
        throws MojoExecutionException
    {
        // extension point.
    }

    private void assertNotEmpty( String string, String elementName )
        throws MojoExecutionException
    {
        if ( string == null )
        {
            throw new MojoExecutionException( Messages.getString( "EclipsePlugin.missingelement", elementName ) ); //$NON-NLS-1$
        }
    }

    /**
     * Fill getProjectnatures() with values.
     * <p>
     * Subclasses should call super and then calculate their own additions and insert them via
     * <code>getProjectnatures().addAll()</code>.
     * 
     * @param packaging the pom's packaging
     */
    protected void fillDefaultNatures( String packaging )
    {
        projectnatures = new ArrayList();

        if ( wtpVersionFloat >= 1.0f )
        {
            projectnatures.add( NATURE_WST_FACET_CORE_NATURE ); // WTP 1.0 nature
        }

        if ( isJavaProject )
        {
            if ( ajdt )
            {
                projectnatures.add( NATURE_AJDT_CORE_JAVA );
            }

            projectnatures.add( NATURE_JDT_CORE_JAVA );
        }

        if ( wtpVersionFloat >= 0.7f )
        {
            projectnatures.add( NATURE_WST_MODULE_CORE_NATURE ); // WTP 0.7/1.0 nature

            if ( isJavaProject )
            {
                projectnatures.add( NATURE_JEM_WORKBENCH_JAVA_EMF ); // WTP 0.7/1.0 nature
            }
        }

    }

    /**
     * Fill getClasspathContainers() with values.
     * <p>
     * Subclasses should call super and then calculate their own additions and insert them via
     * <code>getClasspathContainers().addAll()</code>.
     * 
     * @param packaging the pom's packaging
     */
    protected void fillDefaultClasspathContainers( String packaging )
    {
        classpathContainers = new ArrayList();

        if ( getWorkspaceConfiguration().getDefaultClasspathContainer() != null )
        {
            getLog().info( "Adding default classpath container: "
                               + getWorkspaceConfiguration().getDefaultClasspathContainer() );
            classpathContainers.add( getWorkspaceConfiguration().getDefaultClasspathContainer() );
        }

        if ( ajdt )
        {
            classpathContainers.add( ASPECTJ_RT_CONTAINER );
        }
    }

    /**
     * Fill getBuildcommands() with values.
     * <p>
     * Subclasses should call super and then calculate their own additions and insert them via
     * <code>getBuildcommands().addAll()</code>.
     * 
     * @param packaging the pom's packaging
     */
    protected void fillDefaultBuilders( String packaging )
    {
        buildcommands = new ArrayList();

        if ( wtpVersionFloat == 0.7f )
        {
            buildcommands.add( new BuildCommand( BUILDER_WST_COMPONENT_STRUCTURAL ) ); // WTP 0.7 builder
        }

        if ( isJavaProject )
        {
            if ( ajdt )
            {
                buildcommands.add( new BuildCommand( BUILDER_AJDT_CORE_JAVA ) );
            }
            else
            {
                buildcommands.add( new BuildCommand( BUILDER_JDT_CORE_JAVA ) );
            }
        }

        if ( wtpVersionFloat >= 1.5f )
        {
            buildcommands.add( new BuildCommand( BUILDER_WST_FACET ) ); // WTP 1.5 builder
        }

        if ( wtpVersionFloat >= 0.7f )
        {
            buildcommands.add( new BuildCommand( BUILDER_WST_VALIDATION ) ); // WTP 0.7/1.0 builder
        }

        if ( wtpVersionFloat == 0.7f )
        {
            // WTP 0.7 builder
            buildcommands.add( new BuildCommand( BUILDER_WST_COMPONENT_STRUCTURAL_DEPENDENCY_RESOLVER ) );
        }
    }

    public final EclipseSourceDir[] buildDirectoryList( MavenProject project, File basedir, File buildOutputDirectory )
        throws MojoExecutionException
    {
        File projectBaseDir = project.getFile().getParentFile();

        String mainOutput = IdeUtils.toRelativeAndFixSeparator( projectBaseDir, buildOutputDirectory, false );

        // If using the standard output location, don't mix the test output into it.
        String testOutput = null;
        boolean useStandardOutputDir =
            buildOutputDirectory.equals( new File( project.getBuild().getOutputDirectory() ) );
        if ( useStandardOutputDir )
        {
            getLog().debug( "testOutput toRelativeAndFixSeparator " + projectBaseDir + " , "
                                + project.getBuild().getTestOutputDirectory() );
            testOutput =
                IdeUtils.toRelativeAndFixSeparator( projectBaseDir,
                                                    new File( project.getBuild().getTestOutputDirectory() ), false );
            getLog().debug( "testOutput after toRelative : " + testOutput );
        }

        Set mainDirectories = new LinkedHashSet();

        extractSourceDirs( mainDirectories, project.getCompileSourceRoots(), basedir, projectBaseDir, false, null );

        extractResourceDirs( mainDirectories, project.getBuild().getResources(), basedir, projectBaseDir, false,
                             mainOutput );

        Set testDirectories = new LinkedHashSet();

        extractSourceDirs( testDirectories, project.getTestCompileSourceRoots(), basedir, projectBaseDir, true,
                           testOutput );

        extractResourceDirs( testDirectories, project.getBuild().getTestResources(), basedir, projectBaseDir, true,
                             testOutput );

        // avoid duplicated entries
        Set directories = new LinkedHashSet();

        // NOTE: Since MNG-3118, test classes come before main classes
        boolean testBeforeMain = isMavenVersion( "[2.0.8,)" );

        // let users override this if needed, they need to simulate more than the test phase in eclipse
        if ( testSourcesLast )
        {
            testBeforeMain = false;
        }

        if ( testBeforeMain )
        {
            directories.addAll( testDirectories );
            directories.removeAll( mainDirectories );
            directories.addAll( mainDirectories );
        }
        else
        {
            directories.addAll( mainDirectories );
            directories.addAll( testDirectories );
        }
        if ( ajdt )
            extractAspectDirs( directories, project, basedir, projectBaseDir, testOutput );
        return (EclipseSourceDir[]) directories.toArray( new EclipseSourceDir[directories.size()] );
    }

    private void extractSourceDirs( Set directories, List sourceRoots, File basedir, File projectBaseDir, boolean test,
                                    String output )
        throws MojoExecutionException
    {
        for ( Object sourceRoot1 : sourceRoots )
        {

            File sourceRootFile = new File( (String) sourceRoot1 );

            if ( sourceRootFile.isDirectory() )
            {
                String sourceRoot =
                    IdeUtils.toRelativeAndFixSeparator( projectBaseDir, sourceRootFile,
                                                        !projectBaseDir.equals( basedir ) );

                directories.add( new EclipseSourceDir( sourceRoot, output, false, test, sourceIncludes, sourceExcludes,
                                                       false ) );
            }
        }
    }

    final void extractResourceDirs( Set directories, List resources, File basedir, File workspaceProjectBaseDir,
                                    boolean test, final String output )
        throws MojoExecutionException
    {
        for ( Object resource1 : resources )
        {
            Resource resource = (Resource) resource1;

            getLog().debug( "Processing resource dir: " + resource.getDirectory() );

            List excludes = new ArrayList( resource.getExcludes() );
            // automatically exclude java files: eclipse doesn't have the concept of resource directory so it will
            // try to compile any java file found in maven resource dirs
            excludes.add( JAVA_FILE_PATTERN );

            // TODO: figure out how to merge if the same dir is specified twice
            // with different in/exclude patterns.

            File resourceDirectory = new File( /* basedir, */resource.getDirectory() );

            if ( !resourceDirectory.exists() || !resourceDirectory.isDirectory() )
            {
                getLog().debug( "Resource dir: " + resourceDirectory + " either missing or not a directory." );
                continue;
            }

            String resourcePath =
                IdeUtils.toRelativeAndFixSeparator( workspaceProjectBaseDir, resourceDirectory,
                                                    !workspaceProjectBaseDir.equals( basedir ) );
            String thisOutput = output;
            if ( thisOutput != null )
            {
                // sometimes thisOutput is already an absolute path
                File outputFile = new File( thisOutput );
                if ( !outputFile.isAbsolute() )
                {
                    outputFile = new File( workspaceProjectBaseDir, thisOutput );
                }
                // create output dir if it doesn't exist
                outputFile.mkdirs();

                if ( !StringUtils.isEmpty( resource.getTargetPath() ) )
                {
                    outputFile = new File( outputFile, resource.getTargetPath() );
                    // create output dir if it doesn't exist
                    outputFile.mkdirs();
                }

                getLog().debug( "Making relative and fixing separator: { " + workspaceProjectBaseDir + ", "
                                    + outputFile + ", false }." );
                thisOutput = IdeUtils.toRelativeAndFixSeparator( workspaceProjectBaseDir, outputFile, false );
            }

            EclipseSourceDir resourceDir =
                new EclipseSourceDir( resourcePath, thisOutput, true, test, resource.getIncludes(), excludes,
                                      resource.isFiltering() );

            if ( !directories.add( resourceDir ) )
            {
                EclipseSourceDir originalDir = (EclipseSourceDir) get( directories, resourceDir );

                boolean merged = originalDir.merge( resourceDir );
                if ( merged )
                {
                    getLog().info( "Resource directory's path matches an existing source directory. Resources have been merged with the source directory "
                                       + originalDir.getPath() );
                }
                else
                {
                    getLog().info( "Resource directory's path matches an existing source directory but \"test\", \"filtering\" or \"output\" were different."
                                       + "The resulting eclipse configuration may not accurately reflect the project configuration for "
                                       + originalDir.getPath() );
                }

            }
        }
    }

    /**
     * java.util.Set doesn't have a get() method that returns the matching object. Since we use objects that are
     * different by conceptually "equal" based on the path we need to locate the original object out of the Set.
     * 
     * @param set the set to iterate over looking for the specified object
     * @param o the object to locate in the set
     * @return the object from the set, or null if not found in the set
     */
    private Object get( Set set, Object o )
    {
        for ( Object item : set )
        {
            if ( o.equals( item ) )
            {
                return item;
            }
        }
        return null;
    }

    private void extractAspectDirs( Set directories, MavenProject project, File basedir, File projectBaseDir,
                                    String testOutput )
        throws MojoExecutionException
    {
        Xpp3Dom configuration = getAspectjConfiguration( project );
        if ( configuration != null )
        {
            String aspectDirectory = DEFAULT_ASPECT_DIRECTORY;
            Xpp3Dom aspectDirectoryElement = configuration.getChild( ASPECT_DIRECTORY );
            if ( aspectDirectoryElement != null )
            {
                aspectDirectory = aspectDirectoryElement.getValue();
            }

            File aspectDirectoryFile = new File( basedir, aspectDirectory );
            if ( aspectDirectoryFile.exists() && aspectDirectoryFile.isDirectory() )
            {
                String sourceRoot =
                    IdeUtils.toRelativeAndFixSeparator( projectBaseDir, aspectDirectoryFile,
                                                        !projectBaseDir.equals( basedir ) );

                directories.add( new EclipseSourceDir( sourceRoot, null, false, false, sourceIncludes, sourceExcludes,
                                                       false ) );
            }

            String testAspectDirectory = DEFAULT_TEST_ASPECT_DIRECTORY;
            Xpp3Dom testAspectDirectoryElement = configuration.getChild( TEST_ASPECT_DIRECTORY );
            if ( testAspectDirectoryElement != null )
            {
                testAspectDirectory = testAspectDirectoryElement.getValue();
            }

            File testAspectDirectoryFile = new File( basedir, testAspectDirectory );
            if ( testAspectDirectoryFile.exists() && testAspectDirectoryFile.isDirectory() )
            {
                String sourceRoot =
                    IdeUtils.toRelativeAndFixSeparator( projectBaseDir, testAspectDirectoryFile,
                                                        !projectBaseDir.equals( basedir ) );

                directories.add( new EclipseSourceDir( sourceRoot, testOutput, false, true, sourceIncludes,
                                                       sourceExcludes, false ) );
            }
        }
    }

    private boolean enableAjdt( MavenProject project )
    {
        boolean enable = false;
        List buildPlugins = project.getBuildPlugins();
        for ( Object buildPlugin : buildPlugins )
        {
            Plugin plugin = (Plugin) buildPlugin;
            if ( plugin.getGroupId().equals( ORG_CODEHAUS_MOJO )
                && plugin.getArtifactId().equals( ASPECTJ_MAVEN_PLUGIN ) )
            {
                enable = true;
                break;
            }
        }

        return enable;
    }

    private Xpp3Dom getAspectjConfiguration( MavenProject project )
    {
        Xpp3Dom configuration = null;
        List buildPlugins = project.getBuildPlugins();
        for ( Object buildPlugin : buildPlugins )
        {
            Plugin plugin = (Plugin) buildPlugin;
            if ( plugin.getGroupId().equals( ORG_CODEHAUS_MOJO )
                && plugin.getArtifactId().equals( ASPECTJ_MAVEN_PLUGIN ) )
            {
                configuration = (Xpp3Dom) plugin.getConfiguration();
                break;
            }
        }

        return configuration;
    }

    private void buildAspectjDeps( IdeDependency[] deps )
        throws MojoExecutionException
    {
        Xpp3Dom configuration = getAspectjConfiguration( executedProject );
        if ( configuration != null )
        {
            Xpp3Dom aspectLibrariesParent = configuration.getChild( ASPECT_LIBRARIES );
            if ( aspectLibrariesParent != null )
            {
                Xpp3Dom[] aspectLibraries = aspectLibrariesParent.getChildren( ASPECT_LIBRARY );
                outerLoop: for ( Xpp3Dom aspectLibrary : aspectLibraries )
                {
                    String artifactId = aspectLibrary.getChild( POM_ELT_ARTIFACT_ID ).getValue();
                    String groupId = aspectLibrary.getChild( POM_ELT_GROUP_ID ).getValue();
                    for ( IdeDependency dep : deps )
                    {
                        if ( dep.getArtifactId().equals( artifactId ) && dep.getGroupId().equals( groupId ) )
                        {
                            dep.setAjdtDependency( true );
                            continue outerLoop;
                        }
                    }

                    throw new MojoExecutionException( "AspectLibrary is not a dependency of project" );
                }
            }
        }
    }

    private void buildAjdtWeaveDeps( IdeDependency[] deps )
        throws MojoExecutionException
    {
        Xpp3Dom configuration = getAspectjConfiguration( executedProject );
        if ( configuration != null )
        {
            Xpp3Dom weaveDependenciesParent = configuration.getChild( WEAVE_DEPENDENCIES );
            if ( weaveDependenciesParent != null )
            {
                Xpp3Dom[] weaveDependencies = weaveDependenciesParent.getChildren( WEAVE_DEPENDENCY );
                outerLoop: for ( Xpp3Dom weaveDependency : weaveDependencies )
                {
                    String artifactId = weaveDependency.getChild( POM_ELT_ARTIFACT_ID ).getValue();
                    String groupId = weaveDependency.getChild( POM_ELT_GROUP_ID ).getValue();
                    for ( IdeDependency dep : deps )
                    {
                        if ( dep.getArtifactId().equals( artifactId ) && dep.getGroupId().equals( groupId ) )
                        {
                            dep.setAjdtWeaveDependency( true );
                            continue outerLoop;
                        }
                    }

                    throw new MojoExecutionException( "WeaveDependency is not a dependency of project" );
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public String getProjectNameForArifact( Artifact artifact )
    {
        IdeDependency[] workspaceArtefacts = getWorkspaceArtefacts();
        for ( int index = 0; workspaceArtefacts != null && index < workspaceArtefacts.length; index++ )
        {
            IdeDependency workspaceArtefact = workspaceArtefacts[index];
            if ( workspaceArtefact.isAddedToClasspath()
                && workspaceArtefact.getGroupId().equals( artifact.getGroupId() )
                && workspaceArtefact.getArtifactId().equals( artifact.getArtifactId() ) )
            {
                if ( workspaceArtefact.getVersion().equals( artifact.getBaseVersion() ) )
                {
                    return workspaceArtefact.getEclipseProjectName();
                }
            }
        }
        MavenProject reactorProject = getReactorProject( artifact );
        if ( reactorProject != null )
        {
            return IdeUtils.getProjectName( getProjectNameTemplateForMavenProject( reactorProject ), artifact );
        }
        return IdeUtils.getProjectName( getProjectNameTemplate(), artifact );
    }

    /**
     * @param mavenProject the project to get the projectNameTemplate configuration from
     * @return the projectNameTemplate configuration from the specified MavenProject
     */
    private String getProjectNameTemplateForMavenProject( MavenProject mavenProject )
    {
        String projectNameTemplate = null;
        boolean addVersionToProjectName = false;
        boolean addGroupIdToProjectName = false;

        Build build = mavenProject.getBuild();
        if ( build != null )
        {
            String eclipsePlugin = "org.apache.maven.plugins:maven-eclipse-plugin";
            Plugin plugin = (Plugin) build.getPluginsAsMap().get( eclipsePlugin );
            if ( plugin == null && build.getPluginManagement() != null )
            {
                plugin = (Plugin) build.getPluginManagement().getPluginsAsMap().get( eclipsePlugin );
            }
            if ( plugin != null )
            {
                Xpp3Dom config = (Xpp3Dom) plugin.getConfiguration();
                if ( config != null )
                {
                    Xpp3Dom projectNameTemplateNode = config.getChild( "projectNameTemplate" );
                    if ( projectNameTemplateNode != null )
                    {
                        projectNameTemplate = projectNameTemplateNode.getValue();
                    }
                    Xpp3Dom addVersionToProjectNameNode = config.getChild( "addVersionToProjectName" );
                    addVersionToProjectName = addVersionToProjectNameNode != null;
                    Xpp3Dom addGroupIdToProjectNameNode = config.getChild( "addGroupIdToProjectName" );
                    addGroupIdToProjectName = addGroupIdToProjectNameNode != null;
                }
            }
        }
        return IdeUtils.calculateProjectNameTemplate( projectNameTemplate, addVersionToProjectName,
                                                      addGroupIdToProjectName, getLog() );
    }

    /**
     * {@inheritDoc}
     */
    protected final IdeDependency[] getWorkspaceArtefacts()
    {
        return getWorkspaceConfiguration().getWorkspaceArtefacts();
    }

    public final WorkspaceConfiguration getWorkspaceConfiguration()
    {
        if ( workspaceConfiguration == null )
        {
            workspaceConfiguration = new WorkspaceConfiguration();
            locateWorkspace();
            getLog().info( Messages.getString( "EclipsePlugin.workspace", workspace ) );
            workspaceConfiguration.setWorkspaceDirectory( workspace );

            new ReadWorkspaceLocations().init( getLog(), workspaceConfiguration, project, wtpdefaultserver,
                                               preferStandardClasspathContainer );
        }
        return workspaceConfiguration;
    }

    /**
     * If workspace is not defined, then attempt to locate it by checking up the directory hierarchy.
     */
    private void locateWorkspace()
    {
        if ( workspace == null )
        {
            File currentWorkingDirectory = new File( "." ).getAbsoluteFile();
            while ( currentWorkingDirectory != null )
            {
                File metadataDirectory = new File( currentWorkingDirectory, ".metadata" );
                logger.debug( "Checking for eclipse workspace at " + currentWorkingDirectory );
                if ( metadataDirectory.exists() && metadataDirectory.isDirectory() )
                {
                    logger.debug( "  Found workspace at " + currentWorkingDirectory );
                    workspace = currentWorkingDirectory;
                    return;
                }
                currentWorkingDirectory = currentWorkingDirectory.getParentFile();
            }
        }
    }

    public final List getExcludes()
    {
        return excludes;
    }

    /**
     * Utility method that locates a project producing the given artifact and verifies if it's a valid Eclipse project.
     * 
     * @param artifact the artifact a project should produce.
     * @return <code>true</code> if the artifact is produced by a reactor projectart.
     */
    protected boolean isAvailableAsAReactorProject( Artifact artifact )
    {
        MavenProject project = getReactorProject( artifact );
        return ( project != null && new File( project.getBasedir(), ".project" ).exists() );
    }

    /**
     * Utility method that locates a project in the workspace for the given artifact.
     * 
     * @param artifact the artifact a project should produce.
     * @return <code>true</code> if the artifact is produced by a reactor projectart.
     */
    private boolean isAvailableAsAWorkspaceProject( Artifact artifact )
    {
        IdeDependency[] workspaceArtefacts = getWorkspaceArtefacts();
        for ( int index = 0; workspaceArtefacts != null && index < workspaceArtefacts.length; index++ )
        {
            IdeDependency workspaceArtefact = workspaceArtefacts[index];
            if ( workspaceArtefact.getGroupId().equals( artifact.getGroupId() )
                && workspaceArtefact.getArtifactId().equals( artifact.getArtifactId() ) )
            {
                if ( workspaceArtefact.getVersion().equals( artifact.getBaseVersion() ) )
                {
                    workspaceArtefact.setAddedToClasspath( true );
                    getLog().debug( "Using workspace project: " + workspaceArtefact.getEclipseProjectName() );
                    return true;
                }
                else
                {
                    getLog().info( "Artifact "
                                       + artifact.getId()
                                       + " already available as a workspace project, but with different version. Expected: "
                                       + artifact.getBaseVersion() + ", found: " + workspaceArtefact.getVersion() );
                }
            }
        }
        return false;
    }

    /**
     * Checks if jar has to be resolved for the given artifact
     * 
     * @param art the artifact to check
     * @return true if resolution should happen
     */
    protected final boolean hasToResolveJar( Artifact art )
    {
        return !( getUseProjectReferences() && isAvailableAsAReactorProject( art ) )
            || ( limitProjectReferencesToWorkspace && !( getUseProjectReferences() && isAvailableAsAWorkspaceProject( art ) ) );
    }

    /**
     * Checks if a projects reference has to be used for the given artifact
     * 
     * @param art the artifact to check
     * @return true if a project reference has to be used.
     */
    protected final boolean useProjectReference( Artifact art )
    {
        boolean isReactorProject = getUseProjectReferences() && isAvailableAsAReactorProject( art );
        boolean isWorkspaceProject = getUseProjectReferences() && isAvailableAsAWorkspaceProject( art );
        return ( isReactorProject && !limitProjectReferencesToWorkspace ) || // default
            ( limitProjectReferencesToWorkspace && isWorkspaceProject ) || // limitProjectReferencesToWorkspace
            ( !isReactorProject && isWorkspaceProject ); // default + workspace projects
    }
}
